From 15fb93c2bb3e7cf945cc125ee57af863ffecc606 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 6 Jul 2025 23:41:10 +0800 Subject: [PATCH] :recycle: Moved services & controllers to Pass --- .../Account/AbuseReport.cs | 2 +- .../Account/AccountController.cs | 11 +- .../Account/AccountCurrentController.cs | 123 ++++--- .../Account/AccountEventService.cs | 93 +++-- .../Account/AccountGrpcService.cs | 61 ++++ .../Account/AccountService.cs | 141 ++++---- .../Account/AccountUsernameService.cs | 2 +- .../Account/ActionLogService.cs | 18 +- .../Account/MagicSpell.cs | 2 +- .../Account/MagicSpellController.cs | 2 +- .../Account/MagicSpellService.cs | 95 +++-- .../Account/NotificationController.cs | 76 ++-- .../Account/NotificationService.cs | 311 ++++++++++++++++ .../Account/RelationshipController.cs | 3 +- .../Account/RelationshipService.cs | 36 +- DysonNetwork.Pass/AppDatabase.cs | 178 ++++++++- .../Auth/Auth.cs | 95 +++-- .../Auth/AuthController.cs | 67 ++-- .../Auth/AuthGrpcService.cs | 12 +- .../Auth/AuthService.cs | 184 +++++----- .../Auth/CheckpointModel.cs | 2 +- .../Auth/CompactTokenService.cs | 3 +- .../Controllers/OidcProviderController.cs | 18 +- .../Models/AuthorizationCodeInfo.cs | 4 +- .../Options/OidcProviderOptions.cs | 2 +- .../Responses/AuthorizationResponse.cs | 2 +- .../OidcProvider/Responses/ErrorResponse.cs | 2 +- .../OidcProvider/Responses/TokenResponse.cs | 2 +- .../Services/OidcProviderService.cs | 26 +- .../Auth/OpenId/AfdianOidcService.cs | 8 +- .../Auth/OpenId/AppleMobileSignInRequest.cs | 0 .../Auth/OpenId/AppleOidcService.cs | 6 +- .../Auth/OpenId/ConnectionController.cs | 8 +- .../Auth/OpenId/DiscordOidcService.cs | 6 +- .../Auth/OpenId/GitHubOidcService.cs | 6 +- .../Auth/OpenId/GoogleOidcService.cs | 8 +- .../Auth/OpenId/MicrosoftOidcService.cs | 6 +- .../Auth/OpenId/OidcController.cs | 8 +- .../Auth/OpenId/OidcService.cs | 8 +- .../Auth/OpenId/OidcState.cs | 0 .../Auth/OpenId/OidcUserInfo.cs | 0 DysonNetwork.Pass/Data/AppDatabase.cs | 95 ----- DysonNetwork.Pass/DysonNetwork.Pass.csproj | 39 ++ .../Localization/AccountEventResource.cs | 6 + .../Localization/EmailResource.cs | 5 + .../Localization/NotificationResource.cs | 6 + .../Localization/SharedResource.cs | 6 + .../Pages/Emails/AccountDeletionEmail.razor | 42 +++ .../Emails/ContactVerificationEmail.razor | 43 +++ .../Pages/Emails/EmailLayout.razor | 337 ++++++++++++++++++ .../Pages/Emails/LandingEmail.razor | 43 +++ .../Pages/Emails/PasswordResetEmail.razor | 44 +++ .../Pages/Emails/VerificationEmail.razor | 27 ++ .../Permission/PermissionMiddleware.cs | 4 +- .../Permission/PermissionService.cs | 8 +- DysonNetwork.Pass/Program.cs | 10 +- .../Cache}/CacheService.cs | 2 +- .../DysonNetwork.Shared.csproj | 4 +- .../Account/AccountGrpcService.cs | 88 ----- .../Account/NotificationService.cs | 309 ---------------- .../Activity/ActivityService.cs | 3 +- DysonNetwork.Sphere/AppDatabase.cs | 101 +----- DysonNetwork.Sphere/Chat/ChatRoomService.cs | 1 + .../Chat/Realtime/LivekitService.cs | 1 + .../Connection/WebReader/WebReaderService.cs | 1 + .../DysonNetwork.Sphere.csproj | 3 +- DysonNetwork.Sphere/Post/PostService.cs | 1 + .../Publisher/PublisherService.cs | 1 + .../Publisher/PublisherSubscriptionService.cs | 1 + .../Startup/ServiceCollectionExtensions.cs | 1 + DysonNetwork.Sphere/Sticker/StickerService.cs | 1 + .../Storage/FileReferenceService.cs | 1 + DysonNetwork.Sphere/Storage/FileService.cs | 1 + .../Storage/Handlers/PostViewFlushHandler.cs | 1 + .../Wallet/SubscriptionService.cs | 1 + 75 files changed, 1733 insertions(+), 1141 deletions(-) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/AbuseReport.cs (94%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/AccountController.cs (96%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/AccountCurrentController.cs (89%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/AccountEventService.cs (80%) create mode 100644 DysonNetwork.Pass/Account/AccountGrpcService.cs rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/AccountService.cs (87%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/AccountUsernameService.cs (98%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/ActionLogService.cs (74%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/MagicSpell.cs (95%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/MagicSpellController.cs (92%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/MagicSpellService.cs (76%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/NotificationController.cs (74%) create mode 100644 DysonNetwork.Pass/Account/NotificationService.cs rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/RelationshipController.cs (99%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Account/RelationshipService.cs (85%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/Auth.cs (77%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/AuthController.cs (85%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/AuthGrpcService.cs (91%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/AuthService.cs (59%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/CheckpointModel.cs (69%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/CompactTokenService.cs (97%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OidcProvider/Controllers/OidcProviderController.cs (96%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs (80%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OidcProvider/Options/OidcProviderOptions.cs (95%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OidcProvider/Responses/AuthorizationResponse.cs (88%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OidcProvider/Responses/ErrorResponse.cs (87%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OidcProvider/Responses/TokenResponse.cs (90%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OidcProvider/Services/OidcProviderService.cs (95%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/AfdianOidcService.cs (94%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/AppleMobileSignInRequest.cs (100%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/AppleOidcService.cs (98%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/ConnectionController.cs (98%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/DiscordOidcService.cs (96%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/GitHubOidcService.cs (96%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/GoogleOidcService.cs (97%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/MicrosoftOidcService.cs (97%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/OidcController.cs (97%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/OidcService.cs (98%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/OidcState.cs (100%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Auth/OpenId/OidcUserInfo.cs (100%) delete mode 100644 DysonNetwork.Pass/Data/AppDatabase.cs create mode 100644 DysonNetwork.Pass/Localization/AccountEventResource.cs create mode 100644 DysonNetwork.Pass/Localization/EmailResource.cs create mode 100644 DysonNetwork.Pass/Localization/NotificationResource.cs create mode 100644 DysonNetwork.Pass/Localization/SharedResource.cs create mode 100644 DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/EmailLayout.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/LandingEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor create mode 100644 DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Permission/PermissionMiddleware.cs (95%) rename {DysonNetwork.Sphere => DysonNetwork.Pass}/Permission/PermissionService.cs (98%) rename {DysonNetwork.Sphere/Storage => DysonNetwork.Shared/Cache}/CacheService.cs (99%) delete mode 100644 DysonNetwork.Sphere/Account/AccountGrpcService.cs delete mode 100644 DysonNetwork.Sphere/Account/NotificationService.cs diff --git a/DysonNetwork.Sphere/Account/AbuseReport.cs b/DysonNetwork.Pass/Account/AbuseReport.cs similarity index 94% rename from DysonNetwork.Sphere/Account/AbuseReport.cs rename to DysonNetwork.Pass/Account/AbuseReport.cs index fa07017..16da213 100644 --- a/DysonNetwork.Sphere/Account/AbuseReport.cs +++ b/DysonNetwork.Pass/Account/AbuseReport.cs @@ -1,7 +1,7 @@ using System.ComponentModel.DataAnnotations; using NodaTime; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; public enum AbuseReportType { diff --git a/DysonNetwork.Sphere/Account/AccountController.cs b/DysonNetwork.Pass/Account/AccountController.cs similarity index 96% rename from DysonNetwork.Sphere/Account/AccountController.cs rename to DysonNetwork.Pass/Account/AccountController.cs index d411225..5dabdf3 100644 --- a/DysonNetwork.Sphere/Account/AccountController.cs +++ b/DysonNetwork.Pass/Account/AccountController.cs @@ -1,15 +1,12 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Permission; -using Microsoft.AspNetCore.Authorization; +using DysonNetwork.Shared.Models; +using DysonNetwork.Pass.Auth; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; -using NodaTime.Extensions; -using System.Collections.Generic; -using DysonNetwork.Shared.Models; +using Microsoft.AspNetCore.Http; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; [ApiController] [Route("/accounts")] diff --git a/DysonNetwork.Sphere/Account/AccountCurrentController.cs b/DysonNetwork.Pass/Account/AccountCurrentController.cs similarity index 89% rename from DysonNetwork.Sphere/Account/AccountCurrentController.cs rename to DysonNetwork.Pass/Account/AccountCurrentController.cs index e5d3cbd..9104e0b 100644 --- a/DysonNetwork.Sphere/Account/AccountCurrentController.cs +++ b/DysonNetwork.Pass/Account/AccountCurrentController.cs @@ -1,15 +1,15 @@ using System.ComponentModel.DataAnnotations; +using DysonNetwork.Pass.Auth; +using DysonNetwork.Pass.Permission; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Permission; -using DysonNetwork.Sphere.Storage; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; -using Org.BouncyCastle.Utilities; -namespace DysonNetwork.Sphere.Account; + +namespace DysonNetwork.Pass.Account; [Authorize] [ApiController] @@ -17,7 +17,6 @@ namespace DysonNetwork.Sphere.Account; public class AccountCurrentController( AppDatabase db, AccountService accounts, - FileReferenceService fileRefService, AccountEventService events, AuthService auth ) : ControllerBase @@ -96,61 +95,61 @@ public class AccountCurrentController( if (request.Location is not null) profile.Location = request.Location; if (request.TimeZone is not null) profile.TimeZone = request.TimeZone; - if (request.PictureId is not null) - { - var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); - if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); - - var profileResourceId = $"profile:{profile.Id}"; - - // Remove old references for the profile picture - if (profile.Picture is not null) - { - var oldPictureRefs = - await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.picture"); - foreach (var oldRef in oldPictureRefs) - { - await fileRefService.DeleteReferenceAsync(oldRef.Id); - } - } - - profile.Picture = picture.ToReferenceObject(); - - // Create new reference - await fileRefService.CreateReferenceAsync( - picture.Id, - "profile.picture", - profileResourceId - ); - } - - if (request.BackgroundId is not null) - { - var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); - if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); - - var profileResourceId = $"profile:{profile.Id}"; - - // Remove old references for the profile background - if (profile.Background is not null) - { - var oldBackgroundRefs = - await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.background"); - foreach (var oldRef in oldBackgroundRefs) - { - await fileRefService.DeleteReferenceAsync(oldRef.Id); - } - } - - profile.Background = background.ToReferenceObject(); - - // Create new reference - await fileRefService.CreateReferenceAsync( - background.Id, - "profile.background", - profileResourceId - ); - } + // if (request.PictureId is not null) + // { + // var picture = await db.Files.Where(f => f.Id == request.PictureId).FirstOrDefaultAsync(); + // if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud."); + // + // var profileResourceId = $"profile:{profile.Id}"; + // + // // Remove old references for the profile picture + // if (profile.Picture is not null) + // { + // var oldPictureRefs = + // await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.picture"); + // foreach (var oldRef in oldPictureRefs) + // { + // await fileRefService.DeleteReferenceAsync(oldRef.Id); + // } + // } + // + // profile.Picture = picture.ToReferenceObject(); + // + // // Create new reference + // await fileRefService.CreateReferenceAsync( + // picture.Id, + // "profile.picture", + // profileResourceId + // ); + // } + // + // if (request.BackgroundId is not null) + // { + // var background = await db.Files.Where(f => f.Id == request.BackgroundId).FirstOrDefaultAsync(); + // if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud."); + // + // var profileResourceId = $"profile:{profile.Id}"; + // + // // Remove old references for the profile background + // if (profile.Background is not null) + // { + // var oldBackgroundRefs = + // await fileRefService.GetResourceReferencesAsync(profileResourceId, "profile.background"); + // foreach (var oldRef in oldBackgroundRefs) + // { + // await fileRefService.DeleteReferenceAsync(oldRef.Id); + // } + // } + // + // profile.Background = background.ToReferenceObject(); + // + // // Create new reference + // await fileRefService.CreateReferenceAsync( + // background.Id, + // "profile.background", + // profileResourceId + // ); + // } db.Update(profile); await db.SaveChangesAsync(); @@ -679,7 +678,7 @@ public class AccountCurrentController( { if (HttpContext.Items["CurrentUser"] is not Shared.Models.Account currentUser) return Unauthorized(); - var badges = await db.Badges + var badges = await db.AccountBadges .Where(b => b.AccountId == currentUser.Id) .ToListAsync(); return Ok(badges); diff --git a/DysonNetwork.Sphere/Account/AccountEventService.cs b/DysonNetwork.Pass/Account/AccountEventService.cs similarity index 80% rename from DysonNetwork.Sphere/Account/AccountEventService.cs rename to DysonNetwork.Pass/Account/AccountEventService.cs index d8b7114..5ea0995 100644 --- a/DysonNetwork.Sphere/Account/AccountEventService.cs +++ b/DysonNetwork.Pass/Account/AccountEventService.cs @@ -1,22 +1,17 @@ using System.Globalization; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Activity; -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Wallet; 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.Account; public class AccountEventService( AppDatabase db, - WebSocketService ws, - ICacheService cache, - PaymentService payment, + // WebSocketService ws, + // ICacheService cache, + // PaymentService payment, IStringLocalizer localizer ) { @@ -26,18 +21,18 @@ public class AccountEventService( public void PurgeStatusCache(Guid userId) { var cacheKey = $"{StatusCacheKey}{userId}"; - cache.RemoveAsync(cacheKey); + // cache.RemoveAsync(cacheKey); } public async Task GetStatus(Guid userId) { var cacheKey = $"{StatusCacheKey}{userId}"; - var cachedStatus = await cache.GetAsync(cacheKey); - if (cachedStatus is not null) - { - cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); - return cachedStatus; - } + // var cachedStatus = await cache.GetAsync(cacheKey); + // if (cachedStatus is not null) + // { + // cachedStatus!.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); + // return cachedStatus; + // } var now = SystemClock.Instance.GetCurrentInstant(); var status = await db.AccountStatuses @@ -45,12 +40,13 @@ public class AccountEventService( .Where(e => e.ClearedAt == null || e.ClearedAt > now) .OrderByDescending(e => e.CreatedAt) .FirstOrDefaultAsync(); - var isOnline = ws.GetAccountIsConnected(userId); + // var isOnline = ws.GetAccountIsConnected(userId); + var isOnline = false; // Placeholder if (status is not null) { status.IsOnline = !status.IsInvisible && isOnline; - await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"], - TimeSpan.FromMinutes(5)); + // await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"], + // TimeSpan.FromMinutes(5)); return status; } @@ -84,16 +80,16 @@ public class AccountEventService( foreach (var userId in userIds) { var cacheKey = $"{StatusCacheKey}{userId}"; - var cachedStatus = await cache.GetAsync(cacheKey); - if (cachedStatus != null) - { - cachedStatus.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); - results[userId] = cachedStatus; - } - else - { + // var cachedStatus = await cache.GetAsync(cacheKey); + // if (cachedStatus != null) + // { + // cachedStatus.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId); + // results[userId] = cachedStatus; + // } + // else + // { cacheMissUserIds.Add(userId); - } + // } } if (cacheMissUserIds.Any()) @@ -110,11 +106,12 @@ public class AccountEventService( foreach (var status in statusesFromDb) { - var isOnline = ws.GetAccountIsConnected(status.AccountId); + // var isOnline = ws.GetAccountIsConnected(status.AccountId); + var isOnline = false; // Placeholder status.IsOnline = !status.IsInvisible && isOnline; results[status.AccountId] = status; var cacheKey = $"{StatusCacheKey}{status.AccountId}"; - await cache.SetAsync(cacheKey, status, TimeSpan.FromMinutes(5)); + // await cache.SetAsync(cacheKey, status, TimeSpan.FromMinutes(5)); foundUserIds.Add(status.AccountId); } @@ -123,7 +120,8 @@ public class AccountEventService( { foreach (var userId in usersWithoutStatus) { - var isOnline = ws.GetAccountIsConnected(userId); + // var isOnline = ws.GetAccountIsConnected(userId); + var isOnline = false; // Placeholder var defaultStatus = new Status { Attitude = StatusAttitude.Neutral, @@ -168,12 +166,12 @@ public class AccountEventService( public async Task CheckInDailyDoAskCaptcha(Shared.Models.Account user) { var cacheKey = $"{CaptchaCacheKey}{user.Id}"; - var needsCaptcha = await cache.GetAsync(cacheKey); - if (needsCaptcha is not null) - return needsCaptcha!.Value; + // var needsCaptcha = await cache.GetAsync(cacheKey); + // if (needsCaptcha is not null) + // return needsCaptcha!.Value; var result = Random.Next(100) < CaptchaProbabilityPercent; - await cache.SetAsync(cacheKey, result, TimeSpan.FromHours(24)); + // await cache.SetAsync(cacheKey, result, TimeSpan.FromHours(24)); return result; } @@ -202,10 +200,10 @@ public class AccountEventService( try { - var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100)); + // var lk = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromMilliseconds(100)); - if (lk != null) - await lk.ReleaseAsync(); + // if (lk != null) + // await lk.ReleaseAsync(); } catch { @@ -213,8 +211,8 @@ public class AccountEventService( } // Now try to acquire the lock properly - await using var lockObj = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); - if (lockObj is null) throw new InvalidOperationException("Check-in was in progress."); + // await using var lockObj = await cache.AcquireLockAsync(lockKey, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(5)); + // if (lockObj is null) throw new InvalidOperationException("Check-in was in progress."); var cultureInfo = new CultureInfo(user.Language, false); CultureInfo.CurrentCulture = cultureInfo; @@ -256,13 +254,14 @@ public class AccountEventService( try { if (result.RewardPoints.HasValue) - await payment.CreateTransactionWithAccountAsync( - null, - user.Id, - WalletCurrency.SourcePoint, - result.RewardPoints.Value, - $"Check-in reward on {now:yyyy/MM/dd}" - ); + // await payment.CreateTransactionWithAccountAsync( + // null, + // user.Id, + // WalletCurrency.SourcePoint, + // result.RewardPoints.Value, + // $"Check-in reward on {now:yyyy/MM/dd}" + // ); + Console.WriteLine($"Simulating transaction for {result.RewardPoints.Value} points"); } catch { diff --git a/DysonNetwork.Pass/Account/AccountGrpcService.cs b/DysonNetwork.Pass/Account/AccountGrpcService.cs new file mode 100644 index 0000000..2f53a6b --- /dev/null +++ b/DysonNetwork.Pass/Account/AccountGrpcService.cs @@ -0,0 +1,61 @@ +using DysonNetwork.Shared.Models; +using DysonNetwork.Shared.Protos.Account; +using Grpc.Core; +using Google.Protobuf.WellKnownTypes; +using Microsoft.EntityFrameworkCore; +using DysonNetwork.Pass.Auth; + +namespace DysonNetwork.Pass.Account; + +public class AccountGrpcService(AppDatabase db, AuthService auth) + : DysonNetwork.Shared.Protos.Account.AccountService.AccountServiceBase +{ + public override async Task GetAccount(Empty request, ServerCallContext context) + { + var account = await GetAccountFromContext(context); + return ToAccountResponse(account); + } + + public override async Task UpdateAccount(UpdateAccountRequest request, ServerCallContext context) + { + var account = await GetAccountFromContext(context); + + // TODO: implement + + await db.SaveChangesAsync(); + + return ToAccountResponse(account); + } + + private async Task GetAccountFromContext(ServerCallContext context) + { + var authorizationHeader = context.RequestHeaders.FirstOrDefault(h => h.Key == "authorization"); + if (authorizationHeader == null) + { + throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Missing authorization header.")); + } + + var token = authorizationHeader.Value.Replace("Bearer ", ""); + if (!auth.ValidateToken(token, out var sessionId)) + { + throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid token.")); + } + + var session = await db.AuthSessions.Include(s => s.Account).ThenInclude(a => a.Contacts) + .FirstOrDefaultAsync(s => s.Id == sessionId); + if (session == null) + { + throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Session not found.")); + } + + return session.Account; + } + + private AccountResponse ToAccountResponse(Shared.Models.Account account) + { + // TODO: implement + return new AccountResponse + { + }; + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/AccountService.cs b/DysonNetwork.Pass/Account/AccountService.cs similarity index 87% rename from DysonNetwork.Sphere/Account/AccountService.cs rename to DysonNetwork.Pass/Account/AccountService.cs index dbe2261..8da9b4f 100644 --- a/DysonNetwork.Sphere/Account/AccountService.cs +++ b/DysonNetwork.Pass/Account/AccountService.cs @@ -1,28 +1,24 @@ using System.Globalization; +using DysonNetwork.Pass.Auth; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; -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 EFCore.BulkExtensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using NodaTime; -using Org.BouncyCastle.Utilities; using OtpNet; +using Microsoft.Extensions.Logging; +using EFCore.BulkExtensions; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; public class AccountService( AppDatabase db, - MagicSpellService spells, - AccountUsernameService uname, - NotificationService nty, - EmailService mailer, - IStringLocalizer localizer, + // MagicSpellService spells, + // AccountUsernameService uname, + // NotificationService nty, + // EmailService mailer, + // IStringLocalizer localizer, ICacheService cache, ILogger logger ) @@ -136,15 +132,15 @@ public class AccountService( } else { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.AccountActivation, - new Dictionary - { - { "contact_method", account.Contacts.First().Content } - } - ); - await spells.NotifyMagicSpell(spell, true); + // var spell = await spells.CreateMagicSpell( + // account, + // MagicSpellType.AccountActivation, + // new Dictionary + // { + // { "contact_method", account.Contacts.First().Content } + // } + // ); + // await spells.NotifyMagicSpell(spell, true); } db.Accounts.Add(account); @@ -170,7 +166,8 @@ public class AccountService( : $"{userInfo.FirstName} {userInfo.LastName}".Trim(); // Generate username from email - var username = await uname.GenerateUsernameFromEmailAsync(userInfo.Email); + // var username = await uname.GenerateUsernameFromEmailAsync(userInfo.Email); + var username = userInfo.Email.Split('@')[0]; // Placeholder return await CreateAccount( username, @@ -185,26 +182,26 @@ public class AccountService( public async Task RequestAccountDeletion(Shared.Models.Account account) { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.AccountRemoval, - new Dictionary(), - SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), - preventRepeat: true - ); - await spells.NotifyMagicSpell(spell); + // var spell = await spells.CreateMagicSpell( + // account, + // MagicSpellType.AccountRemoval, + // new Dictionary(), + // SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), + // preventRepeat: true + // ); + // await spells.NotifyMagicSpell(spell); } public async Task RequestPasswordReset(Shared.Models.Account account) { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.AuthPasswordReset, - new Dictionary(), - SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), - preventRepeat: true - ); - await spells.NotifyMagicSpell(spell); + // var spell = await spells.CreateMagicSpell( + // account, + // MagicSpellType.AuthPasswordReset, + // new Dictionary(), + // SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), + // preventRepeat: true + // ); + // await spells.NotifyMagicSpell(spell); } public async Task CheckAuthFactorExists(Shared.Models.Account account, AccountAuthFactorType type) @@ -330,7 +327,7 @@ public class AccountService( { var count = await db.AccountAuthFactors .Where(f => f.AccountId == factor.AccountId) - .If(factor.EnabledAt is not null, q => q.Where(f => f.EnabledAt != null)) + // .If(factor.EnabledAt is not null, q => q.Where(f => f.EnabledAt != null)) .CountAsync(); if (count <= 1) throw new InvalidOperationException("Deleting this auth factor will cause you have no auth factor."); @@ -356,14 +353,14 @@ public class AccountService( if (await _GetFactorCode(factor) is not null) throw new InvalidOperationException("A factor code has been sent and in active duration."); - await nty.SendNotification( - account, - "auth.verification", - localizer["AuthCodeTitle"], - null, - localizer["AuthCodeBody", code], - save: true - ); + // await nty.SendNotification( + // account, + // "auth.verification", + // localizer["AuthCodeTitle"], + // null, + // localizer["AuthCodeBody", code], + // save: true + // ); await _SetFactorCode(factor, code, TimeSpan.FromMinutes(5)); break; case AccountAuthFactorType.EmailCode: @@ -398,16 +395,16 @@ public class AccountService( return; } - await mailer.SendTemplatedEmailAsync( - account.Nick, - contact.Content, - localizer["VerificationEmail"], - new VerificationEmailModel - { - Name = account.Name, - Code = code - } - ); + // await mailer.SendTemplatedEmailAsync( + // account.Nick, + // contact.Content, + // localizer["VerificationEmail"], + // new VerificationEmailModel + // { + // Name = account.Name, + // Code = code + // } + // ); await _SetFactorCode(factor, code, TimeSpan.FromMinutes(30)); break; @@ -492,7 +489,7 @@ public class AccountService( .ToListAsync(); if (session.Challenge.DeviceId is not null) - await nty.UnsubscribePushNotifications(session.Challenge.DeviceId); + // await nty.UnsubscribePushNotifications(session.Challenge.DeviceId); // The current session should be included in the sessions' list await db.AuthSessions @@ -521,14 +518,14 @@ public class AccountService( public async Task VerifyContactMethod(Shared.Models.Account account, AccountContact contact) { - var spell = await spells.CreateMagicSpell( - account, - MagicSpellType.ContactVerification, - new Dictionary { { "contact_method", contact.Content } }, - expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), - preventRepeat: true - ); - await spells.NotifyMagicSpell(spell); + // var spell = await spells.CreateMagicSpell( + // account, + // MagicSpellType.ContactVerification, + // new Dictionary { { "contact_method", contact.Content } }, + // expiredAt: SystemClock.Instance.GetCurrentInstant().Plus(Duration.FromHours(24)), + // preventRepeat: true + // ); + // await spells.NotifyMagicSpell(spell); } public async Task SetContactMethodPrimary(Shared.Models.Account account, AccountContact contact) @@ -578,7 +575,7 @@ public class AccountService( public async Task GrantBadge(Shared.Models.Account account, Badge badge) { badge.AccountId = account.Id; - db.Badges.Add(badge); + db.AccountBadges.Add(badge); await db.SaveChangesAsync(); return badge; } @@ -589,7 +586,7 @@ public class AccountService( /// public async Task RevokeBadge(Shared.Models.Account account, Guid badgeId) { - var badge = await db.Badges + var badge = await db.AccountBadges .Where(b => b.AccountId == account.Id && b.Id == badgeId) .OrderByDescending(b => b.CreatedAt) .FirstOrDefaultAsync(); @@ -611,13 +608,13 @@ public class AccountService( try { - var badge = await db.Badges + var badge = await db.AccountBadges .Where(b => b.AccountId == account.Id && b.Id == badgeId) .OrderByDescending(b => b.CreatedAt) .FirstOrDefaultAsync(); if (badge is null) throw new InvalidOperationException("Badge was not found."); - await db.Badges + await db.AccountBadges .Where(b => b.AccountId == account.Id && b.Id != badgeId) .ExecuteUpdateAsync(s => s.SetProperty(p => p.ActivatedAt, p => null)); diff --git a/DysonNetwork.Sphere/Account/AccountUsernameService.cs b/DysonNetwork.Pass/Account/AccountUsernameService.cs similarity index 98% rename from DysonNetwork.Sphere/Account/AccountUsernameService.cs rename to DysonNetwork.Pass/Account/AccountUsernameService.cs index 094f9f0..25ffcaa 100644 --- a/DysonNetwork.Sphere/Account/AccountUsernameService.cs +++ b/DysonNetwork.Pass/Account/AccountUsernameService.cs @@ -1,7 +1,7 @@ using System.Text.RegularExpressions; using Microsoft.EntityFrameworkCore; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; /// /// Service for handling username generation and validation diff --git a/DysonNetwork.Sphere/Account/ActionLogService.cs b/DysonNetwork.Pass/Account/ActionLogService.cs similarity index 74% rename from DysonNetwork.Sphere/Account/ActionLogService.cs rename to DysonNetwork.Pass/Account/ActionLogService.cs index 92e0bf5..2021797 100644 --- a/DysonNetwork.Sphere/Account/ActionLogService.cs +++ b/DysonNetwork.Pass/Account/ActionLogService.cs @@ -1,12 +1,12 @@ using DysonNetwork.Shared.Models; -using Quartz; -using DysonNetwork.Sphere.Connection; -using DysonNetwork.Sphere.Storage; -using DysonNetwork.Sphere.Storage.Handlers; +using Microsoft.AspNetCore.Http; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; -public class ActionLogService(GeoIpService geo, FlushBufferService fbs) +public class ActionLogService( + // GeoIpService geo, + // FlushBufferService fbs +) { public void CreateActionLog(Guid accountId, string action, Dictionary meta) { @@ -17,7 +17,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs) Meta = meta, }; - fbs.Enqueue(log); + // fbs.Enqueue(log); } public void CreateActionLogFromRequest(string action, Dictionary meta, HttpRequest request, @@ -29,7 +29,7 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs) Meta = meta, UserAgent = request.Headers.UserAgent, IpAddress = request.HttpContext.Connection.RemoteIpAddress?.ToString(), - Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString()) + // Location = geo.GetPointFromIp(request.HttpContext.Connection.RemoteIpAddress?.ToString()) }; if (request.HttpContext.Items["CurrentUser"] is Shared.Models.Account currentUser) @@ -42,6 +42,6 @@ public class ActionLogService(GeoIpService geo, FlushBufferService fbs) if (request.HttpContext.Items["CurrentSession"] is Session currentSession) log.SessionId = currentSession.Id; - fbs.Enqueue(log); + // fbs.Enqueue(log); } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/MagicSpell.cs b/DysonNetwork.Pass/Account/MagicSpell.cs similarity index 95% rename from DysonNetwork.Sphere/Account/MagicSpell.cs rename to DysonNetwork.Pass/Account/MagicSpell.cs index 9b618b4..09d25c9 100644 --- a/DysonNetwork.Sphere/Account/MagicSpell.cs +++ b/DysonNetwork.Pass/Account/MagicSpell.cs @@ -4,7 +4,7 @@ using System.Text.Json.Serialization; using Microsoft.EntityFrameworkCore; using NodaTime; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; public enum MagicSpellType { diff --git a/DysonNetwork.Sphere/Account/MagicSpellController.cs b/DysonNetwork.Pass/Account/MagicSpellController.cs similarity index 92% rename from DysonNetwork.Sphere/Account/MagicSpellController.cs rename to DysonNetwork.Pass/Account/MagicSpellController.cs index d29bb7d..76804f0 100644 --- a/DysonNetwork.Sphere/Account/MagicSpellController.cs +++ b/DysonNetwork.Pass/Account/MagicSpellController.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; [ApiController] [Route("/spells")] diff --git a/DysonNetwork.Sphere/Account/MagicSpellService.cs b/DysonNetwork.Pass/Account/MagicSpellService.cs similarity index 76% rename from DysonNetwork.Sphere/Account/MagicSpellService.cs rename to DysonNetwork.Pass/Account/MagicSpellService.cs index 3c79c94..d9e47ba 100644 --- a/DysonNetwork.Sphere/Account/MagicSpellService.cs +++ b/DysonNetwork.Pass/Account/MagicSpellService.cs @@ -2,23 +2,20 @@ using System.Globalization; using System.Security.Cryptography; using System.Text.Json; using DysonNetwork.Shared.Models; -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; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; public class MagicSpellService( AppDatabase db, - EmailService email, + // EmailService email, IConfiguration configuration, - ILogger logger, - IStringLocalizer localizer + ILogger logger + // IStringLocalizer localizer ) { public async Task CreateMagicSpell( @@ -88,54 +85,54 @@ public class MagicSpellService( switch (spell.Type) { case MagicSpellType.AccountActivation: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailLandingTitle"], - new LandingEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contact.Content, + // localizer["EmailLandingTitle"], + // new LandingEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); break; case MagicSpellType.AccountRemoval: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailAccountDeletionTitle"], - new AccountDeletionEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contact.Content, + // localizer["EmailAccountDeletionTitle"], + // new AccountDeletionEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); break; case MagicSpellType.AuthPasswordReset: - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contact.Content, - localizer["EmailAccountDeletionTitle"], - new PasswordResetEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contact.Content, + // localizer["EmailAccountDeletionTitle"], + // new PasswordResetEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); break; case MagicSpellType.ContactVerification: if (spell.Meta["contact_method"] is not string contactMethod) throw new InvalidOperationException("Contact method is not found."); - await email.SendTemplatedEmailAsync( - contact.Account.Nick, - contactMethod!, - localizer["EmailContactVerificationTitle"], - new ContactVerificationEmailModel - { - Name = contact.Account.Name, - Link = link - } - ); + // await email.SendTemplatedEmailAsync( + // contact.Account.Nick, + // contactMethod!, + // localizer["EmailContactVerificationTitle"], + // new ContactVerificationEmailModel + // { + // Name = contact.Account.Name, + // Link = link + // } + // ); break; default: throw new ArgumentOutOfRangeException(); diff --git a/DysonNetwork.Sphere/Account/NotificationController.cs b/DysonNetwork.Pass/Account/NotificationController.cs similarity index 74% rename from DysonNetwork.Sphere/Account/NotificationController.cs rename to DysonNetwork.Pass/Account/NotificationController.cs index 55783e8..279da4d 100644 --- a/DysonNetwork.Sphere/Account/NotificationController.cs +++ b/DysonNetwork.Pass/Account/NotificationController.cs @@ -1,13 +1,13 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Permission; +using DysonNetwork.Pass.Auth; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; +using Microsoft.AspNetCore.Http; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; [ApiController] [Route("/notifications")] @@ -49,7 +49,7 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C .ToListAsync(); Response.Headers["X-Total"] = totalCount.ToString(); - await nty.MarkNotificationsViewed(notifications); + // await nty.MarkNotificationsViewed(notifications); return Ok(notifications); } @@ -73,11 +73,11 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C var currentSession = currentSessionValue as Session; if (currentSession == null) return Unauthorized(); - var result = - await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!, - request.DeviceToken); + // var result = + // await nty.SubscribePushNotification(currentUser, request.Provider, currentSession.Challenge.DeviceId!, + // request.DeviceToken); - return Ok(result); + return Ok(); } [HttpDelete("subscription")] @@ -111,26 +111,26 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C [HttpPost("broadcast")] [Authorize] - [RequiredPermission("global", "notifications.broadcast")] + // [RequiredPermission("global", "notifications.broadcast")] public async Task BroadcastNotification( [FromBody] NotificationRequest request, [FromQuery] bool save = false ) { - await nty.BroadcastNotification( - new Notification - { - CreatedAt = SystemClock.Instance.GetCurrentInstant(), - UpdatedAt = SystemClock.Instance.GetCurrentInstant(), - Topic = request.Topic, - Title = request.Title, - Subtitle = request.Subtitle, - Content = request.Content, - Meta = request.Meta, - Priority = request.Priority, - }, - save - ); + // await nty.BroadcastNotification( + // new Notification + // { + // CreatedAt = SystemClock.Instance.GetCurrentInstant(), + // UpdatedAt = SystemClock.Instance.GetCurrentInstant(), + // Topic = request.Topic, + // Title = request.Title, + // Subtitle = request.Subtitle, + // Content = request.Content, + // Meta = request.Meta, + // Priority = request.Priority, + // }, + // save + // ); return Ok(); } @@ -141,27 +141,27 @@ public class NotificationController(AppDatabase db, NotificationService nty) : C [HttpPost("send")] [Authorize] - [RequiredPermission("global", "notifications.send")] + // [RequiredPermission("global", "notifications.send")] public async Task SendNotification( [FromBody] NotificationWithAimRequest request, [FromQuery] bool save = false ) { var accounts = await db.Accounts.Where(a => request.AccountId.Contains(a.Id)).ToListAsync(); - await nty.SendNotificationBatch( - new Notification - { - CreatedAt = SystemClock.Instance.GetCurrentInstant(), - UpdatedAt = SystemClock.Instance.GetCurrentInstant(), - Topic = request.Topic, - Title = request.Title, - Subtitle = request.Subtitle, - Content = request.Content, - Meta = request.Meta, - }, - accounts, - save - ); + // await nty.SendNotificationBatch( + // new Notification + // { + // CreatedAt = SystemClock.Instance.GetCurrentInstant(), + // UpdatedAt = SystemClock.Instance.GetCurrentInstant(), + // Topic = request.Topic, + // Title = request.Title, + // Subtitle = request.Subtitle, + // Content = request.Content, + // Meta = request.Meta, + // }, + // accounts, + // save + // ); return Ok(); } } \ No newline at end of file diff --git a/DysonNetwork.Pass/Account/NotificationService.cs b/DysonNetwork.Pass/Account/NotificationService.cs new file mode 100644 index 0000000..b795deb --- /dev/null +++ b/DysonNetwork.Pass/Account/NotificationService.cs @@ -0,0 +1,311 @@ +using System.Text; +using System.Text.Json; +using DysonNetwork.Shared.Models; +using EFCore.BulkExtensions; +using Microsoft.EntityFrameworkCore; +using NodaTime; +using Microsoft.Extensions.Configuration; +using System.Net.Http; + +namespace DysonNetwork.Pass.Account; + +public class NotificationService( + AppDatabase db + // WebSocketService ws, + // IHttpClientFactory httpFactory, + // IConfiguration config +) +{ + // private readonly string _notifyTopic = config["Notifications:Topic"]!; + // private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); + + public async Task UnsubscribePushNotifications(string deviceId) + { + // await db.NotificationPushSubscriptions + // .Where(s => s.DeviceId == deviceId) + // .ExecuteDeleteAsync(); + } + + public async Task SubscribePushNotification( + Shared.Models.Account account, + NotificationPushProvider provider, + string deviceId, + string deviceToken + ) + { + var now = SystemClock.Instance.GetCurrentInstant(); + + // First check if a matching subscription exists + // var existingSubscription = await db.NotificationPushSubscriptions + // .Where(s => s.AccountId == account.Id) + // .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken) + // .FirstOrDefaultAsync(); + + // if (existingSubscription is not null) + // { + // // Update the existing subscription directly in the database + // await db.NotificationPushSubscriptions + // .Where(s => s.Id == existingSubscription.Id) + // .ExecuteUpdateAsync(setters => setters + // .SetProperty(s => s.DeviceId, deviceId) + // .SetProperty(s => s.DeviceToken, deviceToken) + // .SetProperty(s => s.UpdatedAt, now)); + + // // Return the updated subscription + // existingSubscription.DeviceId = deviceId; + // existingSubscription.DeviceToken = deviceToken; + // existingSubscription.UpdatedAt = now; + // return existingSubscription; + // } + + var subscription = new NotificationPushSubscription + { + DeviceId = deviceId, + DeviceToken = deviceToken, + Provider = provider, + AccountId = account.Id, + }; + + // db.NotificationPushSubscriptions.Add(subscription); + // await db.SaveChangesAsync(); + + return subscription; + } + + public async Task SendNotification( + Shared.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 + ) + { + if (title is null && subtitle is null && content is null) + throw new ArgumentException("Unable to send notification that completely empty."); + + meta ??= new Dictionary(); + if (actionUri is not null) meta["action_uri"] = actionUri; + + var notification = new Notification + { + Topic = topic, + Title = title, + Subtitle = subtitle, + Content = content, + Meta = meta, + AccountId = account.Id, + }; + + if (save) + { + // db.Add(notification); + // await db.SaveChangesAsync(); + } + + if (!isSilent) Console.WriteLine("Simulating notification delivery."); // _ = DeliveryNotification(notification); + + return notification; + } + + 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 + // .Where(s => s.AccountId == notification.AccountId) + // .ToListAsync(); + + // await _PushNotification(notification, subscribers); + } + + public async Task MarkNotificationsViewed(ICollection notifications) + { + var now = SystemClock.Instance.GetCurrentInstant(); + var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList(); + if (id.Count == 0) return; + + // await db.Notifications + // .Where(n => id.Contains(n.Id)) + // .ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now) + // ); + } + + public async Task BroadcastNotification(Notification notification, bool save = false) + { + var accounts = new List(); // await db.Accounts.ToListAsync(); + + if (save) + { + var notifications = accounts.Select(x => + { + var newNotification = new Notification + { + Topic = notification.Topic, + Title = notification.Title, + Subtitle = notification.Subtitle, + Content = notification.Content, + Meta = notification.Meta, + Priority = notification.Priority, + Account = x, + AccountId = x.Id + }; + return newNotification; + }).ToList(); + // 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) + { + if (save) + { + var notifications = accounts.Select(x => + { + var newNotification = new Notification + { + Topic = notification.Topic, + Title = notification.Title, + Subtitle = notification.Subtitle, + Content = notification.Content, + Meta = notification.Meta, + Priority = notification.Priority, + Account = x, + AccountId = x.Id + }; + return newNotification; + }).ToList(); + // 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 + // .Where(s => accountsId.Contains(s.AccountId)) + // .ToListAsync(); + // await _PushNotification(notification, subscribers); + } + + // private List> _BuildNotificationPayload(Notification notification, + // IEnumerable subscriptions) + // { + // var subDict = subscriptions + // .GroupBy(x => x.Provider) + // .ToDictionary(x => x.Key, x => x.ToList()); + + // var notifications = subDict.Select(value => + // { + // var platformCode = value.Key switch + // { + // NotificationPushProvider.Apple => 1, + // NotificationPushProvider.Google => 2, + // _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}") + // }; + + // var tokens = value.Value.Select(x => x.DeviceToken).ToList(); + // return _BuildNotificationPayload(notification, platformCode, tokens); + // }).ToList(); + + // return notifications.ToList(); + // } + + // private Dictionary _BuildNotificationPayload(Notification notification, int platformCode, + // IEnumerable deviceTokens) + // { + // var alertDict = new Dictionary(); + // var dict = new Dictionary + // { + // ["notif_id"] = notification.Id.ToString(), + // ["apns_id"] = notification.Id.ToString(), + // ["topic"] = _notifyTopic, + // ["tokens"] = deviceTokens, + // ["data"] = new Dictionary + // { + // ["type"] = notification.Topic, + // ["meta"] = notification.Meta ?? new Dictionary(), + // }, + // ["mutable_content"] = true, + // ["priority"] = notification.Priority >= 5 ? "high" : "normal", + // }; + + // if (!string.IsNullOrWhiteSpace(notification.Title)) + // { + // dict["title"] = notification.Title; + // alertDict["title"] = notification.Title; + // } + + // if (!string.IsNullOrWhiteSpace(notification.Content)) + // { + // dict["message"] = notification.Content; + // alertDict["body"] = notification.Content; + // } + + // if (!string.IsNullOrWhiteSpace(notification.Subtitle)) + // { + // dict["message"] = $"{notification.Subtitle}\n{dict["message"]}"; + // alertDict["subtitle"] = notification.Subtitle; + // } + + // if (notification.Priority >= 5) + // dict["name"] = "default"; + + // dict["platform"] = platformCode; + // dict["alert"] = alertDict; + + // return dict; + // } + + // private async Task _PushNotification(Notification notification, + // IEnumerable subscriptions) + // { + // var subList = subscriptions.ToList(); + // if (subList.Count == 0) return; + + // var requestDict = new Dictionary + // { + // ["notifications"] = _BuildNotificationPayload(notification, subList) + // }; + + // var client = httpFactory.CreateClient(); + // client.BaseAddress = _notifyEndpoint; + // var request = await client.PostAsync("/push", new StringContent( + // JsonSerializer.Serialize(requestDict), + // Encoding.UTF8, + // "application/json" + // )); + // request.EnsureSuccessStatusCode(); + // } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Account/RelationshipController.cs b/DysonNetwork.Pass/Account/RelationshipController.cs similarity index 99% rename from DysonNetwork.Sphere/Account/RelationshipController.cs rename to DysonNetwork.Pass/Account/RelationshipController.cs index e6f74af..2731c7d 100644 --- a/DysonNetwork.Sphere/Account/RelationshipController.cs +++ b/DysonNetwork.Pass/Account/RelationshipController.cs @@ -4,8 +4,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using NodaTime; +using Microsoft.AspNetCore.Http; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; [ApiController] [Route("/relationships")] diff --git a/DysonNetwork.Sphere/Account/RelationshipService.cs b/DysonNetwork.Pass/Account/RelationshipService.cs similarity index 85% rename from DysonNetwork.Sphere/Account/RelationshipService.cs rename to DysonNetwork.Pass/Account/RelationshipService.cs index 49154fc..16037de 100644 --- a/DysonNetwork.Sphere/Account/RelationshipService.cs +++ b/DysonNetwork.Pass/Account/RelationshipService.cs @@ -1,11 +1,13 @@ using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using NodaTime; -namespace DysonNetwork.Sphere.Account; +namespace DysonNetwork.Pass.Account; -public class RelationshipService(AppDatabase db, ICacheService cache) +public class RelationshipService( + AppDatabase db + // ICacheService cache +) { private const string UserFriendsCacheKeyPrefix = "accounts:friends:"; private const string UserBlockedCacheKeyPrefix = "accounts:blocked:"; @@ -53,7 +55,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache) db.AccountRelationships.Add(relationship); await db.SaveChangesAsync(); - await PurgeRelationshipCache(sender.Id, target.Id); + // await PurgeRelationshipCache(sender.Id, target.Id); return relationship; } @@ -72,7 +74,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache) db.Remove(relationship); await db.SaveChangesAsync(); - await PurgeRelationshipCache(sender.Id, target.Id); + // await PurgeRelationshipCache(sender.Id, target.Id); return relationship; } @@ -105,7 +107,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache) .Where(r => r.AccountId == accountId && r.RelatedId == relatedId && r.Status == RelationshipStatus.Pending) .ExecuteDeleteAsync(); - await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); + // await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); } public async Task AcceptFriendRelationship( @@ -134,7 +136,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache) await db.SaveChangesAsync(); - await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); + // await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId); return relationshipBackward; } @@ -148,7 +150,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache) db.Update(relationship); await db.SaveChangesAsync(); - await PurgeRelationshipCache(accountId, relatedId); + // await PurgeRelationshipCache(accountId, relatedId); return relationship; } @@ -156,7 +158,8 @@ public class RelationshipService(AppDatabase db, ICacheService cache) public async Task> ListAccountFriends(Shared.Models.Account account) { var cacheKey = $"{UserFriendsCacheKeyPrefix}{account.Id}"; - var friends = await cache.GetAsync>(cacheKey); + // var friends = await cache.GetAsync>(cacheKey); + var friends = new List(); // Placeholder if (friends == null) { @@ -166,7 +169,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache) .Select(r => r.AccountId) .ToListAsync(); - await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1)); + // await cache.SetAsync(cacheKey, friends, TimeSpan.FromHours(1)); } return friends ?? []; @@ -175,7 +178,8 @@ public class RelationshipService(AppDatabase db, ICacheService cache) public async Task> ListAccountBlocked(Shared.Models.Account account) { var cacheKey = $"{UserBlockedCacheKeyPrefix}{account.Id}"; - var blocked = await cache.GetAsync>(cacheKey); + // var blocked = await cache.GetAsync>(cacheKey); + var blocked = new List(); // Placeholder if (blocked == null) { @@ -185,7 +189,7 @@ public class RelationshipService(AppDatabase db, ICacheService cache) .Select(r => r.AccountId) .ToListAsync(); - await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1)); + // await cache.SetAsync(cacheKey, blocked, TimeSpan.FromHours(1)); } return blocked ?? []; @@ -200,9 +204,9 @@ public class RelationshipService(AppDatabase db, ICacheService cache) private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId) { - await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}"); - await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}"); - await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}"); - await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}"); + // await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{accountId}"); + // await cache.RemoveAsync($"{UserFriendsCacheKeyPrefix}{relatedId}"); + // await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{accountId}"); + // await cache.RemoveAsync($"{UserBlockedCacheKeyPrefix}{relatedId}"); } } \ No newline at end of file diff --git a/DysonNetwork.Pass/AppDatabase.cs b/DysonNetwork.Pass/AppDatabase.cs index 05f88e7..013c85e 100644 --- a/DysonNetwork.Pass/AppDatabase.cs +++ b/DysonNetwork.Pass/AppDatabase.cs @@ -1,8 +1,15 @@ +using System.Linq.Expressions; +using System.Reflection; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Permission; using DysonNetwork.Shared.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using NodaTime; +using Quartz; namespace DysonNetwork.Pass; @@ -22,23 +29,67 @@ public class AppDatabase( public DbSet PermissionGroups { get; set; } public DbSet PermissionGroupMembers { get; set; } - public DbSet Accounts { 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 AccountBadges { get; set; } + + public DbSet ActionLogs { get; set; } + + public DbSet Notifications { get; set; } + public DbSet NotificationPushSubscriptions { get; set; } public DbSet AuthSessions { get; set; } public DbSet AuthChallenges { get; set; } + + public DbSet MagicSpells { get; set; } + public DbSet AbuseReports { 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) => + { + 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); + } + }); base.OnConfiguring(optionsBuilder); } @@ -49,9 +100,40 @@ public class AppDatabase( 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(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) @@ -80,6 +162,66 @@ public class AppDatabase( } } +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) @@ -92,4 +234,36 @@ public class AppDatabaseFactory : IDesignTimeDbContextFactory var optionsBuilder = new DbContextOptionsBuilder(); return new AppDatabase(optionsBuilder.Options, configuration); } -} \ No newline at end of file +} + +public static class OptionalQueryExtensions +{ + public static IQueryable If( + this IQueryable source, + bool condition, + Func, IQueryable> transform + ) + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable source, + bool condition, + Func, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } + + public static IQueryable If( + this IIncludableQueryable> source, + bool condition, + Func>, IQueryable> transform + ) + where T : class + { + return condition ? transform(source) : source; + } +} diff --git a/DysonNetwork.Sphere/Auth/Auth.cs b/DysonNetwork.Pass/Auth/Auth.cs similarity index 77% rename from DysonNetwork.Sphere/Auth/Auth.cs rename to DysonNetwork.Pass/Auth/Auth.cs index 2080a81..5eb90d1 100644 --- a/DysonNetwork.Sphere/Auth/Auth.cs +++ b/DysonNetwork.Pass/Auth/Auth.cs @@ -1,23 +1,16 @@ -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 Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; using NodaTime; -using System.Text; -using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Auth.OidcProvider.Controllers; -using DysonNetwork.Sphere.Auth.OidcProvider.Services; -using SystemClock = NodaTime.SystemClock; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using SystemClock = Microsoft.Extensions.Internal.SystemClock; -namespace DysonNetwork.Sphere.Auth; +namespace DysonNetwork.Pass.Auth; public static class AuthConstants { @@ -47,12 +40,11 @@ public class DysonTokenAuthHandler( IConfiguration configuration, ILoggerFactory logger, UrlEncoder encoder, - AppDatabase database, - OidcProviderService oidc, - ICacheService cache, - FlushBufferService fbs -) - : AuthenticationHandler(options, logger, encoder) + AppDatabase database + // OidcProviderService oidc, + // ICacheService cache, + // FlushBufferService fbs +) : AuthenticationHandler(options, logger, encoder) { public const string AuthCachePrefix = "auth:"; @@ -65,36 +57,42 @@ public class DysonTokenAuthHandler( try { - var now = SystemClock.Instance.GetCurrentInstant(); + var now = NodaTime.SystemClock.Instance.GetCurrentInstant(); // Validate token and extract session ID if (!ValidateToken(tokenInfo.Token, out var sessionId)) return AuthenticateResult.Fail("Invalid token."); // Try to get session from cache first - var session = await cache.GetAsync($"{AuthCachePrefix}{sessionId}"); - - // If not in cache, load from database - if (session is null) - { - session = await database.AuthSessions + // var session = await cache.GetAsync($"{AuthCachePrefix}{sessionId}"); + var session = await database.AuthSessions .Where(e => e.Id == sessionId) .Include(e => e.Challenge) .Include(e => e.Account) .ThenInclude(e => e.Profile) .FirstOrDefaultAsync(); - if (session is not null) - { - // Store in cache for future requests - await cache.SetWithGroupsAsync( - $"auth:{sessionId}", - session, - [$"{AccountService.AccountCachePrefix}{session.Account.Id}"], - TimeSpan.FromHours(1) - ); - } - } + // If not in cache, load from database + // if (session is null) + // { + // session = await database.AuthSessions + // .Where(e => e.Id == sessionId) + // .Include(e => e.Challenge) + // .Include(e => e.Account) + // .ThenInclude(e => e.Profile) + // .FirstOrDefaultAsync(); + + // if (session is not null) + // { + // // Store in cache for future requests + // await cache.SetWithGroupsAsync( + // $"auth:{sessionId}", + // session, + // // [$"{AccountService.AccountCachePrefix}{session.Account.Id}"], + // TimeSpan.FromHours(1) + // ); + // } + // } // Check if the session exists if (session == null) @@ -130,13 +128,13 @@ public class DysonTokenAuthHandler( var ticket = new AuthenticationTicket(principal, AuthConstants.SchemeName); - var lastInfo = new LastActiveInfo - { - Account = session.Account, - Session = session, - SeenAt = SystemClock.Instance.GetCurrentInstant(), - }; - fbs.Enqueue(lastInfo); + // var lastInfo = new LastActiveInfo + // { + // Account = session.Account, + // Session = session, + // SeenAt = SystemClock.Instance.GetCurrentInstant(), + // }; + // fbs.Enqueue(lastInfo); return AuthenticateResult.Success(ticket); } @@ -159,12 +157,13 @@ public class DysonTokenAuthHandler( // Handle JWT tokens (3 parts) case 3: { - var (isValid, jwtResult) = oidc.ValidateToken(token); - if (!isValid) return false; - var jti = jwtResult?.Claims.FirstOrDefault(c => c.Type == "jti")?.Value; - if (jti is null) return false; + // var (isValid, jwtResult) = oidc.ValidateToken(token); + // if (!isValid) return false; + // var jti = jwtResult?.Claims.FirstOrDefault(c => c.Type == "jti")?.Value; + // if (jti is null) return false; - return Guid.TryParse(jti, out sessionId); + // return Guid.TryParse(jti, out sessionId); + return false; // Placeholder } // Handle compact tokens (2 parts) case 2: diff --git a/DysonNetwork.Sphere/Auth/AuthController.cs b/DysonNetwork.Pass/Auth/AuthController.cs similarity index 85% rename from DysonNetwork.Sphere/Auth/AuthController.cs rename to DysonNetwork.Pass/Auth/AuthController.cs index 26bf7bb..306659f 100644 --- a/DysonNetwork.Sphere/Auth/AuthController.cs +++ b/DysonNetwork.Pass/Auth/AuthController.cs @@ -1,24 +1,23 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Account; +using DysonNetwork.Shared.Models; +using DysonNetwork.Pass.Account; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using NodaTime; using Microsoft.EntityFrameworkCore; -using System.IdentityModel.Tokens.Jwt; +using NodaTime; +using Microsoft.AspNetCore.Http; using System.Text.Json; -using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Connection; -namespace DysonNetwork.Sphere.Auth; +namespace DysonNetwork.Pass.Auth; [ApiController] [Route("/auth")] public class AuthController( AppDatabase db, AccountService accounts, - AuthService auth, - GeoIpService geo, - ActionLogService als + AuthService auth + // GeoIpService geo, + // ActionLogService als ) : ControllerBase { public class ChallengeRequest @@ -60,7 +59,7 @@ public class AuthController( Scopes = request.Scopes, IpAddress = ipAddress, UserAgent = userAgent, - Location = geo.GetPointFromIp(ipAddress), + // Location = geo.GetPointFromIp(ipAddress), DeviceId = request.DeviceId, AccountId = account.Id }.Normalize(); @@ -68,9 +67,9 @@ public class AuthController( await db.AuthChallenges.AddAsync(challenge); await db.SaveChangesAsync(); - als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt, - new Dictionary { { "challenge_id", challenge.Id } }, Request, account - ); + // als.CreateActionLogFromRequest(ActionLogType.ChallengeAttempt, + // new Dictionary { { "challenge_id", challenge.Id } }, Request, account + // ); return challenge; } @@ -161,13 +160,13 @@ 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 - ); + // als.CreateActionLogFromRequest(ActionLogType.ChallengeSuccess, + // new Dictionary + // { + // { "challenge_id", challenge.Id }, + // { "factor_id", factor.Id } + // }, Request, challenge.Account + // ); } else { @@ -178,26 +177,26 @@ 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 - ); + // 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 - ); + // als.CreateActionLogFromRequest(ActionLogType.NewLogin, + // new Dictionary + // { + // { "challenge_id", challenge.Id }, + // { "account_id", challenge.AccountId } + // }, Request, challenge.Account + // ); } await db.SaveChangesAsync(); diff --git a/DysonNetwork.Sphere/Auth/AuthGrpcService.cs b/DysonNetwork.Pass/Auth/AuthGrpcService.cs similarity index 91% rename from DysonNetwork.Sphere/Auth/AuthGrpcService.cs rename to DysonNetwork.Pass/Auth/AuthGrpcService.cs index 9c385b8..f583321 100644 --- a/DysonNetwork.Sphere/Auth/AuthGrpcService.cs +++ b/DysonNetwork.Pass/Auth/AuthGrpcService.cs @@ -1,16 +1,18 @@ -using DysonNetwork.Sphere.Auth.Proto; +using DysonNetwork.Shared.Protos.Auth; using Grpc.Core; using Google.Protobuf.WellKnownTypes; -using DysonNetwork.Sphere.Account; using Microsoft.EntityFrameworkCore; using NodaTime; using System.Text.Json; using DysonNetwork.Shared.Models; +using DysonNetwork.Pass.Account; +using Challenge = DysonNetwork.Shared.Models.Challenge; +using Session = DysonNetwork.Shared.Models.Session; -namespace DysonNetwork.Sphere.Auth; +namespace DysonNetwork.Pass.Auth; public class AuthGrpcService(AppDatabase db, AccountService accounts, AuthService auth) - : DysonNetwork.Sphere.Auth.Proto.AuthService.AuthServiceBase + : DysonNetwork.Shared.Protos.Auth.AuthService.AuthServiceBase { public override async Task Login(LoginRequest request, ServerCallContext context) { @@ -31,7 +33,7 @@ public class AuthGrpcService(AppDatabase db, AccountService accounts, AuthServic LastGrantedAt = Instant.FromDateTimeUtc(DateTime.UtcNow), ExpiredAt = Instant.FromDateTimeUtc(DateTime.UtcNow.AddDays(30)), Account = account, - Challenge = new Challenge() // Create a dummy challenge + Challenge = new Challenge() }; db.AuthSessions.Add(session); diff --git a/DysonNetwork.Sphere/Auth/AuthService.cs b/DysonNetwork.Pass/Auth/AuthService.cs similarity index 59% rename from DysonNetwork.Sphere/Auth/AuthService.cs rename to DysonNetwork.Pass/Auth/AuthService.cs index d68f0b6..9dab8eb 100644 --- a/DysonNetwork.Sphere/Auth/AuthService.cs +++ b/DysonNetwork.Pass/Auth/AuthService.cs @@ -1,22 +1,23 @@ using System.Security.Cryptography; using System.Text.Json; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using NodaTime; +using Microsoft.Extensions.Configuration; +using Microsoft.AspNetCore.Http; +using System.IO; -namespace DysonNetwork.Sphere.Auth; +namespace DysonNetwork.Pass.Auth; public class AuthService( AppDatabase db, - IConfiguration config, - IHttpClientFactory httpClientFactory, - IHttpContextAccessor httpContextAccessor, - ICacheService cache + IConfiguration config + // IHttpClientFactory httpClientFactory, + // IHttpContextAccessor httpContextAccessor, + // ICacheService cache ) { - private HttpContext HttpContext => httpContextAccessor.HttpContext!; + // private HttpContext HttpContext => httpContextAccessor.HttpContext!; /// /// Detect the risk of the current request to login @@ -79,8 +80,8 @@ public class AuthService( var challenge = new Challenge { AccountId = account.Id, - IpAddress = HttpContext.Connection.RemoteIpAddress?.ToString(), - UserAgent = HttpContext.Request.Headers.UserAgent, + IpAddress = "127.0.0.1", // HttpContext.Connection.RemoteIpAddress?.ToString(), + UserAgent = "TestAgent", // HttpContext.Request.Headers.UserAgent, StepRemain = 1, StepTotal = 1, Type = customAppId is not null ? ChallengeType.OAuth : ChallengeType.Oidc @@ -106,53 +107,54 @@ public class AuthService( { if (string.IsNullOrWhiteSpace(token)) return false; - var provider = config.GetSection("Captcha")["Provider"]?.ToLower(); - var apiSecret = config.GetSection("Captcha")["ApiSecret"]; + // var provider = config.GetSection("Captcha")["Provider"]?.ToLower(); + // var apiSecret = config.GetSection("Captcha")["ApiSecret"]; - var client = httpClientFactory.CreateClient(); + // var client = httpClientFactory.CreateClient(); - var jsonOpts = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower - }; + // var jsonOpts = new JsonSerializerOptions + // { + // PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + // DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower + // }; - switch (provider) - { - case "cloudflare": - var content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, - "application/x-www-form-urlencoded"); - var response = await client.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", - content); - response.EnsureSuccessStatusCode(); + // switch (provider) + // { + // case "cloudflare": + // var content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, + // "application/x-www-form-urlencoded"); + // var response = await client.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", + // content); + // response.EnsureSuccessStatusCode(); - var json = await response.Content.ReadAsStringAsync(); - var result = JsonSerializer.Deserialize(json, options: jsonOpts); + // var json = await response.Content.ReadAsStringAsync(); + // var result = JsonSerializer.Deserialize(json, options: jsonOpts); - return result?.Success == true; - case "google": - content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, - "application/x-www-form-urlencoded"); - response = await client.PostAsync("https://www.google.com/recaptcha/siteverify", content); - response.EnsureSuccessStatusCode(); + // return result?.Success == true; + // case "google": + // content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, + // "application/x-www-form-urlencoded"); + // response = await client.PostAsync("https://www.google.com/recaptcha/siteverify", content); + // response.EnsureSuccessStatusCode(); - json = await response.Content.ReadAsStringAsync(); - result = JsonSerializer.Deserialize(json, options: jsonOpts); + // json = await response.Content.ReadAsStringAsync(); + // result = JsonSerializer.Deserialize(json, options: jsonOpts); - return result?.Success == true; - case "hcaptcha": - content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, - "application/x-www-form-urlencoded"); - response = await client.PostAsync("https://hcaptcha.com/siteverify", content); - response.EnsureSuccessStatusCode(); + // return result?.Success == true; + // case "hcaptcha": + // content = new StringContent($"secret={apiSecret}&response={token}", System.Text.Encoding.UTF8, + // "application/x-www-form-urlencoded"); + // response = await client.PostAsync("https://hcaptcha.com/siteverify", content); + // response.EnsureSuccessStatusCode(); - json = await response.Content.ReadAsStringAsync(); - result = JsonSerializer.Deserialize(json, options: jsonOpts); + // json = await response.Content.ReadAsStringAsync(); + // result = JsonSerializer.Deserialize(json, options: jsonOpts); - return result?.Success == true; - default: - throw new ArgumentException("The server misconfigured for the captcha."); - } + // return result?.Success == true; + // default: + // throw new ArgumentException("The server misconfigured for the captcha."); + // } + return true; // Placeholder for captcha validation } public string CreateToken(Session session) @@ -184,56 +186,56 @@ public class AuthService( return $"{payloadBase64}.{signatureBase64}"; } - public async Task ValidateSudoMode(Session session, string? pinCode) - { - // Check if the session is already in sudo mode (cached) - var sudoModeKey = $"accounts:{session.Id}:sudo"; - var (found, _) = await cache.GetAsyncWithStatus(sudoModeKey); + // public async Task ValidateSudoMode(Session session, string? pinCode) + // { + // // Check if the session is already in sudo mode (cached) + // var sudoModeKey = $"accounts:{session.Id}:sudo"; + // var (found, _) = await cache.GetAsyncWithStatus(sudoModeKey); - if (found) - { - // Session is already in sudo mode - return true; - } + // if (found) + // { + // // Session is already in sudo mode + // return true; + // } - // Check if the user has a pin code - var hasPinCode = await db.AccountAuthFactors - .Where(f => f.AccountId == session.AccountId) - .Where(f => f.EnabledAt != null) - .Where(f => f.Type == AccountAuthFactorType.PinCode) - .AnyAsync(); + // // Check if the user has a pin code + // var hasPinCode = await db.AccountAuthFactors + // .Where(f => f.AccountId == session.AccountId) + // .Where(f => f.EnabledAt != null) + // .Where(f => f.Type == AccountAuthFactorType.PinCode) + // .AnyAsync(); - if (!hasPinCode) - { - // User doesn't have a pin code, no validation needed - return true; - } + // if (!hasPinCode) + // { + // // User doesn't have a pin code, no validation needed + // return true; + // } - // If pin code is not provided, we can't validate - if (string.IsNullOrEmpty(pinCode)) - { - return false; - } + // // If pin code is not provided, we can't validate + // if (string.IsNullOrEmpty(pinCode)) + // { + // return false; + // } - try - { - // Validate the pin code - var isValid = await ValidatePinCode(session.AccountId, pinCode); + // try + // { + // // Validate the pin code + // var isValid = await ValidatePinCode(session.AccountId, pinCode); - if (isValid) - { - // Set session in sudo mode for 5 minutes - await cache.SetAsync(sudoModeKey, true, TimeSpan.FromMinutes(5)); - } + // if (isValid) + // { + // // Set session in sudo mode for 5 minutes + // await cache.SetAsync(sudoModeKey, true, TimeSpan.FromMinutes(5)); + // } - return isValid; - } - catch (InvalidOperationException) - { - // No pin code enabled for this account, so validation is successful - return true; - } - } + // return isValid; + // } + // catch (InvalidOperationException) + // { + // // No pin code enabled for this account, so validation is successful + // return true; + // } + // } public async Task ValidatePinCode(Guid accountId, string pinCode) { diff --git a/DysonNetwork.Sphere/Auth/CheckpointModel.cs b/DysonNetwork.Pass/Auth/CheckpointModel.cs similarity index 69% rename from DysonNetwork.Sphere/Auth/CheckpointModel.cs rename to DysonNetwork.Pass/Auth/CheckpointModel.cs index 3b4ea12..58466e9 100644 --- a/DysonNetwork.Sphere/Auth/CheckpointModel.cs +++ b/DysonNetwork.Pass/Auth/CheckpointModel.cs @@ -1,4 +1,4 @@ -namespace DysonNetwork.Sphere.Auth; +namespace DysonNetwork.Pass.Auth; public class CaptchaVerificationResponse { diff --git a/DysonNetwork.Sphere/Auth/CompactTokenService.cs b/DysonNetwork.Pass/Auth/CompactTokenService.cs similarity index 97% rename from DysonNetwork.Sphere/Auth/CompactTokenService.cs rename to DysonNetwork.Pass/Auth/CompactTokenService.cs index 0c91142..3947453 100644 --- a/DysonNetwork.Sphere/Auth/CompactTokenService.cs +++ b/DysonNetwork.Pass/Auth/CompactTokenService.cs @@ -1,7 +1,8 @@ using System.Security.Cryptography; using DysonNetwork.Shared.Models; +using Microsoft.Extensions.Configuration; -namespace DysonNetwork.Sphere.Auth; +namespace DysonNetwork.Pass.Auth; public class CompactTokenService(IConfiguration config) { diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs similarity index 96% rename from DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs rename to DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs index 1492421..073ebc8 100644 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Controllers/OidcProviderController.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Controllers/OidcProviderController.cs @@ -1,19 +1,19 @@ 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 System.Text.Json.Serialization; +using DysonNetwork.Pass.Auth.OidcProvider.Options; +using DysonNetwork.Pass.Auth.OidcProvider.Responses; +using DysonNetwork.Pass.Auth.OidcProvider.Services; +using DysonNetwork.Shared.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using System.Text.Json.Serialization; -using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Account; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using NodaTime; -namespace DysonNetwork.Sphere.Auth.OidcProvider.Controllers; +namespace DysonNetwork.Pass.Auth.OidcProvider.Controllers; [Route("/auth/open")] [ApiController] diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs b/DysonNetwork.Pass/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs similarity index 80% rename from DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs rename to DysonNetwork.Pass/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs index d043cab..14bc397 100644 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Models/AuthorizationCodeInfo.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; using NodaTime; -namespace DysonNetwork.Sphere.Auth.OidcProvider.Models; +namespace DysonNetwork.Pass.Auth.OidcProvider.Models; public class AuthorizationCodeInfo { diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs b/DysonNetwork.Pass/Auth/OidcProvider/Options/OidcProviderOptions.cs similarity index 95% rename from DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs rename to DysonNetwork.Pass/Auth/OidcProvider/Options/OidcProviderOptions.cs index 6d57cb3..40397a5 100644 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Options/OidcProviderOptions.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Options/OidcProviderOptions.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; -namespace DysonNetwork.Sphere.Auth.OidcProvider.Options; +namespace DysonNetwork.Pass.Auth.OidcProvider.Options; public class OidcProviderOptions { diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs b/DysonNetwork.Pass/Auth/OidcProvider/Responses/AuthorizationResponse.cs similarity index 88% rename from DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs rename to DysonNetwork.Pass/Auth/OidcProvider/Responses/AuthorizationResponse.cs index 45cf25c..407520b 100644 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/AuthorizationResponse.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Responses/AuthorizationResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses; +namespace DysonNetwork.Pass.Auth.OidcProvider.Responses; public class AuthorizationResponse { diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs b/DysonNetwork.Pass/Auth/OidcProvider/Responses/ErrorResponse.cs similarity index 87% rename from DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs rename to DysonNetwork.Pass/Auth/OidcProvider/Responses/ErrorResponse.cs index 0018a47..b72a4a7 100644 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/ErrorResponse.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Responses/ErrorResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses; +namespace DysonNetwork.Pass.Auth.OidcProvider.Responses; public class ErrorResponse { diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs b/DysonNetwork.Pass/Auth/OidcProvider/Responses/TokenResponse.cs similarity index 90% rename from DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs rename to DysonNetwork.Pass/Auth/OidcProvider/Responses/TokenResponse.cs index 6d41cf4..3c7e50c 100644 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Responses/TokenResponse.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Responses/TokenResponse.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace DysonNetwork.Sphere.Auth.OidcProvider.Responses; +namespace DysonNetwork.Pass.Auth.OidcProvider.Responses; public class TokenResponse { diff --git a/DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs similarity index 95% rename from DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs rename to DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs index c740c17..b8c7b7c 100644 --- a/DysonNetwork.Sphere/Auth/OidcProvider/Services/OidcProviderService.cs +++ b/DysonNetwork.Pass/Auth/OidcProvider/Services/OidcProviderService.cs @@ -2,18 +2,18 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; +using DysonNetwork.Pass.Auth.OidcProvider.Models; +using DysonNetwork.Pass.Auth.OidcProvider.Options; +using DysonNetwork.Pass.Auth.OidcProvider.Responses; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; -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 Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using NodaTime; -namespace DysonNetwork.Sphere.Auth.OidcProvider.Services; +namespace DysonNetwork.Pass.Auth.OidcProvider.Services; public class OidcProviderService( AppDatabase db, @@ -27,16 +27,18 @@ public class OidcProviderService( public async Task FindClientByIdAsync(Guid clientId) { - return await db.CustomApps - .Include(c => c.Secrets) - .FirstOrDefaultAsync(c => c.Id == clientId); + return null; + // return await db.CustomApps + // .Include(c => c.Secrets) + // .FirstOrDefaultAsync(c => c.Id == clientId); } public async Task FindClientByAppIdAsync(Guid appId) { - return await db.CustomApps - .Include(c => c.Secrets) - .FirstOrDefaultAsync(c => c.Id == appId); + return null; + // return await db.CustomApps + // .Include(c => c.Secrets) + // .FirstOrDefaultAsync(c => c.Id == appId); } public async Task FindValidSessionAsync(Guid accountId, Guid clientId) diff --git a/DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs similarity index 94% rename from DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs rename to DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs index c0e0600..0534d29 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/AfdianOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/AfdianOidcService.cs @@ -1,8 +1,10 @@ -using System.Net.Http.Json; using System.Text.Json; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Sphere.Auth.OpenId; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; public class AfdianOidcService( IConfiguration configuration, diff --git a/DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs b/DysonNetwork.Pass/Auth/OpenId/AppleMobileSignInRequest.cs similarity index 100% rename from DysonNetwork.Sphere/Auth/OpenId/AppleMobileSignInRequest.cs rename to DysonNetwork.Pass/Auth/OpenId/AppleMobileSignInRequest.cs diff --git a/DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs similarity index 98% rename from DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs rename to DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs index 75420b8..448f816 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/AppleOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/AppleOidcService.cs @@ -3,10 +3,12 @@ using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Sphere.Auth.OpenId; +using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; /// /// Implementation of OpenID Connect service for Apple Sign In diff --git a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs b/DysonNetwork.Pass/Auth/OpenId/ConnectionController.cs similarity index 98% rename from DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs rename to DysonNetwork.Pass/Auth/OpenId/ConnectionController.cs index 509cf46..993255e 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/ConnectionController.cs +++ b/DysonNetwork.Pass/Auth/OpenId/ConnectionController.cs @@ -1,12 +1,14 @@ +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Account; +using DysonNetwork.Sphere.Auth.OpenId; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using DysonNetwork.Sphere.Storage; using NodaTime; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; [ApiController] [Route("/accounts/me/connections")] diff --git a/DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs similarity index 96% rename from DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs rename to DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs index c710b71..4a7da6c 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/DiscordOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/DiscordOidcService.cs @@ -1,8 +1,10 @@ using System.Net.Http.Json; using System.Text.Json; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Sphere.Auth.OpenId; +using Microsoft.Extensions.Configuration; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; public class DiscordOidcService( IConfiguration configuration, diff --git a/DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs similarity index 96% rename from DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs rename to DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs index fc80bfe..05b20b3 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/GitHubOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/GitHubOidcService.cs @@ -1,8 +1,10 @@ using System.Net.Http.Json; using System.Text.Json; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Sphere.Auth.OpenId; +using Microsoft.Extensions.Configuration; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; public class GitHubOidcService( IConfiguration configuration, diff --git a/DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs similarity index 97% rename from DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs rename to DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs index a446b2e..4966940 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/GoogleOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/GoogleOidcService.cs @@ -1,11 +1,11 @@ using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Json; -using System.Security.Cryptography; -using System.Text; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Sphere.Auth.OpenId; +using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; public class GoogleOidcService( IConfiguration configuration, diff --git a/DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs b/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs similarity index 97% rename from DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs rename to DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs index 83efad1..abeb94f 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/MicrosoftOidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/MicrosoftOidcService.cs @@ -1,8 +1,10 @@ using System.Net.Http.Json; using System.Text.Json; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Sphere.Auth.OpenId; +using Microsoft.Extensions.Configuration; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; public class MicrosoftOidcService( IConfiguration configuration, diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs similarity index 97% rename from DysonNetwork.Sphere/Auth/OpenId/OidcController.cs rename to DysonNetwork.Pass/Auth/OpenId/OidcController.cs index 5833aa7..f079c5e 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/OidcController.cs +++ b/DysonNetwork.Pass/Auth/OpenId/OidcController.cs @@ -1,12 +1,14 @@ +using DysonNetwork.Pass.Account; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Sphere.Auth.OpenId; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using NodaTime; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; [ApiController] [Route("/auth/login")] diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcService.cs b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs similarity index 98% rename from DysonNetwork.Sphere/Auth/OpenId/OidcService.cs rename to DysonNetwork.Pass/Auth/OpenId/OidcService.cs index 0e51f57..a937a21 100644 --- a/DysonNetwork.Sphere/Auth/OpenId/OidcService.cs +++ b/DysonNetwork.Pass/Auth/OpenId/OidcService.cs @@ -1,14 +1,16 @@ using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Json; using System.Text.Json.Serialization; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Storage; +using DysonNetwork.Sphere.Auth.OpenId; +using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using NodaTime; -namespace DysonNetwork.Sphere.Auth.OpenId; +namespace DysonNetwork.Pass.Auth.OpenId; /// /// Base service for OpenID Connect authentication providers diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcState.cs b/DysonNetwork.Pass/Auth/OpenId/OidcState.cs similarity index 100% rename from DysonNetwork.Sphere/Auth/OpenId/OidcState.cs rename to DysonNetwork.Pass/Auth/OpenId/OidcState.cs diff --git a/DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs b/DysonNetwork.Pass/Auth/OpenId/OidcUserInfo.cs similarity index 100% rename from DysonNetwork.Sphere/Auth/OpenId/OidcUserInfo.cs rename to DysonNetwork.Pass/Auth/OpenId/OidcUserInfo.cs diff --git a/DysonNetwork.Pass/Data/AppDatabase.cs b/DysonNetwork.Pass/Data/AppDatabase.cs deleted file mode 100644 index be3f8d7..0000000 --- a/DysonNetwork.Pass/Data/AppDatabase.cs +++ /dev/null @@ -1,95 +0,0 @@ -using DysonNetwork.Shared.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Design; -using Microsoft.Extensions.Configuration; -using NodaTime; - -namespace DysonNetwork.Pass.Data; - -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 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 AuthSessions { get; set; } - public DbSet AuthChallenges { get; set; } - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseNpgsql( - configuration.GetConnectionString("App"), - opt => opt - .UseNodaTime() - ).UseSnakeCaseNamingConvention(); - - base.OnConfiguring(optionsBuilder); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); - - modelBuilder.Entity() - .HasKey(pg => new { pg.GroupId, pg.Actor }); - - modelBuilder.Entity() - .HasKey(r => new { FromAccountId = r.AccountId, ToAccountId = r.RelatedId }); - } - - public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - var now = SystemClock.Instance.GetCurrentInstant(); - - foreach (var entry in ChangeTracker.Entries()) - { - if (entry.State == EntityState.Added) - { - entry.Entity.CreatedAt = now; - entry.Entity.UpdatedAt = now; - } - else if (entry.State == EntityState.Modified) - { - entry.Entity.UpdatedAt = now; - } - else if (entry.State == EntityState.Deleted) - { - entry.State = EntityState.Modified; - entry.Entity.DeletedAt = now; - } - } - - return await base.SaveChangesAsync(cancellationToken); - } -} - -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); - } -} \ No newline at end of file diff --git a/DysonNetwork.Pass/DysonNetwork.Pass.csproj b/DysonNetwork.Pass/DysonNetwork.Pass.csproj index 7938438..8faaeda 100644 --- a/DysonNetwork.Pass/DysonNetwork.Pass.csproj +++ b/DysonNetwork.Pass/DysonNetwork.Pass.csproj @@ -9,6 +9,7 @@ + all @@ -16,10 +17,18 @@ + + + + + + + + @@ -32,4 +41,34 @@ + + + true + + + true + + + true + + + true + + + true + + + true + + + + + + + + + + + + diff --git a/DysonNetwork.Pass/Localization/AccountEventResource.cs b/DysonNetwork.Pass/Localization/AccountEventResource.cs new file mode 100644 index 0000000..239505e --- /dev/null +++ b/DysonNetwork.Pass/Localization/AccountEventResource.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Pass.Localization; + +public class AccountEventResource +{ + +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Localization/EmailResource.cs b/DysonNetwork.Pass/Localization/EmailResource.cs new file mode 100644 index 0000000..2ebcdb4 --- /dev/null +++ b/DysonNetwork.Pass/Localization/EmailResource.cs @@ -0,0 +1,5 @@ +namespace DysonNetwork.Pass.Localization; + +public class EmailResource +{ +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Localization/NotificationResource.cs b/DysonNetwork.Pass/Localization/NotificationResource.cs new file mode 100644 index 0000000..183c09d --- /dev/null +++ b/DysonNetwork.Pass/Localization/NotificationResource.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Pass.Localization; + +public class NotificationResource +{ + +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Localization/SharedResource.cs b/DysonNetwork.Pass/Localization/SharedResource.cs new file mode 100644 index 0000000..f21ad84 --- /dev/null +++ b/DysonNetwork.Pass/Localization/SharedResource.cs @@ -0,0 +1,6 @@ +namespace DysonNetwork.Pass.Localization; + +public class SharedResource +{ + +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor b/DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor new file mode 100644 index 0000000..2d4f94c --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/AccountDeletionEmail.razor @@ -0,0 +1,42 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization + + + + +

@(Localizer["AccountDeletionHeader"])

+

@(Localizer["AccountDeletionPara1"]) @@@Name,

+

@(Localizer["AccountDeletionPara2"])

+

@(Localizer["AccountDeletionPara3"])

+ + + + + + + + + +

@(Localizer["AccountDeletionPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor b/DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor new file mode 100644 index 0000000..1af0c55 --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/ContactVerificationEmail.razor @@ -0,0 +1,43 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["ContactVerificationHeader"])

+

@(Localizer["ContactVerificationPara1"]) @Name,

+

@(Localizer["ContactVerificationPara2"])

+ + + + + + + + + +

@(Localizer["ContactVerificationPara3"])

+

@(Localizer["ContactVerificationPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/EmailLayout.razor b/DysonNetwork.Pass/Pages/Emails/EmailLayout.razor new file mode 100644 index 0000000..c3f22ff --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/EmailLayout.razor @@ -0,0 +1,337 @@ +@inherits LayoutComponentBase + + + + + + + + + + + + + + + + + + + +@code { + [Parameter] public RenderFragment? ChildContent { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/LandingEmail.razor b/DysonNetwork.Pass/Pages/Emails/LandingEmail.razor new file mode 100644 index 0000000..df1e79e --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/LandingEmail.razor @@ -0,0 +1,43 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["LandingHeader1"])

+

@(Localizer["LandingPara1"]) @@@Name,

+

@(Localizer["LandingPara2"])

+

@(Localizer["LandingPara3"])

+ + + + + + + + + +

@(Localizer["LandingPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor b/DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor new file mode 100644 index 0000000..2b867d9 --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/PasswordResetEmail.razor @@ -0,0 +1,44 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["PasswordResetHeader"])

+

@(Localizer["PasswordResetPara1"]) @@@Name,

+

@(Localizer["PasswordResetPara2"])

+

@(Localizer["PasswordResetPara3"])

+ + + + + + + + + +

@(Localizer["PasswordResetPara4"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Link { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; + [Inject] IStringLocalizer LocalizerShared { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor b/DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor new file mode 100644 index 0000000..2763b19 --- /dev/null +++ b/DysonNetwork.Pass/Pages/Emails/VerificationEmail.razor @@ -0,0 +1,27 @@ +@using DysonNetwork.Pass.Localization +@using Microsoft.Extensions.Localization +@using EmailResource = DysonNetwork.Pass.Localization.EmailResource + + + + +

@(Localizer["VerificationHeader1"])

+

@(Localizer["VerificationPara1"]) @@@Name,

+

@(Localizer["VerificationPara2"])

+

@(Localizer["VerificationPara3"])

+ +

@Code

+ +

@(Localizer["VerificationPara4"])

+

@(Localizer["VerificationPara5"])

+ + +
+ +@code { + [Parameter] public required string Name { get; set; } + [Parameter] public required string Code { get; set; } + + [Inject] IStringLocalizer Localizer { get; set; } = null!; + [Inject] IStringLocalizer LocalizerShared { get; set; } = null!; +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs b/DysonNetwork.Pass/Permission/PermissionMiddleware.cs similarity index 95% rename from DysonNetwork.Sphere/Permission/PermissionMiddleware.cs rename to DysonNetwork.Pass/Permission/PermissionMiddleware.cs index f1dcc00..4c85e5b 100644 --- a/DysonNetwork.Sphere/Permission/PermissionMiddleware.cs +++ b/DysonNetwork.Pass/Permission/PermissionMiddleware.cs @@ -1,6 +1,6 @@ -namespace DysonNetwork.Sphere.Permission; +using Microsoft.AspNetCore.Http; -using System; +namespace DysonNetwork.Pass.Permission; [AttributeUsage(AttributeTargets.Method, Inherited = true)] public class RequiredPermissionAttribute(string area, string key) : Attribute diff --git a/DysonNetwork.Sphere/Permission/PermissionService.cs b/DysonNetwork.Pass/Permission/PermissionService.cs similarity index 98% rename from DysonNetwork.Sphere/Permission/PermissionService.cs rename to DysonNetwork.Pass/Permission/PermissionService.cs index d3c5d1c..860da3d 100644 --- a/DysonNetwork.Sphere/Permission/PermissionService.cs +++ b/DysonNetwork.Pass/Permission/PermissionService.cs @@ -1,10 +1,10 @@ +using System.Text.Json; +using DysonNetwork.Shared.Cache; +using DysonNetwork.Shared.Models; using Microsoft.EntityFrameworkCore; using NodaTime; -using System.Text.Json; -using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Storage; -namespace DysonNetwork.Sphere.Permission; +namespace DysonNetwork.Pass.Permission; public class PermissionService( AppDatabase db, diff --git a/DysonNetwork.Pass/Program.cs b/DysonNetwork.Pass/Program.cs index 7310439..6fecead 100644 --- a/DysonNetwork.Pass/Program.cs +++ b/DysonNetwork.Pass/Program.cs @@ -1,4 +1,6 @@ -using DysonNetwork.Pass.Data; +using DysonNetwork.Pass; +using DysonNetwork.Pass.Account; +using DysonNetwork.Pass.Auth; using Microsoft.AspNetCore.Builder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -8,15 +10,21 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); +builder.Services.AddGrpc(); builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("App"))); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + var app = builder.Build(); // Configure the HTTP request pipeline. app.UseAuthorization(); app.MapControllers(); +app.MapGrpcService(); +app.MapGrpcService(); // Run database migrations using (var scope = app.Services.CreateScope()) diff --git a/DysonNetwork.Sphere/Storage/CacheService.cs b/DysonNetwork.Shared/Cache/CacheService.cs similarity index 99% rename from DysonNetwork.Sphere/Storage/CacheService.cs rename to DysonNetwork.Shared/Cache/CacheService.cs index 7562a0f..2157bb7 100644 --- a/DysonNetwork.Sphere/Storage/CacheService.cs +++ b/DysonNetwork.Shared/Cache/CacheService.cs @@ -4,7 +4,7 @@ using NodaTime; using NodaTime.Serialization.JsonNet; using StackExchange.Redis; -namespace DysonNetwork.Sphere.Storage; +namespace DysonNetwork.Shared.Cache; /// /// Represents a distributed lock that can be used to synchronize access across multiple processes diff --git a/DysonNetwork.Shared/DysonNetwork.Shared.csproj b/DysonNetwork.Shared/DysonNetwork.Shared.csproj index b34e175..ffdabc6 100644 --- a/DysonNetwork.Shared/DysonNetwork.Shared.csproj +++ b/DysonNetwork.Shared/DysonNetwork.Shared.csproj @@ -7,7 +7,7 @@ - + @@ -20,7 +20,9 @@ + + diff --git a/DysonNetwork.Sphere/Account/AccountGrpcService.cs b/DysonNetwork.Sphere/Account/AccountGrpcService.cs deleted file mode 100644 index ef5d264..0000000 --- a/DysonNetwork.Sphere/Account/AccountGrpcService.cs +++ /dev/null @@ -1,88 +0,0 @@ -using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Account.Proto; -using Grpc.Core; -using Google.Protobuf.WellKnownTypes; -using Microsoft.EntityFrameworkCore; -using DysonNetwork.Sphere.Auth; - -namespace DysonNetwork.Sphere.Account; - -public class AccountGrpcService : DysonNetwork.Sphere.Account.Proto.AccountService.AccountServiceBase -{ - private readonly AppDatabase _db; - private readonly AuthService _auth; - - public AccountGrpcService(AppDatabase db, AuthService auth) - { - _db = db; - _auth = auth; - } - - public override async Task GetAccount(Empty request, ServerCallContext context) - { - var account = await GetAccountFromContext(context); - return ToAccountResponse(account); - } - - public override async Task UpdateAccount(UpdateAccountRequest request, ServerCallContext context) - { - var account = await GetAccountFromContext(context); - - if (request.Email != null) - { - var emailContact = await _db.AccountContacts.FirstOrDefaultAsync(c => c.AccountId == account.Id && c.Type == AccountContactType.Email); - if (emailContact != null) - { - emailContact.Content = request.Email; - } - else - { - account.Contacts.Add(new AccountContact { Type = AccountContactType.Email, Content = request.Email }); - } - } - - if (request.DisplayName != null) - { - account.Nick = request.DisplayName; - } - - await _db.SaveChangesAsync(); - - return ToAccountResponse(account); - } - - private async Task GetAccountFromContext(ServerCallContext context) - { - var authorizationHeader = context.RequestHeaders.FirstOrDefault(h => h.Key == "authorization"); - if (authorizationHeader == null) - { - throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Missing authorization header.")); - } - - var token = authorizationHeader.Value.Replace("Bearer ", ""); - if (!_auth.ValidateToken(token, out var sessionId)) - { - throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Invalid token.")); - } - - var session = await _db.AuthSessions.Include(s => s.Account).ThenInclude(a => a.Contacts).FirstOrDefaultAsync(s => s.Id == sessionId); - if (session == null) - { - throw new RpcException(new Grpc.Core.Status(StatusCode.Unauthenticated, "Session not found.")); - } - - return session.Account; - } - - private AccountResponse ToAccountResponse(Shared.Models.Account account) - { - var emailContact = account.Contacts.FirstOrDefault(c => c.Type == AccountContactType.Email); - return new AccountResponse - { - Id = account.Id.ToString(), - Username = account.Name, - Email = emailContact?.Content ?? "", - DisplayName = account.Nick - }; - } -} diff --git a/DysonNetwork.Sphere/Account/NotificationService.cs b/DysonNetwork.Sphere/Account/NotificationService.cs deleted file mode 100644 index bac575d..0000000 --- a/DysonNetwork.Sphere/Account/NotificationService.cs +++ /dev/null @@ -1,309 +0,0 @@ -using System.Text; -using System.Text.Json; -using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Connection; -using EFCore.BulkExtensions; -using Microsoft.EntityFrameworkCore; -using NodaTime; - -namespace DysonNetwork.Sphere.Account; - -public class NotificationService( - AppDatabase db, - WebSocketService ws, - IHttpClientFactory httpFactory, - IConfiguration config) -{ - private readonly string _notifyTopic = config["Notifications:Topic"]!; - private readonly Uri _notifyEndpoint = new(config["Notifications:Endpoint"]!); - - public async Task UnsubscribePushNotifications(string deviceId) - { - await db.NotificationPushSubscriptions - .Where(s => s.DeviceId == deviceId) - .ExecuteDeleteAsync(); - } - - public async Task SubscribePushNotification( - Shared.Models.Account account, - NotificationPushProvider provider, - string deviceId, - string deviceToken - ) - { - var now = SystemClock.Instance.GetCurrentInstant(); - - // First check if a matching subscription exists - var existingSubscription = await db.NotificationPushSubscriptions - .Where(s => s.AccountId == account.Id) - .Where(s => s.DeviceId == deviceId || s.DeviceToken == deviceToken) - .FirstOrDefaultAsync(); - - if (existingSubscription is not null) - { - // Update the existing subscription directly in the database - await db.NotificationPushSubscriptions - .Where(s => s.Id == existingSubscription.Id) - .ExecuteUpdateAsync(setters => setters - .SetProperty(s => s.DeviceId, deviceId) - .SetProperty(s => s.DeviceToken, deviceToken) - .SetProperty(s => s.UpdatedAt, now)); - - // Return the updated subscription - existingSubscription.DeviceId = deviceId; - existingSubscription.DeviceToken = deviceToken; - existingSubscription.UpdatedAt = now; - return existingSubscription; - } - - var subscription = new NotificationPushSubscription - { - DeviceId = deviceId, - DeviceToken = deviceToken, - Provider = provider, - AccountId = account.Id, - }; - - db.NotificationPushSubscriptions.Add(subscription); - await db.SaveChangesAsync(); - - return subscription; - } - - public async Task SendNotification( - Shared.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 - ) - { - if (title is null && subtitle is null && content is null) - throw new ArgumentException("Unable to send notification that completely empty."); - - meta ??= new Dictionary(); - if (actionUri is not null) meta["action_uri"] = actionUri; - - var notification = new Notification - { - Topic = topic, - Title = title, - Subtitle = subtitle, - Content = content, - Meta = meta, - AccountId = account.Id, - }; - - if (save) - { - db.Add(notification); - await db.SaveChangesAsync(); - } - - if (!isSilent) _ = DeliveryNotification(notification); - - return notification; - } - - 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 - .Where(s => s.AccountId == notification.AccountId) - .ToListAsync(); - - await _PushNotification(notification, subscribers); - } - - public async Task MarkNotificationsViewed(ICollection notifications) - { - var now = SystemClock.Instance.GetCurrentInstant(); - var id = notifications.Where(n => n.ViewedAt == null).Select(n => n.Id).ToList(); - if (id.Count == 0) return; - - await db.Notifications - .Where(n => id.Contains(n.Id)) - .ExecuteUpdateAsync(s => s.SetProperty(n => n.ViewedAt, now) - ); - } - - public async Task BroadcastNotification(Notification notification, bool save = false) - { - var accounts = await db.Accounts.ToListAsync(); - - if (save) - { - var notifications = accounts.Select(x => - { - var newNotification = new Notification - { - Topic = notification.Topic, - Title = notification.Title, - Subtitle = notification.Subtitle, - Content = notification.Content, - Meta = notification.Meta, - Priority = notification.Priority, - Account = x, - AccountId = x.Id - }; - return newNotification; - }).ToList(); - 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) - { - if (save) - { - var notifications = accounts.Select(x => - { - var newNotification = new Notification - { - Topic = notification.Topic, - Title = notification.Title, - Subtitle = notification.Subtitle, - Content = notification.Content, - Meta = notification.Meta, - Priority = notification.Priority, - Account = x, - AccountId = x.Id - }; - return newNotification; - }).ToList(); - 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 - .Where(s => accountsId.Contains(s.AccountId)) - .ToListAsync(); - await _PushNotification(notification, subscribers); - } - - private List> _BuildNotificationPayload(Notification notification, - IEnumerable subscriptions) - { - var subDict = subscriptions - .GroupBy(x => x.Provider) - .ToDictionary(x => x.Key, x => x.ToList()); - - var notifications = subDict.Select(value => - { - var platformCode = value.Key switch - { - NotificationPushProvider.Apple => 1, - NotificationPushProvider.Google => 2, - _ => throw new InvalidOperationException($"Unknown push provider: {value.Key}") - }; - - var tokens = value.Value.Select(x => x.DeviceToken).ToList(); - return _BuildNotificationPayload(notification, platformCode, tokens); - }).ToList(); - - return notifications.ToList(); - } - - private Dictionary _BuildNotificationPayload(Notification notification, int platformCode, - IEnumerable deviceTokens) - { - var alertDict = new Dictionary(); - var dict = new Dictionary - { - ["notif_id"] = notification.Id.ToString(), - ["apns_id"] = notification.Id.ToString(), - ["topic"] = _notifyTopic, - ["tokens"] = deviceTokens, - ["data"] = new Dictionary - { - ["type"] = notification.Topic, - ["meta"] = notification.Meta ?? new Dictionary(), - }, - ["mutable_content"] = true, - ["priority"] = notification.Priority >= 5 ? "high" : "normal", - }; - - if (!string.IsNullOrWhiteSpace(notification.Title)) - { - dict["title"] = notification.Title; - alertDict["title"] = notification.Title; - } - - if (!string.IsNullOrWhiteSpace(notification.Content)) - { - dict["message"] = notification.Content; - alertDict["body"] = notification.Content; - } - - if (!string.IsNullOrWhiteSpace(notification.Subtitle)) - { - dict["message"] = $"{notification.Subtitle}\n{dict["message"]}"; - alertDict["subtitle"] = notification.Subtitle; - } - - if (notification.Priority >= 5) - dict["name"] = "default"; - - dict["platform"] = platformCode; - dict["alert"] = alertDict; - - return dict; - } - - private async Task _PushNotification(Notification notification, - IEnumerable subscriptions) - { - var subList = subscriptions.ToList(); - if (subList.Count == 0) return; - - var requestDict = new Dictionary - { - ["notifications"] = _BuildNotificationPayload(notification, subList) - }; - - var client = httpFactory.CreateClient(); - client.BaseAddress = _notifyEndpoint; - var request = await client.PostAsync("/push", new StringContent( - JsonSerializer.Serialize(requestDict), - Encoding.UTF8, - "application/json" - )); - request.EnsureSuccessStatusCode(); - } -} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Activity/ActivityService.cs b/DysonNetwork.Sphere/Activity/ActivityService.cs index 7197256..33f55bb 100644 --- a/DysonNetwork.Sphere/Activity/ActivityService.cs +++ b/DysonNetwork.Sphere/Activity/ActivityService.cs @@ -13,7 +13,8 @@ public class ActivityService( PublisherService pub, RelationshipService rels, PostService ps, - DiscoveryService ds) + DiscoveryService ds +) { private static double CalculateHotRank(Post.Post post, Instant now) { diff --git a/DysonNetwork.Sphere/AppDatabase.cs b/DysonNetwork.Sphere/AppDatabase.cs index 83bc4dc..881997e 100644 --- a/DysonNetwork.Sphere/AppDatabase.cs +++ b/DysonNetwork.Sphere/AppDatabase.cs @@ -1,22 +1,12 @@ using System.Linq.Expressions; using System.Reflection; using DysonNetwork.Shared.Models; -using DysonNetwork.Sphere.Account; -using DysonNetwork.Sphere.Auth; -using DysonNetwork.Sphere.Chat; -using DysonNetwork.Sphere.Developer; -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; @@ -33,28 +23,6 @@ public class AppDatabase( 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; } @@ -104,39 +72,7 @@ public class AppDatabase( .UseNetTopologySuite() .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); } @@ -144,25 +80,6 @@ public class AppDatabase( { 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() @@ -333,23 +250,9 @@ public class AppDatabaseRecyclingJob(AppDatabase db, ILogger 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 now = SystemClock.Instance.GetCurrentInstant(); var threshold = now - Duration.FromDays(7); var entityTypes = db.Model.GetEntityTypes() diff --git a/DysonNetwork.Sphere/Chat/ChatRoomService.cs b/DysonNetwork.Sphere/Chat/ChatRoomService.cs index 06d45e2..c547041 100644 --- a/DysonNetwork.Sphere/Chat/ChatRoomService.cs +++ b/DysonNetwork.Sphere/Chat/ChatRoomService.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs index d1131cb..17688ca 100644 --- a/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs +++ b/DysonNetwork.Sphere/Chat/Realtime/LivekitService.cs @@ -4,6 +4,7 @@ using Livekit.Server.Sdk.Dotnet; using Microsoft.EntityFrameworkCore; using NodaTime; using System.Text.Json; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; namespace DysonNetwork.Sphere.Chat.Realtime; diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs b/DysonNetwork.Sphere/Connection/WebReader/WebReaderService.cs index d7f9bda..27f1c04 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.Shared.Cache; using DysonNetwork.Sphere.Storage; using HtmlAgilityPack; diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index f33c66f..85b3b48 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -83,6 +83,7 @@ + @@ -164,7 +165,7 @@ - + diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index 518c462..35997bc 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using DysonNetwork.Shared.Cache; using DysonNetwork.Sphere.Account; using DysonNetwork.Sphere.Connection.WebReader; using DysonNetwork.Sphere.Localization; diff --git a/DysonNetwork.Sphere/Publisher/PublisherService.cs b/DysonNetwork.Sphere/Publisher/PublisherService.cs index 163e647..f0fcbc4 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherService.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Storage; diff --git a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs index 9182ea2..2b73c40 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using DysonNetwork.Sphere.Account; using DysonNetwork.Sphere.Localization; diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index 4db2772..4663bc4 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -24,6 +24,7 @@ using NodaTime.Serialization.SystemTextJson; using StackExchange.Redis; using System.Text.Json; using System.Threading.RateLimiting; +using DysonNetwork.Shared.Cache; using DysonNetwork.Sphere.Auth.OidcProvider.Options; using DysonNetwork.Sphere.Auth.OidcProvider.Services; using DysonNetwork.Sphere.Connection.WebReader; diff --git a/DysonNetwork.Sphere/Sticker/StickerService.cs b/DysonNetwork.Sphere/Sticker/StickerService.cs index f2f5bbd..f269c1c 100644 --- a/DysonNetwork.Sphere/Sticker/StickerService.cs +++ b/DysonNetwork.Sphere/Sticker/StickerService.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; diff --git a/DysonNetwork.Sphere/Storage/FileReferenceService.cs b/DysonNetwork.Sphere/Storage/FileReferenceService.cs index b7279ff..93a4d2e 100644 --- a/DysonNetwork.Sphere/Storage/FileReferenceService.cs +++ b/DysonNetwork.Sphere/Storage/FileReferenceService.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using Microsoft.EntityFrameworkCore; using NodaTime; diff --git a/DysonNetwork.Sphere/Storage/FileService.cs b/DysonNetwork.Sphere/Storage/FileService.cs index d993378..6628ee0 100644 --- a/DysonNetwork.Sphere/Storage/FileService.cs +++ b/DysonNetwork.Sphere/Storage/FileService.cs @@ -2,6 +2,7 @@ using System.Globalization; using FFMpegCore; using System.Security.Cryptography; using AngleSharp.Text; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using Microsoft.EntityFrameworkCore; using Minio; diff --git a/DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs b/DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs index 1984ec0..cad3880 100644 --- a/DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs +++ b/DysonNetwork.Sphere/Storage/Handlers/PostViewFlushHandler.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Shared.Cache; using Microsoft.EntityFrameworkCore; using NodaTime; using Quartz; diff --git a/DysonNetwork.Sphere/Wallet/SubscriptionService.cs b/DysonNetwork.Sphere/Wallet/SubscriptionService.cs index 1934e4f..fddca78 100644 --- a/DysonNetwork.Sphere/Wallet/SubscriptionService.cs +++ b/DysonNetwork.Sphere/Wallet/SubscriptionService.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Models; using DysonNetwork.Sphere.Account; using DysonNetwork.Sphere.Localization;