✨ Account service account deleted broadcast message & sphere service clean up
This commit is contained in:
@@ -3,6 +3,7 @@ using DysonNetwork.Shared.Auth;
|
|||||||
using DysonNetwork.Shared.Http;
|
using DysonNetwork.Shared.Http;
|
||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
using DysonNetwork.Develop.Startup;
|
using DysonNetwork.Develop.Startup;
|
||||||
|
using DysonNetwork.Shared.Stream;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -10,6 +11,7 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
builder.ConfigureAppKestrel(builder.Configuration);
|
builder.ConfigureAppKestrel(builder.Configuration);
|
||||||
|
|
||||||
builder.Services.AddRegistryService(builder.Configuration);
|
builder.Services.AddRegistryService(builder.Configuration);
|
||||||
|
builder.Services.AddStreamConnection(builder.Configuration);
|
||||||
builder.Services.AddAppServices(builder.Configuration);
|
builder.Services.AddAppServices(builder.Configuration);
|
||||||
builder.Services.AddAppAuthentication();
|
builder.Services.AddAppAuthentication();
|
||||||
builder.Services.AddAppSwagger();
|
builder.Services.AddAppSwagger();
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
using DysonNetwork.Pass.Auth;
|
using DysonNetwork.Pass.Auth;
|
||||||
using DysonNetwork.Pass.Auth.OpenId;
|
using DysonNetwork.Pass.Auth.OpenId;
|
||||||
using DysonNetwork.Pass.Email;
|
using DysonNetwork.Pass.Email;
|
||||||
@@ -6,9 +7,11 @@ using DysonNetwork.Pass.Localization;
|
|||||||
using DysonNetwork.Pass.Permission;
|
using DysonNetwork.Pass.Permission;
|
||||||
using DysonNetwork.Shared.Cache;
|
using DysonNetwork.Shared.Cache;
|
||||||
using DysonNetwork.Shared.Proto;
|
using DysonNetwork.Shared.Proto;
|
||||||
|
using DysonNetwork.Shared.Stream;
|
||||||
using EFCore.BulkExtensions;
|
using EFCore.BulkExtensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
using NATS.Client.Core;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using OtpNet;
|
using OtpNet;
|
||||||
using AuthService = DysonNetwork.Pass.Auth.AuthService;
|
using AuthService = DysonNetwork.Pass.Auth.AuthService;
|
||||||
@@ -23,7 +26,8 @@ public class AccountService(
|
|||||||
PusherService.PusherServiceClient pusher,
|
PusherService.PusherServiceClient pusher,
|
||||||
IStringLocalizer<NotificationResource> localizer,
|
IStringLocalizer<NotificationResource> localizer,
|
||||||
ICacheService cache,
|
ICacheService cache,
|
||||||
ILogger<AccountService> logger
|
ILogger<AccountService> logger,
|
||||||
|
INatsConnection nats
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
public static void SetCultureInfo(Account account)
|
public static void SetCultureInfo(Account account)
|
||||||
@@ -183,11 +187,11 @@ public class AccountService(
|
|||||||
var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync();
|
var dupeAutomateCount = await db.Accounts.Where(a => a.AutomatedId == automatedId).CountAsync();
|
||||||
if (dupeAutomateCount > 0)
|
if (dupeAutomateCount > 0)
|
||||||
throw new InvalidOperationException("Automated ID has already been used.");
|
throw new InvalidOperationException("Automated ID has already been used.");
|
||||||
|
|
||||||
var dupeNameCount = await db.Accounts.Where(a => a.Name == account.Name).CountAsync();
|
var dupeNameCount = await db.Accounts.Where(a => a.Name == account.Name).CountAsync();
|
||||||
if (dupeNameCount > 0)
|
if (dupeNameCount > 0)
|
||||||
throw new InvalidOperationException("Account name has already been taken.");
|
throw new InvalidOperationException("Account name has already been taken.");
|
||||||
|
|
||||||
account.AutomatedId = automatedId;
|
account.AutomatedId = automatedId;
|
||||||
account.ActivatedAt = SystemClock.Instance.GetCurrentInstant();
|
account.ActivatedAt = SystemClock.Instance.GetCurrentInstant();
|
||||||
account.IsSuperuser = false;
|
account.IsSuperuser = false;
|
||||||
@@ -195,7 +199,7 @@ public class AccountService(
|
|||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Account?> GetBotAccount(Guid automatedId)
|
public async Task<Account?> GetBotAccount(Guid automatedId)
|
||||||
{
|
{
|
||||||
return await db.Accounts.FirstOrDefaultAsync(a => a.AutomatedId == automatedId);
|
return await db.Accounts.FirstOrDefaultAsync(a => a.AutomatedId == automatedId);
|
||||||
@@ -491,11 +495,11 @@ public class AccountService(
|
|||||||
.Where(s => s.Id == sessionId && s.AccountId == account.Id)
|
.Where(s => s.Id == sessionId && s.AccountId == account.Id)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (session is null) throw new InvalidOperationException("Session was not found.");
|
if (session is null) throw new InvalidOperationException("Session was not found.");
|
||||||
|
|
||||||
// The current session should be included in the sessions' list
|
// The current session should be included in the sessions' list
|
||||||
db.AuthSessions.Remove(session);
|
db.AuthSessions.Remove(session);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
if (session.Challenge.ClientId.HasValue)
|
if (session.Challenge.ClientId.HasValue)
|
||||||
{
|
{
|
||||||
if (!await IsDeviceActive(session.Challenge.ClientId.Value))
|
if (!await IsDeviceActive(session.Challenge.ClientId.Value))
|
||||||
@@ -503,7 +507,7 @@ public class AccountService(
|
|||||||
{ DeviceId = session.Challenge.Client!.DeviceId }
|
{ DeviceId = session.Challenge.Client!.DeviceId }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation("Deleted session #{SessionId}", session.Id);
|
logger.LogInformation("Deleted session #{SessionId}", session.Id);
|
||||||
|
|
||||||
await cache.RemoveAsync($"{AuthService.AuthCachePrefix}{session.Id}");
|
await cache.RemoveAsync($"{AuthService.AuthCachePrefix}{session.Id}");
|
||||||
@@ -531,7 +535,7 @@ public class AccountService(
|
|||||||
.Include(s => s.Challenge)
|
.Include(s => s.Challenge)
|
||||||
.Where(s => s.Challenge.ClientId == device.Id)
|
.Where(s => s.Challenge.ClientId == device.Id)
|
||||||
.ExecuteUpdateAsync(p => p.SetProperty(s => s.DeletedAt, s => now));
|
.ExecuteUpdateAsync(p => p.SetProperty(s => s.DeletedAt, s => now));
|
||||||
|
|
||||||
db.AuthClients.Remove(device);
|
db.AuthClients.Remove(device);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -693,8 +697,14 @@ public class AccountService(
|
|||||||
await db.AuthSessions
|
await db.AuthSessions
|
||||||
.Where(s => s.AccountId == account.Id)
|
.Where(s => s.AccountId == account.Id)
|
||||||
.ExecuteDeleteAsync();
|
.ExecuteDeleteAsync();
|
||||||
|
|
||||||
db.Accounts.Remove(account);
|
db.Accounts.Remove(account);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
await nats.PublishAsync(AccountDeletedEvent.Type, JsonSerializer.SerializeToUtf8Bytes(new AccountDeletedEvent
|
||||||
|
{
|
||||||
|
AccountId = account.Id,
|
||||||
|
DeletedAt = SystemClock.Instance.GetCurrentInstant()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,6 +4,8 @@ namespace DysonNetwork.Shared.Stream;
|
|||||||
|
|
||||||
public class AccountDeletedEvent
|
public class AccountDeletedEvent
|
||||||
{
|
{
|
||||||
|
public static string Type => "account.deleted";
|
||||||
|
|
||||||
public Guid AccountId { get; set; } = Guid.NewGuid();
|
public Guid AccountId { get; set; } = Guid.NewGuid();
|
||||||
public Instant DeletedAt { get; set; } = SystemClock.Instance.GetCurrentInstant();
|
public Instant DeletedAt { get; set; } = SystemClock.Instance.GetCurrentInstant();
|
||||||
}
|
}
|
@@ -2,6 +2,7 @@ using DysonNetwork.Shared.Auth;
|
|||||||
using DysonNetwork.Shared.Http;
|
using DysonNetwork.Shared.Http;
|
||||||
using DysonNetwork.Shared.PageData;
|
using DysonNetwork.Shared.PageData;
|
||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
|
using DysonNetwork.Shared.Stream;
|
||||||
using DysonNetwork.Sphere;
|
using DysonNetwork.Sphere;
|
||||||
using DysonNetwork.Sphere.PageData;
|
using DysonNetwork.Sphere.PageData;
|
||||||
using DysonNetwork.Sphere.Startup;
|
using DysonNetwork.Sphere.Startup;
|
||||||
@@ -18,6 +19,7 @@ builder.Services.AddAppMetrics();
|
|||||||
|
|
||||||
// Add application services
|
// Add application services
|
||||||
builder.Services.AddRegistryService(builder.Configuration);
|
builder.Services.AddRegistryService(builder.Configuration);
|
||||||
|
builder.Services.AddStreamConnection(builder.Configuration);
|
||||||
builder.Services.AddAppServices(builder.Configuration);
|
builder.Services.AddAppServices(builder.Configuration);
|
||||||
builder.Services.AddAppRateLimiting();
|
builder.Services.AddAppRateLimiting();
|
||||||
builder.Services.AddAppAuthentication();
|
builder.Services.AddAppAuthentication();
|
||||||
|
51
DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs
Normal file
51
DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using DysonNetwork.Shared.Stream;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NATS.Client.Core;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Startup;
|
||||||
|
|
||||||
|
public class BroadcastEventHandler(
|
||||||
|
INatsConnection nats,
|
||||||
|
ILogger<BroadcastEventHandler> logger,
|
||||||
|
AppDatabase db
|
||||||
|
) : BackgroundService
|
||||||
|
{
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
await foreach (var msg in nats.SubscribeAsync<byte[]>("accounts.deleted", cancellationToken: stoppingToken))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data);
|
||||||
|
if (evt == null) continue;
|
||||||
|
|
||||||
|
logger.LogInformation("Account deleted: {AccountId}", evt.AccountId);
|
||||||
|
|
||||||
|
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken: stoppingToken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var publishers = await db.Publishers
|
||||||
|
.Where(p => p.Members.All(m => m.AccountId == evt.AccountId))
|
||||||
|
.ToListAsync(cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
foreach (var publisher in publishers)
|
||||||
|
await db.Posts
|
||||||
|
.Where(p => p.PublisherId == publisher.Id)
|
||||||
|
.ExecuteDeleteAsync(cancellationToken: stoppingToken);
|
||||||
|
|
||||||
|
await transaction.CommitAsync(cancellationToken: stoppingToken);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync(cancellationToken: stoppingToken);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error processing AccountDeleted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -73,6 +73,8 @@ public static class ServiceCollectionExtensions
|
|||||||
options.SupportedUICultures = supportedCultures;
|
options.SupportedUICultures = supportedCultures;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
services.AddHostedService<BroadcastEventHandler>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -71,6 +71,7 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5aa524c330cf4033930e4a8661c62bc331a00_003F24_003Fdb8cbeea_003FImage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5aa524c330cf4033930e4a8661c62bc331a00_003F24_003Fdb8cbeea_003FImage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F9f_003Fc5bde8be_003FImage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdaa8d9c408cd4b4286bbef7e35f1a42e31c00_003F9f_003Fc5bde8be_003FImage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIMessage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F331aca3f6f414013b09964063341351379060_003Ff1_003F9fbeae46_003FIMessage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIMessage_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F331aca3f6f414013b09964063341351379060_003Ff1_003F9fbeae46_003FIMessage_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AINatsClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F30191878badd4d5da7e204504ae1f7c075400_003F45_003F90c0c4d0_003FINatsClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIndexAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe38f14ac86274ebb9b366729231d1c1a8838_003F8b_003F2890293d_003FIndexAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIndexAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe38f14ac86274ebb9b366729231d1c1a8838_003F8b_003F2890293d_003FIndexAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcb057cac061449bbbf3252d172d0302a2ce00_003Fb4_003Fd072017c_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcb057cac061449bbbf3252d172d0302a2ce00_003Fb4_003Fd072017c_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Reference in New Issue
Block a user