From 091bfbbc29e3462c966884a8e61415454d53adc6 Mon Sep 17 00:00:00 2001 From: cake Date: Fri, 5 Dec 2025 22:01:48 +0100 Subject: [PATCH] Auto-pruning of syncshell, added metrics for pruning, return of count of users in fullgroupdto. --- .../Hubs/LightlessHub.Groups.cs | 119 +- .../LightlessSyncServer/Hubs/LightlessHub.cs | 8 +- .../Services/Interfaces/IPruneService.cs | 10 + .../Services/PruneService.cs | 60 + .../Services/SystemInfoService.cs | 2 + .../LightlessSyncServer/Startup.cs | 15 +- .../Worker/AutoPruneWorker.cs | 68 + .../{MareModule.cs => LightlessModule.cs} | 0 ...> LightlessWizardModule.AprilFools2024.cs} | 0 ...ete.cs => LightlessWizardModule.Delete.cs} | 0 ...er.cs => LightlessWizardModule.Recover.cs} | 0 ...r.cs => LightlessWizardModule.Register.cs} | 0 ...ink.cs => LightlessWizardModule.Relink.cs} | 0 ....cs => LightlessWizardModule.Secondary.cs} | 0 ...o.cs => LightlessWizardModule.UserInfo.cs} | 0 ...ity.cs => LightlessWizardModule.Vanity.cs} | 2 +- ...zardModule.cs => LightlessWizardModule.cs} | 0 ...MareDbContext.cs => LightlessDbContext.cs} | 0 .../{MareMetrics.cs => LightlessMetrics.cs} | 0 .../LightlessSyncShared/Metrics/MetricsAPI.cs | 1 + ...1205205537_AddAutoPruneInGroup.Designer.cs | 1327 +++++++++++++++++ .../20251205205537_AddAutoPruneInGroup.cs | 40 + .../LightlessDbContextModelSnapshot.cs | 8 + .../LightlessSyncShared/Models/Group.cs | 2 + 24 files changed, 1620 insertions(+), 42 deletions(-) create mode 100644 LightlessSyncServer/LightlessSyncServer/Services/Interfaces/IPruneService.cs create mode 100644 LightlessSyncServer/LightlessSyncServer/Services/PruneService.cs create mode 100644 LightlessSyncServer/LightlessSyncServer/Worker/AutoPruneWorker.cs rename LightlessSyncServer/LightlessSyncServices/Discord/{MareModule.cs => LightlessModule.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.AprilFools2024.cs => LightlessWizardModule.AprilFools2024.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.Delete.cs => LightlessWizardModule.Delete.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.Recover.cs => LightlessWizardModule.Recover.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.Register.cs => LightlessWizardModule.Register.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.Relink.cs => LightlessWizardModule.Relink.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.Secondary.cs => LightlessWizardModule.Secondary.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.UserInfo.cs => LightlessWizardModule.UserInfo.cs} (100%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.Vanity.cs => LightlessWizardModule.Vanity.cs} (99%) rename LightlessSyncServer/LightlessSyncServices/Discord/{MareWizardModule.cs => LightlessWizardModule.cs} (100%) rename LightlessSyncServer/LightlessSyncShared/Data/{MareDbContext.cs => LightlessDbContext.cs} (100%) rename LightlessSyncServer/LightlessSyncShared/Metrics/{MareMetrics.cs => LightlessMetrics.cs} (100%) create mode 100644 LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.Designer.cs create mode 100644 LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.cs diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs index cd7afdc..9017e38 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.Groups.cs @@ -264,7 +264,7 @@ public partial class LightlessHub var self = await DbContext.Users.SingleAsync(u => u.UID == UserUID, cancellationToken: RequestAbortedToken).ConfigureAwait(false); await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(newGroup.ToGroupData(), self.ToUserData(), - newGroup.ToEnum(), initialPrefPermissions.ToEnum(), initialPair.ToEnum(), new(StringComparer.Ordinal))) + newGroup.ToEnum(), initialPrefPermissions.ToEnum(), initialPair.ToEnum(), new(StringComparer.Ordinal), 1)) .ConfigureAwait(false); _logger.LogCallInfo(LightlessHubLogger.Args(gid)); @@ -454,9 +454,14 @@ public partial class LightlessHub await DbContext.SaveChangesAsync(RequestAbortedToken).ConfigureAwait(false); var groupInfos = await DbContext.GroupPairs.Where(u => u.GroupGID == group.GID && (u.IsPinned || u.IsModerator)).ToListAsync(cancellationToken: RequestAbortedToken).ConfigureAwait(false); + var totalUserCount = await DbContext.GroupPairs + .AsNoTracking() + .CountAsync(u => u.GroupGID == group.GID, RequestAbortedToken) + .ConfigureAwait(false); + await Clients.User(UserUID).Client_GroupSendFullInfo(new GroupFullInfoDto(group.ToGroupData(), group.Owner.ToUserData(), group.ToEnum(), preferredPermissions.ToEnum(), newPair.ToEnum(), - groupInfos.ToDictionary(u => u.GroupUserUID, u => u.ToEnum(), StringComparer.Ordinal))).ConfigureAwait(false); + groupInfos.ToDictionary(u => u.GroupUserUID, u => u.ToEnum(), StringComparer.Ordinal), totalUserCount)).ConfigureAwait(false); var self = DbContext.Users.Single(u => u.UID == UserUID); @@ -678,30 +683,38 @@ public partial class LightlessHub { _logger.LogCallInfo(LightlessHubLogger.Args(dto, days, execute)); - var (hasRights, group) = await TryValidateGroupModeratorOrOwner(dto.Group.GID).ConfigureAwait(false); + var (hasRights, group) = await TryValidateGroupModeratorOrOwner(dto.Group.GID) + .ConfigureAwait(false); if (!hasRights) return -1; - var allGroupUsers = await DbContext.GroupPairs.Include(p => p.GroupUser).Include(p => p.Group) - .Where(g => g.GroupGID == dto.Group.GID) - .ToListAsync().ConfigureAwait(false); - var usersToPrune = allGroupUsers.Where(p => !p.IsPinned && !p.IsModerator - && !string.Equals(p.GroupUserUID, UserUID, StringComparison.Ordinal) - && !string.Equals(p.Group.OwnerUID, p.GroupUserUID, StringComparison.Ordinal) - && p.GroupUser.LastLoggedIn.AddDays(days) < DateTime.UtcNow); - - if (!execute) return usersToPrune.Count(); - - DbContext.GroupPairs.RemoveRange(usersToPrune); - - foreach (var pair in usersToPrune) + if (!execute) { - await Clients.Users(allGroupUsers.Where(p => !usersToPrune.Contains(p)).Select(g => g.GroupUserUID)) - .Client_GroupPairLeft(new GroupPairDto(dto.Group, pair.GroupUser.ToUserData())).ConfigureAwait(false); + var count = await _pruneService.CountPrunableUsersAsync(dto.Group.GID, days, RequestAbortedToken).ConfigureAwait(false); + return count; } - await DbContext.SaveChangesAsync(RequestAbortedToken).ConfigureAwait(false); + var allGroupUsers = await DbContext.GroupPairs + .Include(p => p.GroupUser) + .Include(p => p.Group) + .Where(g => g.GroupGID == dto.Group.GID) + .ToListAsync(RequestAbortedToken).ConfigureAwait(false); - return usersToPrune.Count(); + var prunedPairs = await _pruneService.ExecutePruneAsync(dto.Group.GID, days, RequestAbortedToken).ConfigureAwait(false); + + var remainingUserIds = allGroupUsers + .Where(p => !prunedPairs.Any(x => string.Equals(x.GroupUserUID, p.GroupUserUID, StringComparison.Ordinal))) + .Select(p => p.GroupUserUID) + .Distinct(StringComparer.Ordinal) + .ToList(); + + foreach (var pair in prunedPairs) + { + await Clients.Users(remainingUserIds) + .Client_GroupPairLeft(new GroupPairDto(dto.Group, pair.GroupUser.ToUserData())) + .ConfigureAwait(false); + } + + return prunedPairs.Count; } [Authorize(Policy = "Identified")] @@ -910,6 +923,44 @@ public partial class LightlessHub await Clients.Users(groupPairs).Client_GroupPairChangeUserInfo(new GroupPairUserInfoDto(dto.Group, dto.User, userPair.ToEnum())).ConfigureAwait(false); } + [Authorize(Policy = "Identified")] + public async Task GroupGetPruneSettings(GroupDto dto) + { + _logger.LogCallInfo(LightlessHubLogger.Args(dto)); + + var (hasRights, group) = await TryValidateGroupModeratorOrOwner(dto.GID) + .ConfigureAwait(false); + if (!hasRights || group == null) + return null; + + return new GroupPruneSettingsDto( + Group: group.ToGroupData(), + AutoPruneEnabled: group.AutoPruneEnabled, + AutoPruneDays: group.AutoPruneDays + ); + } + + [Authorize(Policy = "Identified")] + public async Task GroupSetPruneSettings(GroupPruneSettingsDto dto) + { + _logger.LogCallInfo(LightlessHubLogger.Args(dto)); + + var (hasRights, group) = await TryValidateGroupModeratorOrOwner(dto.Group.GID) + .ConfigureAwait(false); + if (!hasRights || group == null) + return; + + // if days == 0, auto prune is OFF + var days = dto.AutoPruneDays; + var enabled = dto.AutoPruneEnabled && days > 0; + + group.AutoPruneEnabled = enabled; + group.AutoPruneDays = enabled ? days : 0; + + await DbContext.SaveChangesAsync(RequestAbortedToken) + .ConfigureAwait(false); + } + [Authorize(Policy = "Identified")] public async Task> GroupsGetAll() { @@ -932,6 +983,8 @@ public partial class LightlessHub .Where(x => x.GroupGID == gp.GroupGID && (x.IsPinned || x.IsModerator)) .Select(x => new { x.GroupUserUID, EnumValue = x.ToEnum() }) .ToList(), + UserCount = DbContext.GroupPairs + .Count(x => x.GroupGID == gp.GroupGID), }) .AsNoTracking() .ToListAsync() @@ -940,21 +993,21 @@ public partial class LightlessHub _logger.LogCallInfo(LightlessHubLogger.Args(result)); List List = [.. result.Select(r => - { - var groupInfoDict = r.GroupInfos - .ToDictionary(x => x.GroupUserUID, x => x.EnumValue, StringComparer.Ordinal); + { + var groupInfoDict = r.GroupInfos + .ToDictionary(x => x.GroupUserUID, x => x.EnumValue, StringComparer.Ordinal); + _logger.LogCallInfo(LightlessHubLogger.Args(r)); - _logger.LogCallInfo(LightlessHubLogger.Args(r)); - - return new GroupFullInfoDto( - r.GroupPair.Group.ToGroupData(), - r.GroupPair.Group.Owner.ToUserData(), - r.GroupPair.Group.ToEnum(), - r.PreferredPermission.ToEnum(), - r.GroupPair.ToEnum(), - groupInfoDict - ); + return new GroupFullInfoDto( + r.GroupPair.Group.ToGroupData(), + r.GroupPair.Group.Owner.ToUserData(), + r.GroupPair.Group.ToEnum(), + r.PreferredPermission.ToEnum(), + r.GroupPair.ToEnum(), + groupInfoDict, + r.UserCount + ); }),]; return List; } diff --git a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs index 098eb20..ccfae97 100644 --- a/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs +++ b/LightlessSyncServer/LightlessSyncServer/Hubs/LightlessHub.cs @@ -1,7 +1,6 @@ using LightlessSync.API.Data; using LightlessSync.API.Data.Enum; using LightlessSync.API.Dto; -using LightlessSync.API.Dto.Chat; using LightlessSync.API.SignalR; using LightlessSyncServer.Services; using LightlessSyncServer.Configuration; @@ -17,8 +16,7 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using StackExchange.Redis.Extensions.Core.Abstractions; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; +using LightlessSyncServer.Services.Interfaces; namespace LightlessSyncServer.Hubs; @@ -29,6 +27,7 @@ public partial class LightlessHub : Hub, ILightlessHub private readonly LightlessMetrics _lightlessMetrics; private readonly SystemInfoService _systemInfoService; private readonly PairService _pairService; + private readonly IPruneService _pruneService; private readonly IHttpContextAccessor _contextAccessor; private readonly LightlessHubLogger _logger; private readonly string _shardName; @@ -55,7 +54,7 @@ public partial class LightlessHub : Hub, ILightlessHub IConfigurationService configuration, IHttpContextAccessor contextAccessor, IRedisDatabase redisDb, OnlineSyncedPairCacheService onlineSyncedPairCacheService, LightlessCensus lightlessCensus, GPoseLobbyDistributionService gPoseLobbyDistributionService, IBroadcastConfiguration broadcastConfiguration, PairService pairService, - ChatChannelService chatChannelService) + ChatChannelService chatChannelService, IPruneService pruneService) { _lightlessMetrics = lightlessMetrics; _systemInfoService = systemInfoService; @@ -77,6 +76,7 @@ public partial class LightlessHub : Hub, ILightlessHub _broadcastConfiguration = broadcastConfiguration; _pairService = pairService; _chatChannelService = chatChannelService; + _pruneService = pruneService; } protected override void Dispose(bool disposing) diff --git a/LightlessSyncServer/LightlessSyncServer/Services/Interfaces/IPruneService.cs b/LightlessSyncServer/LightlessSyncServer/Services/Interfaces/IPruneService.cs new file mode 100644 index 0000000..8a05f6f --- /dev/null +++ b/LightlessSyncServer/LightlessSyncServer/Services/Interfaces/IPruneService.cs @@ -0,0 +1,10 @@ +using LightlessSyncShared.Models; + +namespace LightlessSyncServer.Services.Interfaces +{ + public interface IPruneService + { + Task CountPrunableUsersAsync(string groupGid, int days, CancellationToken ct); + Task> ExecutePruneAsync(string groupGid, int days, CancellationToken ct); + } +} diff --git a/LightlessSyncServer/LightlessSyncServer/Services/PruneService.cs b/LightlessSyncServer/LightlessSyncServer/Services/PruneService.cs new file mode 100644 index 0000000..b50029d --- /dev/null +++ b/LightlessSyncServer/LightlessSyncServer/Services/PruneService.cs @@ -0,0 +1,60 @@ +using LightlessSyncServer.Services.Interfaces; +using LightlessSyncShared.Data; +using LightlessSyncShared.Models; +using Microsoft.EntityFrameworkCore; + +namespace LightlessSyncServer.Services +{ + public class PruneService(LightlessDbContext dbContext) : IPruneService + { + private readonly LightlessDbContext _dbContext = dbContext; + + public async Task CountPrunableUsersAsync(string groupGid, int days, CancellationToken ct) + { + var allGroupUsers = await _dbContext.GroupPairs + .Include(p => p.GroupUser) + .Include(p => p.Group) + .Where(g => g.GroupGID == groupGid) + .ToListAsync(ct).ConfigureAwait(false); + + var inactivitySpan = GetInactivitySpan(days); + var now = DateTime.UtcNow; + + var usersToPrune = allGroupUsers.Where(p => + !p.IsPinned && + !p.IsModerator && + !string.Equals(p.Group.OwnerUID, p.GroupUserUID, StringComparison.Ordinal) && + p.GroupUser.LastLoggedIn < now - inactivitySpan); + + return usersToPrune.Count(); + } + + public async Task> ExecutePruneAsync(string groupGid, int days, CancellationToken ct) + { + var allGroupUsers = await _dbContext.GroupPairs + .Include(p => p.GroupUser) + .Include(p => p.Group) + .Where(g => g.GroupGID == groupGid) + .ToListAsync(ct).ConfigureAwait(false); + + var inactivitySpan = GetInactivitySpan(days); + var now = DateTime.UtcNow; + + var usersToPrune = allGroupUsers.Where(p => + !p.IsPinned && + !p.IsModerator && + !string.Equals(p.Group.OwnerUID, p.GroupUserUID, StringComparison.Ordinal) && + p.GroupUser.LastLoggedIn < now - inactivitySpan) + .ToList(); + + _dbContext.GroupPairs.RemoveRange(usersToPrune); + await _dbContext.SaveChangesAsync(ct).ConfigureAwait(false); + + return usersToPrune; + } + + private static TimeSpan GetInactivitySpan(int days) => days == 0 + ? TimeSpan.FromMinutes(15) + : TimeSpan.FromDays(days); + } +} diff --git a/LightlessSyncServer/LightlessSyncServer/Services/SystemInfoService.cs b/LightlessSyncServer/LightlessSyncServer/Services/SystemInfoService.cs index 8778613..3d84309 100644 --- a/LightlessSyncServer/LightlessSyncServer/Services/SystemInfoService.cs +++ b/LightlessSyncServer/LightlessSyncServer/Services/SystemInfoService.cs @@ -72,9 +72,11 @@ public sealed class SystemInfoService : BackgroundService await _hubContext.Clients.All.Client_UpdateSystemInfo(SystemInfoDto).ConfigureAwait(false); using var db = await _dbContextFactory.CreateDbContextAsync(ct).ConfigureAwait(false); + var groupsWithAutoPrune = db.Groups.AsNoTracking().Count(g => g.AutoPruneEnabled && g.AutoPruneDays > 0); _lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, onlineUsers); _lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeLightFinderConnections, countLightFinderUsers); + _lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeGroupAutoPrunesEnabled, groupsWithAutoPrune); _lightlessMetrics.SetGaugeTo(MetricsAPI.GaugePairs, db.ClientPairs.AsNoTracking().Count()); _lightlessMetrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, db.Permissions.AsNoTracking().Count(p => p.IsPaused)); _lightlessMetrics.SetGaugeTo(MetricsAPI.GaugeGroups, db.Groups.AsNoTracking().Count()); diff --git a/LightlessSyncServer/LightlessSyncServer/Startup.cs b/LightlessSyncServer/LightlessSyncServer/Startup.cs index b724a54..d56db65 100644 --- a/LightlessSyncServer/LightlessSyncServer/Startup.cs +++ b/LightlessSyncServer/LightlessSyncServer/Startup.cs @@ -1,9 +1,11 @@ using AspNetCoreRateLimit; using LightlessSync.API.SignalR; -using LightlessSyncServer.Controllers; using LightlessSyncServer.Configuration; +using LightlessSyncServer.Controllers; using LightlessSyncServer.Hubs; using LightlessSyncServer.Services; +using LightlessSyncServer.Services.Interfaces; +using LightlessSyncServer.Worker; using LightlessSyncShared.Data; using LightlessSyncShared.Metrics; using LightlessSyncShared.RequirementHandlers; @@ -18,6 +20,7 @@ using Microsoft.AspNetCore.Http.Connections; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Prometheus; using StackExchange.Redis; @@ -109,9 +112,12 @@ public class Startup services.AddHostedService(provider => provider.GetService()); services.AddHostedService(); services.AddScoped(); + services.AddScoped(); } services.AddSingleton(); + + services.AddHostedService(); services.AddHostedService(provider => provider.GetService()); } @@ -292,11 +298,12 @@ public class Startup MetricsAPI.CounterUserPairCacheMiss, MetricsAPI.CounterUserPairCacheNewEntries, MetricsAPI.CounterUserPairCacheUpdatedEntries, - }, new List - { + }, + [ MetricsAPI.GaugeAuthorizedConnections, MetricsAPI.GaugeLightFinderConnections, MetricsAPI.GaugeLightFinderGroups, + MetricsAPI.GaugeGroupAutoPrunesEnabled, MetricsAPI.GaugeConnections, MetricsAPI.GaugePairs, MetricsAPI.GaugePairsPaused, @@ -312,7 +319,7 @@ public class Startup MetricsAPI.GaugeGposeLobbyUsers, MetricsAPI.GaugeHubConcurrency, MetricsAPI.GaugeHubQueuedConcurrency, - })); + ])); } private static void ConfigureServicesBasedOnShardType(IServiceCollection services, IConfigurationSection lightlessConfig, bool isMainServer) diff --git a/LightlessSyncServer/LightlessSyncServer/Worker/AutoPruneWorker.cs b/LightlessSyncServer/LightlessSyncServer/Worker/AutoPruneWorker.cs new file mode 100644 index 0000000..290c014 --- /dev/null +++ b/LightlessSyncServer/LightlessSyncServer/Worker/AutoPruneWorker.cs @@ -0,0 +1,68 @@ +using LightlessSync.API.Dto.Group; +using LightlessSyncServer.Hubs; +using LightlessSyncServer.Services.Interfaces; +using LightlessSyncServer.Utils; +using LightlessSyncShared.Data; +using Microsoft.AspNetCore.SignalR; +using Microsoft.EntityFrameworkCore; + +namespace LightlessSyncServer.Worker +{ + public class AutoPruneWorker(IServiceProvider services, ILogger logger) : BackgroundService + { + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + var scope = services.CreateAsyncScope(); + await using (scope.ConfigureAwait(false)) + { + var db = scope.ServiceProvider.GetRequiredService(); + var pruneService = scope.ServiceProvider.GetRequiredService(); + var hubContext = scope.ServiceProvider.GetRequiredService>(); + + var groups = await db.Groups + .Where(g => g.AutoPruneEnabled && g.AutoPruneDays > 0) + .ToListAsync(stoppingToken).ConfigureAwait(false); + + foreach (var group in groups) + { + var allGroupUsers = await db.GroupPairs + .Include(p => p.GroupUser) + .Include(p => p.Group) + .Where(p => p.GroupGID == group.GID) + .ToListAsync(stoppingToken).ConfigureAwait(false); + + var prunedPairs = await pruneService.ExecutePruneAsync(group.GID, group.AutoPruneDays, stoppingToken).ConfigureAwait(false); + + if (prunedPairs.Count == 0) + continue; + + var remainingUserIds = allGroupUsers + .Where(p => !prunedPairs.Any(x => string.Equals(x.GroupUserUID, p.GroupUserUID, StringComparison.Ordinal))) + .Select(p => p.GroupUserUID) + .Distinct(StringComparer.Ordinal) + .ToList(); + + foreach (var pair in prunedPairs) + { + await hubContext.Clients.Users(remainingUserIds).SendAsync("Client_GroupPairLeft", new GroupPairDto(group.ToGroupData(), pair.GroupUser.ToUserData()), stoppingToken).ConfigureAwait(false); + } + + logger.LogInformation("Auto-pruned {Count} users from group {GroupId}", prunedPairs.Count, group.GID); + } + } + } + catch (Exception ex) + { + logger.LogError(ex, "Error in auto-prune worker"); + } + + //Run task each hour to check for pruning + await Task.Delay(TimeSpan.FromHours(1), stoppingToken).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareModule.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessModule.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareModule.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessModule.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.AprilFools2024.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.AprilFools2024.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.AprilFools2024.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.AprilFools2024.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Delete.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Delete.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Delete.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Delete.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Recover.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Recover.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Recover.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Recover.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Register.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Register.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Register.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Register.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Relink.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Relink.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Relink.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Relink.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Secondary.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Secondary.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Secondary.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Secondary.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.UserInfo.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.UserInfo.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.UserInfo.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.UserInfo.cs diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Vanity.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Vanity.cs similarity index 99% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Vanity.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Vanity.cs index fb075e3..eae9a2b 100644 --- a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.Vanity.cs +++ b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.Vanity.cs @@ -94,7 +94,7 @@ public partial class LightlessWizardModule bool canAddVanityId = !db.Users.Any(u => u.UID == modal.DesiredVanityUID || u.Alias == modal.DesiredVanityUID); var forbiddenWords = new[] { "null", "nil" }; - Regex rgx = new(@"^[_\-a-zA-Z0-9]{3,15}$", RegexOptions.ECMAScript); + Regex rgx = new(@"^[_\-a-zA-Z0-9\?]{3,15}$", RegexOptions.ECMAScript); if (!rgx.Match(desiredVanityUid).Success) { eb.WithColor(Color.Red); diff --git a/LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.cs b/LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncServices/Discord/MareWizardModule.cs rename to LightlessSyncServer/LightlessSyncServices/Discord/LightlessWizardModule.cs diff --git a/LightlessSyncServer/LightlessSyncShared/Data/MareDbContext.cs b/LightlessSyncServer/LightlessSyncShared/Data/LightlessDbContext.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncShared/Data/MareDbContext.cs rename to LightlessSyncServer/LightlessSyncShared/Data/LightlessDbContext.cs diff --git a/LightlessSyncServer/LightlessSyncShared/Metrics/MareMetrics.cs b/LightlessSyncServer/LightlessSyncShared/Metrics/LightlessMetrics.cs similarity index 100% rename from LightlessSyncServer/LightlessSyncShared/Metrics/MareMetrics.cs rename to LightlessSyncServer/LightlessSyncShared/Metrics/LightlessMetrics.cs diff --git a/LightlessSyncServer/LightlessSyncShared/Metrics/MetricsAPI.cs b/LightlessSyncServer/LightlessSyncShared/Metrics/MetricsAPI.cs index fe97f72..e990188 100644 --- a/LightlessSyncServer/LightlessSyncShared/Metrics/MetricsAPI.cs +++ b/LightlessSyncServer/LightlessSyncShared/Metrics/MetricsAPI.cs @@ -11,6 +11,7 @@ public class MetricsAPI public const string CounterUsersRegisteredDeleted = "lightless_users_registered_deleted"; public const string GaugeLightFinderConnections = "lightless_lightfinder_connections"; public const string GaugeLightFinderGroups = "lightless_lightfinder_groups"; + public const string GaugeGroupAutoPrunesEnabled = "lightless_group_autoprunes_enabled"; public const string GaugePairs = "lightless_pairs"; public const string GaugePairsPaused = "lightless_pairs_paused"; public const string GaugeFilesTotal = "lightless_files"; diff --git a/LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.Designer.cs b/LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.Designer.cs new file mode 100644 index 0000000..29ce469 --- /dev/null +++ b/LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.Designer.cs @@ -0,0 +1,1327 @@ +// +using System; +using LightlessSyncShared.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace LightlessSyncServer.Migrations +{ + [DbContext(typeof(LightlessDbContext))] + [Migration("20251205205537_AddAutoPruneInGroup")] + partial class AddAutoPruneInGroup + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("LightlessSyncShared.Models.Auth", b => + { + b.Property("HashedKey") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("hashed_key"); + + b.Property("IsBanned") + .HasColumnType("boolean") + .HasColumnName("is_banned"); + + b.Property("MarkForBan") + .HasColumnType("boolean") + .HasColumnName("mark_for_ban"); + + b.Property("PrimaryUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("primary_user_uid"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("HashedKey") + .HasName("pk_auth"); + + b.HasIndex("PrimaryUserUID") + .HasDatabaseName("ix_auth_primary_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_auth_user_uid"); + + b.ToTable("auth", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.Banned", b => + { + b.Property("CharacterIdentification") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("character_identification"); + + b.Property("BannedUid") + .HasColumnType("text") + .HasColumnName("banned_uid"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("CharacterIdentification") + .HasName("pk_banned_users"); + + b.ToTable("banned_users", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.BannedRegistrations", b => + { + b.Property("DiscordIdOrLodestoneAuth") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("discord_id_or_lodestone_auth"); + + b.HasKey("DiscordIdOrLodestoneAuth") + .HasName("pk_banned_registrations"); + + b.ToTable("banned_registrations", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaData", b => + { + b.Property("Id") + .HasColumnType("text") + .HasColumnName("id"); + + b.Property("UploaderUID") + .HasColumnType("character varying(10)") + .HasColumnName("uploader_uid"); + + b.Property("AccessType") + .HasColumnType("integer") + .HasColumnName("access_type"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_date"); + + b.Property("CustomizeData") + .HasColumnType("text") + .HasColumnName("customize_data"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("DownloadCount") + .HasColumnType("integer") + .HasColumnName("download_count"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("GlamourerData") + .HasColumnType("text") + .HasColumnName("glamourer_data"); + + b.Property("ManipulationData") + .HasColumnType("text") + .HasColumnName("manipulation_data"); + + b.Property("ShareType") + .HasColumnType("integer") + .HasColumnName("share_type"); + + b.Property("UpdatedDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated_date"); + + b.HasKey("Id", "UploaderUID") + .HasName("pk_chara_data"); + + b.HasIndex("Id") + .HasDatabaseName("ix_chara_data_id"); + + b.HasIndex("UploaderUID") + .HasDatabaseName("ix_chara_data_uploader_uid"); + + b.ToTable("chara_data", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataAllowance", b => + { + b.Property("ParentId") + .HasColumnType("text") + .HasColumnName("parent_id"); + + b.Property("ParentUploaderUID") + .HasColumnType("character varying(10)") + .HasColumnName("parent_uploader_uid"); + + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AllowedGroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("allowed_group_gid"); + + b.Property("AllowedUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("allowed_user_uid"); + + b.HasKey("ParentId", "ParentUploaderUID", "Id") + .HasName("pk_chara_data_allowance"); + + b.HasIndex("AllowedGroupGID") + .HasDatabaseName("ix_chara_data_allowance_allowed_group_gid"); + + b.HasIndex("AllowedUserUID") + .HasDatabaseName("ix_chara_data_allowance_allowed_user_uid"); + + b.HasIndex("ParentId") + .HasDatabaseName("ix_chara_data_allowance_parent_id"); + + b.ToTable("chara_data_allowance", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataFile", b => + { + b.Property("ParentId") + .HasColumnType("text") + .HasColumnName("parent_id"); + + b.Property("ParentUploaderUID") + .HasColumnType("character varying(10)") + .HasColumnName("parent_uploader_uid"); + + b.Property("GamePath") + .HasColumnType("text") + .HasColumnName("game_path"); + + b.Property("FileCacheHash") + .HasColumnType("character varying(40)") + .HasColumnName("file_cache_hash"); + + b.HasKey("ParentId", "ParentUploaderUID", "GamePath") + .HasName("pk_chara_data_files"); + + b.HasIndex("FileCacheHash") + .HasDatabaseName("ix_chara_data_files_file_cache_hash"); + + b.HasIndex("ParentId") + .HasDatabaseName("ix_chara_data_files_parent_id"); + + b.ToTable("chara_data_files", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataFileSwap", b => + { + b.Property("ParentId") + .HasColumnType("text") + .HasColumnName("parent_id"); + + b.Property("ParentUploaderUID") + .HasColumnType("character varying(10)") + .HasColumnName("parent_uploader_uid"); + + b.Property("GamePath") + .HasColumnType("text") + .HasColumnName("game_path"); + + b.Property("FilePath") + .HasColumnType("text") + .HasColumnName("file_path"); + + b.HasKey("ParentId", "ParentUploaderUID", "GamePath") + .HasName("pk_chara_data_file_swaps"); + + b.HasIndex("ParentId") + .HasDatabaseName("ix_chara_data_file_swaps_parent_id"); + + b.ToTable("chara_data_file_swaps", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataOriginalFile", b => + { + b.Property("ParentId") + .HasColumnType("text") + .HasColumnName("parent_id"); + + b.Property("ParentUploaderUID") + .HasColumnType("character varying(10)") + .HasColumnName("parent_uploader_uid"); + + b.Property("GamePath") + .HasColumnType("text") + .HasColumnName("game_path"); + + b.Property("Hash") + .HasColumnType("text") + .HasColumnName("hash"); + + b.HasKey("ParentId", "ParentUploaderUID", "GamePath") + .HasName("pk_chara_data_orig_files"); + + b.HasIndex("ParentId") + .HasDatabaseName("ix_chara_data_orig_files_parent_id"); + + b.ToTable("chara_data_orig_files", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataPose", b => + { + b.Property("ParentId") + .HasColumnType("text") + .HasColumnName("parent_id"); + + b.Property("ParentUploaderUID") + .HasColumnType("character varying(10)") + .HasColumnName("parent_uploader_uid"); + + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("PoseData") + .HasColumnType("text") + .HasColumnName("pose_data"); + + b.Property("WorldData") + .HasColumnType("text") + .HasColumnName("world_data"); + + b.HasKey("ParentId", "ParentUploaderUID", "Id") + .HasName("pk_chara_data_poses"); + + b.HasIndex("ParentId") + .HasDatabaseName("ix_chara_data_poses_parent_id"); + + b.ToTable("chara_data_poses", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.ClientPair", b => + { + b.Property("UserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_client_pairs"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_client_pairs_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_client_pairs_user_uid"); + + b.ToTable("client_pairs", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.FileCache", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("RawSize") + .HasColumnType("bigint") + .HasColumnName("raw_size"); + + b.Property("Size") + .HasColumnType("bigint") + .HasColumnName("size"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.Property("UploadDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("upload_date"); + + b.Property("Uploaded") + .HasColumnType("boolean") + .HasColumnName("uploaded"); + + b.Property("UploaderUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uploader_uid"); + + b.HasKey("Hash") + .HasName("pk_file_caches"); + + b.HasIndex("UploaderUID") + .HasDatabaseName("ix_file_caches_uploader_uid"); + + b.ToTable("file_caches", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.ForbiddenUploadEntry", b => + { + b.Property("Hash") + .HasMaxLength(40) + .HasColumnType("character varying(40)") + .HasColumnName("hash"); + + b.Property("ForbiddenBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("forbidden_by"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("Hash") + .HasName("pk_forbidden_upload_entries"); + + b.ToTable("forbidden_upload_entries", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.Group", b => + { + b.Property("GID") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("gid"); + + b.Property("Alias") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("alias"); + + b.Property("AutoPruneDays") + .HasColumnType("integer") + .HasColumnName("auto_prune_days"); + + b.Property("AutoPruneEnabled") + .HasColumnType("boolean") + .HasColumnName("auto_prune_enabled"); + + b.Property("CreatedDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("created_date") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("HashedPassword") + .HasColumnType("text") + .HasColumnName("hashed_password"); + + b.Property("InvitesEnabled") + .HasColumnType("boolean") + .HasColumnName("invites_enabled"); + + b.Property("OwnerUID") + .HasColumnType("character varying(10)") + .HasColumnName("owner_uid"); + + b.Property("PreferDisableAnimations") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_animations"); + + b.Property("PreferDisableSounds") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_sounds"); + + b.Property("PreferDisableVFX") + .HasColumnType("boolean") + .HasColumnName("prefer_disable_vfx"); + + b.HasKey("GID") + .HasName("pk_groups"); + + b.HasIndex("OwnerUID") + .HasDatabaseName("ix_groups_owner_uid"); + + b.ToTable("groups", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupBan", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("BannedUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_user_uid"); + + b.Property("BannedByUID") + .HasColumnType("character varying(10)") + .HasColumnName("banned_by_uid"); + + b.Property("BannedOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("banned_on"); + + b.Property("BannedReason") + .HasColumnType("text") + .HasColumnName("banned_reason"); + + b.HasKey("GroupGID", "BannedUserUID") + .HasName("pk_group_bans"); + + b.HasIndex("BannedByUID") + .HasDatabaseName("ix_group_bans_banned_by_uid"); + + b.HasIndex("BannedUserUID") + .HasDatabaseName("ix_group_bans_banned_user_uid"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_bans_group_gid"); + + b.ToTable("group_bans", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupPair", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("GroupUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("group_user_uid"); + + b.Property("FromFinder") + .HasColumnType("boolean") + .HasColumnName("from_finder"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("IsPinned") + .HasColumnType("boolean") + .HasColumnName("is_pinned"); + + b.Property("JoinedGroupOn") + .HasColumnType("timestamp with time zone") + .HasColumnName("joined_group_on"); + + b.HasKey("GroupGID", "GroupUserUID") + .HasName("pk_group_pairs"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pairs_group_gid"); + + b.HasIndex("GroupUserUID") + .HasDatabaseName("ix_group_pairs_group_user_uid"); + + b.ToTable("group_pairs", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupPairPreferredPermission", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.HasKey("UserUID", "GroupGID") + .HasName("pk_group_pair_preferred_permissions"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_pair_preferred_permissions_group_gid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_group_pair_preferred_permissions_user_uid"); + + b.ToTable("group_pair_preferred_permissions", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupProfile", b => + { + b.Property("GroupGID") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("Base64GroupBannerImage") + .HasColumnType("text") + .HasColumnName("base64group_banner_image"); + + b.Property("Base64GroupProfileImage") + .HasColumnType("text") + .HasColumnName("base64group_profile_image"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("IsNSFW") + .HasColumnType("boolean") + .HasColumnName("is_nsfw"); + + b.Property("ProfileDisabled") + .HasColumnType("boolean") + .HasColumnName("profile_disabled"); + + b.PrimitiveCollection("Tags") + .HasColumnType("integer[]") + .HasColumnName("tags"); + + b.HasKey("GroupGID") + .HasName("pk_group_profiles"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_profiles_group_gid"); + + b.ToTable("group_profiles", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupTempInvite", b => + { + b.Property("GroupGID") + .HasColumnType("character varying(20)") + .HasColumnName("group_gid"); + + b.Property("Invite") + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("invite"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiration_date"); + + b.HasKey("GroupGID", "Invite") + .HasName("pk_group_temp_invites"); + + b.HasIndex("GroupGID") + .HasDatabaseName("ix_group_temp_invites_group_gid"); + + b.HasIndex("Invite") + .HasDatabaseName("ix_group_temp_invites_invite"); + + b.ToTable("group_temp_invites", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.LodeStoneAuth", b => + { + b.Property("DiscordId") + .ValueGeneratedOnAdd() + .HasColumnType("numeric(20,0)") + .HasColumnName("discord_id"); + + b.Property("HashedLodestoneId") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("hashed_lodestone_id"); + + b.Property("LodestoneAuthString") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("lodestone_auth_string"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("started_at"); + + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.HasKey("DiscordId") + .HasName("pk_lodestone_auth"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_lodestone_auth_user_uid"); + + b.ToTable("lodestone_auth", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.ReportedChatMessage", b => + { + b.Property("ReportId") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("report_id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("ReportId")); + + b.Property("AdditionalContext") + .HasColumnType("text") + .HasColumnName("additional_context"); + + b.Property("ChannelKey") + .IsRequired() + .HasColumnType("text") + .HasColumnName("channel_key"); + + b.Property("ChannelType") + .HasColumnType("smallint") + .HasColumnName("channel_type"); + + b.Property("DiscordMessageId") + .HasColumnType("numeric(20,0)") + .HasColumnName("discord_message_id"); + + b.Property("DiscordMessagePostedAtUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("discord_message_posted_at_utc"); + + b.Property("MessageContent") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message_content"); + + b.Property("MessageId") + .IsRequired() + .HasColumnType("text") + .HasColumnName("message_id"); + + b.Property("MessageSentAtUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("message_sent_at_utc"); + + b.Property("Reason") + .HasColumnType("text") + .HasColumnName("reason"); + + b.Property("ReportTimeUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("report_time_utc"); + + b.Property("ReportedUserUid") + .HasColumnType("text") + .HasColumnName("reported_user_uid"); + + b.Property("ReporterUserUid") + .IsRequired() + .HasColumnType("text") + .HasColumnName("reporter_user_uid"); + + b.Property("ResolutionNotes") + .HasColumnType("text") + .HasColumnName("resolution_notes"); + + b.Property("Resolved") + .HasColumnType("boolean") + .HasColumnName("resolved"); + + b.Property("ResolvedAtUtc") + .HasColumnType("timestamp with time zone") + .HasColumnName("resolved_at_utc"); + + b.Property("ResolvedByUserUid") + .HasColumnType("text") + .HasColumnName("resolved_by_user_uid"); + + b.Property("SenderDisplayName") + .HasColumnType("text") + .HasColumnName("sender_display_name"); + + b.Property("SenderHashedCid") + .HasColumnType("text") + .HasColumnName("sender_hashed_cid"); + + b.Property("SenderToken") + .IsRequired() + .HasColumnType("text") + .HasColumnName("sender_token"); + + b.Property("SenderWasLightfinder") + .HasColumnType("boolean") + .HasColumnName("sender_was_lightfinder"); + + b.Property("SnapshotJson") + .HasColumnType("text") + .HasColumnName("snapshot_json"); + + b.Property("WorldId") + .HasColumnType("integer") + .HasColumnName("world_id"); + + b.Property("ZoneId") + .HasColumnType("integer") + .HasColumnName("zone_id"); + + b.HasKey("ReportId") + .HasName("pk_reported_chat_messages"); + + b.HasIndex("DiscordMessageId") + .HasDatabaseName("ix_reported_chat_messages_discord_message_id"); + + b.HasIndex("MessageId") + .IsUnique() + .HasDatabaseName("ix_reported_chat_messages_message_id"); + + b.HasIndex("ReportedUserUid") + .HasDatabaseName("ix_reported_chat_messages_reported_user_uid"); + + b.HasIndex("ReporterUserUid") + .HasDatabaseName("ix_reported_chat_messages_reporter_user_uid"); + + b.ToTable("reported_chat_messages", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.User", b => + { + b.Property("UID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("uid"); + + b.Property("Alias") + .HasMaxLength(15) + .HasColumnType("character varying(15)") + .HasColumnName("alias"); + + b.Property("ChatBanned") + .HasColumnType("boolean") + .HasColumnName("chat_banned"); + + b.Property("HasVanity") + .HasColumnType("boolean") + .HasColumnName("has_vanity"); + + b.Property("IsAdmin") + .HasColumnType("boolean") + .HasColumnName("is_admin"); + + b.Property("IsModerator") + .HasColumnType("boolean") + .HasColumnName("is_moderator"); + + b.Property("LastLoggedIn") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_logged_in"); + + b.Property("TextColorHex") + .HasMaxLength(9) + .HasColumnType("character varying(9)") + .HasColumnName("text_color_hex"); + + b.Property("TextGlowColorHex") + .HasMaxLength(9) + .HasColumnType("character varying(9)") + .HasColumnName("text_glow_color_hex"); + + b.Property("Timestamp") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("bytea") + .HasColumnName("timestamp"); + + b.HasKey("UID") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.UserDefaultPreferredPermission", b => + { + b.Property("UserUID") + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("DisableGroupAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_group_animations"); + + b.Property("DisableGroupSounds") + .HasColumnType("boolean") + .HasColumnName("disable_group_sounds"); + + b.Property("DisableGroupVFX") + .HasColumnType("boolean") + .HasColumnName("disable_group_vfx"); + + b.Property("DisableIndividualAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_individual_animations"); + + b.Property("DisableIndividualSounds") + .HasColumnType("boolean") + .HasColumnName("disable_individual_sounds"); + + b.Property("DisableIndividualVFX") + .HasColumnType("boolean") + .HasColumnName("disable_individual_vfx"); + + b.Property("IndividualIsSticky") + .HasColumnType("boolean") + .HasColumnName("individual_is_sticky"); + + b.HasKey("UserUID") + .HasName("pk_user_default_preferred_permissions"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_user_default_preferred_permissions_user_uid"); + + b.ToTable("user_default_preferred_permissions", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.UserPermissionSet", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("OtherUserUID") + .HasColumnType("character varying(10)") + .HasColumnName("other_user_uid"); + + b.Property("DisableAnimations") + .HasColumnType("boolean") + .HasColumnName("disable_animations"); + + b.Property("DisableSounds") + .HasColumnType("boolean") + .HasColumnName("disable_sounds"); + + b.Property("DisableVFX") + .HasColumnType("boolean") + .HasColumnName("disable_vfx"); + + b.Property("IsPaused") + .HasColumnType("boolean") + .HasColumnName("is_paused"); + + b.Property("Sticky") + .HasColumnType("boolean") + .HasColumnName("sticky"); + + b.HasKey("UserUID", "OtherUserUID") + .HasName("pk_user_permission_sets"); + + b.HasIndex("OtherUserUID") + .HasDatabaseName("ix_user_permission_sets_other_user_uid"); + + b.HasIndex("UserUID") + .HasDatabaseName("ix_user_permission_sets_user_uid"); + + b.HasIndex("UserUID", "OtherUserUID", "IsPaused") + .HasDatabaseName("ix_user_permission_sets_user_uid_other_user_uid_is_paused"); + + b.ToTable("user_permission_sets", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.UserProfileData", b => + { + b.Property("UserUID") + .HasColumnType("character varying(10)") + .HasColumnName("user_uid"); + + b.Property("Base64BannerImage") + .HasColumnType("text") + .HasColumnName("base64banner_image"); + + b.Property("Base64ProfileImage") + .HasColumnType("text") + .HasColumnName("base64profile_image"); + + b.Property("FlaggedForReport") + .HasColumnType("boolean") + .HasColumnName("flagged_for_report"); + + b.Property("IsNSFW") + .HasColumnType("boolean") + .HasColumnName("is_nsfw"); + + b.Property("ProfileDisabled") + .HasColumnType("boolean") + .HasColumnName("profile_disabled"); + + b.PrimitiveCollection("Tags") + .HasColumnType("integer[]") + .HasColumnName("tags"); + + b.Property("UserDescription") + .HasColumnType("text") + .HasColumnName("user_description"); + + b.HasKey("UserUID") + .HasName("pk_user_profile_data"); + + b.ToTable("user_profile_data", (string)null); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.Auth", b => + { + b.HasOne("LightlessSyncShared.Models.User", "PrimaryUser") + .WithMany() + .HasForeignKey("PrimaryUserUID") + .HasConstraintName("fk_auth_users_primary_user_uid"); + + b.HasOne("LightlessSyncShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_auth_users_user_uid"); + + b.Navigation("PrimaryUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaData", b => + { + b.HasOne("LightlessSyncShared.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chara_data_users_uploader_uid"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataAllowance", b => + { + b.HasOne("LightlessSyncShared.Models.Group", "AllowedGroup") + .WithMany() + .HasForeignKey("AllowedGroupGID") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_chara_data_allowance_groups_allowed_group_gid"); + + b.HasOne("LightlessSyncShared.Models.User", "AllowedUser") + .WithMany() + .HasForeignKey("AllowedUserUID") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_chara_data_allowance_users_allowed_user_uid"); + + b.HasOne("LightlessSyncShared.Models.CharaData", "Parent") + .WithMany("AllowedIndividiuals") + .HasForeignKey("ParentId", "ParentUploaderUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chara_data_allowance_chara_data_parent_id_parent_uploader_u"); + + b.Navigation("AllowedGroup"); + + b.Navigation("AllowedUser"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataFile", b => + { + b.HasOne("LightlessSyncShared.Models.FileCache", "FileCache") + .WithMany() + .HasForeignKey("FileCacheHash") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_chara_data_files_files_file_cache_hash"); + + b.HasOne("LightlessSyncShared.Models.CharaData", "Parent") + .WithMany("Files") + .HasForeignKey("ParentId", "ParentUploaderUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chara_data_files_chara_data_parent_id_parent_uploader_uid"); + + b.Navigation("FileCache"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataFileSwap", b => + { + b.HasOne("LightlessSyncShared.Models.CharaData", "Parent") + .WithMany("FileSwaps") + .HasForeignKey("ParentId", "ParentUploaderUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chara_data_file_swaps_chara_data_parent_id_parent_uploader_"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataOriginalFile", b => + { + b.HasOne("LightlessSyncShared.Models.CharaData", "Parent") + .WithMany("OriginalFiles") + .HasForeignKey("ParentId", "ParentUploaderUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chara_data_orig_files_chara_data_parent_id_parent_uploader_"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaDataPose", b => + { + b.HasOne("LightlessSyncShared.Models.CharaData", "Parent") + .WithMany("Poses") + .HasForeignKey("ParentId", "ParentUploaderUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_chara_data_poses_chara_data_parent_id_parent_uploader_uid"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.ClientPair", b => + { + b.HasOne("LightlessSyncShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_other_user_uid"); + + b.HasOne("LightlessSyncShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_client_pairs_users_user_uid"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.FileCache", b => + { + b.HasOne("LightlessSyncShared.Models.User", "Uploader") + .WithMany() + .HasForeignKey("UploaderUID") + .HasConstraintName("fk_file_caches_users_uploader_uid"); + + b.Navigation("Uploader"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.Group", b => + { + b.HasOne("LightlessSyncShared.Models.User", "Owner") + .WithMany() + .HasForeignKey("OwnerUID") + .HasConstraintName("fk_groups_users_owner_uid"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupBan", b => + { + b.HasOne("LightlessSyncShared.Models.User", "BannedBy") + .WithMany() + .HasForeignKey("BannedByUID") + .HasConstraintName("fk_group_bans_users_banned_by_uid"); + + b.HasOne("LightlessSyncShared.Models.User", "BannedUser") + .WithMany() + .HasForeignKey("BannedUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_users_banned_user_uid"); + + b.HasOne("LightlessSyncShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_bans_groups_group_gid"); + + b.Navigation("BannedBy"); + + b.Navigation("BannedUser"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupPair", b => + { + b.HasOne("LightlessSyncShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_groups_group_gid"); + + b.HasOne("LightlessSyncShared.Models.User", "GroupUser") + .WithMany() + .HasForeignKey("GroupUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pairs_users_group_user_uid"); + + b.Navigation("Group"); + + b.Navigation("GroupUser"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupPairPreferredPermission", b => + { + b.HasOne("LightlessSyncShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_groups_group_gid"); + + b.HasOne("LightlessSyncShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_pair_preferred_permissions_users_user_uid"); + + b.Navigation("Group"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupProfile", b => + { + b.HasOne("LightlessSyncShared.Models.Group", "Group") + .WithOne("Profile") + .HasForeignKey("LightlessSyncShared.Models.GroupProfile", "GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .HasConstraintName("fk_group_profiles_groups_group_gid"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.GroupTempInvite", b => + { + b.HasOne("LightlessSyncShared.Models.Group", "Group") + .WithMany() + .HasForeignKey("GroupGID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_group_temp_invites_groups_group_gid"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.LodeStoneAuth", b => + { + b.HasOne("LightlessSyncShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .HasConstraintName("fk_lodestone_auth_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.UserDefaultPreferredPermission", b => + { + b.HasOne("LightlessSyncShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_default_preferred_permissions_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.UserPermissionSet", b => + { + b.HasOne("LightlessSyncShared.Models.User", "OtherUser") + .WithMany() + .HasForeignKey("OtherUserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_other_user_uid"); + + b.HasOne("LightlessSyncShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_permission_sets_users_user_uid"); + + b.Navigation("OtherUser"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.UserProfileData", b => + { + b.HasOne("LightlessSyncShared.Models.User", "User") + .WithMany() + .HasForeignKey("UserUID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_user_profile_data_users_user_uid"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.CharaData", b => + { + b.Navigation("AllowedIndividiuals"); + + b.Navigation("FileSwaps"); + + b.Navigation("Files"); + + b.Navigation("OriginalFiles"); + + b.Navigation("Poses"); + }); + + modelBuilder.Entity("LightlessSyncShared.Models.Group", b => + { + b.Navigation("Profile"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.cs b/LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.cs new file mode 100644 index 0000000..f7d64a8 --- /dev/null +++ b/LightlessSyncServer/LightlessSyncShared/Migrations/20251205205537_AddAutoPruneInGroup.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace LightlessSyncServer.Migrations +{ + /// + public partial class AddAutoPruneInGroup : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "auto_prune_days", + table: "groups", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "auto_prune_enabled", + table: "groups", + type: "boolean", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "auto_prune_days", + table: "groups"); + + migrationBuilder.DropColumn( + name: "auto_prune_enabled", + table: "groups"); + } + } +} diff --git a/LightlessSyncServer/LightlessSyncShared/Migrations/LightlessDbContextModelSnapshot.cs b/LightlessSyncServer/LightlessSyncShared/Migrations/LightlessDbContextModelSnapshot.cs index def0653..abbe906 100644 --- a/LightlessSyncServer/LightlessSyncShared/Migrations/LightlessDbContextModelSnapshot.cs +++ b/LightlessSyncServer/LightlessSyncShared/Migrations/LightlessDbContextModelSnapshot.cs @@ -430,6 +430,14 @@ namespace LightlessSyncServer.Migrations .HasColumnType("character varying(50)") .HasColumnName("alias"); + b.Property("AutoPruneDays") + .HasColumnType("integer") + .HasColumnName("auto_prune_days"); + + b.Property("AutoPruneEnabled") + .HasColumnType("boolean") + .HasColumnName("auto_prune_enabled"); + b.Property("CreatedDate") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") diff --git a/LightlessSyncServer/LightlessSyncShared/Models/Group.cs b/LightlessSyncServer/LightlessSyncShared/Models/Group.cs index 522c7d8..b712406 100644 --- a/LightlessSyncServer/LightlessSyncShared/Models/Group.cs +++ b/LightlessSyncServer/LightlessSyncShared/Models/Group.cs @@ -12,6 +12,8 @@ public class Group [MaxLength(50)] public string Alias { get; set; } public GroupProfile? Profile { get; set; } + public bool AutoPruneEnabled { get; set; } = false; + public int AutoPruneDays { get; set; } = 0; public bool InvitesEnabled { get; set; } public string HashedPassword { get; set; } public bool PreferDisableSounds { get; set; }