Compare commits
	
		
			10 Commits
		
	
	
		
			73fc5bea2c
			...
			8cc2dd2330
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8cc2dd2330 | ||
|   | ee125aaa85 | ||
|   | 5c7d5ff29a | ||
|   | 0b1e08181d | ||
|   | 17d10e2b65 | ||
|   | fe0835adf8 | ||
|   | bb03885e2d | ||
|   | 4b0c5f6199 | ||
|   | c12559afcc | ||
|   | 82a40d543a | 
							
								
								
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							| @@ -1,3 +1,3 @@ | ||||
| [submodule "MareAPI"] | ||||
| 	path = MareAPI | ||||
| 	url = https://github.com/loporrit/MareAPI.git | ||||
| 	url = https://git.lop-sync.com/huggingway/LopAPI.git | ||||
|   | ||||
							
								
								
									
										2
									
								
								MareAPI
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								MareAPI
									
									
									
									
									
								
							 Submodule MareAPI updated: 4d8c380dab...b2f4453b79
									
								
							| @@ -1,4 +1,6 @@ | ||||
| using MareSynchronos.API.Routes; | ||||
| using MareSynchronos.API.Dto; | ||||
| using MareSynchronos.API.Dto.Account; | ||||
| using MareSynchronos.API.Routes; | ||||
| using MareSynchronosAuthService.Services; | ||||
| using MareSynchronosShared; | ||||
| using MareSynchronosShared.Data; | ||||
| @@ -124,13 +126,54 @@ public class JwtController : Controller | ||||
|         return Content(token.RawData); | ||||
|     } | ||||
|  | ||||
|     [AllowAnonymous] | ||||
|     [HttpPost(MareAuth.Auth_CreateIdentV2)] | ||||
|     public async Task<IActionResult> CreateTokenV2(string auth, string charaIdent) | ||||
|     { | ||||
|         var tokenResponse = await CreateToken(auth, charaIdent); | ||||
|         var tokenContent = tokenResponse as ContentResult; | ||||
|         if (tokenContent == null) | ||||
|             return tokenResponse; | ||||
|         return Json(new AuthReplyDto | ||||
|         { | ||||
|             Token = tokenContent.Content, | ||||
|             WellKnown = _configuration.GetValueOrDefault(nameof(AuthServiceConfiguration.WellKnown), string.Empty), | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     [AllowAnonymous] | ||||
|     [HttpPost(MareAuth.Auth_Register)] | ||||
|     public async Task<IActionResult> Register() | ||||
|     { | ||||
|         var ua = HttpContext.Request.Headers["User-Agent"][0] ?? "-"; | ||||
|         var ip = _accessor.GetIpAddress(); | ||||
|         return Json(await _accountRegistrationService.RegisterAccountAsync(ua, ip)); | ||||
|  | ||||
|         // Legacy endpoint: generate a secret key for the user | ||||
|         var computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString()); | ||||
|         var hashedKey = StringUtils.Sha256String(computedHash); | ||||
|  | ||||
|         var dto = await _accountRegistrationService.RegisterAccountAsync(ua, ip, hashedKey); | ||||
|  | ||||
|         return Json(new RegisterReplyDto() | ||||
|         { | ||||
|             Success = dto.Success, | ||||
|             ErrorMessage = dto.ErrorMessage, | ||||
|             UID = dto.UID, | ||||
|             SecretKey = computedHash | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     [AllowAnonymous] | ||||
|     [HttpPost(MareAuth.Auth_RegisterV2)] | ||||
|     public async Task<IActionResult> RegisterV2(string hashedSecretKey) | ||||
|     { | ||||
|         if (string.IsNullOrEmpty(hashedSecretKey)) return BadRequest("No HashedSecretKey"); | ||||
|         if (hashedSecretKey.Length != 64) return BadRequest("Bad HashedSecretKey"); | ||||
|         if (!hashedSecretKey.All(char.IsAsciiHexDigitUpper)) return BadRequest("Bad HashedSecretKey"); | ||||
|  | ||||
|         var ua = HttpContext.Request.Headers["User-Agent"][0] ?? "-"; | ||||
|         var ip = _accessor.GetIpAddress(); | ||||
|         return Json(await _accountRegistrationService.RegisterAccountAsync(ua, ip, hashedSecretKey)); | ||||
|     } | ||||
|  | ||||
|     private JwtSecurityToken CreateToken(IEnumerable<Claim> authClaims) | ||||
|   | ||||
| @@ -45,9 +45,9 @@ public class AccountRegistrationService | ||||
|         _serviceScopeFactory = serviceScopeFactory; | ||||
|     } | ||||
|  | ||||
|     public async Task<RegisterReplyDto> RegisterAccountAsync(string ua, string ip) | ||||
|     public async Task<RegisterReplyV2Dto> RegisterAccountAsync(string ua, string ip, string hashedSecretKey) | ||||
|     { | ||||
| 		var reply = new RegisterReplyDto(); | ||||
| 		var reply = new RegisterReplyV2Dto(); | ||||
|  | ||||
| 		if (!_registrationUserAgentRegex.Match(ua).Success) | ||||
|         { | ||||
| @@ -99,10 +99,9 @@ public class AccountRegistrationService | ||||
|  | ||||
|         user.LastLoggedIn = DateTime.UtcNow; | ||||
|  | ||||
|         var computedHash = StringUtils.Sha256String(StringUtils.GenerateRandomString(64) + DateTime.UtcNow.ToString()); | ||||
|         var auth = new Auth() | ||||
|         { | ||||
|             HashedKey = StringUtils.Sha256String(computedHash), | ||||
|             HashedKey = hashedSecretKey, | ||||
|             User = user, | ||||
|         }; | ||||
|  | ||||
| @@ -115,7 +114,6 @@ public class AccountRegistrationService | ||||
|  | ||||
|         reply.Success = true; | ||||
|         reply.UID = user.UID; | ||||
|         reply.SecretKey = computedHash; | ||||
|  | ||||
|         RecordIpRegistration(ip); | ||||
|  | ||||
|   | ||||
| @@ -124,6 +124,55 @@ public partial class MareHub | ||||
|         return [.. ownCharaData.Select(GetCharaDataFullDto)]; | ||||
|     } | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task<CharaDataFullDto?> CharaDataAttemptRestore(string id) | ||||
|     { | ||||
|         _logger.LogCallInfo(MareHubLogger.Args(id)); | ||||
|         var charaData = await DbContext.CharaData | ||||
|             .Include(u => u.Files) | ||||
|             .Include(u => u.FileSwaps) | ||||
|             .Include(u => u.OriginalFiles) | ||||
|             .Include(u => u.AllowedIndividiuals) | ||||
|             .ThenInclude(u => u.AllowedUser) | ||||
|             .Include(u => u.AllowedIndividiuals) | ||||
|             .ThenInclude(u => u.AllowedGroup) | ||||
|             .Include(u => u.Poses) | ||||
|             .AsSplitQuery() | ||||
|             .SingleOrDefaultAsync(s => s.Id == id && s.UploaderUID == UserUID) | ||||
|             .ConfigureAwait(false); | ||||
|         if (charaData == null) | ||||
|             return null; | ||||
|  | ||||
|         var currentHashes = charaData.Files.Select(f => f.FileCacheHash).ToList(); | ||||
|         var missingFiles = charaData.OriginalFiles.Where(c => !currentHashes.Contains(c.Hash, StringComparer.Ordinal)).ToList(); | ||||
|  | ||||
|         // now let's see what's on the db still | ||||
|         var existingDbFiles = await DbContext.Files | ||||
|             .Where(f => missingFiles.Select(k => k.Hash).Distinct().Contains(f.Hash)) | ||||
|             .ToListAsync() | ||||
|             .ConfigureAwait(false); | ||||
|  | ||||
|         // now shove it all back into the db | ||||
|         foreach (var dbFile in existingDbFiles) | ||||
|         { | ||||
|             var missingFileEntry = missingFiles.First(f => string.Equals(f.Hash, dbFile.Hash, StringComparison.Ordinal)); | ||||
|             charaData.Files.Add(new CharaDataFile() | ||||
|             { | ||||
|                 FileCache = dbFile, | ||||
|                 GamePath = missingFileEntry.GamePath, | ||||
|                 Parent = charaData | ||||
|             }); | ||||
|             missingFiles.Remove(missingFileEntry); | ||||
|         } | ||||
|  | ||||
|         if (existingDbFiles.Any()) | ||||
|         { | ||||
|             await DbContext.SaveChangesAsync().ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         return GetCharaDataFullDto(charaData); | ||||
|     } | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task<List<CharaDataMetaInfoDto>> CharaDataGetShared() | ||||
|     { | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| using MareSynchronos.API.Data.Enum; | ||||
| using MareSynchronos.API.Data; | ||||
| using MareSynchronos.API.Data.Enum; | ||||
| using MareSynchronos.API.Dto; | ||||
| using MareSynchronos.API.Dto.CharaData; | ||||
| using MareSynchronos.API.Dto.Chat; | ||||
| using MareSynchronos.API.Dto.Group; | ||||
| using MareSynchronos.API.Dto.User; | ||||
| @@ -51,5 +53,11 @@ namespace MareSynchronosServer.Hubs | ||||
|         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_GposeLobbyJoin(UserData userData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||
|         public Task Client_GposeLobbyLeave(UserData userData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||
|         public Task Client_GposeLobbyPushCharacterData(CharaDataDownloadDto charaDownloadDto) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||
|         public Task Client_GposeLobbyPushPoseData(UserData userData, PoseData poseData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||
|         public Task Client_GposeLobbyPushWorldData(UserData userData, WorldData worldData) => throw new PlatformNotSupportedException("Calling clientside method on server not supported"); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,155 @@ | ||||
| using MareSynchronos.API.Data; | ||||
| using MareSynchronos.API.Dto.CharaData; | ||||
| using MareSynchronosServer.Utils; | ||||
| using MareSynchronosShared.Metrics; | ||||
| using MareSynchronosShared.Utils; | ||||
| using Microsoft.AspNetCore.Authorization; | ||||
| using Microsoft.AspNetCore.SignalR; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
|  | ||||
| namespace MareSynchronosServer.Hubs; | ||||
|  | ||||
| public partial class MareHub | ||||
| { | ||||
|     private async Task<string?> GetUserGposeLobby() | ||||
|     { | ||||
|         return await _redis.GetAsync<string>(GposeLobbyUser).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     private async Task<List<string>> GetUsersInLobby(string lobbyId, bool includeSelf = false) | ||||
|     { | ||||
|         var users = await _redis.GetAsync<List<string>>($"GposeLobby:{lobbyId}").ConfigureAwait(false); | ||||
|         return users?.Where(u => includeSelf || !string.Equals(u, UserUID, StringComparison.Ordinal)).ToList() ?? []; | ||||
|     } | ||||
|  | ||||
|     private async Task AddUserToLobby(string lobbyId, List<string> priorUsers) | ||||
|     { | ||||
|         _mareMetrics.IncGauge(MetricsAPI.GaugeGposeLobbyUsers); | ||||
|         if (priorUsers.Count == 0) | ||||
|             _mareMetrics.IncGauge(MetricsAPI.GaugeGposeLobbies); | ||||
|  | ||||
|         await _redis.AddAsync(GposeLobbyUser, lobbyId).ConfigureAwait(false); | ||||
|         await _redis.AddAsync($"GposeLobby:{lobbyId}", priorUsers.Concat([UserUID])).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     private async Task RemoveUserFromLobby(string lobbyId, List<string> priorUsers) | ||||
|     { | ||||
|         await _redis.RemoveAsync(GposeLobbyUser).ConfigureAwait(false); | ||||
|  | ||||
|         _mareMetrics.DecGauge(MetricsAPI.GaugeGposeLobbyUsers); | ||||
|  | ||||
|         if (priorUsers.Count == 1) | ||||
|         { | ||||
|             await _redis.RemoveAsync($"GposeLobby:{lobbyId}").ConfigureAwait(false); | ||||
|             _mareMetrics.DecGauge(MetricsAPI.GaugeGposeLobbies); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             priorUsers.Remove(UserUID); | ||||
|             await _redis.AddAsync($"GposeLobby:{lobbyId}", priorUsers).ConfigureAwait(false); | ||||
|             await Clients.Users(priorUsers).Client_GposeLobbyLeave(new(UserUID)).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private string GposeLobbyUser => $"GposeLobbyUser:{UserUID}"; | ||||
|  | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task<string> GposeLobbyCreate() | ||||
|     { | ||||
|         _logger.LogCallInfo(); | ||||
|         var alreadyInLobby = await GetUserGposeLobby().ConfigureAwait(false); | ||||
|         if (!string.IsNullOrEmpty(alreadyInLobby)) | ||||
|         { | ||||
|             throw new HubException("Already in GPose Lobby, cannot join another"); | ||||
|         } | ||||
|  | ||||
|         string lobbyId = string.Empty; | ||||
|         while (string.IsNullOrEmpty(lobbyId)) | ||||
|         { | ||||
|             lobbyId = StringUtils.GenerateRandomString(30, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); | ||||
|             var result = await _redis.GetAsync<List<string>>($"GposeLobby:{lobbyId}").ConfigureAwait(false); | ||||
|             if (result != null) | ||||
|                 lobbyId = string.Empty; | ||||
|         } | ||||
|  | ||||
|         await AddUserToLobby(lobbyId, []).ConfigureAwait(false); | ||||
|  | ||||
|         return lobbyId; | ||||
|     } | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task<List<UserData>> GposeLobbyJoin(string lobbyId) | ||||
|     { | ||||
|         _logger.LogCallInfo(); | ||||
|         var existingLobbyId = await GetUserGposeLobby().ConfigureAwait(false); | ||||
|         if (!string.IsNullOrEmpty(existingLobbyId)) | ||||
|             await GposeLobbyLeave().ConfigureAwait(false); | ||||
|  | ||||
|         var lobbyUsers = await GetUsersInLobby(lobbyId).ConfigureAwait(false); | ||||
|         if (!lobbyUsers.Any()) | ||||
|             return []; | ||||
|  | ||||
|         await AddUserToLobby(lobbyId, lobbyUsers).ConfigureAwait(false); | ||||
|  | ||||
|         var user = await DbContext.Users.SingleAsync(u => u.UID == UserUID).ConfigureAwait(false); | ||||
|         await Clients.Users(lobbyUsers.Where(u => !string.Equals(u, UserUID, StringComparison.Ordinal))) | ||||
|             .Client_GposeLobbyJoin(user.ToUserData()).ConfigureAwait(false); | ||||
|  | ||||
|         var users = await DbContext.Users.Where(u => lobbyUsers.Contains(u.UID)) | ||||
|             .Select(u => u.ToUserData()) | ||||
|             .ToListAsync() | ||||
|             .ConfigureAwait(false); | ||||
|  | ||||
|         return users; | ||||
|     } | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task<bool> GposeLobbyLeave() | ||||
|     { | ||||
|         var lobbyId = await GetUserGposeLobby().ConfigureAwait(false); | ||||
|         if (string.IsNullOrEmpty(lobbyId)) | ||||
|             return true; | ||||
|  | ||||
|         _logger.LogCallInfo(); | ||||
|  | ||||
|         var lobbyUsers = await GetUsersInLobby(lobbyId, true).ConfigureAwait(false); | ||||
|         await RemoveUserFromLobby(lobbyId, lobbyUsers).ConfigureAwait(false); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task GposeLobbyPushCharacterData(CharaDataDownloadDto charaDataDownloadDto) | ||||
|     { | ||||
|         _logger.LogCallInfo(); | ||||
|         var lobbyId = await GetUserGposeLobby().ConfigureAwait(false); | ||||
|         if (string.IsNullOrEmpty(lobbyId)) | ||||
|             return; | ||||
|  | ||||
|         var lobbyUsers = await GetUsersInLobby(lobbyId).ConfigureAwait(false); | ||||
|         await Clients.Users(lobbyUsers).Client_GposeLobbyPushCharacterData(charaDataDownloadDto).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task GposeLobbyPushPoseData(PoseData poseData) | ||||
|     { | ||||
|         _logger.LogCallInfo(); | ||||
|         var lobbyId = await GetUserGposeLobby().ConfigureAwait(false); | ||||
|         if (string.IsNullOrEmpty(lobbyId)) | ||||
|             return; | ||||
|  | ||||
|         await _gPoseLobbyDistributionService.PushPoseData(lobbyId, UserUID, poseData).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     [Authorize(Policy = "Identified")] | ||||
|     public async Task GposeLobbyPushWorldData(WorldData worldData) | ||||
|     { | ||||
|         _logger.LogCallInfo(); | ||||
|         var lobbyId = await GetUserGposeLobby().ConfigureAwait(false); | ||||
|         if (string.IsNullOrEmpty(lobbyId)) | ||||
|             return; | ||||
|  | ||||
|         await _gPoseLobbyDistributionService.PushWorldData(lobbyId, UserUID, worldData).ConfigureAwait(false); | ||||
|     } | ||||
| } | ||||
| @@ -135,7 +135,6 @@ public partial class MareHub | ||||
|         var prevOwner = await DbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == dto.Group.GID && g.GroupUserUID == UserUID).ConfigureAwait(false); | ||||
|         prevOwner.IsPinned = false; | ||||
|         group.Owner = newOwnerPair.GroupUser; | ||||
|         group.Alias = null; | ||||
|         newOwnerPair.IsPinned = true; | ||||
|         newOwnerPair.IsModerator = false; | ||||
|         await DbContext.SaveChangesAsync().ConfigureAwait(false); | ||||
|   | ||||
| @@ -28,6 +28,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub | ||||
|     private readonly int _maxJoinedGroupsByUser; | ||||
|     private readonly int _maxGroupUserCount; | ||||
|     private readonly IRedisDatabase _redis; | ||||
|     private readonly GPoseLobbyDistributionService _gPoseLobbyDistributionService; | ||||
|     private readonly Uri _fileServerAddress; | ||||
|     private readonly Version _expectedClientVersion; | ||||
|     private readonly int _maxCharaDataByUser; | ||||
| @@ -38,7 +39,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub | ||||
|     public MareHub(MareMetrics mareMetrics, | ||||
|         IDbContextFactory<MareDbContext> mareDbContextFactory, ILogger<MareHub> logger, SystemInfoService systemInfoService, | ||||
|         IConfigurationService<ServerConfiguration> configuration, IHttpContextAccessor contextAccessor, | ||||
|         IRedisDatabase redisDb) | ||||
|         IRedisDatabase redisDb, GPoseLobbyDistributionService gPoseLobbyDistributionService) | ||||
|     { | ||||
|         _mareMetrics = mareMetrics; | ||||
|         _systemInfoService = systemInfoService; | ||||
| @@ -51,6 +52,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub | ||||
|         _maxCharaDataByUser = configuration.GetValueOrDefault(nameof(ServerConfiguration.MaxCharaDataByUser), 10); | ||||
|         _contextAccessor = contextAccessor; | ||||
|         _redis = redisDb; | ||||
|         _gPoseLobbyDistributionService = gPoseLobbyDistributionService; | ||||
|         _logger = new MareHubLogger(this, logger); | ||||
|         _dbContextLazy = new Lazy<MareDbContext>(() => mareDbContextFactory.CreateDbContext()); | ||||
|     } | ||||
| @@ -133,6 +135,7 @@ public partial class MareHub : Hub<IMareHub>, IMareHub | ||||
|             if (exception != null) | ||||
|                 _logger.LogCallWarning(MareHubLogger.Args(_contextAccessor.GetIpAddress(), exception.Message, exception.StackTrace)); | ||||
|  | ||||
|             await GposeLobbyLeave().ConfigureAwait(false); | ||||
|             await RemoveUserFromRedis().ConfigureAwait(false); | ||||
|  | ||||
|             await SendOfflineToAllPairedUsers().ConfigureAwait(false); | ||||
|   | ||||
| @@ -0,0 +1,226 @@ | ||||
| using MareSynchronos.API.Dto.CharaData; | ||||
| using MareSynchronos.API.SignalR; | ||||
| using MareSynchronosServer.Hubs; | ||||
| using Microsoft.AspNetCore.SignalR; | ||||
| using StackExchange.Redis.Extensions.Core.Abstractions; | ||||
|  | ||||
| namespace MareSynchronosServer.Services; | ||||
|  | ||||
| public sealed class GPoseLobbyDistributionService : IHostedService, IDisposable | ||||
| { | ||||
|     private CancellationTokenSource _runtimeCts = new(); | ||||
|     private readonly Dictionary<string, Dictionary<string, WorldData>> _lobbyWorldData = []; | ||||
|     private readonly Dictionary<string, Dictionary<string, PoseData>> _lobbyPoseData = []; | ||||
|     private readonly SemaphoreSlim _lobbyPoseDataModificationSemaphore = new(1, 1); | ||||
|     private readonly SemaphoreSlim _lobbyWorldDataModificationSemaphore = new(1, 1); | ||||
|  | ||||
|     public GPoseLobbyDistributionService(ILogger<GPoseLobbyDistributionService> logger, IRedisDatabase redisDb, | ||||
|         IHubContext<MareHub, IMareHub> hubContext) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         _redisDb = redisDb; | ||||
|         _hubContext = hubContext; | ||||
|     } | ||||
|  | ||||
|     private bool _disposed; | ||||
|     private readonly ILogger<GPoseLobbyDistributionService> _logger; | ||||
|     private readonly IRedisDatabase _redisDb; | ||||
|     private readonly IHubContext<MareHub, IMareHub> _hubContext; | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|         if (_disposed) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         _runtimeCts.Cancel(); | ||||
|         _runtimeCts.Dispose(); | ||||
|         _lobbyPoseDataModificationSemaphore.Dispose(); | ||||
|         _lobbyWorldDataModificationSemaphore.Dispose(); | ||||
|  | ||||
|         _disposed = true; | ||||
|     } | ||||
|  | ||||
|     public async Task PushWorldData(string lobby, string user, WorldData worldData) | ||||
|     { | ||||
|         await _lobbyWorldDataModificationSemaphore.WaitAsync().ConfigureAwait(false); | ||||
|         try | ||||
|         { | ||||
|             if (!_lobbyWorldData.TryGetValue(lobby, out var worldDataDict)) | ||||
|             { | ||||
|                 _lobbyWorldData[lobby] = worldDataDict = new(StringComparer.Ordinal); | ||||
|             } | ||||
|             worldDataDict[user] = worldData; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "Error during Pushing World Data for Lobby {lobby} by User {user}", lobby, user); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             _lobbyWorldDataModificationSemaphore.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public async Task PushPoseData(string lobby, string user, PoseData poseData) | ||||
|     { | ||||
|         await _lobbyPoseDataModificationSemaphore.WaitAsync().ConfigureAwait(false); | ||||
|         try | ||||
|         { | ||||
|             if (!_lobbyPoseData.TryGetValue(lobby, out var poseDataDict)) | ||||
|             { | ||||
|                 _lobbyPoseData[lobby] = poseDataDict = new(StringComparer.Ordinal); | ||||
|             } | ||||
|             poseDataDict[user] = poseData; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "Error during Pushing World Data for Lobby {lobby} by User {user}", lobby, user); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             _lobbyPoseDataModificationSemaphore.Release(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Task StartAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _ = WorldDataDistribution(_runtimeCts.Token); | ||||
|         _ = PoseDataDistribution(_runtimeCts.Token); | ||||
|  | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
|  | ||||
|     private async Task WorldDataDistribution(CancellationToken token) | ||||
|     { | ||||
|         while (!token.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await DistributeWorldData(token).ConfigureAwait(false); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogError(ex, "Error during World Data Distribution"); | ||||
|             } | ||||
|             await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task PoseDataDistribution(CancellationToken token) | ||||
|     { | ||||
|         while (!token.IsCancellationRequested) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await DistributePoseData(token).ConfigureAwait(false); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogError(ex, "Error during Pose Data Distribution"); | ||||
|             } | ||||
|             await Task.Delay(TimeSpan.FromSeconds(2), token).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task DistributeWorldData(CancellationToken token) | ||||
|     { | ||||
|         await _lobbyWorldDataModificationSemaphore.WaitAsync(token).ConfigureAwait(false); | ||||
|         Dictionary<string, Dictionary<string, WorldData>> clone = []; | ||||
|         try | ||||
|         { | ||||
|             clone = _lobbyWorldData.ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal); | ||||
|             _lobbyWorldData.Clear(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "Error during Distributing World Data Clone generation"); | ||||
|             _lobbyWorldData.Clear(); | ||||
|             return; | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             _lobbyWorldDataModificationSemaphore.Release(); | ||||
|         } | ||||
|  | ||||
|         foreach (var lobbyId in clone) | ||||
|         { | ||||
|             token.ThrowIfCancellationRequested(); | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 if (!lobbyId.Value.Values.Any()) | ||||
|                     continue; | ||||
|  | ||||
|                 var gposeLobbyUsers = await _redisDb.GetAsync<List<string>>($"GposeLobby:{lobbyId.Key}").ConfigureAwait(false); | ||||
|                 if (gposeLobbyUsers == null) | ||||
|                     continue; | ||||
|  | ||||
|                 foreach (var data in lobbyId.Value) | ||||
|                 { | ||||
|                     await _hubContext.Clients.Users(gposeLobbyUsers.Where(k => !string.Equals(k, data.Key, StringComparison.Ordinal))) | ||||
|                         .Client_GposeLobbyPushWorldData(new(data.Key), data.Value).ConfigureAwait(false); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogError(ex, "Error during World Data Distribution for Lobby {lobby}", lobbyId.Key); | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private async Task DistributePoseData(CancellationToken token) | ||||
|     { | ||||
|         await _lobbyPoseDataModificationSemaphore.WaitAsync(token).ConfigureAwait(false); | ||||
|         Dictionary<string, Dictionary<string, PoseData>> clone = []; | ||||
|         try | ||||
|         { | ||||
|             clone = _lobbyPoseData.ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal); | ||||
|             _lobbyPoseData.Clear(); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             _logger.LogError(ex, "Error during Distributing Pose Data Clone generation"); | ||||
|             _lobbyPoseData.Clear(); | ||||
|             return; | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             _lobbyPoseDataModificationSemaphore.Release(); | ||||
|         } | ||||
|  | ||||
|         foreach (var lobbyId in clone) | ||||
|         { | ||||
|             token.ThrowIfCancellationRequested(); | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 if (!lobbyId.Value.Values.Any()) | ||||
|                     continue; | ||||
|  | ||||
|                 var gposeLobbyUsers = await _redisDb.GetAsync<List<string>>($"GposeLobby:{lobbyId.Key}").ConfigureAwait(false); | ||||
|                 if (gposeLobbyUsers == null) | ||||
|                     continue; | ||||
|  | ||||
|                 foreach (var data in lobbyId.Value) | ||||
|                 { | ||||
|                     await _hubContext.Clients.Users(gposeLobbyUsers.Where(k => !string.Equals(k, data.Key, StringComparison.Ordinal))) | ||||
|                         .Client_GposeLobbyPushPoseData(new(data.Key), data.Value).ConfigureAwait(false); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 _logger.LogError(ex, "Error during Pose Data Distribution for Lobby {lobby}", lobbyId.Key); | ||||
|                 continue; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public Task StopAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         _runtimeCts.Cancel(); | ||||
|         return Task.CompletedTask; | ||||
|     } | ||||
| } | ||||
| @@ -100,6 +100,9 @@ public class Startup | ||||
|             services.AddSingleton<CharaDataCleanupService>(); | ||||
|             services.AddHostedService(provider => provider.GetService<CharaDataCleanupService>()); | ||||
|         } | ||||
|  | ||||
|         services.AddSingleton<GPoseLobbyDistributionService>(); | ||||
|         services.AddHostedService(provider => provider.GetService<GPoseLobbyDistributionService>()); | ||||
|     } | ||||
|  | ||||
|     private static void ConfigureSignalR(IServiceCollection services, IConfigurationSection mareConfig) | ||||
| @@ -276,6 +279,8 @@ public class Startup | ||||
|             MetricsAPI.GaugeGroupPairs, | ||||
|             MetricsAPI.GaugeGroupPairsPaused, | ||||
|             MetricsAPI.GaugeUsersRegistered, | ||||
|             MetricsAPI.GaugeGposeLobbies, | ||||
|             MetricsAPI.GaugeGposeLobbyUsers | ||||
|         })); | ||||
|     } | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -41,4 +41,6 @@ public class MetricsAPI | ||||
|     public const string CounterFileRequests = "mare_files_requests"; | ||||
|     public const string CounterFileRequestSize = "mare_files_request_size"; | ||||
|     public const string CounterAccountsCreated = "mare_accounts_created"; | ||||
|     public const string GaugeGposeLobbies = "mare_gpose_lobbies"; | ||||
|     public const string GaugeGposeLobbyUsers = "mare_gpose_lobby_users"; | ||||
| } | ||||
| @@ -36,10 +36,6 @@ namespace MareSynchronosServer.Migrations | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_banned"); | ||||
|  | ||||
|                     b.Property<bool>("MarkForBan") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("mark_for_ban"); | ||||
|  | ||||
|                     b.Property<string>("PrimaryUserUID") | ||||
|                         .HasColumnType("character varying(10)") | ||||
|                         .HasColumnName("primary_user_uid"); | ||||
| @@ -332,6 +328,26 @@ namespace MareSynchronosServer.Migrations | ||||
|                         .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>("DisableVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_vfx"); | ||||
|  | ||||
|                     b.Property<bool>("IsPaused") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_paused"); | ||||
|  | ||||
|                     b.Property<byte[]>("Timestamp") | ||||
|                         .IsConcurrencyToken() | ||||
|                         .ValueGeneratedOnAddOrUpdate() | ||||
| @@ -357,10 +373,6 @@ namespace MareSynchronosServer.Migrations | ||||
|                         .HasColumnType("character varying(40)") | ||||
|                         .HasColumnName("hash"); | ||||
|  | ||||
|                     b.Property<long>("RawSize") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("raw_size"); | ||||
|  | ||||
|                     b.Property<long>("Size") | ||||
|                         .HasColumnType("bigint") | ||||
|                         .HasColumnName("size"); | ||||
| @@ -429,6 +441,18 @@ namespace MareSynchronosServer.Migrations | ||||
|                         .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<bool>("DisableVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_vfx"); | ||||
|  | ||||
|                     b.Property<string>("HashedPassword") | ||||
|                         .HasColumnType("text") | ||||
|                         .HasColumnName("hashed_password"); | ||||
| @@ -441,18 +465,6 @@ namespace MareSynchronosServer.Migrations | ||||
|                         .HasColumnType("character varying(10)") | ||||
|                         .HasColumnName("owner_uid"); | ||||
|  | ||||
|                     b.Property<bool>("PreferDisableAnimations") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("prefer_disable_animations"); | ||||
|  | ||||
|                     b.Property<bool>("PreferDisableSounds") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("prefer_disable_sounds"); | ||||
|  | ||||
|                     b.Property<bool>("PreferDisableVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("prefer_disable_vfx"); | ||||
|  | ||||
|                     b.HasKey("GID") | ||||
|                         .HasName("pk_groups"); | ||||
|  | ||||
| @@ -509,10 +521,26 @@ namespace MareSynchronosServer.Migrations | ||||
|                         .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>("DisableVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_vfx"); | ||||
|  | ||||
|                     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"); | ||||
| @@ -529,44 +557,6 @@ namespace MareSynchronosServer.Migrations | ||||
|                     b.ToTable("group_pairs", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => | ||||
|                 { | ||||
|                     b.Property<string>("UserUID") | ||||
|                         .HasColumnType("character varying(10)") | ||||
|                         .HasColumnName("user_uid"); | ||||
|  | ||||
|                     b.Property<string>("GroupGID") | ||||
|                         .HasColumnType("character varying(20)") | ||||
|                         .HasColumnName("group_gid"); | ||||
|  | ||||
|                     b.Property<bool>("DisableAnimations") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_animations"); | ||||
|  | ||||
|                     b.Property<bool>("DisableSounds") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_sounds"); | ||||
|  | ||||
|                     b.Property<bool>("DisableVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_vfx"); | ||||
|  | ||||
|                     b.Property<bool>("IsPaused") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_paused"); | ||||
|  | ||||
|                     b.HasKey("UserUID", "GroupGID") | ||||
|                         .HasName("pk_group_pair_preferred_permissions"); | ||||
|  | ||||
|                     b.HasIndex("GroupGID") | ||||
|                         .HasDatabaseName("ix_group_pair_preferred_permissions_group_gid"); | ||||
|  | ||||
|                     b.HasIndex("UserUID") | ||||
|                         .HasDatabaseName("ix_group_pair_preferred_permissions_user_uid"); | ||||
|  | ||||
|                     b.ToTable("group_pair_preferred_permissions", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => | ||||
|                 { | ||||
|                     b.Property<string>("GroupGID") | ||||
| @@ -664,95 +654,6 @@ namespace MareSynchronosServer.Migrations | ||||
|                     b.ToTable("users", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => | ||||
|                 { | ||||
|                     b.Property<string>("UserUID") | ||||
|                         .HasMaxLength(10) | ||||
|                         .HasColumnType("character varying(10)") | ||||
|                         .HasColumnName("user_uid"); | ||||
|  | ||||
|                     b.Property<bool>("DisableGroupAnimations") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_group_animations"); | ||||
|  | ||||
|                     b.Property<bool>("DisableGroupSounds") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_group_sounds"); | ||||
|  | ||||
|                     b.Property<bool>("DisableGroupVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_group_vfx"); | ||||
|  | ||||
|                     b.Property<bool>("DisableIndividualAnimations") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_individual_animations"); | ||||
|  | ||||
|                     b.Property<bool>("DisableIndividualSounds") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_individual_sounds"); | ||||
|  | ||||
|                     b.Property<bool>("DisableIndividualVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_individual_vfx"); | ||||
|  | ||||
|                     b.Property<bool>("IndividualIsSticky") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("individual_is_sticky"); | ||||
|  | ||||
|                     b.HasKey("UserUID") | ||||
|                         .HasName("pk_user_default_preferred_permissions"); | ||||
|  | ||||
|                     b.HasIndex("UserUID") | ||||
|                         .HasDatabaseName("ix_user_default_preferred_permissions_user_uid"); | ||||
|  | ||||
|                     b.ToTable("user_default_preferred_permissions", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => | ||||
|                 { | ||||
|                     b.Property<string>("UserUID") | ||||
|                         .HasColumnType("character varying(10)") | ||||
|                         .HasColumnName("user_uid"); | ||||
|  | ||||
|                     b.Property<string>("OtherUserUID") | ||||
|                         .HasColumnType("character varying(10)") | ||||
|                         .HasColumnName("other_user_uid"); | ||||
|  | ||||
|                     b.Property<bool>("DisableAnimations") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_animations"); | ||||
|  | ||||
|                     b.Property<bool>("DisableSounds") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_sounds"); | ||||
|  | ||||
|                     b.Property<bool>("DisableVFX") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("disable_vfx"); | ||||
|  | ||||
|                     b.Property<bool>("IsPaused") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("is_paused"); | ||||
|  | ||||
|                     b.Property<bool>("Sticky") | ||||
|                         .HasColumnType("boolean") | ||||
|                         .HasColumnName("sticky"); | ||||
|  | ||||
|                     b.HasKey("UserUID", "OtherUserUID") | ||||
|                         .HasName("pk_user_permission_sets"); | ||||
|  | ||||
|                     b.HasIndex("OtherUserUID") | ||||
|                         .HasDatabaseName("ix_user_permission_sets_other_user_uid"); | ||||
|  | ||||
|                     b.HasIndex("UserUID") | ||||
|                         .HasDatabaseName("ix_user_permission_sets_user_uid"); | ||||
|  | ||||
|                     b.HasIndex("UserUID", "OtherUserUID", "IsPaused") | ||||
|                         .HasDatabaseName("ix_user_permission_sets_user_uid_other_user_uid_is_paused"); | ||||
|  | ||||
|                     b.ToTable("user_permission_sets", (string)null); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||
|                 { | ||||
|                     b.Property<string>("UserUID") | ||||
| @@ -785,6 +686,43 @@ namespace MareSynchronosServer.Migrations | ||||
|                     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") | ||||
| @@ -988,27 +926,6 @@ namespace MareSynchronosServer.Migrations | ||||
|                     b.Navigation("GroupUser"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.GroupPairPreferredPermission", b => | ||||
|                 { | ||||
|                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("GroupGID") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_group_pair_preferred_permissions_groups_group_gid"); | ||||
|  | ||||
|                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("UserUID") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_group_pair_preferred_permissions_users_user_uid"); | ||||
|  | ||||
|                     b.Navigation("Group"); | ||||
|  | ||||
|                     b.Navigation("User"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.GroupTempInvite", b => | ||||
|                 { | ||||
|                     b.HasOne("MareSynchronosShared.Models.Group", "Group") | ||||
| @@ -1031,39 +948,6 @@ namespace MareSynchronosServer.Migrations | ||||
|                     b.Navigation("User"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.UserDefaultPreferredPermission", b => | ||||
|                 { | ||||
|                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("UserUID") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_user_default_preferred_permissions_users_user_uid"); | ||||
|  | ||||
|                     b.Navigation("User"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.UserPermissionSet", b => | ||||
|                 { | ||||
|                     b.HasOne("MareSynchronosShared.Models.User", "OtherUser") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("OtherUserUID") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_user_permission_sets_users_other_user_uid"); | ||||
|  | ||||
|                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||
|                         .WithMany() | ||||
|                         .HasForeignKey("UserUID") | ||||
|                         .OnDelete(DeleteBehavior.Cascade) | ||||
|                         .IsRequired() | ||||
|                         .HasConstraintName("fk_user_permission_sets_users_user_uid"); | ||||
|  | ||||
|                     b.Navigation("OtherUser"); | ||||
|  | ||||
|                     b.Navigation("User"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.UserProfileData", b => | ||||
|                 { | ||||
|                     b.HasOne("MareSynchronosShared.Models.User", "User") | ||||
| @@ -1076,6 +960,23 @@ namespace MareSynchronosServer.Migrations | ||||
|                     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"); | ||||
|                 }); | ||||
|  | ||||
|             modelBuilder.Entity("MareSynchronosShared.Models.CharaData", b => | ||||
|                 { | ||||
|                     b.Navigation("AllowedIndividiuals"); | ||||
|   | ||||
| @@ -13,6 +13,8 @@ public class AuthServiceConfiguration : MareConfigurationBase | ||||
|     public int RegisterIpLimit { get; set; } = 3; | ||||
|     public int RegisterIpDurationInMinutes { get; set; } = 10; | ||||
|  | ||||
|     public string WellKnown { get; set; } = string.Empty; | ||||
|  | ||||
|     public override string ToString() | ||||
|     { | ||||
|         StringBuilder sb = new(); | ||||
| @@ -21,6 +23,7 @@ public class AuthServiceConfiguration : MareConfigurationBase | ||||
|         sb.AppendLine($"{nameof(UseGeoIP)} => {UseGeoIP}"); | ||||
|         sb.AppendLine($"{nameof(RegisterIpLimit)} => {RegisterIpLimit}"); | ||||
|         sb.AppendLine($"{nameof(RegisterIpDurationInMinutes)} => {RegisterIpDurationInMinutes}"); | ||||
|         sb.AppendLine($"{nameof(WellKnown)} => {WellKnown}"); | ||||
|         return sb.ToString(); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user