diff --git a/DysonNetwork.Common/DysonNetwork.Common.csproj b/DysonNetwork.Common/DysonNetwork.Common.csproj
new file mode 100644
index 0000000..ee9c09b
--- /dev/null
+++ b/DysonNetwork.Common/DysonNetwork.Common.csproj
@@ -0,0 +1,23 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DysonNetwork.Sphere/Storage/ICloudFile.cs b/DysonNetwork.Common/Interfaces/ICloudFile.cs
similarity index 97%
rename from DysonNetwork.Sphere/Storage/ICloudFile.cs
rename to DysonNetwork.Common/Interfaces/ICloudFile.cs
index 4a10851..2cb3a58 100644
--- a/DysonNetwork.Sphere/Storage/ICloudFile.cs
+++ b/DysonNetwork.Common/Interfaces/ICloudFile.cs
@@ -1,6 +1,6 @@
using NodaTime;
-namespace DysonNetwork.Sphere.Storage;
+namespace DysonNetwork.Common.Interfaces;
///
/// Common interface for cloud file entities that can be used in file operations.
diff --git a/DysonNetwork.Common/Interfaces/IIdentifiedResource.cs b/DysonNetwork.Common/Interfaces/IIdentifiedResource.cs
new file mode 100644
index 0000000..732db11
--- /dev/null
+++ b/DysonNetwork.Common/Interfaces/IIdentifiedResource.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Common.Interfaces;
+
+public interface IIdentifiedResource
+{
+ public string ResourceIdentifier { get; }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Account/AbuseReport.cs b/DysonNetwork.Common/Models/AbuseReport.cs
similarity index 94%
rename from DysonNetwork.Sphere/Account/AbuseReport.cs
rename to DysonNetwork.Common/Models/AbuseReport.cs
index 87c1be1..2bae022 100644
--- a/DysonNetwork.Sphere/Account/AbuseReport.cs
+++ b/DysonNetwork.Common/Models/AbuseReport.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
public enum AbuseReportType
{
diff --git a/DysonNetwork.Sphere/Account/Account.cs b/DysonNetwork.Common/Models/Account.cs
similarity index 95%
rename from DysonNetwork.Sphere/Account/Account.cs
rename to DysonNetwork.Common/Models/Account.cs
index 5d05364..17a34b1 100644
--- a/DysonNetwork.Sphere/Account/Account.cs
+++ b/DysonNetwork.Common/Models/Account.cs
@@ -1,14 +1,11 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Permission;
-using DysonNetwork.Sphere.Storage;
-using DysonNetwork.Sphere.Wallet;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using OtpNet;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
[Index(nameof(Name), IsUnique = true)]
public class Account : ModelBase
@@ -26,8 +23,8 @@ public class Account : ModelBase
[JsonIgnore] public ICollection AuthFactors { get; set; } = new List();
[JsonIgnore] public ICollection Connections { get; set; } = new List();
- [JsonIgnore] public ICollection Sessions { get; set; } = new List();
- [JsonIgnore] public ICollection Challenges { get; set; } = new List();
+ [JsonIgnore] public ICollection Sessions { get; set; } = new List();
+ [JsonIgnore] public ICollection Challenges { get; set; } = new List();
[JsonIgnore] public ICollection OutgoingRelationships { get; set; } = new List();
[JsonIgnore] public ICollection IncomingRelationships { get; set; } = new List();
@@ -193,4 +190,4 @@ public class AccountConnection : ModelBase
public Guid AccountId { get; set; }
public Account Account { get; set; } = null!;
-}
\ No newline at end of file
+}
diff --git a/DysonNetwork.Sphere/Account/ActionLog.cs b/DysonNetwork.Common/Models/ActionLog.cs
similarity index 96%
rename from DysonNetwork.Sphere/Account/ActionLog.cs
rename to DysonNetwork.Common/Models/ActionLog.cs
index 19c5779..51a61fe 100644
--- a/DysonNetwork.Sphere/Account/ActionLog.cs
+++ b/DysonNetwork.Common/Models/ActionLog.cs
@@ -1,8 +1,8 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
-using Point = NetTopologySuite.Geometries.Point;
+using NetTopologySuite.Geometries;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
public abstract class ActionLogType
{
diff --git a/DysonNetwork.Sphere/Activity/Activity.cs b/DysonNetwork.Common/Models/Activity.cs
similarity index 95%
rename from DysonNetwork.Sphere/Activity/Activity.cs
rename to DysonNetwork.Common/Models/Activity.cs
index 41f6498..2ea34b2 100644
--- a/DysonNetwork.Sphere/Activity/Activity.cs
+++ b/DysonNetwork.Common/Models/Activity.cs
@@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using NodaTime;
-namespace DysonNetwork.Sphere.Activity;
+namespace DysonNetwork.Common.Models;
public interface IActivity
{
diff --git a/DysonNetwork.Sphere/Auth/Session.cs b/DysonNetwork.Common/Models/Auth.cs
similarity index 78%
rename from DysonNetwork.Sphere/Auth/Session.cs
rename to DysonNetwork.Common/Models/Auth.cs
index ac00ab8..f3ddf9c 100644
--- a/DysonNetwork.Sphere/Auth/Session.cs
+++ b/DysonNetwork.Common/Models/Auth.cs
@@ -1,13 +1,12 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Drawing;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Developer;
using NodaTime;
-using Point = NetTopologySuite.Geometries.Point;
-namespace DysonNetwork.Sphere.Auth;
+namespace DysonNetwork.Common.Models;
-public class Session : ModelBase
+public class AuthSession : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(1024)] public string? Label { get; set; }
@@ -15,9 +14,9 @@ public class Session : ModelBase
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }
- [JsonIgnore] public Account.Account Account { get; set; } = null!;
+ [JsonIgnore] public Models.Account Account { get; set; } = null!;
public Guid ChallengeId { get; set; }
- public Challenge Challenge { get; set; } = null!;
+ public AuthChallenge AuthChallenge { get; set; } = null!;
public Guid? AppId { get; set; }
public CustomApp? App { get; set; }
}
@@ -40,7 +39,7 @@ public enum ChallengePlatform
Linux
}
-public class Challenge : ModelBase
+public class AuthChallenge : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
public Instant? ExpiredAt { get; set; }
@@ -49,9 +48,9 @@ public class Challenge : ModelBase
public int FailedAttempts { get; set; }
public ChallengePlatform Platform { get; set; } = ChallengePlatform.Unidentified;
public ChallengeType Type { get; set; } = ChallengeType.Login;
- [Column(TypeName = "jsonb")] public List BlacklistFactors { get; set; } = new();
- [Column(TypeName = "jsonb")] public List Audiences { get; set; } = new();
- [Column(TypeName = "jsonb")] public List Scopes { get; set; } = new();
+ [Column(TypeName = "jsonb")] public List BlacklistFactors { get; set; } = [];
+ [Column(TypeName = "jsonb")] public List Audiences { get; set; } = [];
+ [Column(TypeName = "jsonb")] public List Scopes { get; set; } = [];
[MaxLength(128)] public string? IpAddress { get; set; }
[MaxLength(512)] public string? UserAgent { get; set; }
[MaxLength(256)] public string? DeviceId { get; set; }
@@ -59,9 +58,9 @@ public class Challenge : ModelBase
public Point? Location { get; set; }
public Guid AccountId { get; set; }
- [JsonIgnore] public Account.Account Account { get; set; } = null!;
+ [JsonIgnore] public Models.Account Account { get; set; } = null!;
- public Challenge Normalize()
+ public AuthChallenge Normalize()
{
if (StepRemain == 0 && BlacklistFactors.Count == 0) StepRemain = StepTotal;
return this;
diff --git a/DysonNetwork.Sphere/Account/Badge.cs b/DysonNetwork.Common/Models/Badge.cs
similarity index 92%
rename from DysonNetwork.Sphere/Account/Badge.cs
rename to DysonNetwork.Common/Models/Badge.cs
index 11368c4..c6a1599 100644
--- a/DysonNetwork.Sphere/Account/Badge.cs
+++ b/DysonNetwork.Common/Models/Badge.cs
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
public class Badge : ModelBase
{
@@ -16,7 +16,7 @@ public class Badge : ModelBase
public Instant? ExpiredAt { get; set; }
public Guid AccountId { get; set; }
- [JsonIgnore] public Account Account { get; set; } = null!;
+ [JsonIgnore] public Models.Account Account { get; set; } = null!;
public BadgeReferenceObject ToReference()
{
diff --git a/DysonNetwork.Sphere/Chat/ChatRoom.cs b/DysonNetwork.Common/Models/ChatRoom.cs
similarity index 94%
rename from DysonNetwork.Sphere/Chat/ChatRoom.cs
rename to DysonNetwork.Common/Models/ChatRoom.cs
index 5fd9d53..626003a 100644
--- a/DysonNetwork.Sphere/Chat/ChatRoom.cs
+++ b/DysonNetwork.Common/Models/ChatRoom.cs
@@ -1,10 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Interfaces;
using NodaTime;
-namespace DysonNetwork.Sphere.Chat;
+namespace DysonNetwork.Common.Models;
public enum ChatRoomType
{
@@ -31,7 +31,7 @@ public class ChatRoom : ModelBase, IIdentifiedResource
[JsonIgnore] public ICollection Members { get; set; } = new List();
public Guid? RealmId { get; set; }
- public Realm.Realm? Realm { get; set; }
+ public Common.Models.Realm? Realm { get; set; }
[NotMapped]
[JsonPropertyName("members")]
@@ -73,7 +73,7 @@ public class ChatMember : ModelBase
public Guid ChatRoomId { get; set; }
public ChatRoom ChatRoom { get; set; } = null!;
public Guid AccountId { get; set; }
- public Account.Account Account { get; set; } = null!;
+ public Account Account { get; set; } = null!;
[MaxLength(1024)] public string? Nick { get; set; }
@@ -105,7 +105,7 @@ public class ChatMemberTransmissionObject : ModelBase
public Guid Id { get; set; }
public Guid ChatRoomId { get; set; }
public Guid AccountId { get; set; }
- public Account.Account Account { get; set; } = null!;
+ public Account Account { get; set; } = null!;
[MaxLength(1024)] public string? Nick { get; set; }
diff --git a/DysonNetwork.Sphere/Storage/CloudFile.cs b/DysonNetwork.Common/Models/CloudFile.cs
similarity index 96%
rename from DysonNetwork.Sphere/Storage/CloudFile.cs
rename to DysonNetwork.Common/Models/CloudFile.cs
index 1cc1838..dcea8eb 100644
--- a/DysonNetwork.Sphere/Storage/CloudFile.cs
+++ b/DysonNetwork.Common/Models/CloudFile.cs
@@ -1,9 +1,9 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
-using System.Text.Json.Serialization;
+using DysonNetwork.Common.Interfaces;
using NodaTime;
-namespace DysonNetwork.Sphere.Storage;
+namespace DysonNetwork.Common.Models;
public class RemoteStorageConfig
{
@@ -74,7 +74,7 @@ public class CloudFile : ModelBase, ICloudFile, IIdentifiedResource
[MaxLength(4096)]
public string? StorageUrl { get; set; }
- [JsonIgnore] public Account.Account Account { get; set; } = null!;
+
public Guid AccountId { get; set; }
public CloudFileReferenceObject ToReferenceObject()
diff --git a/DysonNetwork.Sphere/Developer/CustomApp.cs b/DysonNetwork.Common/Models/CustomApp.cs
similarity index 93%
rename from DysonNetwork.Sphere/Developer/CustomApp.cs
rename to DysonNetwork.Common/Models/CustomApp.cs
index 6c5472c..25e3d52 100644
--- a/DysonNetwork.Sphere/Developer/CustomApp.cs
+++ b/DysonNetwork.Common/Models/CustomApp.cs
@@ -1,11 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Account;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Interfaces;
using NodaTime;
-namespace DysonNetwork.Sphere.Developer;
+namespace DysonNetwork.Common.Models;
public enum CustomAppStatus
{
@@ -33,7 +32,7 @@ public class CustomApp : ModelBase, IIdentifiedResource
[JsonIgnore] public ICollection Secrets { get; set; } = new List();
public Guid PublisherId { get; set; }
- public Publisher.Publisher Developer { get; set; } = null!;
+ public Publisher Developer { get; set; } = null!;
[NotMapped] public string ResourceIdentifier => "custom-app/" + Id;
}
diff --git a/DysonNetwork.Sphere/Account/Event.cs b/DysonNetwork.Common/Models/Event.cs
similarity index 91%
rename from DysonNetwork.Sphere/Account/Event.cs
rename to DysonNetwork.Common/Models/Event.cs
index 57fca37..4857db8 100644
--- a/DysonNetwork.Sphere/Account/Event.cs
+++ b/DysonNetwork.Common/Models/Event.cs
@@ -2,7 +2,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
public enum StatusAttitude
{
@@ -23,7 +23,7 @@ public class Status : ModelBase
public Instant? ClearedAt { get; set; }
public Guid AccountId { get; set; }
- public Account Account { get; set; } = null!;
+ public Models.Account Account { get; set; } = null!;
}
public enum CheckInResultLevel
@@ -44,7 +44,7 @@ public class CheckInResult : ModelBase
[Column(TypeName = "jsonb")] public ICollection Tips { get; set; } = new List();
public Guid AccountId { get; set; }
- public Account Account { get; set; } = null!;
+ public Models.Account Account { get; set; } = null!;
}
public class FortuneTip
diff --git a/DysonNetwork.Common/Models/LoginModels.cs b/DysonNetwork.Common/Models/LoginModels.cs
new file mode 100644
index 0000000..8248f0c
--- /dev/null
+++ b/DysonNetwork.Common/Models/LoginModels.cs
@@ -0,0 +1,14 @@
+namespace DysonNetwork.Sphere.Models
+{
+ public class LoginRequest
+ {
+ public string Username { get; set; }
+ public string Password { get; set; }
+ }
+
+ public class LoginResponse
+ {
+ public string AccessToken { get; set; }
+ public string RefreshToken { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Account/MagicSpell.cs b/DysonNetwork.Common/Models/MagicSpell.cs
similarity index 90%
rename from DysonNetwork.Sphere/Account/MagicSpell.cs
rename to DysonNetwork.Common/Models/MagicSpell.cs
index 674809c..0033dfa 100644
--- a/DysonNetwork.Sphere/Account/MagicSpell.cs
+++ b/DysonNetwork.Common/Models/MagicSpell.cs
@@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
public enum MagicSpellType
{
@@ -26,5 +26,5 @@ public class MagicSpell : ModelBase
[Column(TypeName = "jsonb")] public Dictionary Meta { get; set; } = new();
public Guid? AccountId { get; set; }
- public Account? Account { get; set; }
+ public Models.Account? Account { get; set; }
}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Chat/Message.cs b/DysonNetwork.Common/Models/Message.cs
similarity index 95%
rename from DysonNetwork.Sphere/Chat/Message.cs
rename to DysonNetwork.Common/Models/Message.cs
index a0ef968..e11c3a6 100644
--- a/DysonNetwork.Sphere/Chat/Message.cs
+++ b/DysonNetwork.Common/Models/Message.cs
@@ -1,11 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Storage;
-using Microsoft.EntityFrameworkCore;
+using DysonNetwork.Common.Interfaces;
using NodaTime;
-namespace DysonNetwork.Sphere.Chat;
+namespace DysonNetwork.Common.Models;
public class Message : ModelBase, IIdentifiedResource
{
diff --git a/DysonNetwork.Common/Models/ModelBase.cs b/DysonNetwork.Common/Models/ModelBase.cs
new file mode 100644
index 0000000..08a1448
--- /dev/null
+++ b/DysonNetwork.Common/Models/ModelBase.cs
@@ -0,0 +1,10 @@
+using NodaTime;
+
+namespace DysonNetwork.Common.Models;
+
+public abstract class ModelBase
+{
+ public Instant CreatedAt { get; set; }
+ public Instant UpdatedAt { get; set; }
+ public Instant? DeletedAt { get; set; }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Account/Notification.cs b/DysonNetwork.Common/Models/Notification.cs
similarity index 87%
rename from DysonNetwork.Sphere/Account/Notification.cs
rename to DysonNetwork.Common/Models/Notification.cs
index 5095f83..9b99b25 100644
--- a/DysonNetwork.Sphere/Account/Notification.cs
+++ b/DysonNetwork.Common/Models/Notification.cs
@@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
public class Notification : ModelBase
{
@@ -18,7 +18,7 @@ public class Notification : ModelBase
public Instant? ViewedAt { get; set; }
public Guid AccountId { get; set; }
- [JsonIgnore] public Account Account { get; set; } = null!;
+ [JsonIgnore] public Models.Account Account { get; set; } = null!;
}
public enum NotificationPushProvider
@@ -37,5 +37,5 @@ public class NotificationPushSubscription : ModelBase
public Instant? LastUsedAt { get; set; }
public Guid AccountId { get; set; }
- [JsonIgnore] public Account Account { get; set; } = null!;
+ [JsonIgnore] public Models.Account Account { get; set; } = null!;
}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Wallet/Payment.cs b/DysonNetwork.Common/Models/Payment.cs
similarity index 96%
rename from DysonNetwork.Sphere/Wallet/Payment.cs
rename to DysonNetwork.Common/Models/Payment.cs
index d3a2a94..8def408 100644
--- a/DysonNetwork.Sphere/Wallet/Payment.cs
+++ b/DysonNetwork.Common/Models/Payment.cs
@@ -1,9 +1,8 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
-using DysonNetwork.Sphere.Developer;
using NodaTime;
-namespace DysonNetwork.Sphere.Wallet;
+namespace DysonNetwork.Common.Models;
public class WalletCurrency
{
diff --git a/DysonNetwork.Sphere/Permission/Permission.cs b/DysonNetwork.Common/Models/Permission.cs
similarity index 98%
rename from DysonNetwork.Sphere/Permission/Permission.cs
rename to DysonNetwork.Common/Models/Permission.cs
index fe68844..10a5c95 100644
--- a/DysonNetwork.Sphere/Permission/Permission.cs
+++ b/DysonNetwork.Common/Models/Permission.cs
@@ -5,7 +5,7 @@ using System.Text.Json.Serialization;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Permission;
+namespace DysonNetwork.Common.Models;
/// The permission node model provides the infrastructure of permission control in Dyson Network.
/// It based on the ABAC permission model.
diff --git a/DysonNetwork.Sphere/Post/Post.cs b/DysonNetwork.Common/Models/Post.cs
similarity index 91%
rename from DysonNetwork.Sphere/Post/Post.cs
rename to DysonNetwork.Common/Models/Post.cs
index d4acec5..9d33b69 100644
--- a/DysonNetwork.Sphere/Post/Post.cs
+++ b/DysonNetwork.Common/Models/Post.cs
@@ -1,12 +1,12 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Activity;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Interfaces;
using NodaTime;
using NpgsqlTypes;
-namespace DysonNetwork.Sphere.Post;
+namespace DysonNetwork.Common.Models;
public enum PostType
{
@@ -59,7 +59,7 @@ public class Post : ModelBase, IIdentifiedResource, IActivity
[JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!;
public Guid PublisherId { get; set; }
- public Publisher.Publisher Publisher { get; set; } = null!;
+ public Publisher Publisher { get; set; } = null!;
public ICollection Reactions { get; set; } = new List();
public ICollection Tags { get; set; } = new List();
@@ -71,9 +71,9 @@ public class Post : ModelBase, IIdentifiedResource, IActivity
public string ResourceIdentifier => $"post/{Id}";
- public Activity.Activity ToActivity()
+ public Activity ToActivity()
{
- return new Activity.Activity()
+ return new Activity()
{
CreatedAt = PublishedAt ?? CreatedAt,
UpdatedAt = UpdatedAt,
@@ -109,7 +109,7 @@ public class PostCollection : ModelBase
[MaxLength(256)] public string? Name { get; set; }
[MaxLength(4096)] public string? Description { get; set; }
- public Publisher.Publisher Publisher { get; set; } = null!;
+ public Publisher Publisher { get; set; } = null!;
public ICollection Posts { get; set; } = new List();
}
@@ -130,5 +130,5 @@ public class PostReaction : ModelBase
public Guid PostId { get; set; }
[JsonIgnore] public Post Post { get; set; } = null!;
public Guid AccountId { get; set; }
- public Account.Account Account { get; set; } = null!;
+ public Account Account { get; set; } = null!;
}
diff --git a/DysonNetwork.Sphere/Publisher/Publisher.cs b/DysonNetwork.Common/Models/Publisher.cs
similarity index 84%
rename from DysonNetwork.Sphere/Publisher/Publisher.cs
rename to DysonNetwork.Common/Models/Publisher.cs
index bae02ad..fa34e0b 100644
--- a/DysonNetwork.Sphere/Publisher/Publisher.cs
+++ b/DysonNetwork.Common/Models/Publisher.cs
@@ -1,12 +1,11 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Post;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Interfaces;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Publisher;
+namespace DysonNetwork.Common.Models;
public enum PublisherType
{
@@ -30,9 +29,9 @@ public class Publisher : ModelBase, IIdentifiedResource
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
- [Column(TypeName = "jsonb")] public Account.VerificationMark? Verification { get; set; }
+ [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
- [JsonIgnore] public ICollection Posts { get; set; } = new List();
+ [JsonIgnore] public ICollection Posts { get; set; } = new List();
[JsonIgnore] public ICollection Collections { get; set; } = new List();
[JsonIgnore] public ICollection Members { get; set; } = new List();
[JsonIgnore] public ICollection Features { get; set; } = new List();
@@ -41,9 +40,9 @@ public class Publisher : ModelBase, IIdentifiedResource
public ICollection Subscriptions { get; set; } = new List();
public Guid? AccountId { get; set; }
- public Account.Account? Account { get; set; }
+ public Account? Account { get; set; }
public Guid? RealmId { get; set; }
- [JsonIgnore] public Realm.Realm? Realm { get; set; }
+ [JsonIgnore] public Realm? Realm { get; set; }
public string ResourceIdentifier => $"publisher/{Id}";
}
@@ -61,7 +60,7 @@ public class PublisherMember : ModelBase
public Guid PublisherId { get; set; }
[JsonIgnore] public Publisher Publisher { get; set; } = null!;
public Guid AccountId { get; set; }
- public Account.Account Account { get; set; } = null!;
+ public Account Account { get; set; } = null!;
public PublisherMemberRole Role { get; set; } = PublisherMemberRole.Viewer;
public Instant? JoinedAt { get; set; }
@@ -81,7 +80,7 @@ public class PublisherSubscription : ModelBase
public Guid PublisherId { get; set; }
[JsonIgnore] public Publisher Publisher { get; set; } = null!;
public Guid AccountId { get; set; }
- [JsonIgnore] public Account.Account Account { get; set; } = null!;
+ [JsonIgnore] public Account Account { get; set; } = null!;
public PublisherSubscriptionStatus Status { get; set; } = PublisherSubscriptionStatus.Active;
public int Tier { get; set; } = 0;
diff --git a/DysonNetwork.Sphere/Realm/Realm.cs b/DysonNetwork.Common/Models/Realm.cs
similarity index 80%
rename from DysonNetwork.Sphere/Realm/Realm.cs
rename to DysonNetwork.Common/Models/Realm.cs
index 9a26322..7490454 100644
--- a/DysonNetwork.Sphere/Realm/Realm.cs
+++ b/DysonNetwork.Common/Models/Realm.cs
@@ -1,12 +1,11 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Chat;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Interfaces;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Realm;
+namespace DysonNetwork.Common.Models;
[Index(nameof(Slug), IsUnique = true)]
public class Realm : ModelBase, IIdentifiedResource
@@ -25,14 +24,13 @@ public class Realm : ModelBase, IIdentifiedResource
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Picture { get; set; }
[Column(TypeName = "jsonb")] public CloudFileReferenceObject? Background { get; set; }
- [Column(TypeName = "jsonb")] public Account.VerificationMark? Verification { get; set; }
+ [Column(TypeName = "jsonb")] public VerificationMark? Verification { get; set; }
[JsonIgnore] public ICollection Members { get; set; } = new List();
[JsonIgnore] public ICollection ChatRooms { get; set; } = new List();
- [JsonIgnore] public ICollection RealmTags { get; set; } = new List();
public Guid AccountId { get; set; }
- [JsonIgnore] public Account.Account Account { get; set; } = null!;
+ [JsonIgnore] public Account Account { get; set; } = null!;
public string ResourceIdentifier => $"realm/{Id}";
}
@@ -49,7 +47,7 @@ public class RealmMember : ModelBase
public Guid RealmId { get; set; }
public Realm Realm { get; set; } = null!;
public Guid AccountId { get; set; }
- public Account.Account Account { get; set; } = null!;
+ public Account Account { get; set; } = null!;
public int Role { get; set; } = RealmMemberRole.Normal;
public Instant? JoinedAt { get; set; }
diff --git a/DysonNetwork.Sphere/Chat/RealtimeCall.cs b/DysonNetwork.Common/Models/RealtimeCall.cs
similarity index 89%
rename from DysonNetwork.Sphere/Chat/RealtimeCall.cs
rename to DysonNetwork.Common/Models/RealtimeCall.cs
index d363a09..8c9ca90 100644
--- a/DysonNetwork.Sphere/Chat/RealtimeCall.cs
+++ b/DysonNetwork.Common/Models/RealtimeCall.cs
@@ -1,12 +1,8 @@
-using System;
-using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
-using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Chat.Realtime;
using NodaTime;
-namespace DysonNetwork.Sphere.Chat;
+namespace DysonNetwork.Common.Models;
public class RealtimeCall : ModelBase
{
diff --git a/DysonNetwork.Sphere/Account/Relationship.cs b/DysonNetwork.Common/Models/Relationship.cs
similarity index 70%
rename from DysonNetwork.Sphere/Account/Relationship.cs
rename to DysonNetwork.Common/Models/Relationship.cs
index b46b60a..af7c98f 100644
--- a/DysonNetwork.Sphere/Account/Relationship.cs
+++ b/DysonNetwork.Common/Models/Relationship.cs
@@ -1,6 +1,6 @@
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
public enum RelationshipStatus : short
{
@@ -12,9 +12,9 @@ public enum RelationshipStatus : short
public class Relationship : ModelBase
{
public Guid AccountId { get; set; }
- public Account Account { get; set; } = null!;
+ public Models.Account Account { get; set; } = null!;
public Guid RelatedId { get; set; }
- public Account Related { get; set; } = null!;
+ public Models.Account Related { get; set; } = null!;
public Instant? ExpiredAt { get; set; }
diff --git a/DysonNetwork.Sphere/Wallet/Subscription.cs b/DysonNetwork.Common/Models/Subscription.cs
similarity index 98%
rename from DysonNetwork.Sphere/Wallet/Subscription.cs
rename to DysonNetwork.Common/Models/Subscription.cs
index d089f5c..0a58591 100644
--- a/DysonNetwork.Sphere/Wallet/Subscription.cs
+++ b/DysonNetwork.Common/Models/Subscription.cs
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Wallet;
+namespace DysonNetwork.Common.Models;
public record class SubscriptionTypeData(
string Identifier,
@@ -138,7 +138,7 @@ public class Subscription : ModelBase
public Instant? RenewalAt { get; set; }
public Guid AccountId { get; set; }
- public Account.Account Account { get; set; } = null!;
+ public Account Account { get; set; } = null!;
[NotMapped]
public bool IsAvailable
diff --git a/DysonNetwork.Sphere/Account/VerificationMark.cs b/DysonNetwork.Common/Models/VerificationMark.cs
similarity index 94%
rename from DysonNetwork.Sphere/Account/VerificationMark.cs
rename to DysonNetwork.Common/Models/VerificationMark.cs
index 4f0e6c3..c0d041d 100644
--- a/DysonNetwork.Sphere/Account/VerificationMark.cs
+++ b/DysonNetwork.Common/Models/VerificationMark.cs
@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Common.Models;
///
/// The verification info of a resource
diff --git a/DysonNetwork.Sphere/Wallet/Wallet.cs b/DysonNetwork.Common/Models/Wallet.cs
similarity index 86%
rename from DysonNetwork.Sphere/Wallet/Wallet.cs
rename to DysonNetwork.Common/Models/Wallet.cs
index e69d251..5d2db7e 100644
--- a/DysonNetwork.Sphere/Wallet/Wallet.cs
+++ b/DysonNetwork.Common/Models/Wallet.cs
@@ -1,7 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
-namespace DysonNetwork.Sphere.Wallet;
+namespace DysonNetwork.Common.Models;
public class Wallet : ModelBase
{
@@ -10,7 +10,7 @@ public class Wallet : ModelBase
public ICollection Pockets { get; set; } = new List();
public Guid AccountId { get; set; }
- public Account.Account Account { get; set; } = null!;
+
}
public class WalletPocket : ModelBase
diff --git a/DysonNetwork.Sphere/Storage/CacheService.cs b/DysonNetwork.Common/Services/CacheService.cs
similarity index 99%
rename from DysonNetwork.Sphere/Storage/CacheService.cs
rename to DysonNetwork.Common/Services/CacheService.cs
index 7562a0f..fee5d59 100644
--- a/DysonNetwork.Sphere/Storage/CacheService.cs
+++ b/DysonNetwork.Common/Services/CacheService.cs
@@ -4,7 +4,7 @@ using NodaTime;
using NodaTime.Serialization.JsonNet;
using StackExchange.Redis;
-namespace DysonNetwork.Sphere.Storage;
+namespace DysonNetwork.Common.Services;
///
/// Represents a distributed lock that can be used to synchronize access across multiple processes
diff --git a/DysonNetwork.Pass/Connection/DummyConnection.cs b/DysonNetwork.Pass/Connection/DummyConnection.cs
new file mode 100644
index 0000000..3ad86c8
--- /dev/null
+++ b/DysonNetwork.Pass/Connection/DummyConnection.cs
@@ -0,0 +1,17 @@
+namespace DysonNetwork.Pass.Connection;
+
+public class GeoIpService
+{
+ // Dummy class
+}
+
+public class WebSocketService
+{
+ // Dummy class
+}
+
+public class WebSocketPacket
+{
+ public string Type { get; set; } = null!;
+ public object Data { get; set; } = null!;
+}
diff --git a/DysonNetwork.Pass/Data/PassDatabase.cs b/DysonNetwork.Pass/Data/PassDatabase.cs
new file mode 100644
index 0000000..38b4c9f
--- /dev/null
+++ b/DysonNetwork.Pass/Data/PassDatabase.cs
@@ -0,0 +1,196 @@
+using System.Linq.Expressions;
+using System.Reflection;
+using DysonNetwork.Common.Models;
+using DysonNetwork.Pass.Permission;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Design;
+using NodaTime;
+using Quartz;
+
+namespace DysonNetwork.Pass.Data;
+
+public class PassDatabase(
+ DbContextOptions options,
+ IConfiguration configuration
+) : DbContext(options)
+{
+ public DbSet PermissionNodes { get; set; }
+ public DbSet PermissionGroups { get; set; }
+ public DbSet PermissionGroupMembers { get; set; }
+
+ public DbSet MagicSpells { get; set; }
+ public DbSet Accounts { get; set; }
+ public DbSet AccountConnections { get; set; }
+ public DbSet AccountProfiles { get; set; }
+ public DbSet AccountContacts { get; set; }
+ public DbSet AccountAuthFactors { get; set; }
+ public DbSet AccountRelationships { get; set; }
+ public DbSet Notifications { get; set; }
+ public DbSet Badges { get; set; }
+ public DbSet ActionLogs { get; set; }
+ public DbSet AbuseReports { get; set; }
+
+ public DbSet AuthSessions { get; set; }
+ public DbSet AuthChallenges { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseNpgsql(
+ configuration.GetConnectionString("App"),
+ opt => opt
+ .ConfigureDataSource(optSource => optSource.EnableDynamicJson())
+ .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
+ .UseNetTopologySuite()
+ .UseNodaTime()
+ ).UseSnakeCaseNamingConvention();
+
+ optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) =>
+ {
+ // Add any initial seeding logic here if needed for PassDatabase
+ });
+
+ optionsBuilder.UseSeeding((context, _) => {});
+
+ base.OnConfiguring(optionsBuilder);
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity()
+ .HasKey(pg => new { pg.GroupId, pg.Actor });
+ modelBuilder.Entity()
+ .HasOne(pg => pg.Group)
+ .WithMany(g => g.Members)
+ .HasForeignKey(pg => pg.GroupId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ modelBuilder.Entity()
+ .HasKey(r => new { FromAccountId = r.AccountId, ToAccountId = r.RelatedId });
+ modelBuilder.Entity()
+ .HasOne(r => r.Account)
+ .WithMany(a => a.OutgoingRelationships)
+ .HasForeignKey(r => r.AccountId);
+ modelBuilder.Entity()
+ .HasOne(r => r.Related)
+ .WithMany(a => a.IncomingRelationships)
+ .HasForeignKey(r => r.RelatedId);
+
+ // Automatically apply soft-delete filter to all entities inheriting BaseModel
+ foreach (var entityType in modelBuilder.Model.GetEntityTypes())
+ {
+ if (!typeof(ModelBase).IsAssignableFrom(entityType.ClrType)) continue;
+ var method = typeof(PassDatabase)
+ .GetMethod(nameof(SetSoftDeleteFilter),
+ BindingFlags.NonPublic | BindingFlags.Static)!
+ .MakeGenericMethod(entityType.ClrType);
+
+ method.Invoke(null, [modelBuilder]);
+ }
+ }
+
+ private static void SetSoftDeleteFilter(ModelBuilder modelBuilder)
+ where TEntity : ModelBase
+ {
+ modelBuilder.Entity().HasQueryFilter(e => e.DeletedAt == null);
+ }
+
+ public override async Task SaveChangesAsync(CancellationToken cancellationToken = default)
+ {
+ var now = SystemClock.Instance.GetCurrentInstant();
+
+ foreach (var entry in ChangeTracker.Entries())
+ {
+ switch (entry.State)
+ {
+ case EntityState.Added:
+ entry.Entity.CreatedAt = now;
+ entry.Entity.UpdatedAt = now;
+ break;
+ case EntityState.Modified:
+ entry.Entity.UpdatedAt = now;
+ break;
+ case EntityState.Deleted:
+ entry.State = EntityState.Modified;
+ entry.Entity.DeletedAt = now;
+ break;
+ case EntityState.Detached:
+ case EntityState.Unchanged:
+ default:
+ break;
+ }
+ }
+
+ return await base.SaveChangesAsync(cancellationToken);
+ }
+}
+
+public class PassDatabaseFactory : IDesignTimeDbContextFactory
+{
+ public PassDatabase CreateDbContext(string[] args)
+ {
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json")
+ .Build();
+
+ var optionsBuilder = new DbContextOptionsBuilder();
+ return new PassDatabase(optionsBuilder.Options, configuration);
+ }
+}
+
+public class PassDatabaseRecyclingJob(PassDatabase db, ILogger logger) : IJob
+{
+ public async Task Execute(IJobExecutionContext context)
+ {
+ var now = SystemClock.Instance.GetCurrentInstant();
+
+ logger.LogInformation("Cleaning up expired records...");
+
+ // Expired relationships
+ var affectedRows = await db.AccountRelationships
+ .Where(x => x.ExpiredAt != null && x.ExpiredAt <= now)
+ .ExecuteDeleteAsync();
+ logger.LogDebug("Removed {Count} records of expired relationships.", affectedRows);
+
+ logger.LogInformation("Deleting soft-deleted records...");
+
+ var threshold = now - Duration.FromDays(7);
+
+ var entityTypes = db.Model.GetEntityTypes()
+ .Where(t => typeof(ModelBase).IsAssignableFrom(t.ClrType) && t.ClrType != typeof(ModelBase))
+ .Select(t => t.ClrType);
+
+ foreach (var entityType in entityTypes)
+ {
+ var set = (IQueryable)db.GetType().GetMethod(nameof(DbContext.Set), Type.EmptyTypes)!
+ .MakeGenericMethod(entityType).Invoke(db, null)!;
+ var parameter = Expression.Parameter(entityType, "e");
+ var property = Expression.Property(parameter, nameof(ModelBase.DeletedAt));
+ var condition = Expression.LessThan(property, Expression.Constant(threshold, typeof(Instant?)));
+ var notNull = Expression.NotEqual(property, Expression.Constant(null, typeof(Instant?)));
+ var finalCondition = Expression.AndAlso(notNull, condition);
+ var lambda = Expression.Lambda(finalCondition, parameter);
+
+ var queryable = set.Provider.CreateQuery(
+ Expression.Call(
+ typeof(Queryable),
+ "Where",
+ [entityType],
+ set.Expression,
+ Expression.Quote(lambda)
+ )
+ );
+
+ var toListAsync = typeof(EntityFrameworkQueryableExtensions)
+ .GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync))!
+ .MakeGenericMethod(entityType);
+
+ var items = await (dynamic)toListAsync.Invoke(null, [queryable, CancellationToken.None])!;
+ db.RemoveRange(items);
+ }
+
+ await db.SaveChangesAsync();
+ }
+}
diff --git a/DysonNetwork.Pass/Developer/Developer.cs b/DysonNetwork.Pass/Developer/Developer.cs
new file mode 100644
index 0000000..9c57234
--- /dev/null
+++ b/DysonNetwork.Pass/Developer/Developer.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Text.Json.Serialization;
+
+namespace DysonNetwork.Pass.Developer;
+
+public class CustomApp
+{
+ public Guid Id { get; set; } = Guid.NewGuid();
+ [MaxLength(256)] public string Name { get; set; } = null!;
+ [MaxLength(4096)] public string Description { get; set; } = null!;
+ [MaxLength(1024)] public string Homepage { get; set; } = null!;
+ [MaxLength(1024)] public string CallbackUrl { get; set; } = null!;
+ [Column(TypeName = "jsonb")] public OauthConfig? OauthConfig { get; set; }
+
+ public ICollection Secrets { get; set; } = new List();
+}
+
+public class CustomAppSecret
+{
+ public Guid Id { get; set; } = Guid.NewGuid();
+ [MaxLength(4096)] public string Secret { get; set; } = null!;
+ public bool IsOidc { get; set; } = false;
+ public DateTime? ExpiredAt { get; set; }
+
+ public Guid AppId { get; set; }
+ [JsonIgnore] public CustomApp App { get; set; } = null!;
+}
+
+public class OauthConfig
+{
+ public List? AllowedScopes { get; set; }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj
new file mode 100644
index 0000000..b1d44db
--- /dev/null
+++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj
@@ -0,0 +1,49 @@
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.http b/DysonNetwork.Pass/DysonNetwork.Pass.http
new file mode 100644
index 0000000..2769bfa
--- /dev/null
+++ b/DysonNetwork.Pass/DysonNetwork.Pass.http
@@ -0,0 +1,6 @@
+@DysonNetwork.Pass_HostAddress = http://localhost:5048
+
+GET {{DysonNetwork.Pass_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/DysonNetwork.Pass/Email/EmailModels.cs b/DysonNetwork.Pass/Email/EmailModels.cs
new file mode 100644
index 0000000..399e41e
--- /dev/null
+++ b/DysonNetwork.Pass/Email/EmailModels.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Pass.Email;
+
+public class EmailModels
+{
+ // Dummy class for EmailModels
+}
diff --git a/DysonNetwork.Pass/Email/EmailService.cs b/DysonNetwork.Pass/Email/EmailService.cs
new file mode 100644
index 0000000..5fdafbf
--- /dev/null
+++ b/DysonNetwork.Pass/Email/EmailService.cs
@@ -0,0 +1,10 @@
+namespace DysonNetwork.Pass.Email;
+
+public class EmailService
+{
+ public Task SendTemplatedEmailAsync(string recipientName, string recipientEmail, string subject, TModel model) where TTemplate : class where TModel : class
+ {
+ // Dummy implementation
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Pass/Email/RazorViewRenderer.cs b/DysonNetwork.Pass/Email/RazorViewRenderer.cs
new file mode 100644
index 0000000..59cb3ea
--- /dev/null
+++ b/DysonNetwork.Pass/Email/RazorViewRenderer.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Pass.Email;
+
+public class RazorViewRenderer
+{
+ // Dummy class for RazorViewRenderer
+}
diff --git a/DysonNetwork.Pass/Extensions/OptionalQueryExtensions.cs b/DysonNetwork.Pass/Extensions/OptionalQueryExtensions.cs
new file mode 100644
index 0000000..7531d28
--- /dev/null
+++ b/DysonNetwork.Pass/Extensions/OptionalQueryExtensions.cs
@@ -0,0 +1,45 @@
+using System.Collections;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Query;
+
+namespace DysonNetwork.Pass.Extensions;
+
+public static class OptionalQueryExtensions
+{
+ public static IQueryable If<
+ T
+ >(
+ this IQueryable source,
+ bool condition,
+ Func, IQueryable> transform
+ )
+ {
+ return condition ? transform(source) : source;
+ }
+
+ public static IQueryable If<
+ T,
+ TP
+ >(
+ this IIncludableQueryable source,
+ bool condition,
+ Func, IQueryable> transform
+ )
+ where T : class
+ {
+ return condition ? transform(source) : source;
+ }
+
+ public static IQueryable If<
+ T,
+ TP
+ >(
+ this IIncludableQueryable> source,
+ bool condition,
+ Func>, IQueryable> transform
+ )
+ where T : class
+ {
+ return condition ? transform(source) : source;
+ }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Pass/Features/Account/Controllers/AccountController.cs
similarity index 88%
rename from DysonNetwork.Sphere/Account/AccountController.cs
rename to DysonNetwork.Pass/Features/Account/Controllers/AccountController.cs
index 29c4871..99c7d75 100644
--- a/DysonNetwork.Sphere/Account/AccountController.cs
+++ b/DysonNetwork.Pass/Features/Account/Controllers/AccountController.cs
@@ -1,28 +1,27 @@
using System.ComponentModel.DataAnnotations;
-using DysonNetwork.Sphere.Auth;
-using DysonNetwork.Sphere.Permission;
-using Microsoft.AspNetCore.Authorization;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Pass.Features.Auth;
+using DysonNetwork.Common.Models;
+using DysonNetwork.Pass.Features.Account.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-using NodaTime.Extensions;
-using System.Collections.Generic;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account.Controllers;
[ApiController]
[Route("/accounts")]
public class AccountController(
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
AccountService accounts,
AccountEventService events
) : ControllerBase
{
[HttpGet("{name}")]
- [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task> GetByName(string name)
+ public async Task> GetByName(string name)
{
var account = await db.Accounts
.Include(e => e.Badges)
@@ -73,9 +72,9 @@ public class AccountController(
}
[HttpPost]
- [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
- public async Task> CreateAccount([FromBody] AccountCreateRequest request)
+ public async Task> CreateAccount([FromBody] AccountCreateRequest request)
{
if (!await auth.ValidateCaptcha(request.CaptchaToken)) return BadRequest("Invalid captcha token.");
@@ -163,7 +162,7 @@ public class AccountController(
}
[HttpGet("search")]
- public async Task> Search([FromQuery] string query, [FromQuery] int take = 20)
+ public async Task> Search([FromQuery] string query, [FromQuery] int take = 20)
{
if (string.IsNullOrWhiteSpace(query))
return [];
diff --git a/DysonNetwork.Sphere/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Features/Account/Controllers/AccountCurrentController.cs
similarity index 97%
rename from DysonNetwork.Sphere/Account/AccountCurrentController.cs
rename to DysonNetwork.Pass/Features/Account/Controllers/AccountCurrentController.cs
index 453b285..27117f4 100644
--- a/DysonNetwork.Sphere/Account/AccountCurrentController.cs
+++ b/DysonNetwork.Pass/Features/Account/Controllers/AccountCurrentController.cs
@@ -1,20 +1,20 @@
using System.ComponentModel.DataAnnotations;
-using DysonNetwork.Sphere.Auth;
-using DysonNetwork.Sphere.Permission;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Models;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Pass.Features.Account.Services;
+using DysonNetwork.Pass.Features.Auth;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-using Org.BouncyCastle.Utilities;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account.Controllers;
[Authorize]
[ApiController]
[Route("/accounts/me")]
public class AccountCurrentController(
- AppDatabase db,
+ PassDatabase db,
AccountService accounts,
FileReferenceService fileRefService,
AccountEventService events,
@@ -22,10 +22,10 @@ public class AccountCurrentController(
) : ControllerBase
{
[HttpGet]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public async Task> GetCurrentIdentity()
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> GetCurrentIdentity()
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var userId = currentUser.Id;
var account = await db.Accounts
diff --git a/DysonNetwork.Sphere/Account/MagicSpellController.cs b/DysonNetwork.Pass/Features/Account/Controllers/MagicSpellController.cs
similarity index 70%
rename from DysonNetwork.Sphere/Account/MagicSpellController.cs
rename to DysonNetwork.Pass/Features/Account/Controllers/MagicSpellController.cs
index d29bb7d..faaa877 100644
--- a/DysonNetwork.Sphere/Account/MagicSpellController.cs
+++ b/DysonNetwork.Pass/Features/Account/Controllers/MagicSpellController.cs
@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Mvc;
-namespace DysonNetwork.Sphere.Account;
+using DysonNetwork.Pass.Data;
+
+namespace DysonNetwork.Pass.Features.Account;
[ApiController]
[Route("/spells")]
-public class MagicSpellController(AppDatabase db, MagicSpellService sp) : ControllerBase
+public class MagicSpellController(PassDatabase db, MagicSpellService sp) : ControllerBase
{
[HttpPost("{spellId:guid}/resend")]
public async Task ResendMagicSpell(Guid spellId)
diff --git a/DysonNetwork.Sphere/Account/NotificationController.cs b/DysonNetwork.Pass/Features/Account/Controllers/NotificationController.cs
similarity index 91%
rename from DysonNetwork.Sphere/Account/NotificationController.cs
rename to DysonNetwork.Pass/Features/Account/Controllers/NotificationController.cs
index 0e18633..f90b3b3 100644
--- a/DysonNetwork.Sphere/Account/NotificationController.cs
+++ b/DysonNetwork.Pass/Features/Account/Controllers/NotificationController.cs
@@ -1,23 +1,23 @@
using System.ComponentModel.DataAnnotations;
-using DysonNetwork.Sphere.Auth;
-using DysonNetwork.Sphere.Permission;
+using DysonNetwork.Pass.Features.Auth;
+using DysonNetwork.Common.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account;
[ApiController]
[Route("/notifications")]
-public class NotificationController(AppDatabase db, NotificationService nty) : ControllerBase
+public class NotificationController(PassDatabase db, NotificationService nty) : ControllerBase
{
[HttpGet("count")]
[Authorize]
public async Task> CountUnreadNotifications()
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
- if (currentUserValue is not Account currentUser) return Unauthorized();
+ if (currentUserValue is not Common.Models.Account currentUser) return Unauthorized();
var count = await db.Notifications
.Where(s => s.AccountId == currentUser.Id && s.ViewedAt == null)
@@ -35,7 +35,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
)
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
- if (currentUserValue is not Account currentUser) return Unauthorized();
+ if (currentUserValue is not Common.Models.Account currentUser) return Unauthorized();
var totalCount = await db.Notifications
.Where(s => s.AccountId == currentUser.Id)
@@ -67,7 +67,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
{
HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue);
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
- var currentUser = currentUserValue as Account;
+ var currentUser = currentUserValue as Common.Models.Account;
if (currentUser == null) return Unauthorized();
var currentSession = currentSessionValue as Session;
if (currentSession == null) return Unauthorized();
@@ -85,7 +85,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C
{
HttpContext.Items.TryGetValue("CurrentSession", out var currentSessionValue);
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
- var currentUser = currentUserValue as Account;
+ var currentUser = currentUserValue as Common.Models.Account;
if (currentUser == null) return Unauthorized();
var currentSession = currentSessionValue as Session;
if (currentSession == null) return Unauthorized();
diff --git a/DysonNetwork.Sphere/Account/RelationshipController.cs b/DysonNetwork.Pass/Features/Account/Controllers/RelationshipController.cs
similarity index 83%
rename from DysonNetwork.Sphere/Account/RelationshipController.cs
rename to DysonNetwork.Pass/Features/Account/Controllers/RelationshipController.cs
index 5b0a2f4..6866dc7 100644
--- a/DysonNetwork.Sphere/Account/RelationshipController.cs
+++ b/DysonNetwork.Pass/Features/Account/Controllers/RelationshipController.cs
@@ -1,21 +1,23 @@
using System.ComponentModel.DataAnnotations;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Common.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account;
[ApiController]
[Route("/relationships")]
-public class RelationshipController(AppDatabase db, RelationshipService rels) : ControllerBase
+public class RelationshipController(PassDatabase db, RelationshipService rels) : ControllerBase
{
[HttpGet]
[Authorize]
public async Task>> ListRelationships([FromQuery] int offset = 0,
[FromQuery] int take = 20)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var userId = currentUser.Id;
var query = db.AccountRelationships.AsQueryable()
@@ -46,7 +48,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task>> ListSentRequests()
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var relationships = await db.AccountRelationships
.Where(r => r.AccountId == currentUser.Id && r.Status == RelationshipStatus.Pending)
@@ -69,7 +71,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
public async Task> CreateRelationship(Guid userId,
[FromBody] RelationshipRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found.");
@@ -92,7 +94,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
public async Task> UpdateRelationship(Guid userId,
[FromBody] RelationshipRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
try
{
@@ -113,7 +115,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task> GetRelationship(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
var queries = db.AccountRelationships.AsQueryable()
@@ -133,7 +135,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task> SendFriendRequest(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found.");
@@ -158,7 +160,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task DeleteFriendRequest(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
try
{
@@ -175,7 +177,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task> AcceptFriendRequest(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending);
if (relationship is null) return NotFound("Friend request was not found.");
@@ -195,7 +197,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task> DeclineFriendRequest(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var relationship = await rels.GetRelationship(userId, currentUser.Id, RelationshipStatus.Pending);
if (relationship is null) return NotFound("Friend request was not found.");
@@ -215,7 +217,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task> BlockUser(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found.");
@@ -235,7 +237,7 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
[Authorize]
public async Task> UnblockUser(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
+ if (HttpContext.Items["CurrentUser"] is not Common.Models.Account currentUser) return Unauthorized();
var relatedUser = await db.Accounts.FindAsync(userId);
if (relatedUser is null) return NotFound("Account was not found.");
diff --git a/DysonNetwork.Pass/Features/Account/Interfaces/IAccountEventService.cs b/DysonNetwork.Pass/Features/Account/Interfaces/IAccountEventService.cs
new file mode 100644
index 0000000..2f21e4c
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Account/Interfaces/IAccountEventService.cs
@@ -0,0 +1,20 @@
+using System.Globalization;
+using DysonNetwork.Common.Models;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Localization;
+using NodaTime;
+
+namespace DysonNetwork.Pass.Features.Account.Interfaces;
+
+public interface IAccountEventService
+{
+ void PurgeStatusCache(Guid userId);
+ Task GetStatus(Guid userId);
+ Task> GetStatuses(List userIds);
+ Task CreateStatus(Models.Account user, Status status);
+ Task ClearStatus(Models.Account user, Status status);
+ Task CheckInDailyDoAskCaptcha(Models.Account user);
+ Task CheckInDailyIsAvailable(Models.Account user);
+ Task CheckInDaily(Models.Account user);
+ Task> GetEventCalendar(Models.Account user, int month, int year = 0, bool replaceInvisible = false);
+}
diff --git a/DysonNetwork.Pass/Features/Account/Interfaces/IAccountService.cs b/DysonNetwork.Pass/Features/Account/Interfaces/IAccountService.cs
new file mode 100644
index 0000000..d191c87
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Account/Interfaces/IAccountService.cs
@@ -0,0 +1,46 @@
+using System.Globalization;
+using NodaTime;
+using DysonNetwork.Pass.Features.Auth;
+using DysonNetwork.Pass.Features.Auth.OpenId;
+using DysonNetwork.Common.Models;
+
+namespace DysonNetwork.Pass.Features.Account.Interfaces;
+
+public interface IAccountService
+{
+ static void SetCultureInfo(Models.Account account) { }
+ static void SetCultureInfo(string? languageCode) { }
+ Task PurgeAccountCache(Models.Account account);
+ Task LookupAccount(string probe);
+ Task LookupAccountByConnection(string identifier, string provider);
+ Task GetAccountLevel(Guid accountId);
+ Task CreateAccount(
+ string name,
+ string nick,
+ string email,
+ string? password,
+ string language = "en-US",
+ bool isEmailVerified = false,
+ bool isActivated = false
+ );
+ Task CreateAccount(OidcUserInfo userInfo);
+ Task RequestAccountDeletion(Models.Account account);
+ Task RequestPasswordReset(Models.Account account);
+ Task CheckAuthFactorExists(Models.Account account, AccountAuthFactorType type);
+ Task CreateAuthFactor(Models.Account account, AccountAuthFactorType type, string? secret);
+ Task EnableAuthFactor(AccountAuthFactor factor, string? code);
+ Task DisableAuthFactor(AccountAuthFactor factor);
+ Task DeleteAuthFactor(AccountAuthFactor factor);
+ Task SendFactorCode(Models.Account account, AccountAuthFactor factor, string? hint = null);
+ Task VerifyFactorCode(AccountAuthFactor factor, string code);
+ Task UpdateSessionLabel(Models.Account account, Guid sessionId, string label);
+ Task DeleteSession(Models.Account account, Guid sessionId);
+ Task CreateContactMethod(Models.Account account, AccountContactType type, string content);
+ Task VerifyContactMethod(Models.Account account, AccountContact contact);
+ Task SetContactMethodPrimary(Models.Account account, AccountContact contact);
+ Task DeleteContactMethod(Models.Account account, AccountContact contact);
+ Task GrantBadge(Models.Account account, Badge badge);
+ Task RevokeBadge(Models.Account account, Guid badgeId);
+ Task ActiveBadge(Models.Account account, Guid badgeId);
+ Task EnsureAccountProfileCreated();
+}
diff --git a/DysonNetwork.Pass/Features/Account/Interfaces/IAccountUsernameService.cs b/DysonNetwork.Pass/Features/Account/Interfaces/IAccountUsernameService.cs
new file mode 100644
index 0000000..43afdb5
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Account/Interfaces/IAccountUsernameService.cs
@@ -0,0 +1,9 @@
+namespace DysonNetwork.Pass.Features.Account.Interfaces;
+
+public interface IAccountUsernameService
+{
+ Task GenerateUniqueUsernameAsync(string baseName);
+ string SanitizeUsername(string username);
+ Task IsUsernameExistsAsync(string username);
+ Task GenerateUsernameFromEmailAsync(string email);
+}
diff --git a/DysonNetwork.Pass/Features/Account/Interfaces/IActionLogService.cs b/DysonNetwork.Pass/Features/Account/Interfaces/IActionLogService.cs
new file mode 100644
index 0000000..7c01898
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Account/Interfaces/IActionLogService.cs
@@ -0,0 +1,9 @@
+using DysonNetwork.Pass.Connection;
+
+namespace DysonNetwork.Pass.Features.Account.Interfaces;
+
+public interface IActionLogService
+{
+ void CreateActionLog(Guid accountId, string action, Dictionary meta);
+ void CreateActionLogFromRequest(string action, Dictionary meta, HttpRequest request, Models.Account? account = null);
+}
diff --git a/DysonNetwork.Pass/Features/Account/Interfaces/IMagicSpellService.cs b/DysonNetwork.Pass/Features/Account/Interfaces/IMagicSpellService.cs
new file mode 100644
index 0000000..359567e
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Account/Interfaces/IMagicSpellService.cs
@@ -0,0 +1,20 @@
+using NodaTime;
+using DysonNetwork.Pass.Features.Account;
+using DysonNetwork.Common.Models;
+
+namespace DysonNetwork.Pass.Features.Account.Interfaces;
+
+public interface IMagicSpellService
+{
+ Task CreateMagicSpell(
+ Models.Account account,
+ MagicSpellType type,
+ Dictionary meta,
+ Instant? expiredAt = null,
+ Instant? affectedAt = null,
+ bool preventRepeat = false
+ );
+ Task NotifyMagicSpell(MagicSpell spell, bool bypassVerify = false);
+ Task ApplyMagicSpell(MagicSpell spell);
+ Task ApplyPasswordReset(MagicSpell spell, string newPassword);
+}
diff --git a/DysonNetwork.Pass/Features/Account/Interfaces/INotificationService.cs b/DysonNetwork.Pass/Features/Account/Interfaces/INotificationService.cs
new file mode 100644
index 0000000..ceea40f
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Account/Interfaces/INotificationService.cs
@@ -0,0 +1,31 @@
+using System.Collections.Generic;
+using DysonNetwork.Common.Models;
+using NodaTime;
+
+namespace DysonNetwork.Pass.Features.Account.Interfaces;
+
+public interface INotificationService
+{
+ Task UnsubscribePushNotifications(string deviceId);
+ Task SubscribePushNotification(
+ Models.Account account,
+ NotificationPushProvider provider,
+ string deviceId,
+ string deviceToken
+ );
+ Task SendNotification(
+ Models.Account account,
+ string topic,
+ string? title = null,
+ string? subtitle = null,
+ string? content = null,
+ Dictionary? meta = null,
+ string? actionUri = null,
+ bool isSilent = false,
+ bool save = true
+ );
+ Task DeliveryNotification(Notification notification);
+ Task MarkNotificationsViewed(ICollection notifications);
+ Task BroadcastNotification(Notification notification, bool save = false);
+ Task SendNotificationBatch(Notification notification, List accounts, bool save = false);
+}
diff --git a/DysonNetwork.Pass/Features/Account/Interfaces/IRelationshipService.cs b/DysonNetwork.Pass/Features/Account/Interfaces/IRelationshipService.cs
new file mode 100644
index 0000000..e3158f8
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Account/Interfaces/IRelationshipService.cs
@@ -0,0 +1,28 @@
+using DysonNetwork.Common.Models;
+using NodaTime;
+
+namespace DysonNetwork.Pass.Features.Account.Interfaces;
+
+public interface IRelationshipService
+{
+ Task HasExistingRelationship(Guid accountId, Guid relatedId);
+ Task GetRelationship(
+ Guid accountId,
+ Guid relatedId,
+ RelationshipStatus? status = null,
+ bool ignoreExpired = false
+ );
+ Task CreateRelationship(Models.Account sender, Models.Account target, RelationshipStatus status);
+ Task BlockAccount(Models.Account sender, Models.Account target);
+ Task UnblockAccount(Models.Account sender, Models.Account target);
+ Task SendFriendRequest(Models.Account sender, Models.Account target);
+ Task DeleteFriendRequest(Guid accountId, Guid relatedId);
+ Task AcceptFriendRelationship(
+ Relationship relationship,
+ RelationshipStatus status = RelationshipStatus.Friends
+ );
+ Task UpdateRelationship(Guid accountId, Guid relatedId, RelationshipStatus status);
+ Task> ListAccountFriends(Models.Account account);
+ Task> ListAccountBlocked(Models.Account account);
+ Task HasRelationshipWithStatus(Guid accountId, Guid relatedId, RelationshipStatus status = RelationshipStatus.Friends);
+}
diff --git a/DysonNetwork.Sphere/Account/AccountEventService.cs b/DysonNetwork.Pass/Features/Account/Services/AccountEventService.cs
similarity index 91%
rename from DysonNetwork.Sphere/Account/AccountEventService.cs
rename to DysonNetwork.Pass/Features/Account/Services/AccountEventService.cs
index 87e5bb3..9a567d3 100644
--- a/DysonNetwork.Sphere/Account/AccountEventService.cs
+++ b/DysonNetwork.Pass/Features/Account/Services/AccountEventService.cs
@@ -1,24 +1,30 @@
using System.Globalization;
-using DysonNetwork.Sphere.Activity;
-using DysonNetwork.Sphere.Connection;
-using DysonNetwork.Sphere.Storage;
-using DysonNetwork.Sphere.Wallet;
+using DysonNetwork.Common.Models;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Pass.Storage;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Localization;
using NodaTime;
-using Org.BouncyCastle.Asn1.X509;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account.Services;
-public class AccountEventService(
- AppDatabase db,
- WebSocketService ws,
- ICacheService cache,
- PaymentService payment,
- IStringLocalizer localizer
-)
+public class AccountEventService
{
+ private readonly PassDatabase db;
+ private readonly ICacheService cache;
+ private readonly IStringLocalizer localizer;
+
+ public AccountEventService(
+ PassDatabase db,
+ ICacheService cache,
+ IStringLocalizer localizer
+ )
+ {
+ this.db = db;
+ this.cache = cache;
+ this.localizer = localizer;
+ }
+
private static readonly Random Random = new();
private const string StatusCacheKey = "AccountStatus_";
@@ -139,7 +145,7 @@ public class AccountEventService(
return results;
}
- public async Task CreateStatus(Account user, Status status)
+ public async Task CreateStatus(Common.Models.Account user, Status status)
{
var now = SystemClock.Instance.GetCurrentInstant();
await db.AccountStatuses
@@ -152,7 +158,7 @@ public class AccountEventService(
return status;
}
- public async Task ClearStatus(Account user, Status status)
+ public async Task ClearStatus(Common.Models.Account user, Status status)
{
status.ClearedAt = SystemClock.Instance.GetCurrentInstant();
db.Update(status);
@@ -164,7 +170,7 @@ public class AccountEventService(
private const string CaptchaCacheKey = "CheckInCaptcha_";
private const int CaptchaProbabilityPercent = 20;
- public async Task CheckInDailyDoAskCaptcha(Account user)
+ public async Task CheckInDailyDoAskCaptcha(Common.Models.Account user)
{
var cacheKey = $"{CaptchaCacheKey}{user.Id}";
var needsCaptcha = await cache.GetAsync(cacheKey);
@@ -176,7 +182,7 @@ public class AccountEventService(
return result;
}
- public async Task CheckInDailyIsAvailable(Account user)
+ public async Task CheckInDailyIsAvailable(Common.Models.Account user)
{
var now = SystemClock.Instance.GetCurrentInstant();
var lastCheckIn = await db.AccountCheckInResults
@@ -195,7 +201,7 @@ public class AccountEventService(
public const string CheckInLockKey = "CheckInLock_";
- public async Task CheckInDaily(Account user)
+ public async Task CheckInDaily(Common.Models.Account user)
{
var lockKey = $"{CheckInLockKey}{user.Id}";
@@ -280,7 +286,7 @@ public class AccountEventService(
return result;
}
- public async Task> GetEventCalendar(Account user, int month, int year = 0,
+ public async Task> GetEventCalendar(Common.Models.Account user, int month, int year = 0,
bool replaceInvisible = false)
{
if (year == 0)
diff --git a/DysonNetwork.Sphere/Account/AccountService.cs b/DysonNetwork.Pass/Features/Account/Services/AccountService.cs
similarity index 87%
rename from DysonNetwork.Sphere/Account/AccountService.cs
rename to DysonNetwork.Pass/Features/Account/Services/AccountService.cs
index db3b9e8..6803109 100644
--- a/DysonNetwork.Sphere/Account/AccountService.cs
+++ b/DysonNetwork.Pass/Features/Account/Services/AccountService.cs
@@ -1,32 +1,54 @@
using System.Globalization;
-using DysonNetwork.Sphere.Auth;
-using DysonNetwork.Sphere.Auth.OpenId;
-using DysonNetwork.Sphere.Email;
-
-using DysonNetwork.Sphere.Localization;
-using DysonNetwork.Sphere.Permission;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Features.Auth;
+using DysonNetwork.Pass.Features.Auth.OpenId;
+using DysonNetwork.Pass.Email;
+using DysonNetwork.Pass.Localization;
+using DysonNetwork.Pass.Permission;
+using DysonNetwork.Pass.Storage;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using NodaTime;
-using Org.BouncyCastle.Utilities;
using OtpNet;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Common.Models;
+using DysonNetwork.Pass.Features.Auth.Services;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account;
-public class AccountService(
- AppDatabase db,
- MagicSpellService spells,
- AccountUsernameService uname,
- NotificationService nty,
- EmailService mailer,
- IStringLocalizer localizer,
- ICacheService cache,
- ILogger logger
-)
+public class AccountService
{
- public static void SetCultureInfo(Account account)
+ private readonly PassDatabase db;
+ private readonly MagicSpellService spells;
+ private readonly AccountUsernameService uname;
+ private readonly NotificationService nty;
+ private readonly EmailService mailer;
+ private readonly IStringLocalizer localizer;
+ private readonly ICacheService cache;
+ private readonly ILogger logger;
+
+ public AccountService(
+ PassDatabase db,
+ MagicSpellService spells,
+ AccountUsernameService uname,
+ NotificationService nty,
+ EmailService mailer,
+ IStringLocalizer localizer,
+ ICacheService cache,
+ ILogger logger
+ )
+ {
+ this.db = db;
+ this.spells = spells;
+ this.uname = uname;
+ this.nty = nty;
+ this.mailer = mailer;
+ this.localizer = localizer;
+ this.cache = cache;
+ this.logger = logger;
+ }
+
+ public static void SetCultureInfo(Models.Account account)
{
SetCultureInfo(account.Language);
}
@@ -40,12 +62,12 @@ public class AccountService(
public const string AccountCachePrefix = "account:";
- public async Task PurgeAccountCache(Account account)
+ public async Task PurgeAccountCache(Models.Account account)
{
await cache.RemoveGroupAsync($"{AccountCachePrefix}{account.Id}");
}
- public async Task LookupAccount(string probe)
+ public async Task LookupAccount(string probe)
{
var account = await db.Accounts.Where(a => a.Name == probe).FirstOrDefaultAsync();
if (account is not null) return account;
@@ -57,7 +79,7 @@ public class AccountService(
return contact?.Account;
}
- public async Task LookupAccountByConnection(string identifier, string provider)
+ public async Task LookupAccountByConnection(string identifier, string provider)
{
var connection = await db.AccountConnections
.Where(c => c.ProvidedIdentifier == identifier && c.Provider == provider)
@@ -74,7 +96,7 @@ public class AccountService(
return profile?.Level;
}
- public async Task CreateAccount(
+ public async Task CreateAccount(
string name,
string nick,
string email,
@@ -91,7 +113,7 @@ public class AccountService(
if (dupeNameCount > 0)
throw new InvalidOperationException("Account name has already been taken.");
- var account = new Account
+ var account = new Models.Account
{
Name = name,
Nick = nick,
@@ -159,7 +181,7 @@ public class AccountService(
}
}
- public async Task CreateAccount(OidcUserInfo userInfo)
+ public async Task CreateAccount(OidcUserInfo userInfo)
{
if (string.IsNullOrEmpty(userInfo.Email))
throw new ArgumentException("Email is required for account creation");
@@ -182,7 +204,7 @@ public class AccountService(
);
}
- public async Task RequestAccountDeletion(Account account)
+ public async Task RequestAccountDeletion(Models.Account account)
{
var spell = await spells.CreateMagicSpell(
account,
@@ -194,7 +216,7 @@ public class AccountService(
await spells.NotifyMagicSpell(spell);
}
- public async Task RequestPasswordReset(Account account)
+ public async Task RequestPasswordReset(Models.Account account)
{
var spell = await spells.CreateMagicSpell(
account,
@@ -206,7 +228,7 @@ public class AccountService(
await spells.NotifyMagicSpell(spell);
}
- public async Task CheckAuthFactorExists(Account account, AccountAuthFactorType type)
+ public async Task CheckAuthFactorExists(Models.Account account, AccountAuthFactorType type)
{
var isExists = await db.AccountAuthFactors
.Where(x => x.AccountId == account.Id && x.Type == type)
@@ -214,7 +236,7 @@ public class AccountService(
return isExists;
}
- public async Task CreateAuthFactor(Account account, AccountAuthFactorType type, string? secret)
+ public async Task CreateAuthFactor(Models.Account account, AccountAuthFactorType type, string? secret)
{
AccountAuthFactor? factor = null;
switch (type)
@@ -345,7 +367,7 @@ public class AccountService(
/// The owner of the auth factor
/// The auth factor needed to send code
/// The part of the contact method for verification
- public async Task SendFactorCode(Account account, AccountAuthFactor factor, string? hint = null)
+ public async Task SendFactorCode(Models.Account account, AccountAuthFactor factor, string? hint = null)
{
var code = new Random().Next(100000, 999999).ToString("000000");
@@ -454,7 +476,7 @@ public class AccountService(
);
}
- public async Task UpdateSessionLabel(Account account, Guid sessionId, string label)
+ public async Task UpdateSessionLabel(Models.Account account, Guid sessionId, string label)
{
var session = await db.AuthSessions
.Include(s => s.Challenge)
@@ -477,7 +499,7 @@ public class AccountService(
return session;
}
- public async Task DeleteSession(Account account, Guid sessionId)
+ public async Task DeleteSession(Models.Account account, Guid sessionId)
{
var session = await db.AuthSessions
.Include(s => s.Challenge)
@@ -503,7 +525,7 @@ public class AccountService(
await cache.RemoveAsync($"{DysonTokenAuthHandler.AuthCachePrefix}{item.Id}");
}
- public async Task CreateContactMethod(Account account, AccountContactType type, string content)
+ public async Task CreateContactMethod(Models.Account account, AccountContactType type, string content)
{
var contact = new AccountContact
{
@@ -518,7 +540,7 @@ public class AccountService(
return contact;
}
- public async Task VerifyContactMethod(Account account, AccountContact contact)
+ public async Task VerifyContactMethod(Models.Account account, AccountContact contact)
{
var spell = await spells.CreateMagicSpell(
account,
@@ -530,7 +552,7 @@ public class AccountService(
await spells.NotifyMagicSpell(spell);
}
- public async Task SetContactMethodPrimary(Account account, AccountContact contact)
+ public async Task SetContactMethodPrimary(Models.Account account, AccountContact contact)
{
if (contact.AccountId != account.Id)
throw new InvalidOperationException("Contact method does not belong to this account.");
@@ -559,7 +581,7 @@ public class AccountService(
}
}
- public async Task DeleteContactMethod(Account account, AccountContact contact)
+ public async Task DeleteContactMethod(Models.Account account, AccountContact contact)
{
if (contact.AccountId != account.Id)
throw new InvalidOperationException("Contact method does not belong to this account.");
@@ -574,7 +596,7 @@ public class AccountService(
/// This method will grant a badge to the account.
/// Shouldn't be exposed to normal user and the user itself.
///
- public async Task GrantBadge(Account account, Badge badge)
+ public async Task GrantBadge(Models.Account account, Badge badge)
{
badge.AccountId = account.Id;
db.Badges.Add(badge);
@@ -586,7 +608,7 @@ public class AccountService(
/// This method will revoke a badge from the account.
/// Shouldn't be exposed to normal user and the user itself.
///
- public async Task RevokeBadge(Account account, Guid badgeId)
+ public async Task RevokeBadge(Models.Account account, Guid badgeId)
{
var badge = await db.Badges
.Where(b => b.AccountId == account.Id && b.Id == badgeId)
@@ -604,7 +626,7 @@ public class AccountService(
await db.SaveChangesAsync();
}
- public async Task ActiveBadge(Account account, Guid badgeId)
+ public async Task ActiveBadge(Models.Account account, Guid badgeId)
{
await using var transaction = await db.Database.BeginTransactionAsync();
diff --git a/DysonNetwork.Sphere/Account/AccountUsernameService.cs b/DysonNetwork.Pass/Features/Account/Services/AccountUsernameService.cs
similarity index 96%
rename from DysonNetwork.Sphere/Account/AccountUsernameService.cs
rename to DysonNetwork.Pass/Features/Account/Services/AccountUsernameService.cs
index 094f9f0..307a610 100644
--- a/DysonNetwork.Sphere/Account/AccountUsernameService.cs
+++ b/DysonNetwork.Pass/Features/Account/Services/AccountUsernameService.cs
@@ -1,12 +1,12 @@
using System.Text.RegularExpressions;
using Microsoft.EntityFrameworkCore;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account;
///
/// Service for handling username generation and validation
///
-public class AccountUsernameService(AppDatabase db)
+public class AccountUsernameService(AppDatabase db) : IAccountUsernameService
{
private readonly Random _random = new();
diff --git a/DysonNetwork.Sphere/Account/ActionLogService.cs b/DysonNetwork.Pass/Features/Account/Services/ActionLogService.cs
similarity index 79%
rename from DysonNetwork.Sphere/Account/ActionLogService.cs
rename to DysonNetwork.Pass/Features/Account/Services/ActionLogService.cs
index 0b963f2..e882f0d 100644
--- a/DysonNetwork.Sphere/Account/ActionLogService.cs
+++ b/DysonNetwork.Pass/Features/Account/Services/ActionLogService.cs
@@ -1,11 +1,10 @@
-using Quartz;
-using DysonNetwork.Sphere.Connection;
-using DysonNetwork.Sphere.Storage;
-using DysonNetwork.Sphere.Storage.Handlers;
+using DysonNetwork.Pass.Connection;
+using DysonNetwork.Pass.Storage;
+using DysonNetwork.Pass.Storage.Handlers;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account;
-public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
+public class ActionLogService(GeoIpService geo, FlushBufferService fbs) : IActionLogService
{
public void CreateActionLog(Guid accountId, string action, Dictionary meta)
{
@@ -20,7 +19,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
}
public void CreateActionLogFromRequest(string action, Dictionary meta, HttpRequest request,
- Account? account = null)
+ Models.Account? account = null)
{
var log = new ActionLog
{
@@ -31,7 +30,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs)
Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString())
};
- if (request.HttpContext.Items["CurrentUser"] is Account currentUser)
+ if (request.HttpContext.Items["CurrentUser"] is Models.Account currentUser)
log.AccountId = currentUser.Id;
else if (account != null)
log.AccountId = account.Id;
diff --git a/DysonNetwork.Sphere/Account/MagicSpellService.cs b/DysonNetwork.Pass/Features/Account/Services/MagicSpellService.cs
similarity index 91%
rename from DysonNetwork.Sphere/Account/MagicSpellService.cs
rename to DysonNetwork.Pass/Features/Account/Services/MagicSpellService.cs
index 4cf2521..c278a80 100644
--- a/DysonNetwork.Sphere/Account/MagicSpellService.cs
+++ b/DysonNetwork.Pass/Features/Account/Services/MagicSpellService.cs
@@ -1,27 +1,34 @@
-using System.Globalization;
-using System.Security.Cryptography;
-using System.Text.Json;
-using DysonNetwork.Sphere.Email;
-using DysonNetwork.Sphere.Pages.Emails;
-using DysonNetwork.Sphere.Permission;
-using DysonNetwork.Sphere.Resources.Localization;
-using DysonNetwork.Sphere.Resources.Pages.Emails;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Localization;
-using NodaTime;
-namespace DysonNetwork.Sphere.Account;
-public class MagicSpellService(
- AppDatabase db,
- EmailService email,
- IConfiguration configuration,
- ILogger logger,
- IStringLocalizer localizer
-)
+using DysonNetwork.Common.Models;
+
+namespace DysonNetwork.Pass.Features.Account;
+
+public class MagicSpellService
{
+ private readonly PassDatabase db;
+ private readonly EmailService email;
+ private readonly IConfiguration configuration;
+ private readonly ILogger logger;
+ private readonly IStringLocalizer localizer;
+
+ public MagicSpellService(
+ PassDatabase db,
+ EmailService email,
+ IConfiguration configuration,
+ ILogger logger,
+ IStringLocalizer localizer
+ )
+ {
+ this.db = db;
+ this.email = email;
+ this.configuration = configuration;
+ this.logger = logger;
+ this.localizer = localizer;
+ }
+
public async Task CreateMagicSpell(
- Account account,
+ Models.Account account,
MagicSpellType type,
Dictionary meta,
Instant? expiredAt = null,
diff --git a/DysonNetwork.Sphere/Account/NotificationService.cs b/DysonNetwork.Pass/Features/Account/Services/NotificationService.cs
similarity index 89%
rename from DysonNetwork.Sphere/Account/NotificationService.cs
rename to DysonNetwork.Pass/Features/Account/Services/NotificationService.cs
index fdb7502..3783a31 100644
--- a/DysonNetwork.Sphere/Account/NotificationService.cs
+++ b/DysonNetwork.Pass/Features/Account/Services/NotificationService.cs
@@ -1,17 +1,17 @@
using System.Text;
using System.Text.Json;
-using DysonNetwork.Sphere.Connection;
+using DysonNetwork.Pass.Connection;
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using NodaTime;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Common.Models;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account;
public class NotificationService(
- AppDatabase db,
- WebSocketService ws,
- IHttpClientFactory httpFactory,
- IConfiguration config)
+ PassDatabase db,
+ IConfiguration config) : INotificationService
{
private readonly string _notifyTopic = config["Notifications:Topic"]!;
private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!);
@@ -24,7 +24,7 @@ public class NotificationService(
}
public async Task SubscribePushNotification(
- Account account,
+ Models.Account account,
NotificationPushProvider provider,
string deviceId,
string deviceToken
@@ -70,7 +70,7 @@ public class NotificationService(
}
public async Task SendNotification(
- Account account,
+ Models.Account account,
string topic,
string? title = null,
string? subtitle = null,
@@ -110,11 +110,7 @@ public class NotificationService(
public async Task DeliveryNotification(Notification notification)
{
- ws.SendPacketToAccount(notification.AccountId, new WebSocketPacket
- {
- Type = "notifications.new",
- Data = notification
- });
+
// Pushing the notification
var subscribers = await db.NotificationPushSubscriptions
@@ -160,23 +156,14 @@ public class NotificationService(
await db.BulkInsertAsync(notifications);
}
- foreach (var account in accounts)
- {
- notification.Account = account;
- notification.AccountId = account.Id;
- ws.SendPacketToAccount(account.Id, new WebSocketPacket
- {
- Type = "notifications.new",
- Data = notification
- });
- }
+
var subscribers = await db.NotificationPushSubscriptions
.ToListAsync();
await _PushNotification(notification, subscribers);
}
- public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false)
+ public async Task SendNotificationBatch(Notification notification, List accounts, bool save = false)
{
if (save)
{
@@ -198,16 +185,7 @@ public class NotificationService(
await db.BulkInsertAsync(notifications);
}
- foreach (var account in accounts)
- {
- notification.Account = account;
- notification.AccountId = account.Id;
- ws.SendPacketToAccount(account.Id, new WebSocketPacket
- {
- Type = "notifications.new",
- Data = notification
- });
- }
+
var accountsId = accounts.Select(x => x.Id).ToList();
var subscribers = await db.NotificationPushSubscriptions
diff --git a/DysonNetwork.Sphere/Account/RelationshipService.cs b/DysonNetwork.Pass/Features/Account/Services/RelationshipService.cs
similarity index 89%
rename from DysonNetwork.Sphere/Account/RelationshipService.cs
rename to DysonNetwork.Pass/Features/Account/Services/RelationshipService.cs
index 51df3bf..22771d8 100644
--- a/DysonNetwork.Sphere/Account/RelationshipService.cs
+++ b/DysonNetwork.Pass/Features/Account/Services/RelationshipService.cs
@@ -1,10 +1,13 @@
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Pass.Storage;
using Microsoft.EntityFrameworkCore;
using NodaTime;
+using DysonNetwork.Common.Models;
+using DysonNetwork.Pass.Permission;
-namespace DysonNetwork.Sphere.Account;
+namespace DysonNetwork.Pass.Features.Account;
-public class RelationshipService(AppDatabase db, ICacheService cache)
+public class RelationshipService(PassDatabase db, ICacheService cache) : IRelationshipService
{
private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
private const string UserBlockedCacheKeyPrefix = "accounts:blocked:";
@@ -34,7 +37,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
return relationship;
}
- public async Task CreateRelationship(Account sender, Account target, RelationshipStatus status)
+ public async Task CreateRelationship(Models.Account sender, Models.Account target, RelationshipStatus status)
{
if (status == RelationshipStatus.Pending)
throw new InvalidOperationException(
@@ -57,14 +60,14 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
return relationship;
}
- public async Task BlockAccount(Account sender, Account target)
+ public async Task BlockAccount(Models.Account sender, Models.Account target)
{
if (await HasExistingRelationship(sender.Id, target.Id))
return await UpdateRelationship(sender.Id, target.Id, RelationshipStatus.Blocked);
return await CreateRelationship(sender, target, RelationshipStatus.Blocked);
}
- public async Task UnblockAccount(Account sender, Account target)
+ public async Task UnblockAccount(Models.Account sender, Models.Account target)
{
var relationship = await GetRelationship(sender.Id, target.Id, RelationshipStatus.Blocked);
if (relationship is null) throw new ArgumentException("There is no relationship between you and the user.");
@@ -76,7 +79,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
return relationship;
}
- public async Task SendFriendRequest(Account sender, Account target)
+ public async Task SendFriendRequest(Models.Account sender, Models.Account target)
{
if (await HasExistingRelationship(sender.Id, target.Id))
throw new InvalidOperationException("Found existing relationship between you and target user.");
@@ -152,7 +155,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
return relationship;
}
- public async Task> ListAccountFriends(Account account)
+ public async Task> ListAccountFriends(Models.Account account)
{
var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}";
var friends = await cache.GetAsync>(cacheKey);
@@ -171,7 +174,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache)
return friends ?? [];
}
- public async Task> ListAccountBlocked(Account account)
+ public async Task> ListAccountBlocked(Models.Account account)
{
var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}";
var blocked = await cache.GetAsync>(cacheKey);
diff --git a/DysonNetwork.Sphere/Auth/AuthController.cs b/DysonNetwork.Pass/Features/Auth/Controllers/AuthController.cs
similarity index 86%
rename from DysonNetwork.Sphere/Auth/AuthController.cs
rename to DysonNetwork.Pass/Features/Auth/Controllers/AuthController.cs
index 15b99bc..1387e48 100644
--- a/DysonNetwork.Sphere/Auth/AuthController.cs
+++ b/DysonNetwork.Pass/Features/Auth/Controllers/AuthController.cs
@@ -1,23 +1,22 @@
using System.ComponentModel.DataAnnotations;
-using DysonNetwork.Sphere.Account;
+using DysonNetwork.Pass.Features.Account;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NodaTime;
using Microsoft.EntityFrameworkCore;
using System.IdentityModel.Tokens.Jwt;
using System.Text.Json;
-using DysonNetwork.Sphere.Connection;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Common.Models;
-namespace DysonNetwork.Sphere.Auth;
+namespace DysonNetwork.Pass.Features.Auth;
[ApiController]
[Route("/auth")]
public class AuthController(
- AppDatabase db,
+ PassDatabase db,
AccountService accounts,
- AuthService auth,
- GeoIpService geo,
- ActionLogService als
+ AuthService auth
) : ControllerBase
{
public class ChallengeRequest
@@ -59,7 +58,7 @@ public class AuthController(
Scopes = request.Scopes,
IpAddress = ipAddress,
UserAgent = userAgent,
- Location = geo.GetPointFromIp(ipAddress),
+
DeviceId = request.DeviceId,
AccountId = account.Id
}.Normalize();
@@ -67,9 +66,7 @@ public class AuthController(
await db.AuthChallenges.AddAsync(challenge);
await db.SaveChangesAsync();
- als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt,
- new Dictionary { { "challenge_id", challenge.Id } }, Request, account
- );
+
return challenge;
}
@@ -160,13 +157,7 @@ public class AuthController(
challenge.StepRemain = Math.Max(0, challenge.StepRemain);
challenge.BlacklistFactors.Add(factor.Id);
db.Update(challenge);
- als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess,
- new Dictionary
- {
- { "challenge_id", challenge.Id },
- { "factor_id", factor.Id }
- }, Request, challenge.Account
- );
+
}
else
{
@@ -177,26 +168,14 @@ public class AuthController(
{
challenge.FailedAttempts++;
db.Update(challenge);
- als.CreateActionLogFromRequest(ActionLogType.ChallengeFailure,
- new Dictionary
- {
- { "challenge_id", challenge.Id },
- { "factor_id", factor.Id }
- }, Request, challenge.Account
- );
+
await db.SaveChangesAsync();
return BadRequest("Invalid password.");
}
if (challenge.StepRemain == 0)
{
- als.CreateActionLogFromRequest(ActionLogType.NewLogin,
- new Dictionary
- {
- { "challenge_id", challenge.Id },
- { "account_id", challenge.AccountId }
- }, Request, challenge.Account
- );
+
}
await db.SaveChangesAsync();
diff --git a/DysonNetwork.Pass/Features/Auth/Interfaces/IAuthService.cs b/DysonNetwork.Pass/Features/Auth/Interfaces/IAuthService.cs
new file mode 100644
index 0000000..5355392
--- /dev/null
+++ b/DysonNetwork.Pass/Features/Auth/Interfaces/IAuthService.cs
@@ -0,0 +1,15 @@
+using DysonNetwork.Common.Models;
+using NodaTime;
+
+namespace DysonNetwork.Pass.Features.Auth.Interfaces;
+
+public interface IAuthService
+{
+ Task DetectChallengeRisk(HttpRequest request, Common.Models.Account account);
+ Task CreateSessionForOidcAsync(Common.Models.Account account, Instant time, Guid? customAppId = null);
+ Task ValidateCaptcha(string token);
+ string CreateToken(AuthSession session);
+ Task ValidateSudoMode(AuthSession session, string? pinCode);
+ Task ValidatePinCode(Guid accountId, string pinCode);
+ bool ValidateToken(string token, out Guid sessionId);
+}
diff --git a/DysonNetwork.Sphere/Auth/Auth.cs b/DysonNetwork.Pass/Features/Auth/Services/Auth.cs
similarity index 94%
rename from DysonNetwork.Sphere/Auth/Auth.cs
rename to DysonNetwork.Pass/Features/Auth/Services/Auth.cs
index 135f6bd..64444e4 100644
--- a/DysonNetwork.Sphere/Auth/Auth.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/Auth.cs
@@ -1,22 +1,15 @@
-using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Encodings.Web;
-using DysonNetwork.Sphere.Account;
-using DysonNetwork.Sphere.Auth.OidcProvider.Options;
-using DysonNetwork.Sphere.Storage;
-using DysonNetwork.Sphere.Storage.Handlers;
+using DysonNetwork.Pass.Features.Account;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Services;
+using DysonNetwork.Pass.Storage;
+using DysonNetwork.Pass.Storage.Handlers;
using Microsoft.AspNetCore.Authentication;
-using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
-using Microsoft.IdentityModel.Tokens;
-using NodaTime;
-using System.Text;
-using DysonNetwork.Sphere.Auth.OidcProvider.Controllers;
-using DysonNetwork.Sphere.Auth.OidcProvider.Services;
using SystemClock = NodaTime.SystemClock;
-namespace DysonNetwork.Sphere.Auth;
+namespace DysonNetwork.Pass.Features.Auth.Services;
public static class AuthConstants
{
@@ -46,7 +39,7 @@ public class DysonTokenAuthHandler(
IConfiguration configuration,
ILoggerFactory logger,
UrlEncoder encoder,
- AppDatabase database,
+ PassDatabase database,
OidcProviderService oidc,
ICacheService cache,
FlushBufferService fbs
diff --git a/DysonNetwork.Sphere/Auth/AuthService.cs b/DysonNetwork.Pass/Features/Auth/Services/AuthService.cs
similarity index 91%
rename from DysonNetwork.Sphere/Auth/AuthService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/AuthService.cs
index d0c2ee3..b11c151 100644
--- a/DysonNetwork.Sphere/Auth/AuthService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/AuthService.cs
@@ -1,20 +1,36 @@
using System.Security.Cryptography;
using System.Text.Json;
-using DysonNetwork.Sphere.Account;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Features.Account;
+using DysonNetwork.Pass.Storage;
using Microsoft.EntityFrameworkCore;
using NodaTime;
+using DysonNetwork.Pass.Data;
-namespace DysonNetwork.Sphere.Auth;
+namespace DysonNetwork.Pass.Features.Auth;
-public class AuthService(
- AppDatabase db,
- IConfiguration config,
- IHttpClientFactory httpClientFactory,
- IHttpContextAccessor httpContextAccessor,
- ICacheService cache
-)
+public class AuthService
{
+ private readonly PassDatabase db;
+ private readonly IConfiguration config;
+ private readonly IHttpClientFactory httpClientFactory;
+ private readonly IHttpContextAccessor httpContextAccessor;
+ private readonly ICacheService cache;
+
+ public AuthService(
+ PassDatabase db,
+ IConfiguration config,
+ IHttpClientFactory httpClientFactory,
+ IHttpContextAccessor httpContextAccessor,
+ ICacheService cache
+ )
+ {
+ this.db = db;
+ this.config = config;
+ this.httpClientFactory = httpClientFactory;
+ this.httpContextAccessor = httpContextAccessor;
+ this.cache = cache;
+ }
+
private HttpContext HttpContext => httpContextAccessor.HttpContext!;
///
@@ -24,7 +40,7 @@ public class AuthService(
/// The request context
/// The account to login
/// The required steps to login
- public async Task DetectChallengeRisk(HttpRequest request, Account.Account account)
+ public async Task DetectChallengeRisk(HttpRequest request, Models.Account account)
{
// 1) Find out how many authentication factors the account has enabled.
var maxSteps = await db.AccountAuthFactors
@@ -73,7 +89,7 @@ public class AuthService(
return totalRequiredSteps;
}
- public async Task CreateSessionForOidcAsync(Account.Account account, Instant time, Guid? customAppId = null)
+ public async Task CreateSessionForOidcAsync(Models.Account account, Instant time, Guid? customAppId = null)
{
var challenge = new Challenge
{
diff --git a/DysonNetwork.Sphere/Auth/CheckpointModel.cs b/DysonNetwork.Pass/Features/Auth/Services/CheckpointModel.cs
similarity index 65%
rename from DysonNetwork.Sphere/Auth/CheckpointModel.cs
rename to DysonNetwork.Pass/Features/Auth/Services/CheckpointModel.cs
index 3b4ea12..cb61beb 100644
--- a/DysonNetwork.Sphere/Auth/CheckpointModel.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/CheckpointModel.cs
@@ -1,4 +1,4 @@
-namespace DysonNetwork.Sphere.Auth;
+namespace DysonNetwork.Pass.Features.Auth;
public class CaptchaVerificationResponse
{
diff --git a/DysonNetwork.Sphere/Auth/CompactTokenService.cs b/DysonNetwork.Pass/Features/Auth/Services/CompactTokenService.cs
similarity index 98%
rename from DysonNetwork.Sphere/Auth/CompactTokenService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/CompactTokenService.cs
index 53c3e8b..01378a6 100644
--- a/DysonNetwork.Sphere/Auth/CompactTokenService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/CompactTokenService.cs
@@ -1,6 +1,6 @@
using System.Security.Cryptography;
-namespace DysonNetwork.Sphere.Auth;
+namespace DysonNetwork.Pass.Features.Auth;
public class CompactTokenService(IConfiguration config)
{
diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Controllers/OidcProviderController.cs
similarity index 95%
rename from DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Controllers/OidcProviderController.cs
index ce635c8..4386cd4 100644
--- a/DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Controllers/OidcProviderController.cs
@@ -1,23 +1,24 @@
using System.Security.Cryptography;
using System.Text;
-using DysonNetwork.Sphere.Auth.OidcProvider.Options;
-using DysonNetwork.Sphere.Auth.OidcProvider.Responses;
-using DysonNetwork.Sphere.Auth.OidcProvider.Services;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Options;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Responses;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Account;
+using DysonNetwork.Pass.Features.Account;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
+using DysonNetwork.Pass.Data;
-namespace DysonNetwork.Sphere.Auth.OidcProvider.Controllers;
+namespace DysonNetwork.Pass.Features.Auth.OidcProvider.Controllers;
[Route("/auth/open")]
[ApiController]
public class OidcProviderController(
- AppDatabase db,
+ PassDatabase db,
OidcProviderService oidcService,
IConfiguration configuration,
IOptions options,
@@ -114,7 +115,7 @@ public class OidcProviderController(
[Authorize]
public async Task GetUserInfo()
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser ||
+ if (HttpContext.Items["CurrentUser"] is not Pass.Models.Account currentUser ||
HttpContext.Items["CurrentSession"] is not Session currentSession) return Unauthorized();
// Get requested scopes from the token
diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Models/AuthorizationCodeInfo.cs
similarity index 88%
rename from DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Models/AuthorizationCodeInfo.cs
index d043cab..ca1756b 100644
--- a/DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Models/AuthorizationCodeInfo.cs
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using NodaTime;
-namespace DysonNetwork.Sphere.Auth.OidcProvider.Models;
+namespace DysonNetwork.Pass.Features.Auth.OidcProvider.Models;
public class AuthorizationCodeInfo
{
diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Options/OidcProviderOptions.cs
similarity index 94%
rename from DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Options/OidcProviderOptions.cs
index 6d57cb3..d26c103 100644
--- a/DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Options/OidcProviderOptions.cs
@@ -1,6 +1,6 @@
using System.Security.Cryptography;
-namespace DysonNetwork.Sphere.Auth.OidcProvider.Options;
+namespace DysonNetwork.Pass.Features.Auth.OidcProvider.Options;
public class OidcProviderOptions
{
diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/AuthorizationResponse.cs
similarity index 87%
rename from DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/AuthorizationResponse.cs
index 45cf25c..d04cd47 100644
--- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/AuthorizationResponse.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses;
+namespace DysonNetwork.Pass.Features.Auth.OidcProvider.Responses;
public class AuthorizationResponse
{
diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/ErrorResponse.cs
similarity index 85%
rename from DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/ErrorResponse.cs
index 0018a47..7b56425 100644
--- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/ErrorResponse.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses;
+namespace DysonNetwork.Pass.Features.Auth.OidcProvider.Responses;
public class ErrorResponse
{
diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/TokenResponse.cs
similarity index 89%
rename from DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/TokenResponse.cs
index 6d41cf4..136a2ab 100644
--- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Responses/TokenResponse.cs
@@ -1,6 +1,6 @@
using System.Text.Json.Serialization;
-namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses;
+namespace DysonNetwork.Pass.Features.Auth.OidcProvider.Responses;
public class TokenResponse
{
diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Services/OidcProviderService.cs
similarity index 96%
rename from DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Services/OidcProviderService.cs
index 4345dab..b729707 100644
--- a/DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OidcProvider/Services/OidcProviderService.cs
@@ -1,21 +1,18 @@
-using System.IdentityModel.Tokens.Jwt;
-using System.Security.Claims;
-using System.Security.Cryptography;
-using System.Text;
-using DysonNetwork.Sphere.Auth.OidcProvider.Models;
-using DysonNetwork.Sphere.Auth.OidcProvider.Options;
-using DysonNetwork.Sphere.Auth.OidcProvider.Responses;
-using DysonNetwork.Sphere.Developer;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Models;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Options;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Responses;
+using DysonNetwork.Pass.Developer;
+using DysonNetwork.Pass.Storage;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
+using DysonNetwork.Pass.Data;
-namespace DysonNetwork.Sphere.Auth.OidcProvider.Services;
+namespace DysonNetwork.Pass.Features.Auth.OidcProvider.Services;
public class OidcProviderService(
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache,
IOptions options,
diff --git a/DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/AfdianOidcService.cs
similarity index 96%
rename from DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/AfdianOidcService.cs
index c0e0600..0f28e22 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/AfdianOidcService.cs
@@ -1,13 +1,14 @@
using System.Net.Http.Json;
using System.Text.Json;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Storage;
+using DysonNetwork.Pass.Data;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class AfdianOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache,
ILogger logger
diff --git a/DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/AppleMobileSignInRequest.cs
similarity index 89%
rename from DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/AppleMobileSignInRequest.cs
index f5249cd..c706cea 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/AppleMobileSignInRequest.cs
@@ -2,7 +2,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class AppleMobileConnectRequest
{
diff --git a/DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/AppleOidcService.cs
similarity index 98%
rename from DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/AppleOidcService.cs
index 75420b8..3bba22b 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/AppleOidcService.cs
@@ -3,10 +3,10 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Services;
using Microsoft.IdentityModel.Tokens;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
///
/// Implementation of OpenID Connect service for Apple Sign In
@@ -14,7 +14,7 @@ namespace DysonNetwork.Sphere.Auth.OpenId;
public class AppleOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache
)
diff --git a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/ConnectionController.cs
similarity index 96%
rename from DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/ConnectionController.cs
index fbf9f5a..fb68169 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/ConnectionController.cs
@@ -1,17 +1,19 @@
-using DysonNetwork.Sphere.Account;
+using DysonNetwork.Pass.Features.Account;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Storage;
using NodaTime;
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Common.Models;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
[ApiController]
[Route("/accounts/me/connections")]
[Authorize]
public class ConnectionController(
- AppDatabase db,
+ PassDatabase db,
IEnumerable oidcServices,
AccountService accounts,
AuthService auth,
@@ -25,7 +27,7 @@ public class ConnectionController(
[HttpGet]
public async Task>> GetConnections()
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
+ if (HttpContext.Items["CurrentUser"] is not Models.Account currentUser)
return Unauthorized();
var connections = await db.AccountConnections
@@ -48,7 +50,7 @@ public class ConnectionController(
[HttpDelete("{id:guid}")]
public async Task RemoveConnection(Guid id)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
+ if (HttpContext.Items["CurrentUser"] is not Models.Account currentUser)
return Unauthorized();
var connection = await db.AccountConnections
@@ -66,7 +68,7 @@ public class ConnectionController(
[HttpPost("/auth/connect/apple/mobile")]
public async Task ConnectAppleMobile([FromBody] AppleMobileConnectRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
+ if (HttpContext.Items["CurrentUser"] is not Models.Account currentUser)
return Unauthorized();
if (GetOidcService("apple") is not AppleOidcService appleService)
@@ -132,7 +134,7 @@ public class ConnectionController(
[HttpPost("connect")]
public async Task> InitiateConnection([FromBody] ConnectProviderRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
+ if (HttpContext.Items["CurrentUser"] is not Models.Account currentUser)
return Unauthorized();
var oidcService = GetOidcService(request.Provider);
diff --git a/DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/DiscordOidcService.cs
similarity index 97%
rename from DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/DiscordOidcService.cs
index c710b71..7447b0e 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/DiscordOidcService.cs
@@ -1,13 +1,13 @@
using System.Net.Http.Json;
using System.Text.Json;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Storage;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class DiscordOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache
)
diff --git a/DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/GitHubOidcService.cs
similarity index 97%
rename from DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/GitHubOidcService.cs
index fc80bfe..fd7269a 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/GitHubOidcService.cs
@@ -1,13 +1,13 @@
using System.Net.Http.Json;
using System.Text.Json;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Storage;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class GitHubOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache
)
diff --git a/DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/GoogleOidcService.cs
similarity index 97%
rename from DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/GoogleOidcService.cs
index a446b2e..fa13a71 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/GoogleOidcService.cs
@@ -2,15 +2,15 @@ using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Services;
using Microsoft.IdentityModel.Tokens;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class GoogleOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache
)
diff --git a/DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/MicrosoftOidcService.cs
similarity index 97%
rename from DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/MicrosoftOidcService.cs
index 83efad1..4ccbcfe 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/MicrosoftOidcService.cs
@@ -1,13 +1,13 @@
using System.Net.Http.Json;
using System.Text.Json;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Pass.Storage;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
public class MicrosoftOidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache
)
diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcController.cs
similarity index 95%
rename from DysonNetwork.Sphere/Auth/OpenId/OidcController.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcController.cs
index a868ef5..b8c9fa5 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcController.cs
@@ -1,17 +1,16 @@
-using DysonNetwork.Sphere.Account;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
[ApiController]
[Route("/auth/login")]
public class OidcController(
IServiceProvider serviceProvider,
- AppDatabase db,
+ PassDatabase db,
AccountService accounts,
ICacheService cache
)
@@ -32,7 +31,7 @@ public class OidcController(
var oidcService = GetOidcService(provider);
// If the user is already authenticated, treat as an account connection request
- if (HttpContext.Items["CurrentUser"] is Account.Account currentUser)
+ if (HttpContext.Items["CurrentUser"] is Models.Account currentUser)
{
var state = Guid.NewGuid().ToString();
var nonce = Guid.NewGuid().ToString();
@@ -125,7 +124,7 @@ public class OidcController(
};
}
- private async Task FindOrCreateAccount(OidcUserInfo userInfo, string provider)
+ private async Task FindOrCreateAccount(OidcUserInfo userInfo, string provider)
{
if (string.IsNullOrEmpty(userInfo.Email))
throw new ArgumentException("Email is required for account creation");
diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcService.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcService.cs
similarity index 98%
rename from DysonNetwork.Sphere/Auth/OpenId/OidcService.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcService.cs
index 544704a..7a42494 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/OidcService.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcService.cs
@@ -1,13 +1,12 @@
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
-using DysonNetwork.Sphere.Account;
-using DysonNetwork.Sphere.Storage;
+using DysonNetwork.Common.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using NodaTime;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
///
/// Base service for OpenID Connect authentication providers
@@ -15,7 +14,7 @@ namespace DysonNetwork.Sphere.Auth.OpenId;
public abstract class OidcService(
IConfiguration configuration,
IHttpClientFactory httpClientFactory,
- AppDatabase db,
+ PassDatabase db,
AuthService auth,
ICacheService cache
)
@@ -190,7 +189,7 @@ public abstract class OidcService(
///
public async Task CreateChallengeForUserAsync(
OidcUserInfo userInfo,
- Account.Account account,
+ Models.Account account,
HttpContext request,
string deviceId
)
diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcState.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcState.cs
similarity index 99%
rename from DysonNetwork.Sphere/Auth/OpenId/OidcState.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcState.cs
index 608956e..8d5b386 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/OidcState.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcState.cs
@@ -1,7 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
///
/// Represents the state parameter used in OpenID Connect flows.
diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcUserInfo.cs
similarity index 96%
rename from DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs
rename to DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcUserInfo.cs
index fda81a1..e0ea4cc 100644
--- a/DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs
+++ b/DysonNetwork.Pass/Features/Auth/Services/OpenId/OidcUserInfo.cs
@@ -1,4 +1,4 @@
-namespace DysonNetwork.Sphere.Auth.OpenId;
+namespace DysonNetwork.Pass.Features.Auth.OpenId;
///
/// Represents the user information from an OIDC provider
diff --git a/DysonNetwork.Pass/Localization/AccountEventResource.cs b/DysonNetwork.Pass/Localization/AccountEventResource.cs
new file mode 100644
index 0000000..9d88730
--- /dev/null
+++ b/DysonNetwork.Pass/Localization/AccountEventResource.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Pass.Localization;
+
+public class AccountEventResource
+{
+ // Dummy class for AccountEventResource
+}
diff --git a/DysonNetwork.Pass/Localization/EmailResource.cs b/DysonNetwork.Pass/Localization/EmailResource.cs
new file mode 100644
index 0000000..d348a1b
--- /dev/null
+++ b/DysonNetwork.Pass/Localization/EmailResource.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Pass.Localization;
+
+public class EmailResource
+{
+ // Dummy class for EmailResource
+}
diff --git a/DysonNetwork.Pass/Localization/NotificationResource.cs b/DysonNetwork.Pass/Localization/NotificationResource.cs
new file mode 100644
index 0000000..e291f94
--- /dev/null
+++ b/DysonNetwork.Pass/Localization/NotificationResource.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Pass.Localization;
+
+public class NotificationResource
+{
+ // Dummy class for NotificationResource
+}
diff --git a/DysonNetwork.Pass/Localization/SharedResource.cs b/DysonNetwork.Pass/Localization/SharedResource.cs
new file mode 100644
index 0000000..915c5ae
--- /dev/null
+++ b/DysonNetwork.Pass/Localization/SharedResource.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Pass.Localization;
+
+public class SharedResource
+{
+ // Dummy class for SharedResource
+}
diff --git a/DysonNetwork.Pass/Pages/Emails/DummyEmails.cs b/DysonNetwork.Pass/Pages/Emails/DummyEmails.cs
new file mode 100644
index 0000000..1244cba
--- /dev/null
+++ b/DysonNetwork.Pass/Pages/Emails/DummyEmails.cs
@@ -0,0 +1,51 @@
+namespace DysonNetwork.Pass.Pages.Emails;
+
+public class LandingEmail
+{
+ // Dummy class
+}
+
+public class LandingEmailModel
+{
+ // Dummy class
+}
+
+public class AccountDeletionEmail
+{
+ // Dummy class
+}
+
+public class AccountDeletionEmailModel
+{
+ // Dummy class
+}
+
+public class PasswordResetEmail
+{
+ // Dummy class
+}
+
+public class PasswordResetEmailModel
+{
+ // Dummy class
+}
+
+public class ContactVerificationEmail
+{
+ // Dummy class
+}
+
+public class ContactVerificationEmailModel
+{
+ // Dummy class
+}
+
+public class VerificationEmail
+{
+ // Dummy class
+}
+
+public class VerificationEmailModel
+{
+ // Dummy class
+}
diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs
new file mode 100644
index 0000000..61a34a5
--- /dev/null
+++ b/DysonNetwork.Pass/Program.cs
@@ -0,0 +1,115 @@
+using DysonNetwork.Pass.Data;
+using DysonNetwork.Pass.Features.Account;
+using DysonNetwork.Pass.Features.Auth;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Options;
+using DysonNetwork.Pass.Features.Auth.OidcProvider.Services;
+using DysonNetwork.Pass.Features.Auth.OpenId;
+using DysonNetwork.Pass.Storage;
+using DysonNetwork.Pass.Storage.Handlers;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.IdentityModel.Tokens;
+using NodaTime;
+using NodaTime.Serialization.SystemTextJson;
+using System.Text;
+using DysonNetwork.Pass.Email;
+using DysonNetwork.Pass.Developer;
+using DysonNetwork.Pass.Features.Account.DysonNetwork.Pass.Features.Account;
+using DysonNetwork.Pass.Features.Account.Services;
+using DysonNetwork.Pass.Permission;
+using Quartz;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddControllers().AddJsonOptions(options =>
+{
+ options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
+});
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+// Configure AppDatabase
+builder.Services.AddDbContext(options =>
+{
+ options.UseNpgsql(builder.Configuration.GetConnectionString("App"),
+ o => o.UseNodaTime().UseNetTopologySuite().UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
+});
+
+// Add custom services
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+// Add OIDC services
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+// Add other services
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddHttpContextAccessor();
+builder.Services.AddHttpClient();
+
+// Configure OIDC Provider Options
+builder.Services.Configure(builder.Configuration.GetSection("OidcProvider"));
+
+// Configure Authentication
+builder.Services.AddAuthentication(options =>
+{
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+}).AddJwtBearer(options =>
+{
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidateAudience = false, // Will be validated by the OidcProviderService
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidIssuer = builder.Configuration["OidcProvider:IssuerUri"],
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
+ };
+});
+
+// Configure Quartz for background jobs
+builder.Services.AddQuartz(q =>
+{
+ var jobKey = new JobKey("PassDatabaseRecyclingJob");
+ q.AddJob(opts => opts.WithIdentity(jobKey));
+ q.AddTrigger(opts => opts
+ .ForJob(jobKey)
+ .WithSimpleSchedule(s => s.WithIntervalInHours(24).RepeatForever())
+ .StartAt(DateBuilder.EvenHourDate(DateTimeOffset.UtcNow).AddHours(1))
+ );
+});
+builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthentication();
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
\ No newline at end of file
diff --git a/DysonNetwork.Pass/Properties/launchSettings.json b/DysonNetwork.Pass/Properties/launchSettings.json
new file mode 100644
index 0000000..333f241
--- /dev/null
+++ b/DysonNetwork.Pass/Properties/launchSettings.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5048",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:7212;http://localhost:5048",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/DysonNetwork.Pass/Resources/Localization/DummyLocalization.cs b/DysonNetwork.Pass/Resources/Localization/DummyLocalization.cs
new file mode 100644
index 0000000..8904a89
--- /dev/null
+++ b/DysonNetwork.Pass/Resources/Localization/DummyLocalization.cs
@@ -0,0 +1,6 @@
+namespace DysonNetwork.Pass.Resources.Localization;
+
+public class EmailResource
+{
+ // Dummy class
+}
diff --git a/DysonNetwork.Pass/Resources/Pages/Emails/DummyEmails.cs b/DysonNetwork.Pass/Resources/Pages/Emails/DummyEmails.cs
new file mode 100644
index 0000000..748f047
--- /dev/null
+++ b/DysonNetwork.Pass/Resources/Pages/Emails/DummyEmails.cs
@@ -0,0 +1,51 @@
+namespace DysonNetwork.Pass.Resources.Pages.Emails;
+
+public class AccountDeletionEmail
+{
+ // Dummy class
+}
+
+public class AccountDeletionEmailModel
+{
+ // Dummy class
+}
+
+public class ContactVerificationEmail
+{
+ // Dummy class
+}
+
+public class ContactVerificationEmailModel
+{
+ // Dummy class
+}
+
+public class LandingEmail
+{
+ // Dummy class
+}
+
+public class LandingEmailModel
+{
+ // Dummy class
+}
+
+public class PasswordResetEmail
+{
+ // Dummy class
+}
+
+public class PasswordResetEmailModel
+{
+ // Dummy class
+}
+
+public class VerificationEmail
+{
+ // Dummy class
+}
+
+public class VerificationEmailModel
+{
+ // Dummy class
+}
diff --git a/DysonNetwork.Pass/Storage/Handlers/FlushBufferService.cs b/DysonNetwork.Pass/Storage/Handlers/FlushBufferService.cs
new file mode 100644
index 0000000..265e3ad
--- /dev/null
+++ b/DysonNetwork.Pass/Storage/Handlers/FlushBufferService.cs
@@ -0,0 +1,105 @@
+using System.Collections.Concurrent;
+using DysonNetwork.Pass.Features.Account;
+using DysonNetwork.Common.Models;
+using Microsoft.EntityFrameworkCore;
+using NodaTime;
+
+namespace DysonNetwork.Pass.Storage.Handlers;
+
+public class FlushBufferService
+{
+ private readonly ConcurrentQueue
[HttpGet]
- public async Task>> ListActivities(
+ public async Task>> ListActivities(
[FromQuery] string? cursor,
[FromQuery] string? filter,
[FromQuery] int take = 20,
@@ -45,7 +44,7 @@ public class ActivityController(
var debugIncludeSet = debugInclude?.Split(',').ToHashSet() ?? new HashSet();
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
- return currentUserValue is not Account.Account currentUser
+ return currentUserValue is Account.Account currentUser
? Ok(await acts.GetActivitiesForAnyone(take, cursorTimestamp, debugIncludeSet))
: Ok(await acts.GetActivities(take, cursorTimestamp, currentUser, filter, debugIncludeSet));
}
diff --git a/DysonNetwork.Sphere/Activity/ActivityService.cs b/DysonNetwork.Sphere/Activity/ActivityService.cs
index 5adbd50..f252c1b 100644
--- a/DysonNetwork.Sphere/Activity/ActivityService.cs
+++ b/DysonNetwork.Sphere/Activity/ActivityService.cs
@@ -1,4 +1,4 @@
-using DysonNetwork.Sphere.Account;
+
using DysonNetwork.Sphere.Connection.WebReader;
using DysonNetwork.Sphere.Discovery;
using DysonNetwork.Sphere.Post;
@@ -11,11 +11,11 @@ namespace DysonNetwork.Sphere.Activity;
public class ActivityService(
AppDatabase db,
PublisherService pub,
- RelationshipService rels,
+
PostService ps,
DiscoveryService ds)
{
- private static double CalculateHotRank(Post.Post post, Instant now)
+ private static double CalculateHotRank(Common.Models.Post post, Instant now)
{
var score = post.Upvotes - post.Downvotes;
var postTime = post.PublishedAt ?? post.CreatedAt;
@@ -24,10 +24,10 @@ public class ActivityService(
return (score + 1) / Math.Pow(hours + 2, 1.8);
}
- public async Task> GetActivitiesForAnyone(int take, Instant? cursor,
+ public async Task> GetActivitiesForAnyone(int take, Instant? cursor,
HashSet? debugInclude = null)
{
- var activities = new List();
+ var activities = new List();
debugInclude ??= new HashSet();
if (cursor == null && (debugInclude.Contains("realms") || Random.Shared.NextDouble() < 0.2))
@@ -110,12 +110,12 @@ public class ActivityService(
activities.Add(post.ToActivity());
if (activities.Count == 0)
- activities.Add(Activity.Empty());
+ activities.Add(Common.Models.Activity.Empty());
return activities;
}
- public async Task> GetActivities(
+ public async Task> GetActivities(
int take,
Instant? cursor,
Account.Account currentUser,
@@ -123,8 +123,7 @@ public class ActivityService(
HashSet? debugInclude = null
)
{
- var activities = new List();
- var userFriends = await rels.ListAccountFriends(currentUser);
+ var activities = new List();
var userPublishers = await pub.GetUserPublishers(currentUser.Id);
debugInclude ??= [];
@@ -191,9 +190,7 @@ public class ActivityService(
var filteredPublishers = filter switch
{
"subscriptions" => await pub.GetSubscribedPublishers(currentUser.Id),
- "friends" => (await pub.GetUserPublishersBatch(userFriends)).SelectMany(x => x.Value)
- .DistinctBy(x => x.Id)
- .ToList(),
+
_ => null
};
@@ -214,7 +211,7 @@ public class ActivityService(
// Complete the query with visibility filtering and execute
var posts = await postsQuery
- .FilterWithVisibility(currentUser, userFriends, filter is null ? userPublishers : [], isListing: true)
+ .FilterWithVisibility(filter is null ? userPublishers : [], isListing: true)
.Take(take * 5) // Fetch more posts to have a good pool for ranking
.ToListAsync();
@@ -245,12 +242,12 @@ public class ActivityService(
activities.Add(post.ToActivity());
if (activities.Count == 0)
- activities.Add(Activity.Empty());
+ activities.Add(Common.Models.Activity.Empty());
return activities;
}
- private static double CalculatePopularity(List posts)
+ private static double CalculatePopularity(List posts)
{
var score = posts.Sum(p => p.Upvotes - p.Downvotes);
var postCount = posts.Count;
diff --git a/DysonNetwork.Sphere/Activity/DiscoveryActivity.cs b/DysonNetwork.Sphere/Activity/DiscoveryActivity.cs
index 6111589..aa2ab8e 100644
--- a/DysonNetwork.Sphere/Activity/DiscoveryActivity.cs
+++ b/DysonNetwork.Sphere/Activity/DiscoveryActivity.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using DysonNetwork.Common.Models;
using NodaTime;
namespace DysonNetwork.Sphere.Activity;
@@ -8,10 +9,10 @@ public class DiscoveryActivity(List items) : IActivity
{
public List Items { get; set; } = items;
- public Activity ToActivity()
+ public Common.Models.Activity ToActivity()
{
var now = SystemClock.Instance.GetCurrentInstant();
- return new Activity
+ return new Common.Models.Activity
{
Id = Guid.NewGuid(),
Type = "discovery",
diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs
index 1f635c6..fbb4286 100644
--- a/DysonNetwork.Sphere/AppDatabase.cs
+++ b/DysonNetwork.Sphere/AppDatabase.cs
@@ -1,79 +1,31 @@
-using System.Linq.Expressions;
-using System.Reflection;
-using DysonNetwork.Sphere.Account;
-using DysonNetwork.Sphere.Auth;
-using DysonNetwork.Sphere.Chat;
-using DysonNetwork.Sphere.Developer;
+using DysonNetwork.Common.Models;
using DysonNetwork.Sphere.Permission;
-using DysonNetwork.Sphere.Post;
-using DysonNetwork.Sphere.Publisher;
using DysonNetwork.Sphere.Realm;
using DysonNetwork.Sphere.Sticker;
-using DysonNetwork.Sphere.Storage;
-using DysonNetwork.Sphere.Wallet;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Query;
-using NodaTime;
-using Npgsql;
-using Quartz;
namespace DysonNetwork.Sphere;
-public interface IIdentifiedResource
-{
- public string ResourceIdentifier { get; }
-}
-
-public abstract class ModelBase
-{
- public Instant CreatedAt { get; set; }
- public Instant UpdatedAt { get; set; }
- public Instant? DeletedAt { get; set; }
-}
-
public class AppDatabase(
DbContextOptions options,
IConfiguration configuration
) : DbContext(options)
{
- public DbSet PermissionNodes { get; set; }
- public DbSet PermissionGroups { get; set; }
- public DbSet PermissionGroupMembers { get; set; }
-
- public DbSet MagicSpells { get; set; }
- public DbSet Accounts { get; set; }
- public DbSet AccountConnections { get; set; }
- public DbSet AccountProfiles { get; set; }
- public DbSet AccountContacts { get; set; }
- public DbSet AccountAuthFactors { get; set; }
- public DbSet AccountRelationships { get; set; }
- public DbSet AccountStatuses { get; set; }
- public DbSet AccountCheckInResults { get; set; }
- public DbSet Notifications { get; set; }
- public DbSet NotificationPushSubscriptions { get; set; }
- public DbSet Badges { get; set; }
- public DbSet ActionLogs { get; set; }
- public DbSet AbuseReports { get; set; }
-
- public DbSet AuthSessions { get; set; }
- public DbSet AuthChallenges { get; set; }
-
public DbSet Files { get; set; }
public DbSet FileReferences { get; set; }
- public DbSet Publishers { get; set; }
- public DbSet PublisherMembers { get; set; }
- public DbSet PublisherSubscriptions { get; set; }
+ public DbSet Publishers { get; set; }
+
public DbSet PublisherFeatures { get; set; }
- public DbSet Posts { get; set; }
+ public DbSet Posts { get; set; }
public DbSet PostReactions { get; set; }
public DbSet PostTags { get; set; }
public DbSet PostCategories { get; set; }
public DbSet PostCollections { get; set; }
- public DbSet Realms { get; set; }
+ public DbSet Realms { get; set; }
public DbSet RealmMembers { get; set; }
public DbSet Tags { get; set; }
public DbSet RealmTags { get; set; }
@@ -87,7 +39,7 @@ public class AppDatabase(
public DbSet Stickers { get; set; }
public DbSet StickerPacks { get; set; }
- public DbSet Wallets { get; set; }
+ public DbSet Wallets { get; set; }
public DbSet WalletPockets { get; set; }
public DbSet PaymentOrders { get; set; }
public DbSet PaymentTransactions { get; set; }
@@ -111,37 +63,7 @@ public class AppDatabase(
.UseNodaTime()
).UseSnakeCaseNamingConvention();
- optionsBuilder.UseAsyncSeeding(async (context, _, cancellationToken) =>
- {
- var defaultPermissionGroup = await context.Set()
- .FirstOrDefaultAsync(g => g.Key == "default", cancellationToken);
- if (defaultPermissionGroup is null)
- {
- context.Set().Add(new PermissionGroup
- {
- Key = "default",
- Nodes = new List
- {
- "posts.create",
- "posts.react",
- "publishers.create",
- "files.create",
- "chat.create",
- "chat.messages.create",
- "chat.realtime.create",
- "accounts.statuses.create",
- "accounts.statuses.update",
- "stickers.packs.create",
- "stickers.create"
- }.Select(permission =>
- PermissionService.NewPermissionNode("group:default", "global", permission, true))
- .ToList()
- });
- await context.SaveChangesAsync(cancellationToken);
- }
- });
-
- optionsBuilder.UseSeeding((context, _) => {});
+
base.OnConfiguring(optionsBuilder);
}
@@ -149,26 +71,7 @@ public class AppDatabase(
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
-
- modelBuilder.Entity()
- .HasKey(pg => new { pg.GroupId, pg.Actor });
- modelBuilder.Entity()
- .HasOne(pg => pg.Group)
- .WithMany(g => g.Members)
- .HasForeignKey(pg => pg.GroupId)
- .OnDelete(DeleteBehavior.Cascade);
-
- modelBuilder.Entity()
- .HasKey(r => new { FromAccountId = r.AccountId, ToAccountId = r.RelatedId });
- modelBuilder.Entity()
- .HasOne(r => r.Account)
- .WithMany(a => a.OutgoingRelationships)
- .HasForeignKey(r => r.AccountId);
- modelBuilder.Entity()
- .HasOne(r => r.Related)
- .WithMany(a => a.IncomingRelationships)
- .HasForeignKey(r => r.RelatedId);
-
+
modelBuilder.Entity()
.HasKey(pm => new { pm.PublisherId, pm.AccountId });
modelBuilder.Entity()
@@ -176,23 +79,8 @@ public class AppDatabase(
.WithMany(p => p.Members)
.HasForeignKey(pm => pm.PublisherId)
.OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
- .HasOne(pm => pm.Account)
- .WithMany()
- .HasForeignKey(pm => pm.AccountId)
- .OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
- .HasOne(ps => ps.Publisher)
- .WithMany(p => p.Subscriptions)
- .HasForeignKey(ps => ps.PublisherId)
- .OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
- .HasOne(ps => ps.Account)
- .WithMany()
- .HasForeignKey(ps => ps.AccountId)
- .OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
+ modelBuilder.Entity()
.HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content })
.HasIndex(p => p.SearchVector)
.HasMethod("GIN");
@@ -207,69 +95,35 @@ public class AppDatabase(
.HasForeignKey(s => s.AppId)
.OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
+ modelBuilder.Entity()
.HasOne(p => p.RepliedPost)
.WithMany()
.HasForeignKey(p => p.RepliedPostId)
.OnDelete(DeleteBehavior.Restrict);
- modelBuilder.Entity()
+ modelBuilder.Entity()
.HasOne(p => p.ForwardedPost)
.WithMany()
.HasForeignKey(p => p.ForwardedPostId)
.OnDelete(DeleteBehavior.Restrict);
- modelBuilder.Entity()
+ modelBuilder.Entity()
.HasMany(p => p.Tags)
.WithMany(t => t.Posts)
.UsingEntity(j => j.ToTable("post_tag_links"));
- modelBuilder.Entity()
+ modelBuilder.Entity()
.HasMany(p => p.Categories)
.WithMany(c => c.Posts)
.UsingEntity(j => j.ToTable("post_category_links"));
- modelBuilder.Entity()
+ modelBuilder.Entity()
.HasMany(p => p.Collections)
.WithMany(c => c.Posts)
.UsingEntity(j => j.ToTable("post_collection_links"));
- modelBuilder.Entity()
- .HasKey(pm => new { pm.RealmId, pm.AccountId });
- modelBuilder.Entity()
- .HasOne(pm => pm.Realm)
- .WithMany(p => p.Members)
- .HasForeignKey(pm => pm.RealmId)
- .OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
- .HasOne(pm => pm.Account)
- .WithMany()
- .HasForeignKey(pm => pm.AccountId)
- .OnDelete(DeleteBehavior.Cascade);
-
- modelBuilder.Entity()
- .HasKey(rt => new { rt.RealmId, rt.TagId });
- modelBuilder.Entity()
- .HasOne(rt => rt.Realm)
- .WithMany(r => r.RealmTags)
- .HasForeignKey(rt => rt.RealmId)
- .OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
- .HasOne(rt => rt.Tag)
- .WithMany(t => t.RealmTags)
- .HasForeignKey(rt => rt.TagId)
- .OnDelete(DeleteBehavior.Cascade);
-
modelBuilder.Entity()
.HasKey(pm => new { pm.Id });
modelBuilder.Entity()
.HasAlternateKey(pm => new { pm.ChatRoomId, pm.AccountId });
- modelBuilder.Entity()
- .HasOne(pm => pm.ChatRoom)
- .WithMany(p => p.Members)
- .HasForeignKey(pm => pm.ChatRoomId)
- .OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
- .HasOne(pm => pm.Account)
- .WithMany()
- .HasForeignKey(pm => pm.AccountId)
- .OnDelete(DeleteBehavior.Cascade);
+
+
modelBuilder.Entity()
.HasOne(m => m.ForwardedMessage)
.WithMany()
@@ -280,16 +134,8 @@ public class AppDatabase(
.WithMany()
.HasForeignKey(m => m.RepliedMessageId)
.OnDelete(DeleteBehavior.Restrict);
- modelBuilder.Entity()
- .HasOne(m => m.Room)
- .WithMany()
- .HasForeignKey(m => m.RoomId)
- .OnDelete(DeleteBehavior.Cascade);
- modelBuilder.Entity()
- .HasOne(m => m.Sender)
- .WithMany()
- .HasForeignKey(m => m.SenderId)
- .OnDelete(DeleteBehavior.Cascade);
+
+
modelBuilder.Entity()
.HasIndex(f => f.Url)
@@ -299,128 +145,7 @@ public class AppDatabase(
.HasIndex(a => a.Url)
.IsUnique();
- // Automatically apply soft-delete filter to all entities inheriting BaseModel
- foreach (var entityType in modelBuilder.Model.GetEntityTypes())
- {
- if (!typeof(ModelBase).IsAssignableFrom(entityType.ClrType)) continue;
- var method = typeof(AppDatabase)
- .GetMethod(nameof(SetSoftDeleteFilter),
- BindingFlags.NonPublic | BindingFlags.Static)!
- .MakeGenericMethod(entityType.ClrType);
-
- method.Invoke(null, [modelBuilder]);
- }
- }
-
- private static void SetSoftDeleteFilter(ModelBuilder modelBuilder)
- where TEntity : ModelBase
- {
- modelBuilder.Entity().HasQueryFilter(e => e.DeletedAt == null);
- }
-
- public override async Task SaveChangesAsync(CancellationToken cancellationToken = default)
- {
- var now = SystemClock.Instance.GetCurrentInstant();
-
- foreach (var entry in ChangeTracker.Entries())
- {
- switch (entry.State)
- {
- case EntityState.Added:
- entry.Entity.CreatedAt = now;
- entry.Entity.UpdatedAt = now;
- break;
- case EntityState.Modified:
- entry.Entity.UpdatedAt = now;
- break;
- case EntityState.Deleted:
- entry.State = EntityState.Modified;
- entry.Entity.DeletedAt = now;
- break;
- case EntityState.Detached:
- case EntityState.Unchanged:
- default:
- break;
- }
- }
-
- return await base.SaveChangesAsync(cancellationToken);
- }
-}
-
-public class AppDatabaseRecyclingJob(AppDatabase db, ILogger logger) : IJob
-{
- public async Task Execute(IJobExecutionContext context)
- {
- var now = SystemClock.Instance.GetCurrentInstant();
-
- logger.LogInformation("Cleaning up expired records...");
-
- // Expired relationships
- var affectedRows = await db.AccountRelationships
- .Where(x => x.ExpiredAt != null && x.ExpiredAt <= now)
- .ExecuteDeleteAsync();
- logger.LogDebug("Removed {Count} records of expired relationships.", affectedRows);
- // Expired permission group members
- affectedRows = await db.PermissionGroupMembers
- .Where(x => x.ExpiredAt != null && x.ExpiredAt <= now)
- .ExecuteDeleteAsync();
- logger.LogDebug("Removed {Count} records of expired permission group members.", affectedRows);
-
- logger.LogInformation("Deleting soft-deleted records...");
-
- var threshold = now - Duration.FromDays(7);
-
- var entityTypes = db.Model.GetEntityTypes()
- .Where(t => typeof(ModelBase).IsAssignableFrom(t.ClrType) && t.ClrType != typeof(ModelBase))
- .Select(t => t.ClrType);
-
- foreach (var entityType in entityTypes)
- {
- var set = (IQueryable)db.GetType().GetMethod(nameof(DbContext.Set), Type.EmptyTypes)!
- .MakeGenericMethod(entityType).Invoke(db, null)!;
- var parameter = Expression.Parameter(entityType, "e");
- var property = Expression.Property(parameter, nameof(ModelBase.DeletedAt));
- var condition = Expression.LessThan(property, Expression.Constant(threshold, typeof(Instant?)));
- var notNull = Expression.NotEqual(property, Expression.Constant(null, typeof(Instant?)));
- var finalCondition = Expression.AndAlso(notNull, condition);
- var lambda = Expression.Lambda(finalCondition, parameter);
-
- var queryable = set.Provider.CreateQuery(
- Expression.Call(
- typeof(Queryable),
- "Where",
- [entityType],
- set.Expression,
- Expression.Quote(lambda)
- )
- );
-
- var toListAsync = typeof(EntityFrameworkQueryableExtensions)
- .GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync))!
- .MakeGenericMethod(entityType);
-
- var items = await (dynamic)toListAsync.Invoke(null, [queryable, CancellationToken.None])!;
- db.RemoveRange(items);
- }
-
- await db.SaveChangesAsync();
- }
-}
-
-public class AppDatabaseFactory : IDesignTimeDbContextFactory
-{
- public AppDatabase CreateDbContext(string[] args)
- {
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json")
- .Build();
-
- var optionsBuilder = new DbContextOptionsBuilder();
- return new AppDatabase(optionsBuilder.Options, configuration);
- }
-}
+
public static class OptionalQueryExtensions
{
diff --git a/DysonNetwork.Sphere/Chat/ChatController.cs b/DysonNetwork.Sphere/Chat/ChatController.cs
index 822f812..773a5b3 100644
--- a/DysonNetwork.Sphere/Chat/ChatController.cs
+++ b/DysonNetwork.Sphere/Chat/ChatController.cs
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
+using DysonNetwork.Common.Models;
using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Storage;
using Microsoft.AspNetCore.Authorization;
@@ -10,7 +11,7 @@ namespace DysonNetwork.Sphere.Chat;
[ApiController]
[Route("/chat")]
-public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomService crs) : ControllerBase
+public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomService crs, AccountClient accountClient) : ControllerBase
{
public class MarkMessageReadRequest
{
diff --git a/DysonNetwork.Sphere/Chat/ChatRoomController.cs b/DysonNetwork.Sphere/Chat/ChatRoomController.cs
index 6961590..fc8e348 100644
--- a/DysonNetwork.Sphere/Chat/ChatRoomController.cs
+++ b/DysonNetwork.Sphere/Chat/ChatRoomController.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
-using DysonNetwork.Sphere.Account;
+using DysonNetwork.Common.Models;
using DysonNetwork.Sphere.Localization;
using DysonNetwork.Sphere.Permission;
using DysonNetwork.Sphere.Realm;
@@ -23,7 +23,8 @@ public class ChatRoomController(
NotificationService nty,
RelationshipService rels,
IStringLocalizer localizer,
- AccountEventService aes
+
+ PassClient passClient
) : ControllerBase
{
[HttpGet("{id:guid}")]
@@ -36,7 +37,8 @@ public class ChatRoomController(
if (chatRoom is null) return NotFound();
if (chatRoom.Type != ChatRoomType.DirectMessage) return Ok(chatRoom);
- if (HttpContext.Items["CurrentUser"] is Account.Account currentUser)
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is not null)
chatRoom = await crs.LoadDirectMessageMembers(chatRoom, currentUser.Id);
return Ok(chatRoom);
@@ -46,8 +48,8 @@ public class ChatRoomController(
[Authorize]
public async Task>> ListJoinedChatRooms()
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
- return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var userId = currentUser.Id;
var chatRooms = await db.ChatMembers
@@ -72,10 +74,10 @@ public class ChatRoomController(
[Authorize]
public async Task> CreateDirectMessage([FromBody] DirectMessageRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
- return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
- var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId);
+ var relatedUser = await passClient.GetAccountByIdAsync(request.RelatedUserId);
if (relatedUser is null)
return BadRequest("Related user was not found");
@@ -134,8 +136,8 @@ public class ChatRoomController(
[Authorize]
public async Task> GetDirectChatRoom(Guid userId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
- return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var room = await db.ChatRooms
.Include(c => c.Members)
@@ -164,7 +166,8 @@ public class ChatRoomController(
[RequiredPermission("global", "chat.create")]
public async Task> CreateChatRoom(ChatRoomRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
if (request.Name is null) return BadRequest("You cannot create a chat room without a name.");
var chatRoom = new ChatRoom
@@ -236,7 +239,8 @@ public class ChatRoomController(
[HttpPatch("{id:guid}")]
public async Task> UpdateChatRoom(Guid id, [FromBody] ChatRoomRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var chatRoom = await db.ChatRooms
.Where(e => e.Id == id)
@@ -321,7 +325,8 @@ public class ChatRoomController(
[HttpDelete("{id:guid}")]
public async Task DeleteChatRoom(Guid id)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var chatRoom = await db.ChatRooms
.Where(e => e.Id == id)
@@ -356,8 +361,8 @@ public class ChatRoomController(
[Authorize]
public async Task> GetRoomIdentity(Guid roomId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser)
- return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var member = await db.ChatMembers
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
@@ -375,7 +380,7 @@ public class ChatRoomController(
public async Task>> ListMembers(Guid roomId, [FromQuery] int take = 20,
[FromQuery] int skip = 0, [FromQuery] bool withStatus = false, [FromQuery] string? status = null)
{
- var currentUser = HttpContext.Items["CurrentUser"] as Account.Account;
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
var room = await db.ChatRooms
.FirstOrDefaultAsync(r => r.Id == roomId);
@@ -448,10 +453,11 @@ public class ChatRoomController(
public async Task> InviteMember(Guid roomId,
[FromBody] ChatMemberRequest request)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var userId = currentUser.Id;
- var relatedUser = await db.Accounts.FindAsync(request.RelatedUserId);
+ var relatedUser = await passClient.GetAccountByIdAsync(request.RelatedUserId);
if (relatedUser is null) return BadRequest("Related user was not found");
if (await rels.HasRelationshipWithStatus(currentUser.Id, relatedUser.Id, RelationshipStatus.Blocked))
@@ -519,7 +525,8 @@ public class ChatRoomController(
[Authorize]
public async Task>> ListChatInvites()
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var userId = currentUser.Id;
var members = await db.ChatMembers
@@ -544,7 +551,8 @@ public class ChatRoomController(
[Authorize]
public async Task> AcceptChatInvite(Guid roomId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var userId = currentUser.Id;
var member = await db.ChatMembers
@@ -571,7 +579,8 @@ public class ChatRoomController(
[Authorize]
public async Task DeclineChatInvite(Guid roomId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var userId = currentUser.Id;
var member = await db.ChatMembers
@@ -600,7 +609,8 @@ public class ChatRoomController(
[FromBody] ChatMemberNotifyRequest request
)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var chatRoom = await db.ChatRooms
.Where(r => r.Id == roomId)
@@ -629,7 +639,8 @@ public class ChatRoomController(
public async Task> UpdateChatMemberRole(Guid roomId, Guid memberId, [FromBody] int newRole)
{
if (newRole >= ChatMemberRole.Owner) return BadRequest("Unable to set chat member to owner or greater role.");
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var chatRoom = await db.ChatRooms
.Where(r => r.Id == roomId)
@@ -688,7 +699,8 @@ public class ChatRoomController(
[Authorize]
public async Task RemoveChatMember(Guid roomId, Guid memberId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var chatRoom = await db.ChatRooms
.Where(r => r.Id == roomId)
@@ -736,7 +748,8 @@ public class ChatRoomController(
[Authorize]
public async Task> JoinChatRoom(Guid roomId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var chatRoom = await db.ChatRooms
.Where(r => r.Id == roomId)
@@ -774,7 +787,8 @@ public class ChatRoomController(
[Authorize]
public async Task LeaveChat(Guid roomId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var member = await db.ChatMembers
.Where(m => m.AccountId == currentUser.Id)
diff --git a/DysonNetwork.Sphere/Chat/ChatRoomService.cs b/DysonNetwork.Sphere/Chat/ChatRoomService.cs
index be57a69..d3eb517 100644
--- a/DysonNetwork.Sphere/Chat/ChatRoomService.cs
+++ b/DysonNetwork.Sphere/Chat/ChatRoomService.cs
@@ -1,3 +1,5 @@
+using DysonNetwork.Common.Models;
+using DysonNetwork.Common.Services;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
using NodaTime;
diff --git a/DysonNetwork.Sphere/Chat/ChatService.cs b/DysonNetwork.Sphere/Chat/ChatService.cs
index 8e6daa3..32ee092 100644
--- a/DysonNetwork.Sphere/Chat/ChatService.cs
+++ b/DysonNetwork.Sphere/Chat/ChatService.cs
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions;
-using DysonNetwork.Sphere.Account;
+using DysonNetwork.Pass.Features.Account;
+using DysonNetwork.Common.Models;
using DysonNetwork.Sphere.Chat.Realtime;
using DysonNetwork.Sphere.Connection;
using DysonNetwork.Sphere.Storage;
@@ -241,7 +242,6 @@ public partial class ChatService(
Priority = 10,
};
- List accountsToNotify = [];
foreach (var member in members)
{
scopedWs.SendPacketToAccount(member.AccountId, new WebSocketPacket
@@ -260,15 +260,10 @@ public partial class ChatService(
}
else if (member.Notify == ChatMemberNotify.Mentions) continue;
- accountsToNotify.Add(member.Account);
+ await scopedNty.SendNotification(member.Account, "invites.chats", notification.Title, null, notification.Content, actionUri: notification.Meta["action_uri"].ToString());
}
- logger.LogInformation($"Trying to deliver message to {accountsToNotify.Count} accounts...");
- // Only send notifications if there are accounts to notify
- if (accountsToNotify.Count > 0)
- await scopedNty.SendNotificationBatch(notification, accountsToNotify, save: false);
-
- logger.LogInformation($"Delivered message to {accountsToNotify.Count} accounts.");
+ logger.LogInformation($"Delivered message to {members.Count} accounts.");
}
///
diff --git a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs
index b7c7509..a462666 100644
--- a/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs
+++ b/DysonNetwork.Sphere/Chat/Realtime/IRealtimeService.cs
@@ -36,7 +36,7 @@ public interface IRealtimeService
/// The session identifier
/// The user is the admin of session
/// User-specific token for the session
- string GetUserToken(Account.Account account, string sessionId, bool isAdmin = false);
+ string GetUserToken(Guid accountId, string accountName, string sessionId, bool isAdmin = false);
///
/// Processes incoming webhook requests from the realtime service provider
diff --git a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs
index 8e07d05..2b9f616 100644
--- a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs
+++ b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs
@@ -4,6 +4,8 @@ using Livekit.Server.Sdk.Dotnet;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using System.Text.Json;
+using DysonNetwork.Common.Models;
+using DysonNetwork.Common.Services;
namespace DysonNetwork.Sphere.Chat.Realtime;
@@ -126,7 +128,7 @@ public class LivekitRealtimeService : IRealtimeService
Room = sessionId
})
.WithMetadata(JsonSerializer.Serialize(new Dictionary
- { { "account_id", account.Id.ToString() } }))
+ { { "account_id", accountId.ToString() } }))
.WithTtl(TimeSpan.FromHours(1));
return token.ToJwt();
}
diff --git a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs
index 5694dc5..788fd4e 100644
--- a/DysonNetwork.Sphere/Chat/RealtimeCallController.cs
+++ b/DysonNetwork.Sphere/Chat/RealtimeCallController.cs
@@ -1,3 +1,4 @@
+using DysonNetwork.Common.Models;
using DysonNetwork.Sphere.Chat.Realtime;
using Livekit.Server.Sdk.Dotnet;
using Microsoft.AspNetCore.Authorization;
@@ -163,7 +164,8 @@ public class RealtimeCallController(
[Authorize]
public async Task> EndCall(Guid roomId)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var member = await db.ChatMembers
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
diff --git a/DysonNetwork.Sphere/Connection/AutoCompletionController.cs b/DysonNetwork.Sphere/Connection/AutoCompletionController.cs
index 94eae1c..288a355 100644
--- a/DysonNetwork.Sphere/Connection/AutoCompletionController.cs
+++ b/DysonNetwork.Sphere/Connection/AutoCompletionController.cs
@@ -38,19 +38,7 @@ public class AutoCompletionController(AppDatabase db)
private async Task> GetAccountCompletions(string searchTerm)
{
- return await db.Accounts
- .Where(a => EF.Functions.ILike(a.Name, $"%{searchTerm}%"))
- .OrderBy(a => a.Name)
- .Take(10)
- .Select(a => new CompletionItem
- {
- Id = a.Id.ToString(),
- DisplayName = a.Name,
- SecondaryText = a.Nick,
- Type = "account",
- Data = a
- })
- .ToListAsync();
+ return await passClient.GetAccountCompletions(searchTerm);
}
private async Task> GetStickerCompletions(string searchTerm)
diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs
index ee072d2..bc1f144 100644
--- a/DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs
+++ b/DysonNetwork.Sphere/Connection/Handlers/MessageReadHandler.cs
@@ -1,4 +1,5 @@
using System.Net.WebSockets;
+using DysonNetwork.Common.Models;
using DysonNetwork.Sphere.Chat;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
@@ -42,7 +43,7 @@ public class MessageReadHandler(
return;
}
- var sender = await crs.GetRoomMember(currentUser.Id, request.ChatRoomId);
+ var sender = await crs.GetRoomMember(currentUserId, request.ChatRoomId);
if (sender is null)
{
await socket.SendAsync(
diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs
index b6447db..af2f717 100644
--- a/DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs
+++ b/DysonNetwork.Sphere/Connection/Handlers/MessageTypingHandler.cs
@@ -33,7 +33,7 @@ public class MessageTypingHandler(ChatRoomService crs) : IWebSocketPacketHandler
return;
}
- var sender = await crs.GetRoomMember(currentUser.Id, request.ChatRoomId);
+ var sender = await crs.GetRoomMember(currentUserId, request.ChatRoomId);
if (sender is null)
{
await socket.SendAsync(
diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs
index cf5792b..427a5fe 100644
--- a/DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs
+++ b/DysonNetwork.Sphere/Connection/Handlers/MessagesSubscribeHandler.cs
@@ -32,7 +32,7 @@ public class MessagesSubscribeHandler(ChatRoomService crs) : IWebSocketPacketHan
return;
}
- var sender = await crs.GetRoomMember(currentUser.Id, request.ChatRoomId);
+ var sender = await crs.GetRoomMember(currentUserId, request.ChatRoomId);
if (sender is null)
{
await socket.SendAsync(
diff --git a/DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs b/DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs
index cc507ac..fda2cf1 100644
--- a/DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs
+++ b/DysonNetwork.Sphere/Connection/Handlers/MessagesUnsubscribeHandler.cs
@@ -8,7 +8,7 @@ public class MessagesUnsubscribeHandler() : IWebSocketPacketHandler
public string PacketType => "messages.unsubscribe";
public Task HandleAsync(
- Account.Account currentUser,
+ Guid currentUserId,
string deviceId,
WebSocketPacket packet,
WebSocket socket,
diff --git a/DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs b/DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs
index b625a01..2761c3e 100644
--- a/DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs
+++ b/DysonNetwork.Sphere/Connection/IWebSocketPacketHandler.cs
@@ -5,5 +5,5 @@ namespace DysonNetwork.Sphere.Connection;
public interface IWebSocketPacketHandler
{
string PacketType { get; }
- Task HandleAsync(Account.Account currentUser, string deviceId, WebSocketPacket packet, WebSocket socket, WebSocketService srv);
+ Task HandleAsync(Guid currentUserId, string deviceId, WebSocketPacket packet, WebSocket socket, WebSocketService srv);
}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs b/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs
index a93d725..9a4977a 100644
--- a/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs
+++ b/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs
@@ -104,7 +104,8 @@ public class WebFeedController(WebFeedService webFeed, PublisherService ps) : Co
[Authorize]
public async Task Scrap([FromRoute] string pubName, Guid id)
{
- if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
+ var currentUser = await passClient.GetAccountByIdAsync(User.GetUserId());
+ if (currentUser is null) return Unauthorized();
var publisher = await ps.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs b/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs
index d7f9bda..c2ea39d 100644
--- a/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs
+++ b/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs
@@ -1,6 +1,7 @@
using System.Globalization;
using AngleSharp;
using AngleSharp.Dom;
+using DysonNetwork.Common.Services;
using DysonNetwork.Sphere.Storage;
using HtmlAgilityPack;
diff --git a/DysonNetwork.Sphere/Connection/WebSocketController.cs b/DysonNetwork.Sphere/Connection/WebSocketController.cs
index 3bf61cf..1d5e18f 100644
--- a/DysonNetwork.Sphere/Connection/WebSocketController.cs
+++ b/DysonNetwork.Sphere/Connection/WebSocketController.cs
@@ -91,7 +91,7 @@ public class WebSocketController(WebSocketService ws, ILogger
);
var packet = WebSocketPacket.FromBytes(buffer[..receiveResult.Count]);
- _ = ws.HandlePacket(currentUser, connectionKey.DeviceId, packet, webSocket);
+ _ = ws.HandlePacket(currentUserId, connectionKey.DeviceId, packet, webSocket);
}
}
catch (OperationCanceledException)
diff --git a/DysonNetwork.Sphere/Connection/WebSocketService.cs b/DysonNetwork.Sphere/Connection/WebSocketService.cs
index c63575c..a3b5677 100644
--- a/DysonNetwork.Sphere/Connection/WebSocketService.cs
+++ b/DysonNetwork.Sphere/Connection/WebSocketService.cs
@@ -111,7 +111,7 @@ public class WebSocketService
{
if (_handlerMap.TryGetValue(packet.Type, out var handler))
{
- await handler.HandleAsync(currentUser, deviceId, packet, socket, this);
+ await handler.HandleAsync(currentUserId, deviceId, packet, socket, this);
return;
}
diff --git a/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.Designer.cs b/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.Designer.cs
deleted file mode 100644
index 96facd5..0000000
--- a/DysonNetwork.Sphere/Data/Migrations/20250628172328_AddOidcProviderSupport.Designer.cs
+++ /dev/null
@@ -1,4000 +0,0 @@
-//
-using System;
-using System.Collections.Generic;
-using System.Text.Json;
-using DysonNetwork.Sphere;
-using DysonNetwork.Sphere.Account;
-using DysonNetwork.Sphere.Chat;
-using DysonNetwork.Sphere.Connection.WebReader;
-using DysonNetwork.Sphere.Storage;
-using DysonNetwork.Sphere.Wallet;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using NetTopologySuite.Geometries;
-using NodaTime;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-using NpgsqlTypes;
-
-#nullable disable
-
-namespace DysonNetwork.Sphere.Data.Migrations
-{
- [DbContext(typeof(AppDatabase))]
- [Migration("20250628172328_AddOidcProviderSupport")]
- partial class AddOidcProviderSupport
- {
- ///
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "9.0.3")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("DysonNetwork.Sphere.Account.AbuseReport", b =>
- {
- b.Property("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid")
- .HasColumnName("id");
-
- b.Property("AccountId")
- .HasColumnType("uuid")
- .HasColumnName("account_id");
-
- b.Property("CreatedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("created_at");
-
- b.Property("DeletedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("deleted_at");
-
- b.Property("Reason")
- .IsRequired()
- .HasMaxLength(8192)
- .HasColumnType("character varying(8192)")
- .HasColumnName("reason");
-
- b.Property("Resolution")
- .HasMaxLength(8192)
- .HasColumnType("character varying(8192)")
- .HasColumnName("resolution");
-
- b.Property("ResolvedAt")
- .HasColumnType("timestamp with time zone")
- .HasColumnName("resolved_at");
-
- b.Property("ResourceIdentifier")
- .IsRequired()
- .HasMaxLength(4096)
- .HasColumnType("character varying(4096)")
- .HasColumnName("resource_identifier");
-
- b.Property("Type")
- .HasColumnType("integer")
- .HasColumnName("type");
-
- b.Property