add mare profiles
This commit is contained in:
		| @@ -2,6 +2,8 @@ services: | |||||||
|   postgres: |   postgres: | ||||||
|     image: postgres:latest |     image: postgres:latest | ||||||
|     restart: always |     restart: always | ||||||
|  |     ports: | ||||||
|  |       - 5432:5432/tcp | ||||||
|     environment: |     environment: | ||||||
|       POSTGRES_DB: mare |       POSTGRES_DB: mare | ||||||
|       POSTGRES_USER: mare |       POSTGRES_USER: mare | ||||||
| @@ -48,6 +50,7 @@ services: | |||||||
|     environment: |     environment: | ||||||
|       MareSynchronos__DiscordBotToken: "${DEV_MARE_DISCORDTOKEN}" |       MareSynchronos__DiscordBotToken: "${DEV_MARE_DISCORDTOKEN}" | ||||||
|       MareSynchronos__DiscordChannelForMessages: "${DEV_MARE_DISCORDCHANNEL}" |       MareSynchronos__DiscordChannelForMessages: "${DEV_MARE_DISCORDCHANNEL}" | ||||||
|  |       MareSynchronos__DiscordChannelForReports: "${DEV_MARE_DISCORDCHANNEL}" | ||||||
|       DOTNET_USE_POLLING_FILE_WATCHER: 1 |       DOTNET_USE_POLLING_FILE_WATCHER: 1 | ||||||
|     volumes: |     volumes: | ||||||
|       - ../config/standalone/services-standalone.json:/opt/MareSynchronosServices/appsettings.json |       - ../config/standalone/services-standalone.json:/opt/MareSynchronosServices/appsettings.json | ||||||
|   | |||||||
| @@ -43,6 +43,8 @@ namespace MareSynchronosServer.Hubs | |||||||
|  |  | ||||||
|         public Task Client_UserUpdateOtherPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); |         public Task Client_UserUpdateOtherPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||||
|  |  | ||||||
|  |         public Task Client_UserUpdateProfile(UserDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||||
|  |  | ||||||
|         public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); |         public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -12,26 +12,53 @@ namespace MareSynchronosServer.Hubs; | |||||||
|  |  | ||||||
| public partial class MareHub | public partial class MareHub | ||||||
| { | { | ||||||
|     private async Task UpdateUserOnRedis() |     public string UserCharaIdent => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.CharaIdent, StringComparison.Ordinal))?.Value ?? throw new Exception("No Chara Ident in Claims"); | ||||||
|     { |  | ||||||
|         await _redis.AddAsync("UID:" + UserUID, UserCharaIdent, TimeSpan.FromSeconds(60), StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private async Task RemoveUserFromRedis() |     public string UserUID => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value ?? throw new Exception("No UID in Claims"); | ||||||
|     { |  | ||||||
|         await _redis.RemoveAsync("UID:" + UserUID, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private async Task<string> GetUserIdent(string uid) |     private async Task DeleteUser(User user) | ||||||
|     { |     { | ||||||
|         if (uid.IsNullOrEmpty()) return string.Empty; |         var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToListAsync().ConfigureAwait(false); | ||||||
|         return await _redis.GetAsync<string>("UID:" + uid).ConfigureAwait(false); |         var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == user.UID).ConfigureAwait(false); | ||||||
|     } |         var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == user.UID).ConfigureAwait(false); | ||||||
|  |         var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == user.UID).ToListAsync().ConfigureAwait(false); | ||||||
|  |         var userProfileData = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.UID).ConfigureAwait(false); | ||||||
|  |  | ||||||
|     private async Task<Dictionary<string, string>> GetOnlineUsers(List<string> uids) |         if (lodestone != null) | ||||||
|     { |         { | ||||||
|         var result = await _redis.GetAllAsync<string>(uids.Select(u => "UID:" + u).ToHashSet(StringComparer.Ordinal)).ConfigureAwait(false); |             _dbContext.Remove(lodestone); | ||||||
|         return uids.Where(u => result.TryGetValue("UID:" + u, out var ident) && !string.IsNullOrEmpty(ident)).ToDictionary(u => u, u => result["UID:" + u], StringComparer.Ordinal); |         } | ||||||
|  |  | ||||||
|  |         if (userProfileData != null) | ||||||
|  |         { | ||||||
|  |             _dbContext.Remove(userProfileData); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         while (_dbContext.Files.Any(f => f.Uploader == user)) | ||||||
|  |         { | ||||||
|  |             await Task.Delay(1000).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         _dbContext.ClientPairs.RemoveRange(ownPairData); | ||||||
|  |         await _dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |         var otherPairData = await _dbContext.ClientPairs.Include(u => u.User) | ||||||
|  |             .Where(u => u.OtherUser.UID == user.UID).AsNoTracking().ToListAsync().ConfigureAwait(false); | ||||||
|  |         foreach (var pair in otherPairData) | ||||||
|  |         { | ||||||
|  |             await Clients.User(pair.UserUID).Client_UserRemoveClientPair(new(user.ToUserData())).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         foreach (var pair in groupPairs) | ||||||
|  |         { | ||||||
|  |             await UserLeaveGroup(new GroupDto(new GroupData(pair.GroupGID)), user.UID).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         _mareMetrics.IncCounter(MetricsAPI.CounterUsersRegisteredDeleted, 1); | ||||||
|  |  | ||||||
|  |         _dbContext.ClientPairs.RemoveRange(otherPairData); | ||||||
|  |         _dbContext.Users.Remove(user); | ||||||
|  |         _dbContext.Auth.Remove(auth); | ||||||
|  |         await _dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task<List<PausedEntry>> GetAllPairedClientsWithPauseState(string? uid = null) |     private async Task<List<PausedEntry>> GetAllPairedClientsWithPauseState(string? uid = null) | ||||||
| @@ -79,43 +106,21 @@ public partial class MareHub | |||||||
|         return ret.Where(k => !k.IsPaused).Select(k => k.UID).ToList(); |         return ret.Where(k => !k.IsPaused).Select(k => k.UID).ToList(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task<List<string>> SendOnlineToAllPairedUsers() |     private async Task<Dictionary<string, string>> GetOnlineUsers(List<string> uids) | ||||||
|     { |     { | ||||||
|         var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false); |         var result = await _redis.GetAllAsync<string>(uids.Select(u => "UID:" + u).ToHashSet(StringComparer.Ordinal)).ConfigureAwait(false); | ||||||
|         var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); |         return uids.Where(u => result.TryGetValue("UID:" + u, out var ident) && !string.IsNullOrEmpty(ident)).ToDictionary(u => u, u => result["UID:" + u], StringComparer.Ordinal); | ||||||
|         await Clients.Users(usersToSendDataTo).Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         return usersToSendDataTo; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task<List<string>> SendOfflineToAllPairedUsers() |     private async Task<string> GetUserIdent(string uid) | ||||||
|     { |     { | ||||||
|         var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false); |         if (uid.IsNullOrEmpty()) return string.Empty; | ||||||
|         var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); |         return await _redis.GetAsync<string>("UID:" + uid).ConfigureAwait(false); | ||||||
|         await Clients.Users(usersToSendDataTo).Client_UserSendOffline(new(self.ToUserData())).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         return usersToSendDataTo; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public string UserUID => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.Uid, StringComparison.Ordinal))?.Value ?? throw new Exception("No UID in Claims"); |     private async Task RemoveUserFromRedis() | ||||||
|     public string UserCharaIdent => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, MareClaimTypes.CharaIdent, StringComparison.Ordinal))?.Value ?? throw new Exception("No Chara Ident in Claims"); |  | ||||||
|  |  | ||||||
|     private async Task UserGroupLeave(GroupPair groupUserPair, List<PausedEntry> allUserPairs, string userIdent, string? uid = null) |  | ||||||
|     { |     { | ||||||
|         uid ??= UserUID; |         await _redis.RemoveAsync("UID:" + UserUID, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false); | ||||||
|         var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal)); |  | ||||||
|         if (userPair != null) |  | ||||||
|         { |  | ||||||
|             if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) return; |  | ||||||
|             if (userPair.IsPausedPerGroup is PauseInfo.Unpaused) return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         var groupUserIdent = await GetUserIdent(groupUserPair.GroupUserUID).ConfigureAwait(false); |  | ||||||
|         if (!string.IsNullOrEmpty(groupUserIdent)) |  | ||||||
|         { |  | ||||||
|             await Clients.User(uid).Client_UserSendOffline(new(new(groupUserPair.GroupUserUID))).ConfigureAwait(false); |  | ||||||
|             await Clients.User(groupUserPair.GroupUserUID).Client_UserSendOffline(new(new(uid))).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task SendGroupDeletedToAll(List<GroupPair> groupUsers) |     private async Task SendGroupDeletedToAll(List<GroupPair> groupUsers) | ||||||
| @@ -134,15 +139,22 @@ public partial class MareHub | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task<(bool IsValid, GroupPair ReferredPair)> TryValidateUserInGroup(string gid, string? uid = null) |     private async Task<List<string>> SendOfflineToAllPairedUsers() | ||||||
|     { |     { | ||||||
|         uid ??= UserUID; |         var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false); | ||||||
|  |         var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); | ||||||
|  |         await Clients.Users(usersToSendDataTo).Client_UserSendOffline(new(self.ToUserData())).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         var groupPair = await _dbContext.GroupPairs.Include(c => c.GroupUser) |         return usersToSendDataTo; | ||||||
|             .SingleOrDefaultAsync(g => g.GroupGID == gid && (g.GroupUserUID == uid || g.GroupUser.Alias == uid)).ConfigureAwait(false); |     } | ||||||
|         if (groupPair == null) return (false, null); |  | ||||||
|  |  | ||||||
|         return (true, groupPair); |     private async Task<List<string>> SendOnlineToAllPairedUsers() | ||||||
|  |     { | ||||||
|  |         var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false); | ||||||
|  |         var self = await _dbContext.Users.AsNoTracking().SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); | ||||||
|  |         await Clients.Users(usersToSendDataTo).Client_UserSendOnline(new(self.ToUserData(), UserCharaIdent)).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         return usersToSendDataTo; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task<(bool IsValid, Group ReferredGroup)> TryValidateGroupModeratorOrOwner(string gid) |     private async Task<(bool IsValid, Group ReferredGroup)> TryValidateGroupModeratorOrOwner(string gid) | ||||||
| @@ -166,43 +178,38 @@ public partial class MareHub | |||||||
|         return (string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal), group); |         return (string.Equals(group.OwnerUID, UserUID, StringComparison.Ordinal), group); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task DeleteUser(User user) |     private async Task<(bool IsValid, GroupPair ReferredPair)> TryValidateUserInGroup(string gid, string? uid = null) | ||||||
|     { |     { | ||||||
|         var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == user.UID).ToListAsync().ConfigureAwait(false); |         uid ??= UserUID; | ||||||
|         var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == user.UID).ConfigureAwait(false); |  | ||||||
|         var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == user.UID).ConfigureAwait(false); |  | ||||||
|         var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == user.UID).ToListAsync().ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         if (lodestone != null) |         var groupPair = await _dbContext.GroupPairs.Include(c => c.GroupUser) | ||||||
|  |             .SingleOrDefaultAsync(g => g.GroupGID == gid && (g.GroupUserUID == uid || g.GroupUser.Alias == uid)).ConfigureAwait(false); | ||||||
|  |         if (groupPair == null) return (false, null); | ||||||
|  |  | ||||||
|  |         return (true, groupPair); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async Task UpdateUserOnRedis() | ||||||
|  |     { | ||||||
|  |         await _redis.AddAsync("UID:" + UserUID, UserCharaIdent, TimeSpan.FromSeconds(60), StackExchange.Redis.When.Always, StackExchange.Redis.CommandFlags.FireAndForget).ConfigureAwait(false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async Task UserGroupLeave(GroupPair groupUserPair, List<PausedEntry> allUserPairs, string userIdent, string? uid = null) | ||||||
|  |     { | ||||||
|  |         uid ??= UserUID; | ||||||
|  |         var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal)); | ||||||
|  |         if (userPair != null) | ||||||
|         { |         { | ||||||
|             _dbContext.Remove(lodestone); |             if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) return; | ||||||
|  |             if (userPair.IsPausedPerGroup is PauseInfo.Unpaused) return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         while (_dbContext.Files.Any(f => f.Uploader == user)) |         var groupUserIdent = await GetUserIdent(groupUserPair.GroupUserUID).ConfigureAwait(false); | ||||||
|  |         if (!string.IsNullOrEmpty(groupUserIdent)) | ||||||
|         { |         { | ||||||
|             await Task.Delay(1000).ConfigureAwait(false); |             await Clients.User(uid).Client_UserSendOffline(new(new(groupUserPair.GroupUserUID))).ConfigureAwait(false); | ||||||
|  |             await Clients.User(groupUserPair.GroupUserUID).Client_UserSendOffline(new(new(uid))).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         _dbContext.ClientPairs.RemoveRange(ownPairData); |  | ||||||
|         await _dbContext.SaveChangesAsync().ConfigureAwait(false); |  | ||||||
|         var otherPairData = await _dbContext.ClientPairs.Include(u => u.User) |  | ||||||
|             .Where(u => u.OtherUser.UID == user.UID).AsNoTracking().ToListAsync().ConfigureAwait(false); |  | ||||||
|         foreach (var pair in otherPairData) |  | ||||||
|         { |  | ||||||
|             await Clients.User(pair.UserUID).Client_UserRemoveClientPair(new(user.ToUserData())).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         foreach (var pair in groupPairs) |  | ||||||
|         { |  | ||||||
|             await UserLeaveGroup(new GroupDto(new GroupData(pair.GroupGID)), user.UID).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         _mareMetrics.IncCounter(MetricsAPI.CounterUsersRegisteredDeleted, 1); |  | ||||||
|  |  | ||||||
|         _dbContext.ClientPairs.RemoveRange(otherPairData); |  | ||||||
|         _dbContext.Users.Remove(user); |  | ||||||
|         _dbContext.Auth.Remove(auth); |  | ||||||
|         await _dbContext.SaveChangesAsync().ConfigureAwait(false); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async Task UserLeaveGroup(GroupDto dto, string userUid) |     private async Task UserLeaveGroup(GroupDto dto, string userUid) | ||||||
|   | |||||||
| @@ -14,140 +14,8 @@ namespace MareSynchronosServer.Hubs; | |||||||
|  |  | ||||||
| public partial class MareHub | public partial class MareHub | ||||||
| { | { | ||||||
|     [Authorize(Policy = "Identified")] |  | ||||||
|     public async Task UserDelete() |  | ||||||
|     { |  | ||||||
|         _logger.LogCallInfo(); |  | ||||||
|  |  | ||||||
|         var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); |  | ||||||
|         var secondaryUsers = await _dbContext.Auth.Include(u => u.User).Where(u => u.PrimaryUserUID == UserUID).Select(c => c.User).ToListAsync().ConfigureAwait(false); |  | ||||||
|         foreach (var user in secondaryUsers) |  | ||||||
|         { |  | ||||||
|             await DeleteUser(user).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         await DeleteUser(userEntry).ConfigureAwait(false); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     [Authorize(Policy = "Identified")] |  | ||||||
|     public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs() |  | ||||||
|     { |  | ||||||
|         _logger.LogCallInfo(); |  | ||||||
|  |  | ||||||
|         var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); |  | ||||||
|         var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         return pairs.Select(p => new OnlineUserIdentDto(new UserData(p.Key), p.Value)).ToList(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     [Authorize(Policy = "Identified")] |  | ||||||
|     public async Task<List<UserPairDto>> UserGetPairedClients() |  | ||||||
|     { |  | ||||||
|         _logger.LogCallInfo(); |  | ||||||
|  |  | ||||||
|         var query = |  | ||||||
|             from userToOther in _dbContext.ClientPairs |  | ||||||
|             join otherToUser in _dbContext.ClientPairs |  | ||||||
|                 on new |  | ||||||
|                 { |  | ||||||
|                     user = userToOther.UserUID, |  | ||||||
|                     other = userToOther.OtherUserUID, |  | ||||||
|  |  | ||||||
|                 } equals new |  | ||||||
|                 { |  | ||||||
|                     user = otherToUser.OtherUserUID, |  | ||||||
|                     other = otherToUser.UserUID, |  | ||||||
|                 } into leftJoin |  | ||||||
|             from otherEntry in leftJoin.DefaultIfEmpty() |  | ||||||
|             where |  | ||||||
|                 userToOther.UserUID == UserUID |  | ||||||
|             select new |  | ||||||
|             { |  | ||||||
|                 userToOther.OtherUser.Alias, |  | ||||||
|                 userToOther.IsPaused, |  | ||||||
|                 OtherIsPaused = otherEntry != null && otherEntry.IsPaused, |  | ||||||
|                 userToOther.OtherUserUID, |  | ||||||
|                 IsSynced = otherEntry != null, |  | ||||||
|                 DisableOwnAnimations = userToOther.DisableAnimations, |  | ||||||
|                 DisableOwnSounds = userToOther.DisableSounds, |  | ||||||
|                 DisableOtherAnimations = otherEntry == null ? false : otherEntry.DisableAnimations, |  | ||||||
|                 DisableOtherSounds = otherEntry == null ? false : otherEntry.DisableSounds |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|         var results = await query.AsNoTracking().ToListAsync().ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         return results.Select(c => |  | ||||||
|         { |  | ||||||
|             var ownPerm = UserPermissions.Paired; |  | ||||||
|             ownPerm.SetPaused(c.IsPaused); |  | ||||||
|             ownPerm.SetDisableAnimations(c.DisableOwnAnimations); |  | ||||||
|             ownPerm.SetDisableSounds(c.DisableOwnSounds); |  | ||||||
|             var otherPerm = UserPermissions.NoneSet; |  | ||||||
|             otherPerm.SetPaired(c.IsSynced); |  | ||||||
|             otherPerm.SetPaused(c.OtherIsPaused); |  | ||||||
|             otherPerm.SetDisableAnimations(c.DisableOtherAnimations); |  | ||||||
|             otherPerm.SetDisableSounds(c.DisableOtherSounds); |  | ||||||
|             return new UserPairDto(new(c.OtherUserUID, c.Alias), ownPerm, otherPerm); |  | ||||||
|         }).ToList(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     [GeneratedRegex(@"^[A-Z0-9]{40}$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)] |  | ||||||
|     private static partial Regex HashRegex(); |  | ||||||
|  |  | ||||||
|     [GeneratedRegex(@"^([a-z0-9_ '+&,\.\-\{\}]+\/)+([a-z0-9_ '+&,\.\-\{\}]+\.[a-z]{3,4})$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)] |  | ||||||
|     private static partial Regex GamePathRegex(); |  | ||||||
|  |  | ||||||
|     private static readonly string[] AllowedExtensionsForGamePaths = { ".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".scd", ".skp", ".shpk" }; |     private static readonly string[] AllowedExtensionsForGamePaths = { ".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".scd", ".skp", ".shpk" }; | ||||||
|  |  | ||||||
|     [Authorize(Policy = "Identified")] |  | ||||||
|     public async Task UserPushData(UserCharaDataMessageDto dto) |  | ||||||
|     { |  | ||||||
|         _logger.LogCallInfo(MareHubLogger.Args(dto.CharaData.FileReplacements.Count)); |  | ||||||
|  |  | ||||||
|         bool hadInvalidData = false; |  | ||||||
|         List<string> invalidGamePaths = new(); |  | ||||||
|         List<string> invalidFileSwapPaths = new(); |  | ||||||
|         foreach (var replacement in dto.CharaData.FileReplacements.SelectMany(p => p.Value)) |  | ||||||
|         { |  | ||||||
|             var invalidPaths = replacement.GamePaths.Where(p => !GamePathRegex().IsMatch(p)).ToList(); |  | ||||||
|             invalidPaths.AddRange(replacement.GamePaths.Where(p => !AllowedExtensionsForGamePaths.Any(e => p.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); |  | ||||||
|             replacement.GamePaths = replacement.GamePaths.Where(p => !invalidPaths.Contains(p, StringComparer.OrdinalIgnoreCase)).ToArray(); |  | ||||||
|             bool validGamePaths = replacement.GamePaths.Any(); |  | ||||||
|             bool validHash = string.IsNullOrEmpty(replacement.Hash) || HashRegex().IsMatch(replacement.Hash); |  | ||||||
|             bool validFileSwapPath = string.IsNullOrEmpty(replacement.FileSwapPath) || GamePathRegex().IsMatch(replacement.FileSwapPath); |  | ||||||
|             if (!validGamePaths || !validHash || !validFileSwapPath) |  | ||||||
|             { |  | ||||||
|                 _logger.LogCallWarning(MareHubLogger.Args("Invalid Data", "GamePaths", validGamePaths, string.Join(",", invalidPaths), "Hash", validHash, replacement.Hash, "FileSwap", validFileSwapPath, replacement.FileSwapPath)); |  | ||||||
|                 hadInvalidData = true; |  | ||||||
|                 if (!validFileSwapPath) invalidFileSwapPaths.Add(replacement.FileSwapPath); |  | ||||||
|                 if (!validGamePaths) invalidGamePaths.AddRange(replacement.GamePaths); |  | ||||||
|                 if (!validHash) invalidFileSwapPaths.Add(replacement.Hash); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (hadInvalidData) |  | ||||||
|         { |  | ||||||
|             await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "One or more of your supplied mods were rejected from the server. Consult /xllog for more information.").ConfigureAwait(false); |  | ||||||
|             throw new HubException("Invalid data provided, contact the appropriate mod creator to resolve those issues" |  | ||||||
|             + Environment.NewLine |  | ||||||
|             + string.Join(Environment.NewLine, invalidGamePaths.Select(p => "Invalid Game Path: " + p)) |  | ||||||
|             + Environment.NewLine |  | ||||||
|             + string.Join(Environment.NewLine, invalidFileSwapPaths.Select(p => "Invalid FileSwap Path: " + p))); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); |  | ||||||
|         var idents = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         var recipients = allPairedUsers.Where(f => dto.Recipients.Select(r => r.UID).Contains(f, StringComparer.Ordinal)).ToList(); |  | ||||||
|  |  | ||||||
|         _logger.LogCallInfo(MareHubLogger.Args(idents.Count, recipients.Count())); |  | ||||||
|  |  | ||||||
|         await Clients.Users(recipients).Client_UserReceiveCharacterData(new OnlineUserCharaDataDto(new UserData(UserUID), dto.CharaData)).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|         _mareMetrics.IncCounter(MetricsAPI.CounterUserPushData); |  | ||||||
|         _mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, recipients.Count()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     [Authorize(Policy = "Identified")] |     [Authorize(Policy = "Identified")] | ||||||
|     public async Task UserAddPair(UserDto dto) |     public async Task UserAddPair(UserDto dto) | ||||||
|     { |     { | ||||||
| @@ -214,6 +82,255 @@ public partial class MareHub | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     [Authorize(Policy = "Identified")] | ||||||
|  |     public async Task UserDelete() | ||||||
|  |     { | ||||||
|  |         _logger.LogCallInfo(); | ||||||
|  |  | ||||||
|  |         var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); | ||||||
|  |         var secondaryUsers = await _dbContext.Auth.Include(u => u.User).Where(u => u.PrimaryUserUID == UserUID).Select(c => c.User).ToListAsync().ConfigureAwait(false); | ||||||
|  |         foreach (var user in secondaryUsers) | ||||||
|  |         { | ||||||
|  |             await DeleteUser(user).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         await DeleteUser(userEntry).ConfigureAwait(false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Authorize(Policy = "Identified")] | ||||||
|  |     public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs() | ||||||
|  |     { | ||||||
|  |         _logger.LogCallInfo(); | ||||||
|  |  | ||||||
|  |         var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); | ||||||
|  |         var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         return pairs.Select(p => new OnlineUserIdentDto(new UserData(p.Key), p.Value)).ToList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Authorize(Policy = "Identified")] | ||||||
|  |     public async Task<List<UserPairDto>> UserGetPairedClients() | ||||||
|  |     { | ||||||
|  |         _logger.LogCallInfo(); | ||||||
|  |  | ||||||
|  |         var query = | ||||||
|  |             from userToOther in _dbContext.ClientPairs | ||||||
|  |             join otherToUser in _dbContext.ClientPairs | ||||||
|  |                 on new | ||||||
|  |                 { | ||||||
|  |                     user = userToOther.UserUID, | ||||||
|  |                     other = userToOther.OtherUserUID, | ||||||
|  |                 } equals new | ||||||
|  |                 { | ||||||
|  |                     user = otherToUser.OtherUserUID, | ||||||
|  |                     other = otherToUser.UserUID, | ||||||
|  |                 } into leftJoin | ||||||
|  |             from otherEntry in leftJoin.DefaultIfEmpty() | ||||||
|  |             where | ||||||
|  |                 userToOther.UserUID == UserUID | ||||||
|  |             select new | ||||||
|  |             { | ||||||
|  |                 userToOther.OtherUser.Alias, | ||||||
|  |                 userToOther.IsPaused, | ||||||
|  |                 OtherIsPaused = otherEntry != null && otherEntry.IsPaused, | ||||||
|  |                 userToOther.OtherUserUID, | ||||||
|  |                 IsSynced = otherEntry != null, | ||||||
|  |                 DisableOwnAnimations = userToOther.DisableAnimations, | ||||||
|  |                 DisableOwnSounds = userToOther.DisableSounds, | ||||||
|  |                 DisableOtherAnimations = otherEntry == null ? false : otherEntry.DisableAnimations, | ||||||
|  |                 DisableOtherSounds = otherEntry == null ? false : otherEntry.DisableSounds | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |         var results = await query.AsNoTracking().ToListAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         return results.Select(c => | ||||||
|  |         { | ||||||
|  |             var ownPerm = UserPermissions.Paired; | ||||||
|  |             ownPerm.SetPaused(c.IsPaused); | ||||||
|  |             ownPerm.SetDisableAnimations(c.DisableOwnAnimations); | ||||||
|  |             ownPerm.SetDisableSounds(c.DisableOwnSounds); | ||||||
|  |             var otherPerm = UserPermissions.NoneSet; | ||||||
|  |             otherPerm.SetPaired(c.IsSynced); | ||||||
|  |             otherPerm.SetPaused(c.OtherIsPaused); | ||||||
|  |             otherPerm.SetDisableAnimations(c.DisableOtherAnimations); | ||||||
|  |             otherPerm.SetDisableSounds(c.DisableOtherSounds); | ||||||
|  |             return new UserPairDto(new(c.OtherUserUID, c.Alias), ownPerm, otherPerm); | ||||||
|  |         }).ToList(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Authorize(Policy = "Identified")] | ||||||
|  |     public async Task<UserProfileDto> UserGetProfile(UserDto user) | ||||||
|  |     { | ||||||
|  |         _logger.LogCallInfo(MareHubLogger.Args(user)); | ||||||
|  |  | ||||||
|  |         var allUserPairs = await GetAllPairedUnpausedUsers().ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         if (!allUserPairs.Contains(user.User.UID, StringComparer.Ordinal) && !string.Equals(user.User.UID, UserUID, StringComparison.Ordinal)) | ||||||
|  |         { | ||||||
|  |             return new UserProfileDto(user.User, false, null, null, "Due to the pause status you cannot access this users profile."); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var data = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.User.UID).ConfigureAwait(false); | ||||||
|  |         if (data == null) return new UserProfileDto(user.User, false, null, null, null); | ||||||
|  |  | ||||||
|  |         if (data.FlaggedForReport) return new UserProfileDto(user.User, true, null, null, "This profile is flagged for report and pending evaluation"); | ||||||
|  |         if (data.ProfileDisabled) return new UserProfileDto(user.User, true, null, null, "This profile was permanently disabled"); | ||||||
|  |  | ||||||
|  |         return new UserProfileDto(user.User, false, data.IsNSFW, data.Base64ProfileImage, data.UserDescription); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Authorize(Policy = "Identified")] | ||||||
|  |     public async Task UserPushData(UserCharaDataMessageDto dto) | ||||||
|  |     { | ||||||
|  |         _logger.LogCallInfo(MareHubLogger.Args(dto.CharaData.FileReplacements.Count)); | ||||||
|  |  | ||||||
|  |         bool hadInvalidData = false; | ||||||
|  |         List<string> invalidGamePaths = new(); | ||||||
|  |         List<string> invalidFileSwapPaths = new(); | ||||||
|  |         foreach (var replacement in dto.CharaData.FileReplacements.SelectMany(p => p.Value)) | ||||||
|  |         { | ||||||
|  |             var invalidPaths = replacement.GamePaths.Where(p => !GamePathRegex().IsMatch(p)).ToList(); | ||||||
|  |             invalidPaths.AddRange(replacement.GamePaths.Where(p => !AllowedExtensionsForGamePaths.Any(e => p.EndsWith(e, StringComparison.OrdinalIgnoreCase)))); | ||||||
|  |             replacement.GamePaths = replacement.GamePaths.Where(p => !invalidPaths.Contains(p, StringComparer.OrdinalIgnoreCase)).ToArray(); | ||||||
|  |             bool validGamePaths = replacement.GamePaths.Any(); | ||||||
|  |             bool validHash = string.IsNullOrEmpty(replacement.Hash) || HashRegex().IsMatch(replacement.Hash); | ||||||
|  |             bool validFileSwapPath = string.IsNullOrEmpty(replacement.FileSwapPath) || GamePathRegex().IsMatch(replacement.FileSwapPath); | ||||||
|  |             if (!validGamePaths || !validHash || !validFileSwapPath) | ||||||
|  |             { | ||||||
|  |                 _logger.LogCallWarning(MareHubLogger.Args("Invalid Data", "GamePaths", validGamePaths, string.Join(",", invalidPaths), "Hash", validHash, replacement.Hash, "FileSwap", validFileSwapPath, replacement.FileSwapPath)); | ||||||
|  |                 hadInvalidData = true; | ||||||
|  |                 if (!validFileSwapPath) invalidFileSwapPaths.Add(replacement.FileSwapPath); | ||||||
|  |                 if (!validGamePaths) invalidGamePaths.AddRange(replacement.GamePaths); | ||||||
|  |                 if (!validHash) invalidFileSwapPaths.Add(replacement.Hash); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (hadInvalidData) | ||||||
|  |         { | ||||||
|  |             await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "One or more of your supplied mods were rejected from the server. Consult /xllog for more information.").ConfigureAwait(false); | ||||||
|  |             throw new HubException("Invalid data provided, contact the appropriate mod creator to resolve those issues" | ||||||
|  |             + Environment.NewLine | ||||||
|  |             + string.Join(Environment.NewLine, invalidGamePaths.Select(p => "Invalid Game Path: " + p)) | ||||||
|  |             + Environment.NewLine | ||||||
|  |             + string.Join(Environment.NewLine, invalidFileSwapPaths.Select(p => "Invalid FileSwap Path: " + p))); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); | ||||||
|  |         var idents = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         var recipients = allPairedUsers.Where(f => dto.Recipients.Select(r => r.UID).Contains(f, StringComparer.Ordinal)).ToList(); | ||||||
|  |  | ||||||
|  |         _logger.LogCallInfo(MareHubLogger.Args(idents.Count, recipients.Count())); | ||||||
|  |  | ||||||
|  |         await Clients.Users(recipients).Client_UserReceiveCharacterData(new OnlineUserCharaDataDto(new UserData(UserUID), dto.CharaData)).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         _mareMetrics.IncCounter(MetricsAPI.CounterUserPushData); | ||||||
|  |         _mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, recipients.Count()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Authorize(Policy = "Identified")] | ||||||
|  |     public async Task UserRemovePair(UserDto dto) | ||||||
|  |     { | ||||||
|  |         _logger.LogCallInfo(MareHubLogger.Args(dto)); | ||||||
|  |  | ||||||
|  |         if (string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) return; | ||||||
|  |  | ||||||
|  |         // check if client pair even exists | ||||||
|  |         ClientPair callerPair = | ||||||
|  |             await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == dto.User.UID).ConfigureAwait(false); | ||||||
|  |         if (callerPair == null) return; | ||||||
|  |  | ||||||
|  |         bool callerHadPaused = callerPair.IsPaused; | ||||||
|  |  | ||||||
|  |         // delete from database, send update info to users pair list | ||||||
|  |         _dbContext.ClientPairs.Remove(callerPair); | ||||||
|  |         await _dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         _logger.LogCallInfo(MareHubLogger.Args(dto, "Success")); | ||||||
|  |  | ||||||
|  |         await Clients.User(UserUID).Client_UserRemoveClientPair(dto).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         // check if opposite entry exists | ||||||
|  |         var oppositeClientPair = OppositeEntry(dto.User.UID); | ||||||
|  |         if (oppositeClientPair == null) return; | ||||||
|  |  | ||||||
|  |         // check if other user is online, if no then there is no need to do anything further | ||||||
|  |         var otherIdent = await GetUserIdent(dto.User.UID).ConfigureAwait(false); | ||||||
|  |         if (otherIdent == null) return; | ||||||
|  |  | ||||||
|  |         // get own ident and | ||||||
|  |         await Clients.User(dto.User.UID) | ||||||
|  |             .Client_UserUpdateOtherPairPermissions(new UserPermissionsDto(new UserData(UserUID), | ||||||
|  |                 UserPermissions.NoneSet)).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         // if the other user had paused the user the state will be offline for either, do nothing | ||||||
|  |         bool otherHadPaused = oppositeClientPair.IsPaused; | ||||||
|  |         if (!callerHadPaused && otherHadPaused) return; | ||||||
|  |  | ||||||
|  |         var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false); | ||||||
|  |         var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, dto.User.UID, StringComparison.Ordinal)); | ||||||
|  |         var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection; | ||||||
|  |  | ||||||
|  |         // if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing | ||||||
|  |         if (!callerHadPaused && !otherHadPaused && !isPausedInGroup) return; | ||||||
|  |  | ||||||
|  |         // if neither user had paused each other and either is not in an unpaused group with each other, change state to offline | ||||||
|  |         if (!callerHadPaused && !otherHadPaused && isPausedInGroup) | ||||||
|  |         { | ||||||
|  |             await Clients.User(UserUID).Client_UserSendOffline(dto).ConfigureAwait(false); | ||||||
|  |             await Clients.User(dto.User.UID).Client_UserSendOffline(new(new(UserUID))).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // if the caller had paused other but not the other has paused the caller and they are in an unpaused group together, change state to online | ||||||
|  |         if (callerHadPaused && !otherHadPaused && !isPausedInGroup) | ||||||
|  |         { | ||||||
|  |             await Clients.User(UserUID).Client_UserSendOnline(new(dto.User, otherIdent)).ConfigureAwait(false); | ||||||
|  |             await Clients.User(dto.User.UID).Client_UserSendOnline(new(new(UserUID), UserCharaIdent)).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [Authorize(Policy = "Identified")] | ||||||
|  |     public async Task UserReportProfile(UserProfileReportDto dto) | ||||||
|  |     { | ||||||
|  |         _logger.LogCallInfo(MareHubLogger.Args(dto)); | ||||||
|  |  | ||||||
|  |         UserProfileDataReport report = await _dbContext.UserProfileReports.SingleOrDefaultAsync(u => u.ReportedUserUID == dto.User.UID && u.ReportingUserUID == UserUID).ConfigureAwait(false); | ||||||
|  |         if (report != null) | ||||||
|  |         { | ||||||
|  |             await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "You already reported this profile and it's pending validation").ConfigureAwait(false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         UserProfileData profile = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == dto.User.UID).ConfigureAwait(false); | ||||||
|  |         if (profile == null) | ||||||
|  |         { | ||||||
|  |             await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "This user has no profile").ConfigureAwait(false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         UserProfileDataReport reportToAdd = new() | ||||||
|  |         { | ||||||
|  |             ReportDate = DateTime.UtcNow, | ||||||
|  |             ReportingUserUID = UserUID, | ||||||
|  |             ReportReason = dto.ProfileReport, | ||||||
|  |             ReportedUserUID = dto.User.UID, | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         profile.FlaggedForReport = true; | ||||||
|  |  | ||||||
|  |         await _dbContext.UserProfileReports.AddAsync(reportToAdd).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         await _dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         await Clients.User(dto.User.UID).Client_ReceiveServerMessage(MessageSeverity.Warning, "Your Mare profile has been reported and disabled for admin validation").ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         var allPairedUsers = await GetAllPairedUnpausedUsers(dto.User.UID).ConfigureAwait(false); | ||||||
|  |         var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         await Clients.Users(pairs.Select(p => p.Key)).Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false); | ||||||
|  |         await Clients.Users(dto.User.UID).Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     [Authorize(Policy = "Identified")] |     [Authorize(Policy = "Identified")] | ||||||
|     public async Task UserSetPairPermissions(UserPermissionsDto dto) |     public async Task UserSetPairPermissions(UserPermissionsDto dto) | ||||||
|     { |     { | ||||||
| @@ -262,67 +379,94 @@ public partial class MareHub | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     [Authorize(Policy = "Identified")] |     [Authorize(Policy = "Identified")] | ||||||
|     public async Task UserRemovePair(UserDto dto) |     public async Task UserSetProfile(UserProfileDto dto) | ||||||
|     { |     { | ||||||
|         _logger.LogCallInfo(MareHubLogger.Args(dto)); |         _logger.LogCallInfo(MareHubLogger.Args(dto)); | ||||||
|  |  | ||||||
|         if (string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) return; |         if (!string.Equals(dto.User.UID, UserUID, StringComparison.Ordinal)) throw new HubException("Cannot modify profile data for anyone but yourself"); | ||||||
|  |  | ||||||
|         // check if client pair even exists |         var existingData = await _dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == dto.User.UID).ConfigureAwait(false); | ||||||
|         ClientPair callerPair = |  | ||||||
|             await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == UserUID && w.OtherUserUID == dto.User.UID).ConfigureAwait(false); |  | ||||||
|         if (callerPair == null) return; |  | ||||||
|  |  | ||||||
|         bool callerHadPaused = callerPair.IsPaused; |         if (existingData.FlaggedForReport) | ||||||
|  |         { | ||||||
|  |             await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile is currently flagged for report and cannot be edited").ConfigureAwait(false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (existingData.ProfileDisabled) | ||||||
|  |         { | ||||||
|  |             await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your profile was permanently disabled and cannot be edited").ConfigureAwait(false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!string.IsNullOrEmpty(dto.ProfilePictureBase64)) | ||||||
|  |         { | ||||||
|  |             byte[] imageData = Convert.FromBase64String(dto.ProfilePictureBase64); | ||||||
|  |             using MemoryStream ms = new(imageData); | ||||||
|  |             var format = await Image.DetectFormatAsync(ms).ConfigureAwait(false); | ||||||
|  |             if (!format.FileExtensions.Contains("png", StringComparer.OrdinalIgnoreCase)) | ||||||
|  |             { | ||||||
|  |                 await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is not in PNG format").ConfigureAwait(false); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             using var image = Image.Load<Rgba32>(imageData); | ||||||
|  |  | ||||||
|  |             if (image.Width > 256 || image.Height > 256 || (imageData.Length > 250 * 1024)) | ||||||
|  |             { | ||||||
|  |                 await Clients.Caller.Client_ReceiveServerMessage(MessageSeverity.Error, "Your provided image file is larger than 256x256 or more than 250KiB.").ConfigureAwait(false); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (existingData != null) | ||||||
|  |         { | ||||||
|  |             if (string.Equals("", dto.ProfilePictureBase64, StringComparison.OrdinalIgnoreCase)) | ||||||
|  |             { | ||||||
|  |                 existingData.Base64ProfileImage = null; | ||||||
|  |             } | ||||||
|  |             else if (dto.ProfilePictureBase64 != null) | ||||||
|  |             { | ||||||
|  |                 existingData.Base64ProfileImage = dto.ProfilePictureBase64; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (dto.IsNSFW != null) | ||||||
|  |             { | ||||||
|  |                 existingData.IsNSFW = dto.IsNSFW.Value; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (dto.Description != null) | ||||||
|  |             { | ||||||
|  |                 existingData.UserDescription = dto.Description; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             UserProfileData userProfileData = new() | ||||||
|  |             { | ||||||
|  |                 UserUID = dto.User.UID, | ||||||
|  |                 Base64ProfileImage = dto.ProfilePictureBase64 ?? null, | ||||||
|  |                 UserDescription = dto.Description ?? null, | ||||||
|  |                 IsNSFW = dto.IsNSFW ?? false | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             await _dbContext.UserProfileData.AddAsync(userProfileData).ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // delete from database, send update info to users pair list |  | ||||||
|         _dbContext.ClientPairs.Remove(callerPair); |  | ||||||
|         await _dbContext.SaveChangesAsync().ConfigureAwait(false); |         await _dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|         _logger.LogCallInfo(MareHubLogger.Args(dto, "Success")); |         var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false); | ||||||
|  |         var pairs = await GetOnlineUsers(allPairedUsers).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         await Clients.User(UserUID).Client_UserRemoveClientPair(dto).ConfigureAwait(false); |         await Clients.Users(pairs.Select(p => p.Key)).Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false); | ||||||
|  |         await Clients.Caller.Client_UserUpdateProfile(new(dto.User)).ConfigureAwait(false); | ||||||
|         // check if opposite entry exists |  | ||||||
|         var oppositeClientPair = OppositeEntry(dto.User.UID); |  | ||||||
|         if (oppositeClientPair == null) return; |  | ||||||
|  |  | ||||||
|         // check if other user is online, if no then there is no need to do anything further |  | ||||||
|         var otherIdent = await GetUserIdent(dto.User.UID).ConfigureAwait(false); |  | ||||||
|         if (otherIdent == null) return; |  | ||||||
|  |  | ||||||
|         // get own ident and  |  | ||||||
|         await Clients.User(dto.User.UID) |  | ||||||
|             .Client_UserUpdateOtherPairPermissions(new UserPermissionsDto(new UserData(UserUID), |  | ||||||
|                 UserPermissions.NoneSet)).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|         // if the other user had paused the user the state will be offline for either, do nothing |  | ||||||
|         bool otherHadPaused = oppositeClientPair.IsPaused; |  | ||||||
|         if (!callerHadPaused && otherHadPaused) return; |  | ||||||
|  |  | ||||||
|         var allUsers = await GetAllPairedClientsWithPauseState().ConfigureAwait(false); |  | ||||||
|         var pauseEntry = allUsers.SingleOrDefault(f => string.Equals(f.UID, dto.User.UID, StringComparison.Ordinal)); |  | ||||||
|         var isPausedInGroup = pauseEntry == null || pauseEntry.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection; |  | ||||||
|  |  | ||||||
|         // if neither user had paused each other and both are in unpaused groups, state will be online for both, do nothing |  | ||||||
|         if (!callerHadPaused && !otherHadPaused && !isPausedInGroup) return; |  | ||||||
|  |  | ||||||
|         // if neither user had paused each other and either is not in an unpaused group with each other, change state to offline |  | ||||||
|         if (!callerHadPaused && !otherHadPaused && isPausedInGroup) |  | ||||||
|         { |  | ||||||
|             await Clients.User(UserUID).Client_UserSendOffline(dto).ConfigureAwait(false); |  | ||||||
|             await Clients.User(dto.User.UID).Client_UserSendOffline(new(new(UserUID))).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // if the caller had paused other but not the other has paused the caller and they are in an unpaused group together, change state to online |  | ||||||
|         if (callerHadPaused && !otherHadPaused && !isPausedInGroup) |  | ||||||
|         { |  | ||||||
|             await Clients.User(UserUID).Client_UserSendOnline(new(dto.User, otherIdent)).ConfigureAwait(false); |  | ||||||
|             await Clients.User(dto.User.UID).Client_UserSendOnline(new(new(UserUID), UserCharaIdent)).ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     [GeneratedRegex(@"^([a-z0-9_ '+&,\.\-\{\}]+\/)+([a-z0-9_ '+&,\.\-\{\}]+\.[a-z]{3,4})$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)] | ||||||
|  |     private static partial Regex GamePathRegex(); | ||||||
|  |  | ||||||
|  |     [GeneratedRegex(@"^[A-Z0-9]{40}$", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ECMAScript)] | ||||||
|  |     private static partial Regex HashRegex(); | ||||||
|  |  | ||||||
|     private ClientPair OppositeEntry(string otherUID) => |     private ClientPair OppositeEntry(string otherUID) => | ||||||
|                                 _dbContext.ClientPairs.AsNoTracking().SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == UserUID); |                                 _dbContext.ClientPairs.AsNoTracking().SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == UserUID); | ||||||
| } | } | ||||||
| @@ -28,10 +28,9 @@ | |||||||
|       <PrivateAssets>all</PrivateAssets> |       <PrivateAssets>all</PrivateAssets> | ||||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|     </PackageReference> |     </PackageReference> | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.4" /> |  | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.4" /> |  | ||||||
|     <PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" /> |     <PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" /> | ||||||
|     <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.27.0" /> |     <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.27.0" /> | ||||||
|  |     <PackageReference Include="SixLabors.ImageSharp" Version="3.0.0" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|   | |||||||
| @@ -2,32 +2,42 @@ | |||||||
| using Discord.Interactions; | using Discord.Interactions; | ||||||
| using Discord.Rest; | using Discord.Rest; | ||||||
| using Discord.WebSocket; | using Discord.WebSocket; | ||||||
|  | using MareSynchronos.API.Data.Enum; | ||||||
|  | using MareSynchronos.API.Dto.User; | ||||||
|  | using MareSynchronos.API.SignalR; | ||||||
|  | using MareSynchronosServer.Hubs; | ||||||
| using MareSynchronosShared.Data; | using MareSynchronosShared.Data; | ||||||
| using MareSynchronosShared.Services; | using MareSynchronosShared.Services; | ||||||
| using MareSynchronosShared.Utils; | using MareSynchronosShared.Utils; | ||||||
|  | using Microsoft.AspNetCore.SignalR; | ||||||
| using Microsoft.EntityFrameworkCore; | using Microsoft.EntityFrameworkCore; | ||||||
| using StackExchange.Redis; | using StackExchange.Redis; | ||||||
|  | using System.Text; | ||||||
|  |  | ||||||
| namespace MareSynchronosServices.Discord; | namespace MareSynchronosServices.Discord; | ||||||
|  |  | ||||||
| internal class DiscordBot : IHostedService | internal class DiscordBot : IHostedService | ||||||
| { | { | ||||||
|     private readonly DiscordBotServices _botServices; |     private readonly DiscordBotServices _botServices; | ||||||
|     private readonly IServiceProvider _services; |  | ||||||
|     private readonly IConfigurationService<ServicesConfiguration> _configurationService; |     private readonly IConfigurationService<ServicesConfiguration> _configurationService; | ||||||
|     private readonly ILogger<DiscordBot> _logger; |  | ||||||
|     private readonly IConnectionMultiplexer _connectionMultiplexer; |     private readonly IConnectionMultiplexer _connectionMultiplexer; | ||||||
|     private readonly DiscordSocketClient _discordClient; |     private readonly DiscordSocketClient _discordClient; | ||||||
|  |     private readonly ILogger<DiscordBot> _logger; | ||||||
|  |     private readonly IHubContext<MareHub> _mareHubContext; | ||||||
|  |     private readonly IServiceProvider _services; | ||||||
|  |     private InteractionService _interactionModule; | ||||||
|  |     private CancellationTokenSource? _processReportQueueCts; | ||||||
|     private CancellationTokenSource? _updateStatusCts; |     private CancellationTokenSource? _updateStatusCts; | ||||||
|     private CancellationTokenSource? _vanityUpdateCts; |     private CancellationTokenSource? _vanityUpdateCts; | ||||||
|     private InteractionService _interactionModule; |  | ||||||
|  |  | ||||||
|     public DiscordBot(DiscordBotServices botServices, IServiceProvider services, IConfigurationService<ServicesConfiguration> configuration, |     public DiscordBot(DiscordBotServices botServices, IServiceProvider services, IConfigurationService<ServicesConfiguration> configuration, | ||||||
|  |         IHubContext<MareHub> mareHubContext, | ||||||
|         ILogger<DiscordBot> logger, IConnectionMultiplexer connectionMultiplexer) |         ILogger<DiscordBot> logger, IConnectionMultiplexer connectionMultiplexer) | ||||||
|     { |     { | ||||||
|         _botServices = botServices; |         _botServices = botServices; | ||||||
|         _services = services; |         _services = services; | ||||||
|         _configurationService = configuration; |         _configurationService = configuration; | ||||||
|  |         _mareHubContext = mareHubContext; | ||||||
|         _logger = logger; |         _logger = logger; | ||||||
|         _connectionMultiplexer = connectionMultiplexer; |         _connectionMultiplexer = connectionMultiplexer; | ||||||
|         _discordClient = new(new DiscordSocketConfig() |         _discordClient = new(new DiscordSocketConfig() | ||||||
| @@ -38,12 +48,131 @@ internal class DiscordBot : IHostedService | |||||||
|         _discordClient.Log += Log; |         _discordClient.Log += Log; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public async Task StartAsync(CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         var token = _configurationService.GetValueOrDefault(nameof(ServicesConfiguration.DiscordBotToken), string.Empty); | ||||||
|  |         if (!string.IsNullOrEmpty(token)) | ||||||
|  |         { | ||||||
|  |             _interactionModule = new InteractionService(_discordClient); | ||||||
|  |             await _interactionModule.AddModuleAsync(typeof(MareModule), _services).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             await _discordClient.LoginAsync(TokenType.Bot, token).ConfigureAwait(false); | ||||||
|  |             await _discordClient.StartAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             _discordClient.Ready += DiscordClient_Ready; | ||||||
|  |             _discordClient.ButtonExecuted += ButtonExecutedHandler; | ||||||
|  |             _discordClient.InteractionCreated += async (x) => | ||||||
|  |             { | ||||||
|  |                 var ctx = new SocketInteractionContext(_discordClient, x); | ||||||
|  |                 await _interactionModule.ExecuteCommandAsync(ctx, _services); | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             await _botServices.Start(); | ||||||
|  |             _ = UpdateStatusAsync(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public async Task StopAsync(CancellationToken cancellationToken) | ||||||
|  |     { | ||||||
|  |         if (!string.IsNullOrEmpty(_configurationService.GetValueOrDefault(nameof(ServicesConfiguration.DiscordBotToken), string.Empty))) | ||||||
|  |         { | ||||||
|  |             _discordClient.ButtonExecuted -= ButtonExecutedHandler; | ||||||
|  |  | ||||||
|  |             await _botServices.Stop(); | ||||||
|  |             _processReportQueueCts?.Cancel(); | ||||||
|  |             _updateStatusCts?.Cancel(); | ||||||
|  |             _vanityUpdateCts?.Cancel(); | ||||||
|  |  | ||||||
|  |             await _discordClient.LogoutAsync().ConfigureAwait(false); | ||||||
|  |             await _discordClient.StopAsync().ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async Task ButtonExecutedHandler(SocketMessageComponent arg) | ||||||
|  |     { | ||||||
|  |         var id = arg.Data.CustomId; | ||||||
|  |         if (!id.StartsWith("mare-report-button", StringComparison.Ordinal)) return; | ||||||
|  |  | ||||||
|  |         var userId = arg.User.Id; | ||||||
|  |         using var scope = _services.CreateScope(); | ||||||
|  |         using var dbContext = scope.ServiceProvider.GetRequiredService<MareDbContext>(); | ||||||
|  |         var user = await dbContext.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(u => u.DiscordId == userId).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         if (user == null || (!user.User.IsModerator && !user.User.IsAdmin)) | ||||||
|  |         { | ||||||
|  |             EmbedBuilder eb = new(); | ||||||
|  |             eb.WithTitle($"Cannot resolve report"); | ||||||
|  |             eb.WithDescription($"<@{userId}>: You have no rights to resolve this report"); | ||||||
|  |             await arg.RespondAsync(embed: eb.Build()).ConfigureAwait(false); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         id = id.Remove(0, "mare-report-button-".Length); | ||||||
|  |         var split = id.Split('-', StringSplitOptions.RemoveEmptyEntries); | ||||||
|  |  | ||||||
|  |         var profile = await dbContext.UserProfileData.SingleAsync(u => u.UserUID == split[1]).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         var embed = arg.Message.Embeds.First(); | ||||||
|  |  | ||||||
|  |         var builder = embed.ToEmbedBuilder(); | ||||||
|  |         var otherPairs = await dbContext.ClientPairs.Where(p => p.UserUID == split[1]).Select(p => p.OtherUserUID).ToListAsync().ConfigureAwait(false); | ||||||
|  |         switch (split[0]) | ||||||
|  |         { | ||||||
|  |             case "dismiss": | ||||||
|  |                 builder.AddField("Resolution", $"Dismissed by <@{userId}>"); | ||||||
|  |                 builder.WithColor(Color.Green); | ||||||
|  |                 profile.FlaggedForReport = false; | ||||||
|  |                 await _mareHubContext.Clients.User(split[1]).SendAsync(nameof(IMareHub.Client_ReceiveServerMessage), | ||||||
|  |                         MessageSeverity.Warning, "The Mare profile report against you has been evaluated and your profile re-enabled.") | ||||||
|  |                     .ConfigureAwait(false); | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |             case "banprofile": | ||||||
|  |                 builder.AddField("Resolution", $"Profile has been banned by <@{userId}>"); | ||||||
|  |                 builder.WithColor(Color.Red); | ||||||
|  |                 profile.Base64ProfileImage = null; | ||||||
|  |                 profile.UserDescription = null; | ||||||
|  |                 profile.ProfileDisabled = true; | ||||||
|  |                 profile.FlaggedForReport = false; | ||||||
|  |                 await _mareHubContext.Clients.User(split[1]).SendAsync(nameof(IMareHub.Client_ReceiveServerMessage), | ||||||
|  |                     MessageSeverity.Warning, "The Mare profile report against you has been evaluated and the profile functionality permanently disabled.") | ||||||
|  |                     .ConfigureAwait(false); | ||||||
|  |                 break; | ||||||
|  |  | ||||||
|  |             case "banuser": | ||||||
|  |                 builder.AddField("Resolution", $"User has been banned by <@{userId}>"); | ||||||
|  |                 builder.WithColor(Color.DarkRed); | ||||||
|  |                 var offendingUser = await dbContext.Auth.SingleAsync(u => u.UserUID == split[1]).ConfigureAwait(false); | ||||||
|  |                 offendingUser.IsBanned = true; | ||||||
|  |                 profile.Base64ProfileImage = null; | ||||||
|  |                 profile.UserDescription = null; | ||||||
|  |                 profile.ProfileDisabled = true; | ||||||
|  |                 await _mareHubContext.Clients.User(split[1]).SendAsync(nameof(IMareHub.Client_ReceiveServerMessage), | ||||||
|  |                     MessageSeverity.Warning, "The Mare profile report against you has been evaluated and your account permanently banned.") | ||||||
|  |                     .ConfigureAwait(false); | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         await dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         await _mareHubContext.Clients.Users(otherPairs).SendAsync(nameof(IMareHub.Client_UserUpdateProfile), new UserDto(new(split[1]))).ConfigureAwait(false); | ||||||
|  |         await _mareHubContext.Clients.User(split[1]).SendAsync(nameof(IMareHub.Client_UserUpdateProfile), new UserDto(new(split[1]))).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         await arg.Message.ModifyAsync(msg => | ||||||
|  |         { | ||||||
|  |             msg.Content = arg.Message.Content; | ||||||
|  |             msg.Components = null; | ||||||
|  |             msg.Embed = new Optional<Embed>(builder.Build()); | ||||||
|  |         }).ConfigureAwait(false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private async Task DiscordClient_Ready() |     private async Task DiscordClient_Ready() | ||||||
|     { |     { | ||||||
|         var guild = (await _discordClient.Rest.GetGuildsAsync()).First(); |         var guild = (await _discordClient.Rest.GetGuildsAsync()).First(); | ||||||
|         await _interactionModule.RegisterCommandsToGuildAsync(guild.Id, true).ConfigureAwait(false); |         await _interactionModule.RegisterCommandsToGuildAsync(guild.Id, true).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         _ = RemoveUsersNotInVanityRole(); |         _ = RemoveUsersNotInVanityRole(); | ||||||
|  |         _ = ProcessReportsQueue(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private Task Log(LogMessage msg) |     private Task Log(LogMessage msg) | ||||||
| @@ -53,15 +182,107 @@ internal class DiscordBot : IHostedService | |||||||
|         return Task.CompletedTask; |         return Task.CompletedTask; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private async Task ProcessReportsQueue() | ||||||
|  |     { | ||||||
|  |         var guild = (await _discordClient.Rest.GetGuildsAsync()).First(); | ||||||
|  |  | ||||||
|  |         _processReportQueueCts?.Cancel(); | ||||||
|  |         _processReportQueueCts?.Dispose(); | ||||||
|  |         _processReportQueueCts = new(); | ||||||
|  |         var token = _processReportQueueCts.Token; | ||||||
|  |         while (!token.IsCancellationRequested) | ||||||
|  |         { | ||||||
|  |             await Task.Delay(TimeSpan.FromSeconds(30)).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             if (_discordClient.ConnectionState != ConnectionState.Connected) continue; | ||||||
|  |             var reportChannelId = _configurationService.GetValue<ulong?>(nameof(ServicesConfiguration.DiscordChannelForReports)); | ||||||
|  |             if (reportChannelId == null) continue; | ||||||
|  |  | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 using (var scope = _services.CreateScope()) | ||||||
|  |                 { | ||||||
|  |                     _logger.LogInformation("Checking for Profile Reports"); | ||||||
|  |                     var dbContext = scope.ServiceProvider.GetRequiredService<MareDbContext>(); | ||||||
|  |                     if (!dbContext.UserProfileReports.Any()) | ||||||
|  |                     { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     var reports = await dbContext.UserProfileReports.ToListAsync().ConfigureAwait(false); | ||||||
|  |                     var restChannel = await guild.GetTextChannelAsync(reportChannelId.Value).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |                     foreach (var report in reports) | ||||||
|  |                     { | ||||||
|  |                         var reportedUser = await dbContext.Users.SingleAsync(u => u.UID == report.ReportedUserUID).ConfigureAwait(false); | ||||||
|  |                         var reportedUserLodestone = await dbContext.LodeStoneAuth.SingleOrDefaultAsync(u => u.User.UID == report.ReportedUserUID).ConfigureAwait(false); | ||||||
|  |                         var reportingUser = await dbContext.Users.SingleAsync(u => u.UID == report.ReportingUserUID).ConfigureAwait(false); | ||||||
|  |                         var reportingUserLodestone = await dbContext.LodeStoneAuth.SingleOrDefaultAsync(u => u.User.UID == report.ReportingUserUID).ConfigureAwait(false); | ||||||
|  |                         var reportedUserProfile = await dbContext.UserProfileData.SingleAsync(u => u.UserUID == report.ReportedUserUID).ConfigureAwait(false); | ||||||
|  |                         EmbedBuilder eb = new(); | ||||||
|  |                         eb.WithTitle("Mare Synchronos Profile Report"); | ||||||
|  |  | ||||||
|  |                         StringBuilder reportedUserSb = new(); | ||||||
|  |                         StringBuilder reportingUserSb = new(); | ||||||
|  |                         reportedUserSb.Append(reportedUser.UID); | ||||||
|  |                         reportingUserSb.Append(reportingUser.UID); | ||||||
|  |                         if (reportedUserLodestone != null) | ||||||
|  |                         { | ||||||
|  |                             reportedUserSb.AppendLine($" (<@{reportedUserLodestone.DiscordId}>)"); | ||||||
|  |                         } | ||||||
|  |                         if (reportingUserLodestone != null) | ||||||
|  |                         { | ||||||
|  |                             reportingUserSb.AppendLine($" (<@{reportingUserLodestone.DiscordId}>)"); | ||||||
|  |                         } | ||||||
|  |                         eb.AddField("Reported User", reportedUserSb.ToString()); | ||||||
|  |                         eb.AddField("Reporting User", reportingUserSb.ToString()); | ||||||
|  |                         eb.AddField("Report Date (UTC)", report.ReportDate); | ||||||
|  |                         eb.AddField("Report Reason", report.ReportReason ?? "-"); | ||||||
|  |                         eb.AddField("Reported User Profile Description", string.IsNullOrEmpty(reportedUserProfile.UserDescription) ? "-" : reportedUserProfile.UserDescription); | ||||||
|  |                         eb.AddField("Reported User Profile Is NSFW", reportedUserProfile.IsNSFW); | ||||||
|  |  | ||||||
|  |                         var cb = new ComponentBuilder(); | ||||||
|  |                         cb.WithButton("Dismiss Report", customId: $"mare-report-button-dismiss-{reportedUser.UID}", style: ButtonStyle.Primary); | ||||||
|  |                         cb.WithButton("Ban profile", customId: $"mare-report-button-banprofile-{reportedUser.UID}", style: ButtonStyle.Secondary); | ||||||
|  |                         cb.WithButton("Ban user", customId: $"mare-report-button-banuser-{reportedUser.UID}", style: ButtonStyle.Danger); | ||||||
|  |  | ||||||
|  |                         if (!string.IsNullOrEmpty(reportedUserProfile.Base64ProfileImage)) | ||||||
|  |                         { | ||||||
|  |                             var fileName = reportedUser.UID + "_profile_" + Guid.NewGuid().ToString("N") + ".png"; | ||||||
|  |                             eb.WithImageUrl($"attachment://{fileName}"); | ||||||
|  |                             using MemoryStream ms = new(Convert.FromBase64String(reportedUserProfile.Base64ProfileImage)); | ||||||
|  |                             await restChannel.SendFileAsync(ms, fileName, "User Report", embed: eb.Build(), components: cb.Build(), isSpoiler: true).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |                         else | ||||||
|  |                         { | ||||||
|  |                             var msg = await restChannel.SendMessageAsync(embed: eb.Build(), components: cb.Build()).ConfigureAwait(false); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         dbContext.Remove(report); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     await dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 _logger.LogWarning(ex, "Failed to process reports"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     private async Task RemoveUsersNotInVanityRole() |     private async Task RemoveUsersNotInVanityRole() | ||||||
|     { |     { | ||||||
|  |         _vanityUpdateCts?.Cancel(); | ||||||
|  |         _vanityUpdateCts?.Dispose(); | ||||||
|         _vanityUpdateCts = new(); |         _vanityUpdateCts = new(); | ||||||
|  |         var token = _vanityUpdateCts.Token; | ||||||
|         var guild = (await _discordClient.Rest.GetGuildsAsync()).First(); |         var guild = (await _discordClient.Rest.GetGuildsAsync()).First(); | ||||||
|         var commands = await guild.GetApplicationCommandsAsync(); |         var commands = await guild.GetApplicationCommandsAsync(); | ||||||
|         var appId = await _discordClient.GetApplicationInfoAsync().ConfigureAwait(false); |         var appId = await _discordClient.GetApplicationInfoAsync().ConfigureAwait(false); | ||||||
|         var vanityCommandId = commands.First(c => c.ApplicationId == appId.Id && c.Name == "setvanityuid").Id; |         var vanityCommandId = commands.First(c => c.ApplicationId == appId.Id && c.Name == "setvanityuid").Id; | ||||||
|  |  | ||||||
|         while (!_vanityUpdateCts.IsCancellationRequested) |         while (!token.IsCancellationRequested) | ||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
| @@ -179,40 +400,4 @@ internal class DiscordBot : IHostedService | |||||||
|             await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false); |             await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public async Task StartAsync(CancellationToken cancellationToken) |  | ||||||
|     { |  | ||||||
|         var token = _configurationService.GetValueOrDefault(nameof(ServicesConfiguration.DiscordBotToken), string.Empty); |  | ||||||
|         if (!string.IsNullOrEmpty(token)) |  | ||||||
|         { |  | ||||||
|             _interactionModule = new InteractionService(_discordClient); |  | ||||||
|             await _interactionModule.AddModuleAsync(typeof(MareModule), _services).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             await _discordClient.LoginAsync(TokenType.Bot, token).ConfigureAwait(false); |  | ||||||
|             await _discordClient.StartAsync().ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             _discordClient.Ready += DiscordClient_Ready; |  | ||||||
|             _discordClient.InteractionCreated += async (x) => |  | ||||||
|             { |  | ||||||
|                 var ctx = new SocketInteractionContext(_discordClient, x); |  | ||||||
|                 await _interactionModule.ExecuteCommandAsync(ctx, _services); |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             await _botServices.Start(); |  | ||||||
|             _ = UpdateStatusAsync(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public async Task StopAsync(CancellationToken cancellationToken) |  | ||||||
|     { |  | ||||||
|         if (!string.IsNullOrEmpty(_configurationService.GetValueOrDefault(nameof(ServicesConfiguration.DiscordBotToken), string.Empty))) |  | ||||||
|         { |  | ||||||
|             await _botServices.Stop(); |  | ||||||
|             _updateStatusCts?.Cancel(); |  | ||||||
|             _vanityUpdateCts?.Cancel(); |  | ||||||
|  |  | ||||||
|             await _discordClient.LogoutAsync().ConfigureAwait(false); |  | ||||||
|             await _discordClient.StopAsync().ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -5,16 +5,12 @@ namespace MareSynchronosServices.Discord; | |||||||
|  |  | ||||||
| public class DiscordBotServices | public class DiscordBotServices | ||||||
| { | { | ||||||
|     public ConcurrentQueue<KeyValuePair<ulong, Action<IServiceProvider>>> VerificationQueue { get; } = new(); |     public readonly string[] LodestoneServers = new[] { "eu", "na", "jp", "fr", "de" }; | ||||||
|     public ConcurrentDictionary<ulong, DateTime> LastVanityChange = new(); |  | ||||||
|     public ConcurrentDictionary<string, DateTime> LastVanityGidChange = new(); |  | ||||||
|     public ConcurrentDictionary<ulong, string> DiscordLodestoneMapping = new(); |     public ConcurrentDictionary<ulong, string> DiscordLodestoneMapping = new(); | ||||||
|     public ConcurrentDictionary<ulong, string> DiscordRelinkLodestoneMapping = new(); |     public ConcurrentDictionary<ulong, string> DiscordRelinkLodestoneMapping = new(); | ||||||
|     public readonly string[] LodestoneServers = new[] { "eu", "na", "jp", "fr", "de" }; |     public ConcurrentDictionary<ulong, DateTime> LastVanityChange = new(); | ||||||
|  |     public ConcurrentDictionary<string, DateTime> LastVanityGidChange = new(); | ||||||
|     private readonly IServiceProvider _serviceProvider; |     private readonly IServiceProvider _serviceProvider; | ||||||
|  |  | ||||||
|     public ILogger<DiscordBotServices> Logger { get; init; } |  | ||||||
|     public MareMetrics Metrics { get; init; } |  | ||||||
|     private CancellationTokenSource? verificationTaskCts; |     private CancellationTokenSource? verificationTaskCts; | ||||||
|  |  | ||||||
|     public DiscordBotServices(IServiceProvider serviceProvider, ILogger<DiscordBotServices> logger, MareMetrics metrics) |     public DiscordBotServices(IServiceProvider serviceProvider, ILogger<DiscordBotServices> logger, MareMetrics metrics) | ||||||
| @@ -24,6 +20,10 @@ public class DiscordBotServices | |||||||
|         Metrics = metrics; |         Metrics = metrics; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public ILogger<DiscordBotServices> Logger { get; init; } | ||||||
|  |     public MareMetrics Metrics { get; init; } | ||||||
|  |     public ConcurrentQueue<KeyValuePair<ulong, Action<IServiceProvider>>> VerificationQueue { get; } = new(); | ||||||
|  |  | ||||||
|     public Task Start() |     public Task Start() | ||||||
|     { |     { | ||||||
|         _ = ProcessVerificationQueue(); |         _ = ProcessVerificationQueue(); | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								MareSynchronosServer/MareSynchronosServices/DummyHub.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								MareSynchronosServer/MareSynchronosServices/DummyHub.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | using Microsoft.AspNetCore.SignalR; | ||||||
|  |  | ||||||
|  | // this is a very hacky way to attach this file server to the main mare hub signalr instance via redis | ||||||
|  | // signalr publishes the namespace and hubname into the redis backend so this needs to be equal to the original | ||||||
|  | // but I don't need to reimplement the hub completely as I only exclusively use it for internal connection calling | ||||||
|  | // from the queue service so I keep the namespace and name of the class the same so it can connect to the same channel | ||||||
|  | // if anyone finds a better way to do this let me know | ||||||
|  |  | ||||||
|  | #pragma warning disable IDE0130 // Namespace does not match folder structure | ||||||
|  | #pragma warning disable MA0048 // File name must match type name | ||||||
|  | namespace MareSynchronosServer.Hubs; | ||||||
|  | public class MareHub : Hub | ||||||
|  | { | ||||||
|  |     public override Task OnConnectedAsync() | ||||||
|  |     { | ||||||
|  |         throw new NotSupportedException(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public override Task OnDisconnectedAsync(Exception exception) | ||||||
|  |     { | ||||||
|  |         throw new NotSupportedException(); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | #pragma warning restore IDE0130 // Namespace does not match folder structure | ||||||
|  | #pragma warning restore MA0048 // File name must match type name | ||||||
| @@ -32,6 +32,7 @@ | |||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|  |     <ProjectReference Include="..\..\MareAPI\MareSynchronosAPI\MareSynchronos.API.csproj" /> | ||||||
|     <ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" /> |     <ProjectReference Include="..\MareSynchronosShared\MareSynchronosShared.csproj" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,9 @@ using Grpc.Net.Client.Configuration; | |||||||
| using MareSynchronosShared.Protos; | using MareSynchronosShared.Protos; | ||||||
| using MareSynchronosShared.Services; | using MareSynchronosShared.Services; | ||||||
| using StackExchange.Redis; | using StackExchange.Redis; | ||||||
|  | using MessagePack.Resolvers; | ||||||
|  | using MessagePack; | ||||||
|  | using Microsoft.AspNetCore.Authorization; | ||||||
|  |  | ||||||
| namespace MareSynchronosServices; | namespace MareSynchronosServices; | ||||||
|  |  | ||||||
| @@ -20,6 +23,20 @@ public class Startup | |||||||
|  |  | ||||||
|     public IConfiguration Configuration { get; } |     public IConfiguration Configuration { get; } | ||||||
|  |  | ||||||
|  |     public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | ||||||
|  |     { | ||||||
|  |         var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationAuthBase>>(); | ||||||
|  |  | ||||||
|  |         var metricServer = new KestrelMetricServer(config.GetValueOrDefault<int>(nameof(MareConfigurationBase.MetricsPort), 4982)); | ||||||
|  |         metricServer.Start(); | ||||||
|  |  | ||||||
|  |         app.UseRouting(); | ||||||
|  |         app.UseEndpoints(e => | ||||||
|  |         { | ||||||
|  |             e.MapHub<MareSynchronosServer.Hubs.MareHub>("/dummyhub"); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public void ConfigureServices(IServiceCollection services) |     public void ConfigureServices(IServiceCollection services) | ||||||
|     { |     { | ||||||
|         var mareConfig = Configuration.GetSection("MareSynchronos"); |         var mareConfig = Configuration.GetSection("MareSynchronos"); | ||||||
| @@ -61,6 +78,35 @@ public class Startup | |||||||
|             }; |             }; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         var signalRServiceBuilder = services.AddSignalR(hubOptions => | ||||||
|  |         { | ||||||
|  |             hubOptions.MaximumReceiveMessageSize = long.MaxValue; | ||||||
|  |             hubOptions.EnableDetailedErrors = true; | ||||||
|  |             hubOptions.MaximumParallelInvocationsPerClient = 10; | ||||||
|  |             hubOptions.StreamBufferCapacity = 200; | ||||||
|  |         }).AddMessagePackProtocol(opt => | ||||||
|  |         { | ||||||
|  |             var resolver = CompositeResolver.Create(StandardResolverAllowPrivate.Instance, | ||||||
|  |                 BuiltinResolver.Instance, | ||||||
|  |                 AttributeFormatterResolver.Instance, | ||||||
|  |                 // replace enum resolver | ||||||
|  |                 DynamicEnumAsStringResolver.Instance, | ||||||
|  |                 DynamicGenericResolver.Instance, | ||||||
|  |                 DynamicUnionResolver.Instance, | ||||||
|  |                 DynamicObjectResolver.Instance, | ||||||
|  |                 PrimitiveObjectResolver.Instance, | ||||||
|  |                 // final fallback(last priority) | ||||||
|  |                 StandardResolver.Instance); | ||||||
|  |  | ||||||
|  |             opt.SerializerOptions = MessagePackSerializerOptions.Standard | ||||||
|  |                 .WithCompression(MessagePackCompression.Lz4Block) | ||||||
|  |                 .WithResolver(resolver); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // configure redis for SignalR | ||||||
|  |         var redisConnection = mareConfig.GetValue(nameof(MareConfigurationBase.RedisConnectionString), string.Empty); | ||||||
|  |         signalRServiceBuilder.AddStackExchangeRedis(redisConnection, options => { }); | ||||||
|  |  | ||||||
|         services.Configure<ServicesConfiguration>(Configuration.GetRequiredSection("MareSynchronos")); |         services.Configure<ServicesConfiguration>(Configuration.GetRequiredSection("MareSynchronos")); | ||||||
|         services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("MareSynchronos")); |         services.Configure<ServerConfiguration>(Configuration.GetRequiredSection("MareSynchronos")); | ||||||
|         services.Configure<MareConfigurationAuthBase>(Configuration.GetRequiredSection("MareSynchronos")); |         services.Configure<MareConfigurationAuthBase>(Configuration.GetRequiredSection("MareSynchronos")); | ||||||
| @@ -75,12 +121,4 @@ public class Startup | |||||||
|         services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>()); |         services.AddHostedService(p => (MareConfigurationServiceClient<MareConfigurationAuthBase>)p.GetService<IConfigurationService<MareConfigurationAuthBase>>()); | ||||||
|         services.AddHostedService(p => (MareConfigurationServiceClient<ServerConfiguration>)p.GetService<IConfigurationService<ServerConfiguration>>()); |         services.AddHostedService(p => (MareConfigurationServiceClient<ServerConfiguration>)p.GetService<IConfigurationService<ServerConfiguration>>()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void Configure(IApplicationBuilder app, IWebHostEnvironment env) |  | ||||||
|     { |  | ||||||
|         var config = app.ApplicationServices.GetRequiredService<IConfigurationService<MareConfigurationAuthBase>>(); |  | ||||||
|  |  | ||||||
|         var metricServer = new KestrelMetricServer(config.GetValueOrDefault<int>(nameof(MareConfigurationBase.MetricsPort), 4982)); |  | ||||||
|         metricServer.Start(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -31,19 +31,20 @@ public class MareDbContext : DbContext | |||||||
|     { |     { | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public DbSet<User> Users { get; set; } |  | ||||||
|     public DbSet<FileCache> Files { get; set; } |  | ||||||
|     public DbSet<ClientPair> ClientPairs { get; set; } |  | ||||||
|     public DbSet<ForbiddenUploadEntry> ForbiddenUploadEntries { get; set; } |  | ||||||
|     public DbSet<Banned> BannedUsers { get; set; } |  | ||||||
|     public DbSet<Auth> Auth { get; set; } |     public DbSet<Auth> Auth { get; set; } | ||||||
|     public DbSet<LodeStoneAuth> LodeStoneAuth { get; set; } |  | ||||||
|     public DbSet<BannedRegistrations> BannedRegistrations { get; set; } |     public DbSet<BannedRegistrations> BannedRegistrations { get; set; } | ||||||
|     public DbSet<Group> Groups { get; set; } |     public DbSet<Banned> BannedUsers { get; set; } | ||||||
|     public DbSet<GroupPair> GroupPairs { get; set; } |     public DbSet<ClientPair> ClientPairs { get; set; } | ||||||
|  |     public DbSet<FileCache> Files { get; set; } | ||||||
|  |     public DbSet<ForbiddenUploadEntry> ForbiddenUploadEntries { get; set; } | ||||||
|     public DbSet<GroupBan> GroupBans { get; set; } |     public DbSet<GroupBan> GroupBans { get; set; } | ||||||
|  |     public DbSet<GroupPair> GroupPairs { get; set; } | ||||||
|  |     public DbSet<Group> Groups { get; set; } | ||||||
|     public DbSet<GroupTempInvite> GroupTempInvites { get; set; } |     public DbSet<GroupTempInvite> GroupTempInvites { get; set; } | ||||||
|  |     public DbSet<LodeStoneAuth> LodeStoneAuth { get; set; } | ||||||
|  |     public DbSet<UserProfileData> UserProfileData { get; set; } | ||||||
|  |     public DbSet<UserProfileDataReport> UserProfileReports { get; set; } | ||||||
|  |     public DbSet<User> Users { get; set; } | ||||||
|  |  | ||||||
|     protected override void OnModelCreating(ModelBuilder modelBuilder) |     protected override void OnModelCreating(ModelBuilder modelBuilder) | ||||||
|     { |     { | ||||||
| @@ -73,5 +74,8 @@ public class MareDbContext : DbContext | |||||||
|         modelBuilder.Entity<GroupTempInvite>().HasKey(u => new { u.GroupGID, u.Invite }); |         modelBuilder.Entity<GroupTempInvite>().HasKey(u => new { u.GroupGID, u.Invite }); | ||||||
|         modelBuilder.Entity<GroupTempInvite>().HasIndex(c => c.GroupGID); |         modelBuilder.Entity<GroupTempInvite>().HasIndex(c => c.GroupGID); | ||||||
|         modelBuilder.Entity<GroupTempInvite>().HasIndex(c => c.Invite); |         modelBuilder.Entity<GroupTempInvite>().HasIndex(c => c.Invite); | ||||||
|  |         modelBuilder.Entity<UserProfileData>().ToTable("user_profile_data"); | ||||||
|  |         modelBuilder.Entity<UserProfileData>().HasKey(c => c.UserUID); | ||||||
|  |         modelBuilder.Entity<UserProfileDataReport>().ToTable("user_profile_data_reports"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -32,6 +32,8 @@ | |||||||
| 		<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" /> | 		<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" /> | ||||||
| 		<PackageReference Include="Microsoft.AspNetCore.Authentication.Core" Version="2.2.0" /> | 		<PackageReference Include="Microsoft.AspNetCore.Authentication.Core" Version="2.2.0" /> | ||||||
| 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.4" /> | 		<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.4" /> | ||||||
|  | 		<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.4" /> | ||||||
|  | 		<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.4" /> | ||||||
| 		<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4" /> | 		<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.4" /> | ||||||
| 		<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.4" /> | 		<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.4" /> | ||||||
| 		<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4"> | 		<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4"> | ||||||
|   | |||||||
							
								
								
									
										584
									
								
								MareSynchronosServer/MareSynchronosShared/Migrations/20230319015307_UserProfileData.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										584
									
								
								MareSynchronosServer/MareSynchronosShared/Migrations/20230319015307_UserProfileData.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,584 @@ | |||||||
|  | // <auto-generated /> | ||||||
|  | using System; | ||||||
|  | using MareSynchronosShared.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 MareSynchronosServer.Migrations | ||||||
|  | { | ||||||
|  |     [DbContext(typeof(MareDbContext))] | ||||||
|  |     [Migration("20230319015307_UserProfileData")] | ||||||
|  |     partial class UserProfileData | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||||
|  |         { | ||||||
|  | #pragma warning disable 612, 618 | ||||||
|  |             modelBuilder | ||||||
|  |                 .HasAnnotation("ProductVersion", "7.0.4") | ||||||
|  |                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||||
|  |  | ||||||
|  |             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("HashedKey") | ||||||
|  |                         .HasMaxLength(64) | ||||||
|  |                         .HasColumnType("character varying(64)") | ||||||
|  |                         .HasColumnName("hashed_key"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsBanned") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_banned"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("PrimaryUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("primary_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.Banned", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("CharacterIdentification") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("character_identification"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Reason") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("reason"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("CharacterIdentification") | ||||||
|  |                         .HasName("pk_banned_users"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("banned_users", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.BannedRegistrations", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.ClientPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserUID") | ||||||
|  |                         .HasMaxLength(10) | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("OtherUserUID") | ||||||
|  |                         .HasMaxLength(10) | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("other_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("AllowReceivingMessages") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("allow_receiving_messages"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableAnimations") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_animations"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableSounds") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_sounds"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsPaused") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_paused"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("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("MareSynchronosShared.Models.FileCache", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Hash") | ||||||
|  |                         .HasMaxLength(40) | ||||||
|  |                         .HasColumnType("character varying(40)") | ||||||
|  |                         .HasColumnName("hash"); | ||||||
|  |  | ||||||
|  |                     b.Property<long>("Size") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("size"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("UploadDate") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("upload_date"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("Uploaded") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("uploaded"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.ForbiddenUploadEntry", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Hash") | ||||||
|  |                         .HasMaxLength(40) | ||||||
|  |                         .HasColumnType("character varying(40)") | ||||||
|  |                         .HasColumnName("hash"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ForbiddenBy") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("forbidden_by"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("Hash") | ||||||
|  |                         .HasName("pk_forbidden_upload_entries"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("forbidden_upload_entries", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Group", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GID") | ||||||
|  |                         .HasMaxLength(20) | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Alias") | ||||||
|  |                         .HasMaxLength(50) | ||||||
|  |                         .HasColumnType("character varying(50)") | ||||||
|  |                         .HasColumnName("alias"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableAnimations") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_animations"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableSounds") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_sounds"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("HashedPassword") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("hashed_password"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("InvitesEnabled") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("invites_enabled"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("OwnerUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("owner_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("GID") | ||||||
|  |                         .HasName("pk_groups"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("OwnerUID") | ||||||
|  |                         .HasDatabaseName("ix_groups_owner_uid"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("groups", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GroupGID") | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("BannedUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("banned_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("BannedByUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("banned_by_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("BannedOn") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("banned_on"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.GroupPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GroupGID") | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("GroupUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("group_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableAnimations") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_animations"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableSounds") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_sounds"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsModerator") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_moderator"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsPaused") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_paused"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsPinned") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_pinned"); | ||||||
|  |  | ||||||
|  |                     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("MareSynchronosShared.Models.GroupTempInvite", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GroupGID") | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Invite") | ||||||
|  |                         .HasMaxLength(64) | ||||||
|  |                         .HasColumnType("character varying(64)") | ||||||
|  |                         .HasColumnName("invite"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("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("MareSynchronosShared.Models.LodeStoneAuth", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<decimal>("DiscordId") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("numeric(20,0)") | ||||||
|  |                         .HasColumnName("discord_id"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("HashedLodestoneId") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("hashed_lodestone_id"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("LodestoneAuthString") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("lodestone_auth_string"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime?>("StartedAt") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("started_at"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.User", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UID") | ||||||
|  |                         .HasMaxLength(10) | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Alias") | ||||||
|  |                         .HasMaxLength(15) | ||||||
|  |                         .HasColumnType("character varying(15)") | ||||||
|  |                         .HasColumnName("alias"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsAdmin") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_admin"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsModerator") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_moderator"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("LastLoggedIn") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("last_logged_in"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("UID") | ||||||
|  |                         .HasName("pk_users"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("users", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Base64ProfileImage") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("base64profile_image"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("UserDescription") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("user_description"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("UserUID") | ||||||
|  |                         .HasName("pk_user_profile_data"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("user_profile_data", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("PrimaryUserUID") | ||||||
|  |                         .HasConstraintName("fk_auth_users_primary_user_temp_id"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .HasConstraintName("fk_auth_users_user_temp_id1"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("PrimaryUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "OtherUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("OtherUserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_client_pairs_users_other_user_temp_id2"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_client_pairs_users_user_temp_id3"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("OtherUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "Uploader") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UploaderUID") | ||||||
|  |                         .HasConstraintName("fk_file_caches_users_uploader_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Uploader"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Group", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "Owner") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("OwnerUID") | ||||||
|  |                         .HasConstraintName("fk_groups_users_owner_temp_id8"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Owner"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "BannedBy") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("BannedByUID") | ||||||
|  |                         .HasConstraintName("fk_group_bans_users_banned_by_temp_id5"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "BannedUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("BannedUserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_bans_users_banned_user_temp_id6"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupGID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_bans_groups_group_temp_id"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("BannedBy"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("BannedUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Group"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupGID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_pairs_groups_group_temp_id1"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "GroupUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupUserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_pairs_users_group_user_temp_id7"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Group"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("GroupUser"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupGID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_temp_invites_groups_group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Group"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .HasConstraintName("fk_lodestone_auth_users_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_user_profile_data_users_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  | #pragma warning restore 612, 618 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | namespace MareSynchronosServer.Migrations | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public partial class UserProfileData : Migration | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "user_profile_data", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     user_uid = table.Column<string>(type: "character varying(10)", nullable: false), | ||||||
|  |                     base64profile_image = table.Column<string>(type: "text", nullable: true), | ||||||
|  |                     user_description = table.Column<string>(type: "text", nullable: true) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("pk_user_profile_data", x => x.user_uid); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "fk_user_profile_data_users_user_uid", | ||||||
|  |                         column: x => x.user_uid, | ||||||
|  |                         principalTable: "users", | ||||||
|  |                         principalColumn: "uid", | ||||||
|  |                         onDelete: ReferentialAction.Cascade); | ||||||
|  |                 }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "user_profile_data"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										650
									
								
								MareSynchronosServer/MareSynchronosShared/Migrations/20230319114005_UserProfileReports.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										650
									
								
								MareSynchronosServer/MareSynchronosShared/Migrations/20230319114005_UserProfileReports.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,650 @@ | |||||||
|  | // <auto-generated /> | ||||||
|  | using System; | ||||||
|  | using MareSynchronosShared.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 MareSynchronosServer.Migrations | ||||||
|  | { | ||||||
|  |     [DbContext(typeof(MareDbContext))] | ||||||
|  |     [Migration("20230319114005_UserProfileReports")] | ||||||
|  |     partial class UserProfileReports | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void BuildTargetModel(ModelBuilder modelBuilder) | ||||||
|  |         { | ||||||
|  | #pragma warning disable 612, 618 | ||||||
|  |             modelBuilder | ||||||
|  |                 .HasAnnotation("ProductVersion", "7.0.4") | ||||||
|  |                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||||
|  |  | ||||||
|  |             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("HashedKey") | ||||||
|  |                         .HasMaxLength(64) | ||||||
|  |                         .HasColumnType("character varying(64)") | ||||||
|  |                         .HasColumnName("hashed_key"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsBanned") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_banned"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("PrimaryUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("primary_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.Banned", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("CharacterIdentification") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("character_identification"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Reason") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("reason"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("CharacterIdentification") | ||||||
|  |                         .HasName("pk_banned_users"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("banned_users", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.BannedRegistrations", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.ClientPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserUID") | ||||||
|  |                         .HasMaxLength(10) | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("OtherUserUID") | ||||||
|  |                         .HasMaxLength(10) | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("other_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("AllowReceivingMessages") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("allow_receiving_messages"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableAnimations") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_animations"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableSounds") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_sounds"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsPaused") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_paused"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("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("MareSynchronosShared.Models.FileCache", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Hash") | ||||||
|  |                         .HasMaxLength(40) | ||||||
|  |                         .HasColumnType("character varying(40)") | ||||||
|  |                         .HasColumnName("hash"); | ||||||
|  |  | ||||||
|  |                     b.Property<long>("Size") | ||||||
|  |                         .HasColumnType("bigint") | ||||||
|  |                         .HasColumnName("size"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("UploadDate") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("upload_date"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("Uploaded") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("uploaded"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.ForbiddenUploadEntry", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("Hash") | ||||||
|  |                         .HasMaxLength(40) | ||||||
|  |                         .HasColumnType("character varying(40)") | ||||||
|  |                         .HasColumnName("hash"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ForbiddenBy") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("forbidden_by"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("Hash") | ||||||
|  |                         .HasName("pk_forbidden_upload_entries"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("forbidden_upload_entries", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Group", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GID") | ||||||
|  |                         .HasMaxLength(20) | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Alias") | ||||||
|  |                         .HasMaxLength(50) | ||||||
|  |                         .HasColumnType("character varying(50)") | ||||||
|  |                         .HasColumnName("alias"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableAnimations") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_animations"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableSounds") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_sounds"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("HashedPassword") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("hashed_password"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("InvitesEnabled") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("invites_enabled"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("OwnerUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("owner_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("GID") | ||||||
|  |                         .HasName("pk_groups"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("OwnerUID") | ||||||
|  |                         .HasDatabaseName("ix_groups_owner_uid"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("groups", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GroupGID") | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("BannedUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("banned_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("BannedByUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("banned_by_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("BannedOn") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("banned_on"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.GroupPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GroupGID") | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("GroupUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("group_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableAnimations") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_animations"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("DisableSounds") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("disable_sounds"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsModerator") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_moderator"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsPaused") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_paused"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsPinned") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_pinned"); | ||||||
|  |  | ||||||
|  |                     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("MareSynchronosShared.Models.GroupTempInvite", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("GroupGID") | ||||||
|  |                         .HasColumnType("character varying(20)") | ||||||
|  |                         .HasColumnName("group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Invite") | ||||||
|  |                         .HasMaxLength(64) | ||||||
|  |                         .HasColumnType("character varying(64)") | ||||||
|  |                         .HasColumnName("invite"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("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("MareSynchronosShared.Models.LodeStoneAuth", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<decimal>("DiscordId") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("numeric(20,0)") | ||||||
|  |                         .HasColumnName("discord_id"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("HashedLodestoneId") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("hashed_lodestone_id"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("LodestoneAuthString") | ||||||
|  |                         .HasMaxLength(100) | ||||||
|  |                         .HasColumnType("character varying(100)") | ||||||
|  |                         .HasColumnName("lodestone_auth_string"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime?>("StartedAt") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("started_at"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("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("MareSynchronosShared.Models.User", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UID") | ||||||
|  |                         .HasMaxLength(10) | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Alias") | ||||||
|  |                         .HasMaxLength(15) | ||||||
|  |                         .HasColumnType("character varying(15)") | ||||||
|  |                         .HasColumnName("alias"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsAdmin") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_admin"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsModerator") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_moderator"); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("LastLoggedIn") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("last_logged_in"); | ||||||
|  |  | ||||||
|  |                     b.Property<byte[]>("Timestamp") | ||||||
|  |                         .IsConcurrencyToken() | ||||||
|  |                         .ValueGeneratedOnAddOrUpdate() | ||||||
|  |                         .HasColumnType("bytea") | ||||||
|  |                         .HasColumnName("timestamp"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("UID") | ||||||
|  |                         .HasName("pk_users"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("users", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Base64ProfileImage") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("base64profile_image"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("FlaggedForReport") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("flagged_for_report"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsNSFW") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_nsfw"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("ProfileDisabled") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("profile_disabled"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("UserDescription") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("user_description"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("UserUID") | ||||||
|  |                         .HasName("pk_user_profile_data"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("user_profile_data", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("integer") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  |  | ||||||
|  |                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("ReportDate") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("report_date"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ReportReason") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("report_reason"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ReportedUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("reported_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ReportingUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("reporting_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_user_profile_data_reports"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("ReportedUserUID") | ||||||
|  |                         .HasDatabaseName("ix_user_profile_data_reports_reported_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("ReportingUserUID") | ||||||
|  |                         .HasDatabaseName("ix_user_profile_data_reports_reporting_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("user_profile_data_reports", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("PrimaryUserUID") | ||||||
|  |                         .HasConstraintName("fk_auth_users_primary_user_temp_id"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .HasConstraintName("fk_auth_users_user_temp_id1"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("PrimaryUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.ClientPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "OtherUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("OtherUserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_client_pairs_users_other_user_temp_id2"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_client_pairs_users_user_temp_id3"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("OtherUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.FileCache", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "Uploader") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UploaderUID") | ||||||
|  |                         .HasConstraintName("fk_file_caches_users_uploader_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Uploader"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.Group", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "Owner") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("OwnerUID") | ||||||
|  |                         .HasConstraintName("fk_groups_users_owner_temp_id8"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Owner"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupBan", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "BannedBy") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("BannedByUID") | ||||||
|  |                         .HasConstraintName("fk_group_bans_users_banned_by_temp_id5"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "BannedUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("BannedUserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_bans_users_banned_user_temp_id6"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupGID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_bans_groups_group_temp_id"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("BannedBy"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("BannedUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Group"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupPair", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupGID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_pairs_groups_group_temp_id1"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "GroupUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupUserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_pairs_users_group_user_temp_id7"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Group"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("GroupUser"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("GroupGID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_group_temp_invites_groups_group_gid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("Group"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .HasConstraintName("fk_lodestone_auth_users_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_user_profile_data_users_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "ReportedUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("ReportedUserUID") | ||||||
|  |                         .HasConstraintName("fk_user_profile_data_reports_users_reported_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "ReportingUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("ReportingUserUID") | ||||||
|  |                         .HasConstraintName("fk_user_profile_data_reports_users_reporting_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("ReportedUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("ReportingUser"); | ||||||
|  |                 }); | ||||||
|  | #pragma warning restore 612, 618 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,92 @@ | |||||||
|  | using System; | ||||||
|  | using Microsoft.EntityFrameworkCore.Migrations; | ||||||
|  | using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; | ||||||
|  |  | ||||||
|  | #nullable disable | ||||||
|  |  | ||||||
|  | namespace MareSynchronosServer.Migrations | ||||||
|  | { | ||||||
|  |     /// <inheritdoc /> | ||||||
|  |     public partial class UserProfileReports : Migration | ||||||
|  |     { | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Up(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.AddColumn<bool>( | ||||||
|  |                 name: "flagged_for_report", | ||||||
|  |                 table: "user_profile_data", | ||||||
|  |                 type: "boolean", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValue: false); | ||||||
|  |  | ||||||
|  |             migrationBuilder.AddColumn<bool>( | ||||||
|  |                 name: "is_nsfw", | ||||||
|  |                 table: "user_profile_data", | ||||||
|  |                 type: "boolean", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValue: false); | ||||||
|  |  | ||||||
|  |             migrationBuilder.AddColumn<bool>( | ||||||
|  |                 name: "profile_disabled", | ||||||
|  |                 table: "user_profile_data", | ||||||
|  |                 type: "boolean", | ||||||
|  |                 nullable: false, | ||||||
|  |                 defaultValue: false); | ||||||
|  |  | ||||||
|  |             migrationBuilder.CreateTable( | ||||||
|  |                 name: "user_profile_data_reports", | ||||||
|  |                 columns: table => new | ||||||
|  |                 { | ||||||
|  |                     id = table.Column<int>(type: "integer", nullable: false) | ||||||
|  |                         .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), | ||||||
|  |                     report_date = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), | ||||||
|  |                     reported_user_uid = table.Column<string>(type: "character varying(10)", nullable: true), | ||||||
|  |                     reporting_user_uid = table.Column<string>(type: "character varying(10)", nullable: true), | ||||||
|  |                     report_reason = table.Column<string>(type: "text", nullable: true) | ||||||
|  |                 }, | ||||||
|  |                 constraints: table => | ||||||
|  |                 { | ||||||
|  |                     table.PrimaryKey("pk_user_profile_data_reports", x => x.id); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "fk_user_profile_data_reports_users_reported_user_uid", | ||||||
|  |                         column: x => x.reported_user_uid, | ||||||
|  |                         principalTable: "users", | ||||||
|  |                         principalColumn: "uid"); | ||||||
|  |                     table.ForeignKey( | ||||||
|  |                         name: "fk_user_profile_data_reports_users_reporting_user_uid", | ||||||
|  |                         column: x => x.reporting_user_uid, | ||||||
|  |                         principalTable: "users", | ||||||
|  |                         principalColumn: "uid"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "ix_user_profile_data_reports_reported_user_uid", | ||||||
|  |                 table: "user_profile_data_reports", | ||||||
|  |                 column: "reported_user_uid"); | ||||||
|  |  | ||||||
|  |             migrationBuilder.CreateIndex( | ||||||
|  |                 name: "ix_user_profile_data_reports_reporting_user_uid", | ||||||
|  |                 table: "user_profile_data_reports", | ||||||
|  |                 column: "reporting_user_uid"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         /// <inheritdoc /> | ||||||
|  |         protected override void Down(MigrationBuilder migrationBuilder) | ||||||
|  |         { | ||||||
|  |             migrationBuilder.DropTable( | ||||||
|  |                 name: "user_profile_data_reports"); | ||||||
|  |  | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "flagged_for_report", | ||||||
|  |                 table: "user_profile_data"); | ||||||
|  |  | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "is_nsfw", | ||||||
|  |                 table: "user_profile_data"); | ||||||
|  |  | ||||||
|  |             migrationBuilder.DropColumn( | ||||||
|  |                 name: "profile_disabled", | ||||||
|  |                 table: "user_profile_data"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -17,7 +17,7 @@ namespace MareSynchronosServer.Migrations | |||||||
|         { |         { | ||||||
| #pragma warning disable 612, 618 | #pragma warning disable 612, 618 | ||||||
|             modelBuilder |             modelBuilder | ||||||
|                 .HasAnnotation("ProductVersion", "7.0.3") |                 .HasAnnotation("ProductVersion", "7.0.4") | ||||||
|                 .HasAnnotation("Relational:MaxIdentifierLength", 63); |                 .HasAnnotation("Relational:MaxIdentifierLength", 63); | ||||||
|  |  | ||||||
|             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); |             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); | ||||||
| @@ -415,6 +415,75 @@ namespace MareSynchronosServer.Migrations | |||||||
|                     b.ToTable("users", (string)null); |                     b.ToTable("users", (string)null); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<string>("UserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("Base64ProfileImage") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("base64profile_image"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("FlaggedForReport") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("flagged_for_report"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("IsNSFW") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("is_nsfw"); | ||||||
|  |  | ||||||
|  |                     b.Property<bool>("ProfileDisabled") | ||||||
|  |                         .HasColumnType("boolean") | ||||||
|  |                         .HasColumnName("profile_disabled"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("UserDescription") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("user_description"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("UserUID") | ||||||
|  |                         .HasName("pk_user_profile_data"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("user_profile_data", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => | ||||||
|  |                 { | ||||||
|  |                     b.Property<int>("Id") | ||||||
|  |                         .ValueGeneratedOnAdd() | ||||||
|  |                         .HasColumnType("integer") | ||||||
|  |                         .HasColumnName("id"); | ||||||
|  |  | ||||||
|  |                     NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); | ||||||
|  |  | ||||||
|  |                     b.Property<DateTime>("ReportDate") | ||||||
|  |                         .HasColumnType("timestamp with time zone") | ||||||
|  |                         .HasColumnName("report_date"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ReportReason") | ||||||
|  |                         .HasColumnType("text") | ||||||
|  |                         .HasColumnName("report_reason"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ReportedUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("reported_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Property<string>("ReportingUserUID") | ||||||
|  |                         .HasColumnType("character varying(10)") | ||||||
|  |                         .HasColumnName("reporting_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasKey("Id") | ||||||
|  |                         .HasName("pk_user_profile_data_reports"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("ReportedUserUID") | ||||||
|  |                         .HasDatabaseName("ix_user_profile_data_reports_reported_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasIndex("ReportingUserUID") | ||||||
|  |                         .HasDatabaseName("ix_user_profile_data_reports_reporting_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.ToTable("user_profile_data_reports", (string)null); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|             modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => |             modelBuilder.Entity("MareSynchronosShared.Models.Auth", b => | ||||||
|                 { |                 { | ||||||
|                     b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") |                     b.HasOne("MareSynchronosShared.Models.User", "PrimaryUser") | ||||||
| @@ -543,6 +612,35 @@ namespace MareSynchronosServer.Migrations | |||||||
|  |  | ||||||
|                     b.Navigation("User"); |                     b.Navigation("User"); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("UserUID") | ||||||
|  |                         .OnDelete(DeleteBehavior.Cascade) | ||||||
|  |                         .IsRequired() | ||||||
|  |                         .HasConstraintName("fk_user_profile_data_users_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("User"); | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileDataReport", b => | ||||||
|  |                 { | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "ReportedUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("ReportedUserUID") | ||||||
|  |                         .HasConstraintName("fk_user_profile_data_reports_users_reported_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.HasOne("MareSynchronosShared.Models.User", "ReportingUser") | ||||||
|  |                         .WithMany() | ||||||
|  |                         .HasForeignKey("ReportingUserUID") | ||||||
|  |                         .HasConstraintName("fk_user_profile_data_reports_users_reporting_user_uid"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("ReportedUser"); | ||||||
|  |  | ||||||
|  |                     b.Navigation("ReportingUser"); | ||||||
|  |                 }); | ||||||
| #pragma warning restore 612, 618 | #pragma warning restore 612, 618 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -0,0 +1,20 @@ | |||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.ComponentModel.DataAnnotations.Schema; | ||||||
|  |  | ||||||
|  | namespace MareSynchronosShared.Models; | ||||||
|  |  | ||||||
|  | public class UserProfileData | ||||||
|  | { | ||||||
|  |     public string Base64ProfileImage { get; set; } | ||||||
|  |     public bool FlaggedForReport { get; set; } | ||||||
|  |     public bool IsNSFW { get; set; } | ||||||
|  |     public bool ProfileDisabled { get; set; } | ||||||
|  |     public User User { get; set; } | ||||||
|  |  | ||||||
|  |     public string UserDescription { get; set; } | ||||||
|  |  | ||||||
|  |     [Required] | ||||||
|  |     [Key] | ||||||
|  |     [ForeignKey(nameof(User))] | ||||||
|  |     public string UserUID { get; set; } | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | using System.ComponentModel.DataAnnotations; | ||||||
|  | using System.ComponentModel.DataAnnotations.Schema; | ||||||
|  |  | ||||||
|  | namespace MareSynchronosShared.Models; | ||||||
|  |  | ||||||
|  | public class UserProfileDataReport | ||||||
|  | { | ||||||
|  |     [Key] | ||||||
|  |     [DatabaseGenerated(DatabaseGeneratedOption.Identity)] | ||||||
|  |     public int Id { get; set; } | ||||||
|  |  | ||||||
|  |     public DateTime ReportDate { get; set; } | ||||||
|  |     public User ReportedUser { get; set; } | ||||||
|  |  | ||||||
|  |     [ForeignKey(nameof(ReportedUser))] | ||||||
|  |     public string ReportedUserUID { get; set; } | ||||||
|  |  | ||||||
|  |     public User ReportingUser { get; set; } | ||||||
|  |  | ||||||
|  |     [ForeignKey(nameof(ReportingUser))] | ||||||
|  |     public string ReportingUserUID { get; set; } | ||||||
|  |  | ||||||
|  |     public string ReportReason { get; set; } | ||||||
|  | } | ||||||
| @@ -6,11 +6,12 @@ namespace MareSynchronosShared.Utils; | |||||||
|  |  | ||||||
| public class MareConfigurationBase : IMareConfiguration | public class MareConfigurationBase : IMareConfiguration | ||||||
| { | { | ||||||
|     public Uri MainServerAddress { get; set; } |  | ||||||
|     public int DbContextPoolSize { get; set; } = 100; |     public int DbContextPoolSize { get; set; } = 100; | ||||||
|     public string ShardName { get; set; } = string.Empty; |  | ||||||
|     public int MetricsPort { get; set; } |  | ||||||
|     public string Jwt { get; set; } = string.Empty; |     public string Jwt { get; set; } = string.Empty; | ||||||
|  |     public Uri MainServerAddress { get; set; } | ||||||
|  |     public int MetricsPort { get; set; } | ||||||
|  |     public string RedisConnectionString { get; set; } = string.Empty; | ||||||
|  |     public string ShardName { get; set; } = string.Empty; | ||||||
|  |  | ||||||
|     public T GetValue<T>(string key) |     public T GetValue<T>(string key) | ||||||
|     { |     { | ||||||
| @@ -41,6 +42,7 @@ public class MareConfigurationBase : IMareConfiguration | |||||||
|         StringBuilder sb = new(); |         StringBuilder sb = new(); | ||||||
|         sb.AppendLine(base.ToString()); |         sb.AppendLine(base.ToString()); | ||||||
|         sb.AppendLine($"{nameof(MainServerAddress)} => {MainServerAddress}"); |         sb.AppendLine($"{nameof(MainServerAddress)} => {MainServerAddress}"); | ||||||
|  |         sb.AppendLine($"{nameof(RedisConnectionString)} => {RedisConnectionString}"); | ||||||
|         sb.AppendLine($"{nameof(ShardName)} => {ShardName}"); |         sb.AppendLine($"{nameof(ShardName)} => {ShardName}"); | ||||||
|         sb.AppendLine($"{nameof(DbContextPoolSize)} => {DbContextPoolSize}"); |         sb.AppendLine($"{nameof(DbContextPoolSize)} => {DbContextPoolSize}"); | ||||||
|         return sb.ToString(); |         return sb.ToString(); | ||||||
|   | |||||||
| @@ -4,23 +4,29 @@ namespace MareSynchronosShared.Utils; | |||||||
|  |  | ||||||
| public class ServerConfiguration : MareConfigurationAuthBase | public class ServerConfiguration : MareConfigurationAuthBase | ||||||
| { | { | ||||||
|     public string RedisConnectionString { get; set; } = string.Empty; |  | ||||||
|     public int RedisPool { get; set; } = 50; |  | ||||||
|     [RemoteConfiguration] |  | ||||||
|     public Version ExpectedClientVersion { get; set; } = new Version(0, 0, 0); |  | ||||||
|     [RemoteConfiguration] |     [RemoteConfiguration] | ||||||
|     public Uri CdnFullUrl { get; set; } = null; |     public Uri CdnFullUrl { get; set; } = null; | ||||||
|  |  | ||||||
|  |     [RemoteConfiguration] | ||||||
|  |     public Version ExpectedClientVersion { get; set; } = new Version(0, 0, 0); | ||||||
|  |  | ||||||
|     [RemoteConfiguration] |     [RemoteConfiguration] | ||||||
|     public int MaxExistingGroupsByUser { get; set; } = 3; |     public int MaxExistingGroupsByUser { get; set; } = 3; | ||||||
|     [RemoteConfiguration] |  | ||||||
|     public int MaxJoinedGroupsByUser { get; set; } = 6; |  | ||||||
|     [RemoteConfiguration] |     [RemoteConfiguration] | ||||||
|     public int MaxGroupUserCount { get; set; } = 100; |     public int MaxGroupUserCount { get; set; } = 100; | ||||||
|  |  | ||||||
|  |     [RemoteConfiguration] | ||||||
|  |     public int MaxJoinedGroupsByUser { get; set; } = 6; | ||||||
|  |  | ||||||
|     [RemoteConfiguration] |     [RemoteConfiguration] | ||||||
|     public bool PurgeUnusedAccounts { get; set; } = false; |     public bool PurgeUnusedAccounts { get; set; } = false; | ||||||
|  |  | ||||||
|     [RemoteConfiguration] |     [RemoteConfiguration] | ||||||
|     public int PurgeUnusedAccountsPeriodInDays { get; set; } = 14; |     public int PurgeUnusedAccountsPeriodInDays { get; set; } = 14; | ||||||
|  |  | ||||||
|  |     public int RedisPool { get; set; } = 50; | ||||||
|  |  | ||||||
|     public override string ToString() |     public override string ToString() | ||||||
|     { |     { | ||||||
|         StringBuilder sb = new(); |         StringBuilder sb = new(); | ||||||
|   | |||||||
| @@ -5,8 +5,9 @@ namespace MareSynchronosShared.Utils; | |||||||
| public class ServicesConfiguration : MareConfigurationBase | public class ServicesConfiguration : MareConfigurationBase | ||||||
| { | { | ||||||
|     public string DiscordBotToken { get; set; } = string.Empty; |     public string DiscordBotToken { get; set; } = string.Empty; | ||||||
|     public Uri MainServerGrpcAddress { get; set; } = null; |  | ||||||
|     public ulong? DiscordChannelForMessages { get; set; } = null; |     public ulong? DiscordChannelForMessages { get; set; } = null; | ||||||
|  |     public ulong? DiscordChannelForReports { get; set; } = null; | ||||||
|  |     public Uri MainServerGrpcAddress { get; set; } = null; | ||||||
|  |  | ||||||
|     public override string ToString() |     public override string ToString() | ||||||
|     { |     { | ||||||
| @@ -14,6 +15,8 @@ public class ServicesConfiguration : MareConfigurationBase | |||||||
|         sb.AppendLine(base.ToString()); |         sb.AppendLine(base.ToString()); | ||||||
|         sb.AppendLine($"{nameof(DiscordBotToken)} => {DiscordBotToken}"); |         sb.AppendLine($"{nameof(DiscordBotToken)} => {DiscordBotToken}"); | ||||||
|         sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}"); |         sb.AppendLine($"{nameof(MainServerGrpcAddress)} => {MainServerGrpcAddress}"); | ||||||
|  |         sb.AppendLine($"{nameof(DiscordChannelForMessages)} => {DiscordChannelForMessages}"); | ||||||
|  |         sb.AppendLine($"{nameof(DiscordChannelForReports)} => {DiscordChannelForReports}"); | ||||||
|         return sb.ToString(); |         return sb.ToString(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -7,17 +7,54 @@ namespace MareSynchronosShared.Utils; | |||||||
|  |  | ||||||
| public static class SharedDbFunctions | public static class SharedDbFunctions | ||||||
| { | { | ||||||
|  |     public static async Task<(bool, string)> MigrateOrDeleteGroup(MareDbContext context, Group group, List<GroupPair> groupPairs, int maxGroupsByUser) | ||||||
|  |     { | ||||||
|  |         bool groupHasMigrated = false; | ||||||
|  |         string newOwner = string.Empty; | ||||||
|  |         foreach (var potentialNewOwner in groupPairs.OrderByDescending(p => p.IsModerator).ThenByDescending(p => p.IsPinned).ToList()) | ||||||
|  |         { | ||||||
|  |             groupHasMigrated = await TryMigrateGroup(context, group, potentialNewOwner.GroupUserUID, maxGroupsByUser).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |             if (groupHasMigrated) | ||||||
|  |             { | ||||||
|  |                 newOwner = potentialNewOwner.GroupUserUID; | ||||||
|  |                 potentialNewOwner.IsPinned = true; | ||||||
|  |                 potentialNewOwner.IsModerator = false; | ||||||
|  |  | ||||||
|  |                 await context.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (!groupHasMigrated) | ||||||
|  |         { | ||||||
|  |             context.GroupPairs.RemoveRange(groupPairs); | ||||||
|  |             context.Groups.Remove(group); | ||||||
|  |  | ||||||
|  |             await context.SaveChangesAsync().ConfigureAwait(false); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return (groupHasMigrated, newOwner); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public static async Task PurgeUser(ILogger _logger, User user, MareDbContext dbContext, int maxGroupsByUser) |     public static async Task PurgeUser(ILogger _logger, User user, MareDbContext dbContext, int maxGroupsByUser) | ||||||
|     { |     { | ||||||
|         _logger.LogInformation("Purging user: {uid}", user.UID); |         _logger.LogInformation("Purging user: {uid}", user.UID); | ||||||
|  |  | ||||||
|         var lodestone = dbContext.LodeStoneAuth.SingleOrDefault(a => a.User.UID == user.UID); |         var lodestone = dbContext.LodeStoneAuth.SingleOrDefault(a => a.User.UID == user.UID); | ||||||
|  |  | ||||||
|  |         var userProfileData = await dbContext.UserProfileData.SingleOrDefaultAsync(u => u.UserUID == user.UID).ConfigureAwait(false); | ||||||
|  |  | ||||||
|         if (lodestone != null) |         if (lodestone != null) | ||||||
|         { |         { | ||||||
|             dbContext.Remove(lodestone); |             dbContext.Remove(lodestone); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (userProfileData != null) | ||||||
|  |         { | ||||||
|  |             dbContext.Remove(userProfileData); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         var auth = dbContext.Auth.Single(a => a.UserUID == user.UID); |         var auth = dbContext.Auth.Single(a => a.UserUID == user.UID); | ||||||
|  |  | ||||||
|         var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList(); |         var userFiles = dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == user.UID).ToList(); | ||||||
| @@ -63,36 +100,6 @@ public static class SharedDbFunctions | |||||||
|         await dbContext.SaveChangesAsync().ConfigureAwait(false); |         await dbContext.SaveChangesAsync().ConfigureAwait(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static async Task<(bool, string)> MigrateOrDeleteGroup(MareDbContext context, Group group, List<GroupPair> groupPairs, int maxGroupsByUser) |  | ||||||
|     { |  | ||||||
|         bool groupHasMigrated = false; |  | ||||||
|         string newOwner = string.Empty; |  | ||||||
|         foreach (var potentialNewOwner in groupPairs.OrderByDescending(p => p.IsModerator).ThenByDescending(p => p.IsPinned).ToList()) |  | ||||||
|         { |  | ||||||
|             groupHasMigrated = await TryMigrateGroup(context, group, potentialNewOwner.GroupUserUID, maxGroupsByUser).ConfigureAwait(false); |  | ||||||
|  |  | ||||||
|             if (groupHasMigrated) |  | ||||||
|             { |  | ||||||
|                 newOwner = potentialNewOwner.GroupUserUID; |  | ||||||
|                 potentialNewOwner.IsPinned = true; |  | ||||||
|                 potentialNewOwner.IsModerator = false; |  | ||||||
|  |  | ||||||
|                 await context.SaveChangesAsync().ConfigureAwait(false); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!groupHasMigrated) |  | ||||||
|         { |  | ||||||
|             context.GroupPairs.RemoveRange(groupPairs); |  | ||||||
|             context.Groups.Remove(group); |  | ||||||
|  |  | ||||||
|             await context.SaveChangesAsync().ConfigureAwait(false); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return (groupHasMigrated, newOwner); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static async Task<bool> TryMigrateGroup(MareDbContext context, Group group, string potentialNewOwnerUid, int maxGroupsByUser) |     private static async Task<bool> TryMigrateGroup(MareDbContext context, Group group, string potentialNewOwnerUid, int maxGroupsByUser) | ||||||
|     { |     { | ||||||
|         var newOwnerOwnedGroups = await context.Groups.CountAsync(g => g.OwnerUID == potentialNewOwnerUid).ConfigureAwait(false); |         var newOwnerOwnedGroups = await context.Groups.CountAsync(g => g.OwnerUID == potentialNewOwnerUid).ConfigureAwait(false); | ||||||
|   | |||||||
| @@ -23,8 +23,6 @@ | |||||||
|       <PrivateAssets>all</PrivateAssets> |       <PrivateAssets>all</PrivateAssets> | ||||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||||
|     </PackageReference> |     </PackageReference> | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="7.0.4" /> |  | ||||||
|     <PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="7.0.4" /> |  | ||||||
|     <PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" /> |     <PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 rootdarkarchon
					rootdarkarchon