Syncshells (#11)
* some groups stuff * further groups rework * fixes for pause changes * adjsut timeout interval * fixes and namespace change to file scoped * more fixes * further implement groups * fix change group ownership * add some more stuff for groups * more fixes and additions * some fixes based on analyzers, add shard info to ui * add discord command, cleanup * fix regex * add group migration and deletion on user deletion * add api method for client to check health of connection * adjust regex for vanity * fixes for server and bot * fixes some string comparison in linq queries * fixes group leave and sets alias to null * fix syntax in changeownership * add better logging, fixes for group leaving * fixes for group leave Co-authored-by: Stanley Dimant <root.darkarchon@outlook.com>
This commit is contained in:
2
MareAPI
2
MareAPI
Submodule MareAPI updated: 9dc1e901aa...2d5d9d9d1c
4
MareSynchronosServer/.editorconfig
Normal file
4
MareSynchronosServer/.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
[*.cs]
|
||||
|
||||
# MA0048: File name must match type name
|
||||
dotnet_diagnostic.MA0048.severity = suggestion
|
||||
@@ -11,9 +11,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosServerTest",
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosShared", "MareSynchronosShared\MareSynchronosShared.csproj", "{67B1461D-E215-4BA8-A64D-E1836724D5E6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MareSynchronosStaticFilesServer", "MareSynchronosStaticFilesServer\MareSynchronosStaticFilesServer.csproj", "{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosStaticFilesServer", "MareSynchronosStaticFilesServer\MareSynchronosStaticFilesServer.csproj", "{3C7F43BB-FE4C-48BC-BF42-D24E70E8FCB7}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MareSynchronosServices", "MareSynchronosServices\MareSynchronosServices.csproj", "{E29C8677-AB44-4950-9EB1-D8E70B710A56}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronosServices", "MareSynchronosServices\MareSynchronosServices.csproj", "{E29C8677-AB44-4950-9EB1-D8E70B710A56}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7D5C2B87-5CC9-4FE7-AD13-4C13F6600683}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "6.0.6",
|
||||
"version": "6.0.9",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace MareSynchronosServer.Hubs
|
||||
{
|
||||
public class IdBasedUserIdProvider : IUserIdProvider
|
||||
{
|
||||
public string GetUserId(HubConnectionContext context)
|
||||
{
|
||||
return context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MareSynchronosServer.Hubs
|
||||
{
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
private bool IsAdmin => _dbContext.Users.Single(b => b.UID == AuthenticatedUserId).IsAdmin;
|
||||
@@ -102,13 +102,13 @@ namespace MareSynchronosServer.Hubs
|
||||
if (!IsModerator) return null;
|
||||
|
||||
var users = await _dbContext.Users.AsNoTracking().ToListAsync().ConfigureAwait(false);
|
||||
return users.Where(c => !string.IsNullOrEmpty(_clientIdentService.GetCharacterIdentForUid(c.UID))).Select(b => new OnlineUserDto
|
||||
return users.Where(c => !string.IsNullOrEmpty(_clientIdentService.GetCharacterIdentForUid(c.UID).Result)).Select(async b => new OnlineUserDto
|
||||
{
|
||||
CharacterNameHash = _clientIdentService.GetCharacterIdentForUid(b.UID),
|
||||
CharacterNameHash = await _clientIdentService.GetCharacterIdentForUid(b.UID).ConfigureAwait(false),
|
||||
UID = b.UID,
|
||||
IsModerator = b.IsModerator,
|
||||
IsAdmin = b.IsAdmin
|
||||
}).ToList();
|
||||
}).Select(c => c.Result).ToList();
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
@@ -135,7 +135,7 @@ namespace MareSynchronosServer.Hubs
|
||||
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
await Clients.Users(OnlineAdmins).SendAsync(Api.OnAdminUpdateOrAddBannedUser, dto).ConfigureAwait(false);
|
||||
var bannedUser = _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash);
|
||||
var bannedUser = await _clientIdentService.GetUidForCharacterIdent(dto.CharacterHash).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(bannedUser))
|
||||
{
|
||||
await Clients.User(bannedUser).SendAsync(Api.OnAdminForcedReconnect).ConfigureAwait(false);
|
||||
@@ -169,4 +169,3 @@ namespace MareSynchronosServer.Hubs
|
||||
await Clients.Users(OnlineAdmins).SendAsync(Api.OnAdminUpdateOrAddForbiddenFile, dto).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,15 @@ using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServer.Hubs
|
||||
{
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendFileAbortUpload)]
|
||||
public async Task AbortUpload()
|
||||
{
|
||||
_logger.LogInformation("User {AuthenticatedUserId} aborted upload", AuthenticatedUserId);
|
||||
_logger.LogCallInfo(Api.SendFileAbortUpload);
|
||||
var userId = AuthenticatedUserId;
|
||||
var notUploadedFiles = _dbContext.Files.Where(f => !f.Uploaded && f.Uploader.UID == userId).ToList();
|
||||
_dbContext.RemoveRange(notUploadedFiles);
|
||||
@@ -35,14 +35,14 @@ namespace MareSynchronosServer.Hubs
|
||||
[HubMethodName(Api.SendFileDeleteAllFiles)]
|
||||
public async Task DeleteAllFiles()
|
||||
{
|
||||
_logger.LogInformation("User {AuthenticatedUserId} deleted all their files", AuthenticatedUserId);
|
||||
_logger.LogCallInfo(Api.SendFileDeleteAllFiles);
|
||||
|
||||
var ownFiles = await _dbContext.Files.Where(f => f.Uploaded && f.Uploader.UID == AuthenticatedUserId).ToListAsync().ConfigureAwait(false);
|
||||
var request = new DeleteFilesRequest();
|
||||
request.Hash.AddRange(ownFiles.Select(f => f.Hash));
|
||||
Metadata headers = new Metadata()
|
||||
{
|
||||
{ "Authorization", Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.Authentication)?.Value }
|
||||
{ "Authorization", Context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.Authentication, StringComparison.Ordinal))?.Value }
|
||||
};
|
||||
_ = await _fileServiceClient.DeleteFilesAsync(request, headers).ConfigureAwait(false);
|
||||
}
|
||||
@@ -51,6 +51,8 @@ namespace MareSynchronosServer.Hubs
|
||||
[HubMethodName(Api.InvokeGetFilesSizes)]
|
||||
public async Task<List<DownloadFileDto>> GetFilesSizes(List<string> hashes)
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeGetFilesSizes, hashes.Count.ToString());
|
||||
|
||||
var allFiles = await _dbContext.Files.Where(f => hashes.Contains(f.Hash)).ToListAsync().ConfigureAwait(false);
|
||||
var forbiddenFiles = await _dbContext.ForbiddenUploadEntries.
|
||||
Where(f => hashes.Contains(f.Hash)).ToListAsync().ConfigureAwait(false);
|
||||
@@ -60,14 +62,14 @@ namespace MareSynchronosServer.Hubs
|
||||
request.Hash.AddRange(hashes);
|
||||
Metadata headers = new Metadata()
|
||||
{
|
||||
{ "Authorization", Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.Authentication)?.Value }
|
||||
{ "Authorization", Context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.Authentication, StringComparison.Ordinal))?.Value }
|
||||
};
|
||||
var grpcResponse = await _fileServiceClient.GetFileSizesAsync(request, headers).ConfigureAwait(false);
|
||||
|
||||
foreach (var hash in grpcResponse.HashToFileSize)
|
||||
{
|
||||
var forbiddenFile = forbiddenFiles.SingleOrDefault(f => f.Hash == hash.Key);
|
||||
var downloadFile = allFiles.SingleOrDefault(f => f.Hash == hash.Key);
|
||||
var forbiddenFile = forbiddenFiles.SingleOrDefault(f => string.Equals(f.Hash, hash.Key, StringComparison.Ordinal));
|
||||
var downloadFile = allFiles.SingleOrDefault(f => string.Equals(f.Hash, hash.Key, StringComparison.Ordinal));
|
||||
|
||||
response.Add(new DownloadFileDto
|
||||
{
|
||||
@@ -87,6 +89,7 @@ namespace MareSynchronosServer.Hubs
|
||||
[HubMethodName(Api.InvokeFileIsUploadFinished)]
|
||||
public async Task<bool> IsUploadFinished()
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeFileIsUploadFinished);
|
||||
var userUid = AuthenticatedUserId;
|
||||
return await _dbContext.Files.AsNoTracking()
|
||||
.AnyAsync(f => f.Uploader.UID == userUid && !f.Uploaded).ConfigureAwait(false);
|
||||
@@ -96,9 +99,9 @@ namespace MareSynchronosServer.Hubs
|
||||
[HubMethodName(Api.InvokeFileSendFiles)]
|
||||
public async Task<List<UploadFileDto>> SendFiles(List<string> fileListHashes)
|
||||
{
|
||||
var userSentHashes = new HashSet<string>(fileListHashes.Distinct());
|
||||
_logger.LogInformation("User {AuthenticatedUserId} sending files: {count}", AuthenticatedUserId, userSentHashes.Count);
|
||||
var notCoveredFiles = new Dictionary<string, UploadFileDto>();
|
||||
var userSentHashes = new HashSet<string>(fileListHashes.Distinct(StringComparer.Ordinal), StringComparer.Ordinal);
|
||||
_logger.LogCallInfo(Api.InvokeFileSendFiles, userSentHashes.Count.ToString());
|
||||
var notCoveredFiles = new Dictionary<string, UploadFileDto>(StringComparer.Ordinal);
|
||||
var forbiddenFiles = await _dbContext.ForbiddenUploadEntries.AsNoTracking().Where(f => userSentHashes.Contains(f.Hash)).ToDictionaryAsync(f => f.Hash, f => f).ConfigureAwait(false);
|
||||
var existingFiles = await _dbContext.Files.AsNoTracking().Where(f => userSentHashes.Contains(f.Hash)).ToDictionaryAsync(f => f.Hash, f => f).ConfigureAwait(false);
|
||||
var uploader = await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
@@ -122,7 +125,8 @@ namespace MareSynchronosServer.Hubs
|
||||
}
|
||||
if (existingFiles.ContainsKey(file)) { continue; }
|
||||
|
||||
_logger.LogInformation("User {AuthenticatedUserId} needs upload: {file}", AuthenticatedUserId, file);
|
||||
_logger.LogCallInfo(Api.InvokeFileSendFiles, file, "Missing");
|
||||
|
||||
var userId = AuthenticatedUserId;
|
||||
fileCachesToUpload.Add(new FileCache()
|
||||
{
|
||||
@@ -146,9 +150,9 @@ namespace MareSynchronosServer.Hubs
|
||||
[HubMethodName(Api.SendFileUploadFileStreamAsync)]
|
||||
public async Task UploadFileStreamAsync(string hash, IAsyncEnumerable<byte[]> fileContent)
|
||||
{
|
||||
_logger.LogInformation("User {AuthenticatedUserId} uploading file: {hash}", AuthenticatedUserId, hash);
|
||||
_logger.LogCallInfo(Api.SendFileUploadFileStreamAsync, hash);
|
||||
|
||||
var relatedFile = _dbContext.Files.SingleOrDefault(f => f.Hash == hash && f.Uploader.UID == AuthenticatedUserId && f.Uploaded == false);
|
||||
var relatedFile = _dbContext.Files.SingleOrDefault(f => f.Hash == hash && f.Uploader.UID == AuthenticatedUserId && !f.Uploaded);
|
||||
if (relatedFile == null) return;
|
||||
var forbiddenFile = _dbContext.ForbiddenUploadEntries.SingleOrDefault(f => f.Hash == hash);
|
||||
if (forbiddenFile != null) return;
|
||||
@@ -188,7 +192,7 @@ namespace MareSynchronosServer.Hubs
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("User {AuthenticatedUserId} upload finished: {hash}, size: {length}", AuthenticatedUserId, hash, length);
|
||||
_logger.LogCallInfo(Api.SendFileUploadFileStreamAsync, hash, "Uploaded");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -196,10 +200,10 @@ namespace MareSynchronosServer.Hubs
|
||||
using var sha1 = SHA1.Create();
|
||||
using var ms = new MemoryStream(decodedFile);
|
||||
var computedHash = await sha1.ComputeHashAsync(ms).ConfigureAwait(false);
|
||||
var computedHashString = BitConverter.ToString(computedHash).Replace("-", "");
|
||||
if (hash != computedHashString)
|
||||
var computedHashString = BitConverter.ToString(computedHash).Replace("-", "", StringComparison.Ordinal);
|
||||
if (!string.Equals(hash, computedHashString, StringComparison.Ordinal))
|
||||
{
|
||||
_logger.LogWarning("Computed file hash was not expected file hash. Computed: {computedHashString}, Expected {hash}", computedHashString, hash);
|
||||
_logger.LogCallWarning(Api.SendFileUploadFileStreamAsync, hash, "Invalid", computedHashString);
|
||||
_dbContext.Remove(relatedFile);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
@@ -208,7 +212,7 @@ namespace MareSynchronosServer.Hubs
|
||||
|
||||
Metadata headers = new Metadata()
|
||||
{
|
||||
{ "Authorization", Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.Authentication)?.Value }
|
||||
{ "Authorization", Context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.Authentication, StringComparison.Ordinal))?.Value }
|
||||
};
|
||||
var streamingCall = _fileServiceClient.UploadFile(headers);
|
||||
using var tempFileStream = new FileStream(tempFileName, FileMode.Open, FileAccess.Read);
|
||||
@@ -224,13 +228,15 @@ namespace MareSynchronosServer.Hubs
|
||||
Uploader = AuthenticatedUserId
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
await streamingCall.RequestStream.CompleteAsync();
|
||||
await streamingCall.RequestStream.CompleteAsync().ConfigureAwait(false);
|
||||
tempFileStream.Close();
|
||||
await tempFileStream.DisposeAsync();
|
||||
await tempFileStream.DisposeAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.SendFileUploadFileStreamAsync, hash, "Pushed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Upload failed");
|
||||
_logger.LogCallWarning(Api.SendFileUploadFileStreamAsync, "Failed", hash, ex.Message);
|
||||
_dbContext.Remove(relatedFile);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
@@ -240,4 +246,3 @@ namespace MareSynchronosServer.Hubs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
using MareSynchronosShared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using System.Globalization;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Utils;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
private async Task<List<PausedEntry>> GetAllPairedClientsWithPauseState(string? uid = null)
|
||||
{
|
||||
uid ??= AuthenticatedUserId;
|
||||
|
||||
var query = await (from userPair in _dbContext.ClientPairs
|
||||
join otherUserPair in _dbContext.ClientPairs on userPair.OtherUserUID equals otherUserPair.UserUID
|
||||
where otherUserPair.OtherUserUID == uid && userPair.UserUID == uid
|
||||
select new
|
||||
{
|
||||
UID = Convert.ToString(userPair.OtherUserUID),
|
||||
GID = "DIRECT",
|
||||
PauseState = (userPair.IsPaused || otherUserPair.IsPaused)
|
||||
})
|
||||
.Union(
|
||||
(from userGroupPair in _dbContext.GroupPairs
|
||||
join otherGroupPair in _dbContext.GroupPairs on userGroupPair.GroupGID equals otherGroupPair.GroupGID
|
||||
where
|
||||
userGroupPair.GroupUserUID == uid
|
||||
&& otherGroupPair.GroupUserUID != uid
|
||||
select new
|
||||
{
|
||||
UID = Convert.ToString(otherGroupPair.GroupUserUID),
|
||||
GID = Convert.ToString(otherGroupPair.GroupGID),
|
||||
PauseState = (userGroupPair.IsPaused || otherGroupPair.IsPaused)
|
||||
})
|
||||
).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
return query.GroupBy(g => g.UID, g => (g.GID, g.PauseState),
|
||||
(key, g) => new PausedEntry
|
||||
{
|
||||
UID = key,
|
||||
PauseStates = g.Select(p => new PauseState() { GID = string.Equals(p.GID, "DIRECT", StringComparison.Ordinal) ? null : p.GID, IsPaused = p.PauseState })
|
||||
.ToList()
|
||||
}, StringComparer.Ordinal).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<string>> GetAllPairedUnpausedUsers(string? uid = null)
|
||||
{
|
||||
uid ??= AuthenticatedUserId;
|
||||
var ret = await GetAllPairedClientsWithPauseState(uid).ConfigureAwait(false);
|
||||
return ret.Where(k => !k.IsPaused).Select(k => k.UID).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<string>> SendDataToAllPairedUsers(string apiMethod, object arg)
|
||||
{
|
||||
var usersToSendDataTo = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
await Clients.Users(usersToSendDataTo).SendAsync(apiMethod, arg).ConfigureAwait(false);
|
||||
|
||||
return usersToSendDataTo;
|
||||
}
|
||||
|
||||
public string AuthenticatedUserId => Context.User?.Claims?.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value ?? "Unknown";
|
||||
|
||||
protected async Task<User> GetAuthenticatedUserUntrackedAsync()
|
||||
{
|
||||
return await _dbContext.Users.AsNoTrackingWithIdentityResolution().SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task UserGroupLeave(GroupPair groupUserPair, List<PausedEntry> allUserPairs, string userIdent, string? uid = null)
|
||||
{
|
||||
uid ??= AuthenticatedUserId;
|
||||
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 _clientIdentService.GetCharacterIdentForUid(groupUserPair.GroupUserUID).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(groupUserIdent))
|
||||
{
|
||||
await Clients.User(uid).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, groupUserIdent).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, userIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendGroupDeletedToAll(List<GroupPair> groupUsers)
|
||||
{
|
||||
foreach (var pair in groupUsers)
|
||||
{
|
||||
var pairIdent = await _clientIdentService.GetCharacterIdentForUid(pair.GroupUserUID).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(pairIdent)) continue;
|
||||
|
||||
var pairs = await GetAllPairedClientsWithPauseState(pair.GroupUserUID).ConfigureAwait(false);
|
||||
|
||||
foreach (var groupUserPair in groupUsers.Where(g => !string.Equals(g.GroupUserUID, pair.GroupUserUID, StringComparison.Ordinal)))
|
||||
{
|
||||
await UserGroupLeave(groupUserPair, pairs, pairIdent, pair.GroupUserUID).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
548
MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs
Normal file
548
MareSynchronosServer/MareSynchronosServer/Hubs/MareHub.Groups.cs
Normal file
@@ -0,0 +1,548 @@
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Models;
|
||||
using MareSynchronosShared.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.InvokeGroupCreate)]
|
||||
public async Task<GroupCreatedDto> CreateGroup()
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeGroupCreate);
|
||||
var existingGroupsByUser = _dbContext.Groups.Count(u => u.OwnerUID == AuthenticatedUserId);
|
||||
var existingJoinedGroups = _dbContext.GroupPairs.Count(u => u.GroupUserUID == AuthenticatedUserId);
|
||||
if (existingGroupsByUser >= _maxExistingGroupsByUser || existingJoinedGroups >= _maxJoinedGroupsByUser)
|
||||
{
|
||||
throw new System.Exception($"Max groups for user is {_maxExistingGroupsByUser}, max joined groups is {_maxJoinedGroupsByUser}.");
|
||||
}
|
||||
|
||||
var gid = StringUtils.GenerateRandomString(12);
|
||||
while (await _dbContext.Groups.AnyAsync(g => g.GID == "MSS-" + gid).ConfigureAwait(false))
|
||||
{
|
||||
gid = StringUtils.GenerateRandomString(12);
|
||||
}
|
||||
gid = "MSS-" + gid;
|
||||
|
||||
var passwd = StringUtils.GenerateRandomString(16);
|
||||
var sha = SHA256.Create();
|
||||
var hashedPw = StringUtils.Sha256String(passwd);
|
||||
|
||||
Group newGroup = new()
|
||||
{
|
||||
GID = gid,
|
||||
HashedPassword = hashedPw,
|
||||
InvitesEnabled = true,
|
||||
OwnerUID = AuthenticatedUserId
|
||||
};
|
||||
|
||||
GroupPair initialPair = new()
|
||||
{
|
||||
GroupGID = newGroup.GID,
|
||||
GroupUserUID = AuthenticatedUserId,
|
||||
IsPaused = false,
|
||||
IsPinned = true
|
||||
};
|
||||
|
||||
await _dbContext.Groups.AddAsync(newGroup).ConfigureAwait(false);
|
||||
await _dbContext.GroupPairs.AddAsync(initialPair).ConfigureAwait(false);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
var self = _dbContext.Users.Single(u => u.UID == AuthenticatedUserId);
|
||||
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = newGroup.GID,
|
||||
OwnedBy = string.IsNullOrEmpty(self.Alias) ? self.UID : self.Alias,
|
||||
IsDeleted = false,
|
||||
IsPaused = false,
|
||||
InvitesEnabled = true
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.InvokeGroupCreate, gid);
|
||||
|
||||
return new GroupCreatedDto()
|
||||
{
|
||||
GID = newGroup.GID,
|
||||
Password = passwd
|
||||
};
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.InvokeGroupGetGroups)]
|
||||
public async Task<List<GroupDto>> GetGroups()
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeGroupGetGroups);
|
||||
|
||||
var groups = await _dbContext.GroupPairs.Include(g => g.Group).Include(g => g.Group.Owner).Where(g => g.GroupUserUID == AuthenticatedUserId).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
return groups.Select(g => new GroupDto()
|
||||
{
|
||||
GID = g.GroupGID,
|
||||
Alias = g.Group.Alias,
|
||||
InvitesEnabled = g.Group.InvitesEnabled,
|
||||
OwnedBy = string.IsNullOrEmpty(g.Group.Owner.Alias) ? g.Group.Owner.UID : g.Group.Owner.Alias,
|
||||
IsPaused = g.IsPaused
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.InvokeGroupGetUsersInGroup)]
|
||||
public async Task<List<GroupPairDto>> GetUsersInGroup(string gid)
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeGroupGetUsersInGroup, gid);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
var existingPair = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
if (group == null || existingPair == null) return new List<GroupPairDto>();
|
||||
|
||||
var allPairs = await _dbContext.GroupPairs.Include(g => g.GroupUser).Where(g => g.GroupGID == gid && g.GroupUserUID != AuthenticatedUserId).ToListAsync().ConfigureAwait(false);
|
||||
return allPairs.Select(p => new GroupPairDto()
|
||||
{
|
||||
GroupGID = gid,
|
||||
IsPaused = p.IsPaused,
|
||||
IsRemoved = false,
|
||||
UserUID = p.GroupUser.UID,
|
||||
UserAlias = p.GroupUser.Alias,
|
||||
IsPinned = p.IsPinned
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupChangeInviteState)]
|
||||
public async Task GroupChangeInviteState(string gid, bool enabled)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupChangeInviteState, gid, enabled.ToString());
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null || !string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
|
||||
group.InvitesEnabled = enabled;
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.SendGroupChangeInviteState, gid, enabled.ToString(), "Success");
|
||||
|
||||
var groupPairs = _dbContext.GroupPairs.Where(p => p.GroupGID == gid).Select(p => p.GroupUserUID).ToList();
|
||||
await Clients.Users(groupPairs).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = gid,
|
||||
InvitesEnabled = enabled,
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupDelete)]
|
||||
public async Task GroupDelete(string gid)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupDelete, gid);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null || !string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
|
||||
_logger.LogCallInfo(Api.SendGroupDelete, gid, "Success");
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == gid).ToListAsync().ConfigureAwait(false);
|
||||
_dbContext.RemoveRange(groupPairs);
|
||||
_dbContext.Remove(group);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
await Clients.Users(groupPairs.Select(g => g.GroupUserUID)).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
IsDeleted = true,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
|
||||
await SendGroupDeletedToAll(groupPairs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.InvokeGroupJoin)]
|
||||
public async Task<bool> GroupJoin(string gid, string password)
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeGroupJoin, gid);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid || g.Alias == gid).ConfigureAwait(false);
|
||||
var existingPair = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var hashedPw = StringUtils.Sha256String(password);
|
||||
var existingUserCount = await _dbContext.GroupPairs.CountAsync(g => g.GroupGID == gid).ConfigureAwait(false);
|
||||
var joinedGroups = await _dbContext.GroupPairs.CountAsync(g => g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
if (group == null
|
||||
|| !string.Equals(group.HashedPassword, hashedPw, StringComparison.Ordinal)
|
||||
|| existingPair != null
|
||||
|| existingUserCount >= _maxGroupUserCount
|
||||
|| !group.InvitesEnabled
|
||||
|| joinedGroups >= _maxJoinedGroupsByUser)
|
||||
return false;
|
||||
|
||||
GroupPair newPair = new()
|
||||
{
|
||||
GroupGID = group.GID,
|
||||
GroupUserUID = AuthenticatedUserId
|
||||
};
|
||||
|
||||
await _dbContext.GroupPairs.AddAsync(newPair).ConfigureAwait(false);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.InvokeGroupJoin, gid, "Success");
|
||||
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
OwnedBy = group.OwnerUID,
|
||||
IsDeleted = false,
|
||||
IsPaused = false,
|
||||
Alias = group.Alias,
|
||||
InvitesEnabled = true
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var self = _dbContext.Users.Single(u => u.UID == AuthenticatedUserId);
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == group.GID && p.GroupUserUID != AuthenticatedUserId).ToListAsync().ConfigureAwait(false);
|
||||
await Clients.Users(groupPairs.Select(p => p.GroupUserUID)).SendAsync(Api.OnGroupUserChange, new GroupPairDto()
|
||||
{
|
||||
GroupGID = group.GID,
|
||||
IsPaused = false,
|
||||
IsRemoved = false,
|
||||
UserUID = AuthenticatedUserId,
|
||||
UserAlias = self.Alias,
|
||||
IsPinned = false
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
var userIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
foreach (var groupUserPair in groupPairs)
|
||||
{
|
||||
var userPair = allUserPairs.Single(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal));
|
||||
if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) continue;
|
||||
if (userPair.IsPausedExcludingGroup(gid) is PauseInfo.Unpaused) continue;
|
||||
if (userPair.IsPausedPerGroup is PauseInfo.Paused) continue;
|
||||
|
||||
var groupUserIdent = await _clientIdentService.GetCharacterIdentForUid(groupUserPair.GroupUserUID).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(groupUserIdent))
|
||||
{
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnUserAddOnlinePairedPlayer, groupUserIdent).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).SendAsync(Api.OnUserAddOnlinePairedPlayer, userIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupLeave)]
|
||||
public async Task GroupLeave(string gid)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupLeave, gid);
|
||||
|
||||
var groupPair = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
if (groupPair == null) return;
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == group.GID).ToListAsync().ConfigureAwait(false);
|
||||
var groupPairsWithoutSelf = groupPairs.Where(p => !string.Equals(p.GroupUserUID, AuthenticatedUserId, StringComparison.Ordinal)).ToList();
|
||||
|
||||
_dbContext.GroupPairs.Remove(groupPair);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
IsDeleted = true
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
bool ownerHasLeft = string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal);
|
||||
if (ownerHasLeft)
|
||||
{
|
||||
if (!groupPairsWithoutSelf.Any())
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupLeave, gid, "Deleted");
|
||||
|
||||
_dbContext.Remove(group);
|
||||
}
|
||||
else
|
||||
{
|
||||
var groupHasMigrated = await SharedDbFunctions.MigrateOrDeleteGroup(_dbContext, group, groupPairsWithoutSelf, _maxExistingGroupsByUser).ConfigureAwait(false);
|
||||
|
||||
if (groupHasMigrated.Item1)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupLeave, gid, "Migrated", groupHasMigrated.Item2);
|
||||
|
||||
await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
OwnedBy = groupHasMigrated.Item2,
|
||||
Alias = null
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupLeave, gid, "Deleted");
|
||||
|
||||
await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
IsDeleted = true
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await SendGroupDeletedToAll(groupPairs).ConfigureAwait(false);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.SendGroupLeave, gid, "Success");
|
||||
|
||||
await Clients.Users(groupPairsWithoutSelf.Select(p => p.GroupUserUID)).SendAsync(Api.OnGroupUserChange, new GroupPairDto()
|
||||
{
|
||||
GroupGID = group.GID,
|
||||
IsRemoved = true,
|
||||
UserUID = AuthenticatedUserId,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
var userIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
foreach (var groupUserPair in groupPairsWithoutSelf)
|
||||
{
|
||||
await UserGroupLeave(groupUserPair, allUserPairs, userIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupPause)]
|
||||
public async Task GroupChangePauseState(string gid, bool isPaused)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupPause, gid, isPaused);
|
||||
|
||||
var groupPair = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
if (groupPair == null) return;
|
||||
|
||||
groupPair.IsPaused = isPaused;
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.SendGroupPause, gid, isPaused, "Success");
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == gid && p.GroupUserUID != AuthenticatedUserId).ToListAsync().ConfigureAwait(false);
|
||||
await Clients.Users(groupPairs.Select(p => p.GroupUserUID)).SendAsync(Api.OnGroupUserChange, new GroupPairDto()
|
||||
{
|
||||
GroupGID = gid,
|
||||
IsPaused = isPaused,
|
||||
UserUID = AuthenticatedUserId,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnGroupChange, new GroupDto
|
||||
{
|
||||
GID = gid,
|
||||
IsPaused = isPaused
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
var userIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
foreach (var groupUserPair in groupPairs)
|
||||
{
|
||||
var userPair = allUserPairs.SingleOrDefault(p => string.Equals(p.UID, groupUserPair.GroupUserUID, StringComparison.Ordinal));
|
||||
if (userPair != null)
|
||||
{
|
||||
if (userPair.IsDirectlyPaused != PauseInfo.NoConnection) continue;
|
||||
if (userPair.IsPausedExcludingGroup(gid) is PauseInfo.Unpaused) continue;
|
||||
}
|
||||
|
||||
var groupUserIdent = await _clientIdentService.GetCharacterIdentForUid(groupUserPair.GroupUserUID).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(groupUserIdent))
|
||||
{
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(isPaused ? Api.OnUserRemoveOnlinePairedPlayer : Api.OnUserAddOnlinePairedPlayer, groupUserIdent).ConfigureAwait(false);
|
||||
await Clients.User(groupUserPair.GroupUserUID).SendAsync(isPaused ? Api.OnUserRemoveOnlinePairedPlayer : Api.OnUserAddOnlinePairedPlayer, userIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupRemoveUser)]
|
||||
public async Task GroupRemoveUser(string gid, string uid)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupRemoveUser, gid, uid);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null || !string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
var groupPair = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == uid).ConfigureAwait(false);
|
||||
if (groupPair == null) return;
|
||||
|
||||
_logger.LogCallInfo(Api.SendGroupRemoveUser, gid, uid, "Success");
|
||||
|
||||
_dbContext.GroupPairs.Remove(groupPair);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
var groupPairs = _dbContext.GroupPairs.Where(p => p.GroupGID == group.GID).ToList();
|
||||
await Clients.Users(groupPairs.Select(p => p.GroupUserUID)).SendAsync(Api.OnGroupUserChange, new GroupPairDto()
|
||||
{
|
||||
GroupGID = group.GID,
|
||||
IsRemoved = true,
|
||||
UserUID = uid,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var userIdent = await _clientIdentService.GetCharacterIdentForUid(uid).ConfigureAwait(false);
|
||||
if (userIdent == null) return;
|
||||
|
||||
await Clients.User(uid).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = gid,
|
||||
IsDeleted = true,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState(uid).ConfigureAwait(false);
|
||||
|
||||
foreach (var groupUserPair in groupPairs)
|
||||
{
|
||||
await UserGroupLeave(groupUserPair, allUserPairs, userIdent, uid).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupChangeOwner)]
|
||||
public async Task ChangeOwnership(string gid, string uid)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupChangeOwner, gid, uid);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null || !string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
var groupPair = await _dbContext.GroupPairs.Include(g => g.GroupUser).SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == uid).ConfigureAwait(false);
|
||||
if (groupPair == null) return;
|
||||
var ownedShells = await _dbContext.Groups.CountAsync(g => g.OwnerUID == uid).ConfigureAwait(false);
|
||||
if (ownedShells >= _maxExistingGroupsByUser) return;
|
||||
|
||||
var prevOwner = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
prevOwner.IsPinned = false;
|
||||
group.Owner = groupPair.GroupUser;
|
||||
group.Alias = null;
|
||||
groupPair.IsPinned = true;
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.SendGroupChangeOwner, gid, uid, "Success");
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == gid).Select(p => p.GroupUserUID).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
await Clients.Users(groupPairs).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = gid,
|
||||
OwnedBy = string.IsNullOrEmpty(group.Owner.Alias) ? group.Owner.UID : group.Owner.Alias,
|
||||
Alias = null
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await Clients.Users(groupPairs.Where(p => !string.Equals(p, uid, StringComparison.Ordinal))).SendAsync(Api.OnGroupUserChange, new GroupPairDto()
|
||||
{
|
||||
GroupGID = gid,
|
||||
UserUID = uid,
|
||||
IsPinned = true
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.InvokeGroupChangePassword)]
|
||||
public async Task<bool> ChangeGroupPassword(string gid, string password)
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeGroupChangePassword, gid);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null || !string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal)) return false;
|
||||
|
||||
if (password.Length < 10) return false;
|
||||
|
||||
_logger.LogCallInfo(Api.InvokeGroupChangePassword, gid, "Success");
|
||||
|
||||
group.HashedPassword = StringUtils.Sha256String(password);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupChangePinned)]
|
||||
public async Task ChangePinned(string gid, string uid, bool isPinned)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupChangePinned, gid, uid, isPinned);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null || !string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
var groupPair = await _dbContext.GroupPairs.SingleOrDefaultAsync(g => g.GroupGID == gid && g.GroupUserUID == uid).ConfigureAwait(false);
|
||||
if (groupPair == null) return;
|
||||
|
||||
groupPair.IsPinned = isPinned;
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.InvokeGroupChangePassword, gid, uid, isPinned, "Success");
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == gid).Select(p => p.GroupUserUID).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
await Clients.Users(groupPairs.Where(p => !string.Equals(p, uid, StringComparison.Ordinal))).SendAsync(Api.OnGroupUserChange, new GroupPairDto()
|
||||
{
|
||||
GroupGID = gid,
|
||||
UserUID = uid,
|
||||
IsPinned = isPinned
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendGroupClear)]
|
||||
public async Task ClearGroup(string gid)
|
||||
{
|
||||
_logger.LogCallInfo(Api.SendGroupClear, gid);
|
||||
|
||||
var group = await _dbContext.Groups.SingleOrDefaultAsync(g => g.GID == gid).ConfigureAwait(false);
|
||||
if (group == null || !string.Equals(group.OwnerUID, AuthenticatedUserId, StringComparison.Ordinal)) return;
|
||||
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(p => p.GroupGID == gid).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
await Clients.Users(groupPairs.Where(p => !p.IsPinned).Select(g => g.GroupUserUID)).SendAsync(Api.OnGroupChange, new GroupDto()
|
||||
{
|
||||
GID = group.GID,
|
||||
IsDeleted = true,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.SendGroupClear, gid, "Success");
|
||||
|
||||
var notPinned = groupPairs.Where(g => !g.IsPinned).ToList();
|
||||
|
||||
_dbContext.GroupPairs.RemoveRange(notPinned);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
foreach (var pair in notPinned)
|
||||
{
|
||||
await Clients.Users(groupPairs.Where(p => p.IsPinned).Select(g => g.GroupUserUID)).SendAsync(Api.OnGroupUserChange, new GroupPairDto()
|
||||
{
|
||||
GroupGID = pair.GroupGID,
|
||||
IsRemoved = true,
|
||||
UserUID = pair.GroupUserUID
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var pairIdent = await _clientIdentService.GetCharacterIdentForUid(pair.GroupUserUID).ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(pairIdent)) continue;
|
||||
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState(pair.GroupUserUID).ConfigureAwait(false);
|
||||
|
||||
foreach (var groupUserPair in groupPairs.Where(p => !string.Equals(p.GroupUserUID, pair.GroupUserUID, StringComparison.Ordinal)))
|
||||
{
|
||||
await UserGroupLeave(groupUserPair, allUserPairs, pairIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Models;
|
||||
@@ -11,22 +12,23 @@ using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServer.Hubs
|
||||
{
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendUserDeleteAccount)]
|
||||
public async Task DeleteAccount()
|
||||
{
|
||||
_logger.LogInformation("User {AuthenticatedUserId} deleted their account", AuthenticatedUserId);
|
||||
_logger.LogCallInfo(Api.SendUserDeleteAccount);
|
||||
|
||||
string userid = AuthenticatedUserId;
|
||||
var userEntry = await _dbContext.Users.SingleAsync(u => u.UID == userid).ConfigureAwait(false);
|
||||
var charaIdent = _clientIdentService.GetCharacterIdentForUid(userid);
|
||||
var charaIdent = await _clientIdentService.GetCharacterIdentForUid(userid).ConfigureAwait(false);
|
||||
var ownPairData = await _dbContext.ClientPairs.Where(u => u.User.UID == userid).ToListAsync().ConfigureAwait(false);
|
||||
var auth = await _dbContext.Auth.SingleAsync(u => u.UserUID == userid).ConfigureAwait(false);
|
||||
var lodestone = await _dbContext.LodeStoneAuth.SingleOrDefaultAsync(a => a.User.UID == userid).ConfigureAwait(false);
|
||||
var groupPairs = await _dbContext.GroupPairs.Where(g => g.GroupUserUID == userid).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
if (lodestone != null)
|
||||
{
|
||||
@@ -40,7 +42,6 @@ namespace MareSynchronosServer.Hubs
|
||||
|
||||
await _authServiceClient.RemoveAuthAsync(new RemoveAuthRequest() { Uid = userid }).ConfigureAwait(false);
|
||||
|
||||
|
||||
_dbContext.RemoveRange(ownPairData);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
var otherPairData = await _dbContext.ClientPairs.Include(u => u.User)
|
||||
@@ -55,8 +56,11 @@ namespace MareSynchronosServer.Hubs
|
||||
}, charaIdent).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_mareMetrics.DecGauge(MetricsAPI.GaugePairs, ownPairData.Count + otherPairData.Count);
|
||||
_mareMetrics.DecGauge(MetricsAPI.GaugePairsPaused, ownPairData.Count(c => c.IsPaused));
|
||||
foreach (var pair in groupPairs)
|
||||
{
|
||||
await GroupLeave(pair.GroupGID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUsersRegisteredDeleted, 1);
|
||||
|
||||
_dbContext.RemoveRange(otherPairData);
|
||||
@@ -69,32 +73,20 @@ namespace MareSynchronosServer.Hubs
|
||||
[HubMethodName(Api.InvokeUserGetOnlineCharacters)]
|
||||
public async Task<List<string>> GetOnlineCharacters()
|
||||
{
|
||||
_logger.LogInformation("User {AuthenticatedUserId} requested online characters", AuthenticatedUserId);
|
||||
_logger.LogCallInfo(Api.InvokeUserGetOnlineCharacters);
|
||||
|
||||
var ownUser = await GetAuthenticatedUserUntrackedAsync().ConfigureAwait(false);
|
||||
var ownIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
|
||||
var otherUsers = await _dbContext.ClientPairs.AsNoTracking()
|
||||
.Include(u => u.User)
|
||||
.Include(u => u.OtherUser)
|
||||
.Where(w => w.User.UID == ownUser.UID && !w.IsPaused)
|
||||
//.Where(w => !string.IsNullOrEmpty(w.OtherUser.CharacterIdentification))
|
||||
.Select(e => e.OtherUser).ToListAsync().ConfigureAwait(false);
|
||||
var otherOnlineUsers =
|
||||
otherUsers.Where(u => !string.IsNullOrEmpty(_clientIdentService.GetCharacterIdentForUid(u.UID)));
|
||||
var otherEntries = await _dbContext.ClientPairs.AsNoTracking()
|
||||
.Include(u => u.User)
|
||||
.Where(u => otherOnlineUsers.Any(e => e == u.User) && u.OtherUser == ownUser && !u.IsPaused)
|
||||
.ToListAsync().ConfigureAwait(false);
|
||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(ownUser.UID);
|
||||
|
||||
await Clients.Users(otherEntries.Select(e => e.User.UID)).SendAsync(Api.OnUserAddOnlinePairedPlayer, ownIdent).ConfigureAwait(false);
|
||||
return otherEntries.Select(e => _clientIdentService.GetCharacterIdentForUid(e.User.UID)).Distinct().ToList();
|
||||
var usersToSendOnlineTo = await SendDataToAllPairedUsers(Api.OnUserAddOnlinePairedPlayer, ownIdent).ConfigureAwait(false);
|
||||
return usersToSendOnlineTo.Select(async e => await _clientIdentService.GetCharacterIdentForUid(e).ConfigureAwait(false)).Select(t => t.Result).Where(t => !string.IsNullOrEmpty(t)).Distinct(System.StringComparer.Ordinal).ToList();
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.InvokeUserGetPairedClients)]
|
||||
public async Task<List<ClientPairDto>> GetPairedClients()
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeUserGetPairedClients);
|
||||
|
||||
string userid = AuthenticatedUserId;
|
||||
var query =
|
||||
from userToOther in _dbContext.ClientPairs
|
||||
@@ -135,56 +127,46 @@ namespace MareSynchronosServer.Hubs
|
||||
[HubMethodName(Api.InvokeUserPushCharacterDataToVisibleClients)]
|
||||
public async Task PushCharacterDataToVisibleClients(CharacterCacheDto characterCache, List<string> visibleCharacterIds)
|
||||
{
|
||||
_logger.LogInformation("User {AuthenticatedUserId} pushing character data to {visibleCharacterIds} visible clients", AuthenticatedUserId, visibleCharacterIds.Count);
|
||||
_logger.LogCallInfo(Api.InvokeUserPushCharacterDataToVisibleClients, visibleCharacterIds.Count);
|
||||
|
||||
var user = await GetAuthenticatedUserUntrackedAsync().ConfigureAwait(false);
|
||||
var allPairedUsers = await GetAllPairedUnpausedUsers().ConfigureAwait(false);
|
||||
|
||||
var query =
|
||||
from userToOther in _dbContext.ClientPairs
|
||||
join otherToUser in _dbContext.ClientPairs
|
||||
on new
|
||||
{
|
||||
user = userToOther.UserUID,
|
||||
other = userToOther.OtherUserUID
|
||||
var allPairedUsersDict = allPairedUsers.ToDictionary(f => f, async f => await _clientIdentService.GetCharacterIdentForUid(f).ConfigureAwait(false), System.StringComparer.Ordinal)
|
||||
.Where(f => visibleCharacterIds.Contains(f.Value.Result, System.StringComparer.Ordinal));
|
||||
|
||||
} equals new
|
||||
{
|
||||
user = otherToUser.OtherUserUID,
|
||||
other = otherToUser.UserUID
|
||||
}
|
||||
where
|
||||
userToOther.UserUID == user.UID
|
||||
&& !userToOther.IsPaused
|
||||
&& !otherToUser.IsPaused
|
||||
select otherToUser.UserUID;
|
||||
var ownIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
|
||||
var otherEntries = await query.ToListAsync().ConfigureAwait(false);
|
||||
otherEntries =
|
||||
otherEntries.Where(c => visibleCharacterIds.Select(c => c.ToLowerInvariant()).Contains(_clientIdentService.GetCharacterIdentForUid(c)?.ToLowerInvariant() ?? "")).ToList();
|
||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
_logger.LogCallInfo(Api.InvokeUserPushCharacterDataToVisibleClients, visibleCharacterIds.Count, allPairedUsersDict.Count());
|
||||
|
||||
await Clients.Users(otherEntries).SendAsync(Api.OnUserReceiveCharacterData, characterCache, ownIdent).ConfigureAwait(false);
|
||||
await Clients.Users(allPairedUsersDict.Select(f => f.Key)).SendAsync(Api.OnUserReceiveCharacterData, characterCache, ownIdent).ConfigureAwait(false);
|
||||
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushData);
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, otherEntries.Count);
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterUserPushDataTo, allPairedUsersDict.Count());
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendUserPairedClientAddition)]
|
||||
public async Task SendPairedClientAddition(string uid)
|
||||
{
|
||||
if (uid == AuthenticatedUserId || string.IsNullOrWhiteSpace(uid)) return;
|
||||
uid = uid.Trim();
|
||||
var user = await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
_logger.LogCallInfo(Api.SendUserPairedClientAddition, uid);
|
||||
|
||||
var otherUser = await _dbContext.Users
|
||||
.SingleOrDefaultAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false);
|
||||
// don't allow adding yourself or nothing
|
||||
uid = uid.Trim();
|
||||
if (string.Equals(uid, AuthenticatedUserId, System.StringComparison.Ordinal) || string.IsNullOrWhiteSpace(uid)) return;
|
||||
|
||||
// grab other user, check if it exists and if a pair already exists
|
||||
var otherUser = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == uid || u.Alias == uid).ConfigureAwait(false);
|
||||
var existingEntry =
|
||||
await _dbContext.ClientPairs.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p =>
|
||||
p.User.UID == AuthenticatedUserId && p.OtherUser.UID == otherUser.UID).ConfigureAwait(false);
|
||||
p.User.UID == AuthenticatedUserId && p.OtherUserUID == uid).ConfigureAwait(false);
|
||||
if (otherUser == null || existingEntry != null) return;
|
||||
_logger.LogInformation("User {AuthenticatedUserId} adding {uid} to whitelist", AuthenticatedUserId, uid);
|
||||
|
||||
// grab self create new client pair and save
|
||||
var user = await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.SendUserPairedClientAddition, uid, "Success");
|
||||
|
||||
ClientPair wl = new ClientPair()
|
||||
{
|
||||
IsPaused = false,
|
||||
@@ -193,6 +175,8 @@ namespace MareSynchronosServer.Hubs
|
||||
};
|
||||
await _dbContext.ClientPairs.AddAsync(wl).ConfigureAwait(false);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
// get the opposite entry of the client pair
|
||||
var otherEntry = OppositeEntry(otherUser.UID);
|
||||
await Clients.User(user.UID)
|
||||
.SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto()
|
||||
@@ -202,10 +186,16 @@ namespace MareSynchronosServer.Hubs
|
||||
IsPaused = false,
|
||||
IsPausedFromOthers = otherEntry?.IsPaused ?? false,
|
||||
IsSynced = otherEntry != null
|
||||
}, string.Empty).ConfigureAwait(false);
|
||||
if (otherEntry != null)
|
||||
{
|
||||
var userIdent = _clientIdentService.GetCharacterIdentForUid(user.UID);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
// if there's no opposite entry do nothing
|
||||
if (otherEntry == null) return;
|
||||
|
||||
// check if other user is online
|
||||
var otherIdent = await _clientIdentService.GetCharacterIdentForUid(otherUser.UID).ConfigureAwait(false);
|
||||
if (otherIdent == null) return;
|
||||
|
||||
// send push with update to other user if other user is online
|
||||
await Clients.User(otherUser.UID).SendAsync(Api.OnUserUpdateClientPairs,
|
||||
new ClientPairDto()
|
||||
{
|
||||
@@ -214,10 +204,14 @@ namespace MareSynchronosServer.Hubs
|
||||
IsPaused = otherEntry.IsPaused,
|
||||
IsPausedFromOthers = false,
|
||||
IsSynced = true
|
||||
}, userIdent).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var otherIdent = _clientIdentService.GetCharacterIdentForUid(otherUser.UID);
|
||||
if (!string.IsNullOrEmpty(otherIdent))
|
||||
// get own ident and all pairs
|
||||
var userIdent = await _clientIdentService.GetCharacterIdentForUid(user.UID).ConfigureAwait(false);
|
||||
var allUserPairs = await GetAllPairedClientsWithPauseState().ConfigureAwait(false);
|
||||
|
||||
// if the other user has paused the main user and there was no previous group connection don't send anything
|
||||
if (!otherEntry.IsPaused && allUserPairs.Any(p => string.Equals(p.UID, uid, System.StringComparison.Ordinal) && p.IsPausedPerGroup is PauseInfo.Paused or PauseInfo.NoConnection))
|
||||
{
|
||||
await Clients.User(user.UID)
|
||||
.SendAsync(Api.OnUserAddOnlinePairedPlayer, otherIdent).ConfigureAwait(false);
|
||||
@@ -226,23 +220,22 @@ namespace MareSynchronosServer.Hubs
|
||||
}
|
||||
}
|
||||
|
||||
_mareMetrics.IncGauge(MetricsAPI.GaugePairs);
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendUserPairedClientPauseChange)]
|
||||
public async Task SendPairedClientPauseChange(string otherUserUid, bool isPaused)
|
||||
{
|
||||
if (otherUserUid == AuthenticatedUserId) return;
|
||||
_logger.LogCallInfo(Api.SendUserPairedClientPauseChange, otherUserUid, isPaused);
|
||||
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
|
||||
ClientPair pair = await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||
if (pair == null) return;
|
||||
|
||||
_logger.LogInformation("User {AuthenticatedUserId} changed pause status with {otherUserUid} to {isPaused}", AuthenticatedUserId, otherUserUid, isPaused);
|
||||
pair.IsPaused = isPaused;
|
||||
_dbContext.Update(pair);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
var selfCharaIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
var otherCharaIdent = _clientIdentService.GetCharacterIdentForUid(pair.OtherUserUID);
|
||||
|
||||
_logger.LogCallInfo(Api.SendUserPairedClientPauseChange, otherUserUid, isPaused, "Success");
|
||||
|
||||
var otherEntry = OppositeEntry(otherUserUid);
|
||||
|
||||
await Clients.User(AuthenticatedUserId)
|
||||
@@ -252,7 +245,7 @@ namespace MareSynchronosServer.Hubs
|
||||
IsPaused = isPaused,
|
||||
IsPausedFromOthers = otherEntry?.IsPaused ?? false,
|
||||
IsSynced = otherEntry != null
|
||||
}, otherCharaIdent).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
if (otherEntry != null)
|
||||
{
|
||||
await Clients.User(otherUserUid).SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto()
|
||||
@@ -261,65 +254,98 @@ namespace MareSynchronosServer.Hubs
|
||||
IsPaused = otherEntry.IsPaused,
|
||||
IsPausedFromOthers = isPaused,
|
||||
IsSynced = true
|
||||
}, selfCharaIdent).ConfigureAwait(false);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var selfCharaIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
var otherCharaIdent = await _clientIdentService.GetCharacterIdentForUid(pair.OtherUserUID).ConfigureAwait(false);
|
||||
|
||||
if (selfCharaIdent == null || otherCharaIdent == null || otherEntry.IsPaused) return;
|
||||
|
||||
if (isPaused)
|
||||
{
|
||||
_mareMetrics.IncGauge(MetricsAPI.GaugePairsPaused);
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, otherCharaIdent).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, selfCharaIdent).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_mareMetrics.DecGauge(MetricsAPI.GaugePairsPaused);
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnUserAddOnlinePairedPlayer, otherCharaIdent).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).SendAsync(Api.OnUserAddOnlinePairedPlayer, selfCharaIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
[HubMethodName(Api.SendUserPairedClientRemoval)]
|
||||
public async Task SendPairedClientRemoval(string uid)
|
||||
public async Task SendPairedClientRemoval(string otherUserUid)
|
||||
{
|
||||
if (uid == AuthenticatedUserId) return;
|
||||
_logger.LogCallInfo(Api.SendUserPairedClientRemoval, otherUserUid);
|
||||
|
||||
var sender = await _dbContext.Users.SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
var otherUser = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == uid).ConfigureAwait(false);
|
||||
if (otherUser == null) return;
|
||||
_logger.LogInformation("User {AuthenticatedUserId} removed {uid} from whitelist", AuthenticatedUserId, uid);
|
||||
ClientPair wl =
|
||||
await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.User == sender && w.OtherUser == otherUser).ConfigureAwait(false);
|
||||
if (wl == null) return;
|
||||
_dbContext.ClientPairs.Remove(wl);
|
||||
if (string.Equals(otherUserUid, AuthenticatedUserId, System.StringComparison.Ordinal)) return;
|
||||
|
||||
// check if client pair even exists
|
||||
ClientPair callerPair =
|
||||
await _dbContext.ClientPairs.SingleOrDefaultAsync(w => w.UserUID == AuthenticatedUserId && w.OtherUserUID == otherUserUid).ConfigureAwait(false);
|
||||
bool callerHadPaused = callerPair.IsPaused;
|
||||
if (callerPair == null) return;
|
||||
|
||||
// delete from database, send update info to users pair list
|
||||
_dbContext.ClientPairs.Remove(callerPair);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
var otherEntry = OppositeEntry(uid);
|
||||
var otherIdent = _clientIdentService.GetCharacterIdentForUid(otherUser.UID);
|
||||
await Clients.User(sender.UID)
|
||||
|
||||
_logger.LogCallInfo(Api.SendUserPairedClientRemoval, otherUserUid, "Success");
|
||||
|
||||
await Clients.User(AuthenticatedUserId)
|
||||
.SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto()
|
||||
{
|
||||
OtherUID = otherUser.UID,
|
||||
OtherUID = otherUserUid,
|
||||
IsRemoved = true
|
||||
}, otherIdent).ConfigureAwait(false);
|
||||
if (otherEntry != null)
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
// check if opposite entry exists
|
||||
var oppositeClientPair = OppositeEntry(otherUserUid);
|
||||
if (oppositeClientPair == null) return;
|
||||
|
||||
// check if other user is online, if no then there is no need to do anything further
|
||||
var otherIdent = await _clientIdentService.GetCharacterIdentForUid(otherUserUid).ConfigureAwait(false);
|
||||
if (otherIdent == null) return;
|
||||
|
||||
// get own ident and
|
||||
await Clients.User(otherUserUid).SendAsync(Api.OnUserUpdateClientPairs,
|
||||
new ClientPairDto()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(otherIdent))
|
||||
{
|
||||
var ownIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
await Clients.User(sender.UID)
|
||||
.SendAsync(Api.OnUserRemoveOnlinePairedPlayer, otherIdent).ConfigureAwait(false);
|
||||
await Clients.User(otherUser.UID)
|
||||
.SendAsync(Api.OnUserRemoveOnlinePairedPlayer, ownIdent).ConfigureAwait(false);
|
||||
await Clients.User(otherUser.UID).SendAsync(Api.OnUserUpdateClientPairs, new ClientPairDto()
|
||||
{
|
||||
OtherUID = sender.UID,
|
||||
IsPaused = otherEntry.IsPaused,
|
||||
OtherUID = AuthenticatedUserId,
|
||||
IsPausedFromOthers = false,
|
||||
IsSynced = false
|
||||
}, ownIdent).ConfigureAwait(false);
|
||||
}
|
||||
}).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, otherUserUid, System.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)
|
||||
{
|
||||
var userIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, otherIdent).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, userIdent).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_mareMetrics.DecGauge(MetricsAPI.GaugePairs);
|
||||
// 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)
|
||||
{
|
||||
var userIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
await Clients.User(AuthenticatedUserId).SendAsync(Api.OnUserAddOnlinePairedPlayer, otherIdent).ConfigureAwait(false);
|
||||
await Clients.User(otherUserUid).SendAsync(Api.OnUserAddOnlinePairedPlayer, userIdent).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private ClientPair OppositeEntry(string otherUID) =>
|
||||
_dbContext.ClientPairs.AsNoTracking().SingleOrDefault(w => w.User.UID == otherUID && w.OtherUser.UID == AuthenticatedUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosServer.Utils;
|
||||
using MareSynchronosShared.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
@@ -27,9 +28,14 @@ public partial class MareHub : Hub
|
||||
private readonly SystemInfoService _systemInfoService;
|
||||
private readonly IHttpContextAccessor _contextAccessor;
|
||||
private readonly IClientIdentificationService _clientIdentService;
|
||||
private readonly ILogger<MareHub> _logger;
|
||||
private readonly MareHubLogger _logger;
|
||||
private readonly MareDbContext _dbContext;
|
||||
private readonly Uri _cdnFullUri;
|
||||
private readonly string _shardName;
|
||||
private readonly int _maxExistingGroupsByUser;
|
||||
private readonly int _maxJoinedGroupsByUser;
|
||||
private readonly int _maxGroupUserCount;
|
||||
|
||||
public MareHub(MareMetrics mareMetrics, AuthService.AuthServiceClient authServiceClient, FileService.FileServiceClient fileServiceClient,
|
||||
MareDbContext mareDbContext, ILogger<MareHub> logger, SystemInfoService systemInfoService, IConfiguration configuration, IHttpContextAccessor contextAccessor,
|
||||
IClientIdentificationService clientIdentService)
|
||||
@@ -38,10 +44,15 @@ public partial class MareHub : Hub
|
||||
_authServiceClient = authServiceClient;
|
||||
_fileServiceClient = fileServiceClient;
|
||||
_systemInfoService = systemInfoService;
|
||||
_cdnFullUri = new Uri(configuration.GetRequiredSection("MareSynchronos").GetValue<string>("CdnFullUrl"));
|
||||
var config = configuration.GetRequiredSection("MareSynchronos");
|
||||
_cdnFullUri = new Uri(config.GetValue<string>("CdnFullUrl"));
|
||||
_shardName = config.GetValue("ShardName", "Main");
|
||||
_maxExistingGroupsByUser = config.GetValue<int>("MaxExistingGroupsByUser", 3);
|
||||
_maxJoinedGroupsByUser = config.GetValue<int>("MaxJoinedGroupsByUser", 6);
|
||||
_maxGroupUserCount = config.GetValue<int>("MaxGroupUserCount", 100);
|
||||
_contextAccessor = contextAccessor;
|
||||
_clientIdentService = clientIdentService;
|
||||
_logger = logger;
|
||||
_logger = new MareHubLogger(this, logger);
|
||||
_dbContext = mareDbContext;
|
||||
}
|
||||
|
||||
@@ -51,9 +62,9 @@ public partial class MareHub : Hub
|
||||
{
|
||||
_mareMetrics.IncCounter(MetricsAPI.CounterInitializedConnections);
|
||||
|
||||
var userId = Context.User!.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||
var userId = Context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value;
|
||||
|
||||
_logger.LogInformation("Connection from {userId}, CI: {characterIdentification}", userId, characterIdentification);
|
||||
_logger.LogCallInfo(Api.InvokeHeartbeat, characterIdentification);
|
||||
|
||||
await Clients.Caller.SendAsync(Api.OnUpdateSystemInfo, _systemInfoService.SystemInfoDto).ConfigureAwait(false);
|
||||
|
||||
@@ -62,9 +73,11 @@ public partial class MareHub : Hub
|
||||
if (!string.IsNullOrEmpty(userId) && !isBanned && !string.IsNullOrEmpty(characterIdentification))
|
||||
{
|
||||
var user = (await _dbContext.Users.SingleAsync(u => u.UID == userId).ConfigureAwait(false));
|
||||
var existingIdent = _clientIdentService.GetCharacterIdentForUid(userId);
|
||||
if (!string.IsNullOrEmpty(existingIdent) && characterIdentification != existingIdent)
|
||||
var existingIdent = await _clientIdentService.GetCharacterIdentForUid(userId).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(existingIdent) && !string.Equals(characterIdentification, existingIdent, StringComparison.Ordinal))
|
||||
{
|
||||
_logger.LogCallInfo(Api.InvokeHeartbeat, characterIdentification, "Failure", "LoggedIn");
|
||||
|
||||
return new ConnectionDto()
|
||||
{
|
||||
ServerVersion = Api.Version
|
||||
@@ -72,27 +85,50 @@ public partial class MareHub : Hub
|
||||
}
|
||||
|
||||
user.LastLoggedIn = DateTime.UtcNow;
|
||||
_clientIdentService.MarkUserOnline(user.UID, characterIdentification);
|
||||
await _clientIdentService.MarkUserOnline(user.UID, characterIdentification).ConfigureAwait(false);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
|
||||
_logger.LogCallInfo(Api.InvokeHeartbeat, characterIdentification, "Success");
|
||||
|
||||
return new ConnectionDto
|
||||
{
|
||||
ServerVersion = Api.Version,
|
||||
UID = string.IsNullOrEmpty(user.Alias) ? user.UID : user.Alias,
|
||||
IsModerator = user.IsModerator,
|
||||
IsAdmin = user.IsAdmin
|
||||
IsAdmin = user.IsAdmin,
|
||||
ServerInfo = new ServerInfoDto()
|
||||
{
|
||||
MaxGroupsCreatedByUser = _maxExistingGroupsByUser,
|
||||
ShardName = _shardName,
|
||||
MaxGroupsJoinedByUser = _maxJoinedGroupsByUser,
|
||||
MaxGroupUserCount = _maxGroupUserCount
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_logger.LogCallInfo(Api.InvokeHeartbeat, characterIdentification, "Failure");
|
||||
|
||||
return new ConnectionDto()
|
||||
{
|
||||
ServerVersion = Api.Version
|
||||
};
|
||||
}
|
||||
|
||||
[HubMethodName(Api.InvokeCheckClientHealth)]
|
||||
[Authorize(AuthenticationSchemes = SecretKeyGrpcAuthenticationHandler.AuthScheme)]
|
||||
public async Task<bool> CheckClientHealth()
|
||||
{
|
||||
var needsReconnect = string.IsNullOrEmpty(await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false));
|
||||
if (needsReconnect)
|
||||
{
|
||||
_logger.LogCallWarning(Api.InvokeCheckClientHealth, needsReconnect);
|
||||
}
|
||||
return needsReconnect;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
_logger.LogInformation("Connection from {ip}", _contextAccessor.GetIpAddress());
|
||||
_logger.LogCallInfo("Connect", _contextAccessor.GetIpAddress());
|
||||
_mareMetrics.IncGauge(MetricsAPI.GaugeConnections);
|
||||
await base.OnConnectedAsync().ConfigureAwait(false);
|
||||
}
|
||||
@@ -101,50 +137,22 @@ public partial class MareHub : Hub
|
||||
{
|
||||
_mareMetrics.DecGauge(MetricsAPI.GaugeConnections);
|
||||
|
||||
var userCharaIdent = _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId);
|
||||
var userCharaIdent = await _clientIdentService.GetCharacterIdentForUid(AuthenticatedUserId).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(userCharaIdent))
|
||||
{
|
||||
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.UID == AuthenticatedUserId)!.ConfigureAwait(false);
|
||||
_mareMetrics.DecGauge(MetricsAPI.GaugeAuthorizedConnections);
|
||||
|
||||
_logger.LogInformation("Disconnect from {id}", AuthenticatedUserId);
|
||||
_logger.LogCallInfo("Disconnect");
|
||||
|
||||
var query =
|
||||
from userToOther in _dbContext.ClientPairs
|
||||
join otherToUser in _dbContext.ClientPairs
|
||||
on new
|
||||
{
|
||||
user = userToOther.UserUID,
|
||||
other = userToOther.OtherUserUID
|
||||
await SendDataToAllPairedUsers(Api.OnUserRemoveOnlinePairedPlayer, userCharaIdent).ConfigureAwait(false);
|
||||
|
||||
} equals new
|
||||
{
|
||||
user = otherToUser.OtherUserUID,
|
||||
other = otherToUser.UserUID
|
||||
}
|
||||
where
|
||||
userToOther.UserUID == user.UID
|
||||
&& !userToOther.IsPaused
|
||||
&& !otherToUser.IsPaused
|
||||
select otherToUser.UserUID;
|
||||
var otherEntries = await query.ToListAsync().ConfigureAwait(false);
|
||||
_dbContext.RemoveRange(_dbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == AuthenticatedUserId));
|
||||
|
||||
await Clients.Users(otherEntries).SendAsync(Api.OnUserRemoveOnlinePairedPlayer, userCharaIdent).ConfigureAwait(false);
|
||||
|
||||
_dbContext.RemoveRange(_dbContext.Files.Where(f => !f.Uploaded && f.UploaderUID == user.UID));
|
||||
|
||||
_clientIdentService.MarkUserOffline(user.UID);
|
||||
await _clientIdentService.MarkUserOffline(AuthenticatedUserId).ConfigureAwait(false);
|
||||
await _dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected string AuthenticatedUserId => Context.User?.Claims?.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? "Unknown";
|
||||
|
||||
protected async Task<User> GetAuthenticatedUserUntrackedAsync()
|
||||
{
|
||||
return await _dbContext.Users.AsNoTrackingWithIdentityResolution().SingleAsync(u => u.UID == AuthenticatedUserId).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,10 @@
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.47.0" />
|
||||
<PackageReference Include="Karambolo.Extensions.Logging.File" Version="3.3.1" />
|
||||
<PackageReference Include="lz4net" Version="1.0.15.93" />
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="1.0.733">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="6.0.8" />
|
||||
|
||||
@@ -9,8 +9,8 @@ using Microsoft.Extensions.Logging;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
|
||||
namespace MareSynchronosServer
|
||||
{
|
||||
namespace MareSynchronosServer;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
@@ -23,7 +23,7 @@ namespace MareSynchronosServer
|
||||
using var context = services.GetRequiredService<MareDbContext>();
|
||||
|
||||
var secondaryServer = Environment.GetEnvironmentVariable("SECONDARY_SERVER");
|
||||
if (string.IsNullOrEmpty(secondaryServer) || secondaryServer == "0")
|
||||
if (string.IsNullOrEmpty(secondaryServer) || string.Equals(secondaryServer, "0", StringComparison.Ordinal))
|
||||
{
|
||||
context.Database.Migrate();
|
||||
context.SaveChanges();
|
||||
@@ -42,7 +42,7 @@ namespace MareSynchronosServer
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, context.ClientPairs.Count(p => p.IsPaused));
|
||||
}
|
||||
|
||||
if (args.Length == 0 || args[0] != "dry")
|
||||
if (args.Length == 0 || !string.Equals(args[0], "dry", StringComparison.Ordinal))
|
||||
{
|
||||
host.Run();
|
||||
}
|
||||
@@ -63,4 +63,3 @@ namespace MareSynchronosServer
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MareSynchronos.API;
|
||||
@@ -7,6 +8,7 @@ using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Services;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -16,16 +18,19 @@ namespace MareSynchronosServer.Services;
|
||||
public class SystemInfoService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly MareMetrics _mareMetrics;
|
||||
private readonly IClientIdentificationService clientIdentService;
|
||||
private readonly IServiceProvider _services;
|
||||
private readonly IClientIdentificationService _clientIdentService;
|
||||
private readonly ILogger<SystemInfoService> _logger;
|
||||
private readonly IHubContext<MareHub> _hubContext;
|
||||
private Timer _timer;
|
||||
private string _shardName;
|
||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||
|
||||
public SystemInfoService(MareMetrics mareMetrics, IClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
|
||||
public SystemInfoService(MareMetrics mareMetrics, IConfiguration configuration, IServiceProvider services, IClientIdentificationService clientIdentService, ILogger<SystemInfoService> logger, IHubContext<MareHub> hubContext)
|
||||
{
|
||||
_mareMetrics = mareMetrics;
|
||||
this.clientIdentService = clientIdentService;
|
||||
_services = services;
|
||||
_clientIdentService = clientIdentService;
|
||||
_logger = logger;
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
@@ -42,27 +47,28 @@ public class SystemInfoService : IHostedService, IDisposable
|
||||
private void PushSystemInfo(object state)
|
||||
{
|
||||
ThreadPool.GetAvailableThreads(out int workerThreads, out int ioThreads);
|
||||
_logger.LogInformation("ThreadPool: {workerThreads} workers available, {ioThreads} IO workers available", workerThreads, ioThreads);
|
||||
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableWorkerThreads, workerThreads);
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeAvailableIOWorkerThreads, ioThreads);
|
||||
|
||||
|
||||
var secondaryServer = Environment.GetEnvironmentVariable("SECONDARY_SERVER");
|
||||
if (string.IsNullOrEmpty(secondaryServer) || secondaryServer == "0")
|
||||
if (string.IsNullOrEmpty(secondaryServer) || string.Equals(secondaryServer, "0", StringComparison.Ordinal))
|
||||
{
|
||||
SystemInfoDto = new SystemInfoDto()
|
||||
{
|
||||
CacheUsage = 0,
|
||||
CpuUsage = 0,
|
||||
RAMUsage = 0,
|
||||
NetworkIn = 0,
|
||||
NetworkOut = 0,
|
||||
OnlineUsers = clientIdentService.GetOnlineUsers(),
|
||||
UploadedFiles = 0
|
||||
OnlineUsers = _clientIdentService.GetOnlineUsers().Result,
|
||||
};
|
||||
|
||||
_hubContext.Clients.All.SendAsync(Api.OnUpdateSystemInfo, SystemInfoDto);
|
||||
|
||||
using var scope = _services.CreateScope();
|
||||
using var db = scope.ServiceProvider.GetService<MareDbContext>()!;
|
||||
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugePairs, db.ClientPairs.Count());
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugePairsPaused, db.ClientPairs.Count(p => p.IsPaused));
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeGroups, db.Groups.Count());
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeGroupPairs, db.GroupPairs.Count());
|
||||
_mareMetrics.SetGaugeTo(MetricsAPI.GaugeGroupPairsPaused, db.GroupPairs.Count(p => p.IsPaused));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,9 +22,10 @@ using System.Collections.Generic;
|
||||
using MareSynchronosServer.Services;
|
||||
using MareSynchronosShared.Services;
|
||||
using System.Net.Http;
|
||||
using MareSynchronosServer.Utils;
|
||||
|
||||
namespace MareSynchronosServer;
|
||||
|
||||
namespace MareSynchronosServer
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
@@ -122,6 +123,7 @@ namespace MareSynchronosServer
|
||||
hubOptions.EnableDetailedErrors = true;
|
||||
hubOptions.MaximumParallelInvocationsPerClient = 10;
|
||||
hubOptions.StreamBufferCapacity = 200;
|
||||
|
||||
hubOptions.AddFilter<SignalRLimitFilter>();
|
||||
});
|
||||
|
||||
@@ -187,4 +189,3 @@ namespace MareSynchronosServer
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace MareSynchronosServer.Utils;
|
||||
|
||||
public class IdBasedUserIdProvider : IUserIdProvider
|
||||
{
|
||||
public string GetUserId(HubConnectionContext context)
|
||||
{
|
||||
return context.User!.Claims.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, System.StringComparison.Ordinal))?.Value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using MareSynchronosServer.Hubs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronosServer.Utils;
|
||||
|
||||
public class MareHubLogger
|
||||
{
|
||||
private readonly MareHub _hub;
|
||||
private readonly ILogger<MareHub> _logger;
|
||||
|
||||
public MareHubLogger(MareHub hub, ILogger<MareHub> logger)
|
||||
{
|
||||
_hub = hub;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void LogCallInfo(string methodName, params object[] args)
|
||||
{
|
||||
string formattedArgs = args.Length != 0 ? "|" + string.Join(":", args) : string.Empty;
|
||||
_logger.LogInformation("{uid}:{method}{args}", _hub.AuthenticatedUserId, methodName, formattedArgs);
|
||||
}
|
||||
|
||||
public void LogCallWarning(string methodName, params object[] args)
|
||||
{
|
||||
string formattedArgs = args.Length != 0 ? "|" + string.Join(":", args) : string.Empty;
|
||||
_logger.LogWarning("{uid}:{method}{args}", _hub.AuthenticatedUserId, methodName, formattedArgs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MareSynchronosServer.Utils;
|
||||
|
||||
public enum PauseInfo
|
||||
{
|
||||
NoConnection,
|
||||
Paused,
|
||||
Unpaused
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MareSynchronosServer.Utils;
|
||||
|
||||
public record PauseState
|
||||
{
|
||||
public string GID { get; set; }
|
||||
public bool IsPaused { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MareSynchronosServer.Utils;
|
||||
|
||||
public record PausedEntry
|
||||
{
|
||||
public string UID { get; set; }
|
||||
public List<PauseState> PauseStates { get; set; } = new();
|
||||
|
||||
public PauseInfo IsDirectlyPaused => PauseStateWithoutGroups == null ? PauseInfo.NoConnection
|
||||
: PauseStates.First(g => g.GID == null).IsPaused ? PauseInfo.Paused : PauseInfo.Unpaused;
|
||||
|
||||
public PauseInfo IsPausedPerGroup => !PauseStatesWithoutDirect.Any() ? PauseInfo.NoConnection
|
||||
: PauseStatesWithoutDirect.All(p => p.IsPaused) ? PauseInfo.Paused : PauseInfo.Unpaused;
|
||||
|
||||
private IEnumerable<PauseState> PauseStatesWithoutDirect => PauseStates.Where(f => f.GID != null);
|
||||
private PauseState PauseStateWithoutGroups => PauseStates.SingleOrDefault(p => p.GID == null);
|
||||
|
||||
public bool IsPaused
|
||||
{
|
||||
get
|
||||
{
|
||||
var isDirectlyPaused = IsDirectlyPaused;
|
||||
bool result;
|
||||
if (isDirectlyPaused != PauseInfo.NoConnection)
|
||||
{
|
||||
result = isDirectlyPaused == PauseInfo.Paused;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = IsPausedPerGroup == PauseInfo.Paused;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public PauseInfo IsPausedForSpecificGroup(string gid)
|
||||
{
|
||||
var state = PauseStatesWithoutDirect.SingleOrDefault(g => string.Equals(g.GID, gid, StringComparison.Ordinal));
|
||||
if (state == null) return PauseInfo.NoConnection;
|
||||
return state.IsPaused ? PauseInfo.Paused : PauseInfo.NoConnection;
|
||||
}
|
||||
|
||||
public PauseInfo IsPausedExcludingGroup(string gid)
|
||||
{
|
||||
var states = PauseStatesWithoutDirect.Where(f => !string.Equals(f.GID, gid, StringComparison.Ordinal)).ToList();
|
||||
if (!states.Any()) return PauseInfo.NoConnection;
|
||||
var result = states.All(p => p.IsPaused);
|
||||
if (result) return PauseInfo.Paused;
|
||||
return PauseInfo.Unpaused;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
namespace MareSynchronosServer.Utils;
|
||||
public class SignalRLimitFilter : IHubFilter
|
||||
{
|
||||
private readonly IRateLimitProcessor _processor;
|
||||
@@ -42,7 +42,7 @@ public class SignalRLimitFilter : IHubFilter
|
||||
var counter = await _processor.ProcessRequestAsync(client, rule).ConfigureAwait(false);
|
||||
if (counter.Count > rule.Limit)
|
||||
{
|
||||
var authUserId = invocationContext.Context.User.Claims?.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value ?? "Unknown";
|
||||
var authUserId = invocationContext.Context.User.Claims?.SingleOrDefault(c => string.Equals(c.Type, ClaimTypes.NameIdentifier, StringComparison.Ordinal))?.Value ?? "Unknown";
|
||||
var retry = counter.Timestamp.RetryAfterFrom(rule);
|
||||
logger.LogWarning("Method rate limit triggered from {ip}/{authUserId}: {method}", ip, authUserId, invocationContext.HubMethodName);
|
||||
throw new HubException($"call limit {retry}");
|
||||
12
MareSynchronosServer/MareSynchronosServer/Utils/UserPair.cs
Normal file
12
MareSynchronosServer/MareSynchronosServer/Utils/UserPair.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace MareSynchronosServer.Hubs;
|
||||
|
||||
public partial class MareHub
|
||||
{
|
||||
private record UserPair
|
||||
{
|
||||
public string UserUID { get; set; }
|
||||
public string OtherUserUID { get; set; }
|
||||
public bool UserPausedOther { get; set; }
|
||||
public bool OtherPausedUser { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Models;
|
||||
using MareSynchronosShared.Utils;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -9,13 +10,12 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices
|
||||
{
|
||||
namespace MareSynchronosServices;
|
||||
|
||||
public class CleanupService : IHostedService, IDisposable
|
||||
{
|
||||
private readonly MareMetrics metrics;
|
||||
@@ -43,7 +43,7 @@ namespace MareSynchronosServices
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void CleanUp(object state)
|
||||
private async void CleanUp(object state)
|
||||
{
|
||||
using var scope = _services.CreateScope();
|
||||
using var dbContext = scope.ServiceProvider.GetService<MareDbContext>()!;
|
||||
@@ -98,7 +98,7 @@ namespace MareSynchronosServices
|
||||
|
||||
foreach (var user in usersToRemove)
|
||||
{
|
||||
PurgeUser(user, dbContext);
|
||||
await PurgeUser(user, dbContext);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace MareSynchronosServices
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void PurgeUser(User user, MareDbContext dbContext)
|
||||
public async Task PurgeUser(User user, MareDbContext dbContext)
|
||||
{
|
||||
var lodestone = dbContext.LodeStoneAuth.SingleOrDefault(a => a.User.UID == user.UID);
|
||||
|
||||
@@ -138,10 +138,36 @@ namespace MareSynchronosServices
|
||||
var otherPairData = dbContext.ClientPairs.Include(u => u.User)
|
||||
.Where(u => u.OtherUser.UID == user.UID).ToList();
|
||||
|
||||
var userGroupPairs = dbContext.GroupPairs.Include(g => g.Group).Where(u => u.GroupUserUID == user.UID);
|
||||
|
||||
foreach (var groupPair in userGroupPairs)
|
||||
{
|
||||
bool ownerHasLeft = string.Equals(groupPair.Group.OwnerUID, user.UID, StringComparison.Ordinal);
|
||||
if (ownerHasLeft)
|
||||
{
|
||||
var groupPairs = await dbContext.GroupPairs.Where(g => g.GroupGID == groupPair.GroupGID).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
if (!groupPairs.Any())
|
||||
{
|
||||
_logger.LogInformation("Group {gid} has no new owner, deleting", groupPair.GroupGID);
|
||||
dbContext.Remove(groupPair.Group);
|
||||
}
|
||||
else
|
||||
{
|
||||
var groupHasMigrated = await SharedDbFunctions.MigrateOrDeleteGroup(dbContext, groupPair.Group, groupPairs, _configuration.GetValue<int>("MaxExistingGroupsByUser", 3)).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dbContext.Remove(groupPair);
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
_logger.LogInformation("User purged: {uid}", user.UID);
|
||||
|
||||
metrics.DecGauge(MetricsAPI.GaugePairs, ownPairData.Count + otherPairData.Count);
|
||||
metrics.DecGauge(MetricsAPI.GaugePairsPaused, ownPairData.Count(c => c.IsPaused) + otherPairData.Count(c => c.IsPaused));
|
||||
metrics.DecGauge(MetricsAPI.GaugeUsersRegistered, 1);
|
||||
|
||||
dbContext.RemoveRange(otherPairData);
|
||||
@@ -161,4 +187,3 @@ namespace MareSynchronosServices
|
||||
_timer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Discord;
|
||||
using Discord.Rest;
|
||||
using Discord.WebSocket;
|
||||
using MareSynchronosServices.Authentication;
|
||||
using MareSynchronosShared.Data;
|
||||
@@ -41,7 +42,9 @@ public class DiscordBot : IHostedService
|
||||
private readonly string[] LodestoneServers = new[] { "eu", "na", "jp", "fr", "de" };
|
||||
private readonly ConcurrentQueue<SocketSlashCommand> verificationQueue = new();
|
||||
private ConcurrentDictionary<ulong, DateTime> LastVanityChange = new();
|
||||
private ConcurrentDictionary<string, DateTime> LastVanityGidChange = new();
|
||||
private ulong vanityCommandId;
|
||||
private ulong vanityGidCommandId;
|
||||
private Task cleanUpUserTask = null;
|
||||
|
||||
private SemaphoreSlim semaphore;
|
||||
@@ -128,6 +131,18 @@ public class DiscordBot : IHostedService
|
||||
eb = await HandleVanityUid(eb, arg.User.Id, newUid);
|
||||
|
||||
await arg.RespondAsync(embeds: new[] { eb.Build() }, ephemeral: true).ConfigureAwait(false);
|
||||
break;
|
||||
}
|
||||
case "setsyncshellvanityid":
|
||||
{
|
||||
EmbedBuilder eb = new();
|
||||
var oldGid = (string)arg.Data.Options.First(f => f.Name == "syncshell_id").Value;
|
||||
var newGid = (string)arg.Data.Options.First(f => f.Name == "vanity_syncshell_id").Value;
|
||||
|
||||
eb = await HandleVanityGid(eb, arg.User.Id, oldGid, newGid);
|
||||
|
||||
await arg.RespondAsync(embeds: new[] { eb.Build() }, ephemeral: true).ConfigureAwait(false);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -141,11 +156,75 @@ public class DiscordBot : IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<EmbedBuilder> HandleVanityUid(EmbedBuilder eb, ulong id, string newUid)
|
||||
private async Task<EmbedBuilder> HandleVanityGid(EmbedBuilder eb, ulong id, string oldGid, string newGid)
|
||||
{
|
||||
if (LastVanityGidChange.TryGetValue(oldGid, out var lastChange))
|
||||
{
|
||||
var dateTimeDiff = DateTime.UtcNow.Subtract(lastChange);
|
||||
if (dateTimeDiff.TotalHours < 24)
|
||||
{
|
||||
eb.WithTitle(("Failed to set Vanity Syncshell Id"));
|
||||
eb.WithDescription(
|
||||
$"You can only change the Vanity Syncshell Id once every 24h. Your last change is {dateTimeDiff} ago.");
|
||||
}
|
||||
}
|
||||
|
||||
Regex rgx = new(@"[_\-a-zA-Z0-9]{5,20}", RegexOptions.ECMAScript);
|
||||
if (!rgx.Match(newGid).Success || newGid.Length < 5 || newGid.Length > 20)
|
||||
{
|
||||
eb.WithTitle("Failed to set Vanity Syncshell Id");
|
||||
eb.WithDescription("The Vanity Syncshell Id must be between 5 and 20 characters and only contain letters A-Z, numbers 0-9 as well as - and _.");
|
||||
return eb;
|
||||
}
|
||||
|
||||
using var scope = services.CreateScope();
|
||||
await using var db = scope.ServiceProvider.GetRequiredService<MareDbContext>();
|
||||
|
||||
var lodestoneUser = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(u => u.DiscordId == id).ConfigureAwait(false);
|
||||
if (lodestoneUser == null)
|
||||
{
|
||||
eb.WithTitle("Failed to set Vanity Syncshell Id");
|
||||
eb.WithDescription("You do not have a registered account on this server.");
|
||||
return eb;
|
||||
}
|
||||
|
||||
var group = await db.Groups.FirstOrDefaultAsync(g => g.GID == oldGid || g.Alias == oldGid).ConfigureAwait(false);
|
||||
if (group == null)
|
||||
{
|
||||
eb.WithTitle("Failed to set Vanity Syncshell Id");
|
||||
eb.WithDescription("The provided Syncshell Id does not exist.");
|
||||
return eb;
|
||||
}
|
||||
|
||||
if (lodestoneUser.User.UID != group.OwnerUID)
|
||||
{
|
||||
eb.WithTitle("Failed to set Vanity Syncshell Id");
|
||||
eb.WithDescription("You are not the owner of this Syncshell");
|
||||
return eb;
|
||||
}
|
||||
|
||||
var uidExists = await db.Groups.AnyAsync(u => u.GID == newGid || u.Alias == newGid).ConfigureAwait(false);
|
||||
if (uidExists)
|
||||
{
|
||||
eb.WithTitle("Failed to set Vanity Syncshell Id");
|
||||
eb.WithDescription("This Syncshell Id is already taken.");
|
||||
return eb;
|
||||
}
|
||||
|
||||
group.Alias = newGid;
|
||||
db.Update(group);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
LastVanityGidChange[newGid] = DateTime.UtcNow;
|
||||
LastVanityGidChange[oldGid] = DateTime.UtcNow;
|
||||
|
||||
eb.WithTitle("Vanity Syncshell Id set");
|
||||
eb.WithDescription("The Vanity Syncshell Id was set to **" + newGid + "**." + Environment.NewLine + "For those changes to apply you will have to reconnect to Mare.");
|
||||
return eb;
|
||||
}
|
||||
|
||||
private async Task<EmbedBuilder> HandleVanityUid(EmbedBuilder eb, ulong id, string newUid)
|
||||
{
|
||||
if (LastVanityChange.TryGetValue(id, out var lastChange))
|
||||
{
|
||||
var timeRemaining = DateTime.UtcNow.Subtract(lastChange);
|
||||
@@ -157,14 +236,17 @@ public class DiscordBot : IHostedService
|
||||
}
|
||||
}
|
||||
|
||||
Regex rgx = new("[A-Z0-9]{10}", RegexOptions.ECMAScript);
|
||||
if (!rgx.Match(newUid).Success || newUid.Length != 10)
|
||||
Regex rgx = new(@"[_\-a-zA-Z0-9]{5,15}", RegexOptions.ECMAScript);
|
||||
if (!rgx.Match(newUid).Success || newUid.Length < 5 || newUid.Length > 15)
|
||||
{
|
||||
eb.WithTitle("Failed to set Vanity UID");
|
||||
eb.WithDescription("The Vanity UID must be 10 characters long and only contain uppercase letters A-Z and numbers 0-9.");
|
||||
eb.WithDescription("The Vanity UID must be between 5 and 20 characters and only contain letters A-Z, numbers 0-9, as well as - and _.");
|
||||
return eb;
|
||||
}
|
||||
|
||||
using var scope = services.CreateScope();
|
||||
await using var db = scope.ServiceProvider.GetRequiredService<MareDbContext>();
|
||||
|
||||
var lodestoneUser = await db.LodeStoneAuth.Include("User").SingleOrDefaultAsync(u => u.DiscordId == id).ConfigureAwait(false);
|
||||
if (lodestoneUser == null)
|
||||
{
|
||||
@@ -202,7 +284,7 @@ public class DiscordBot : IHostedService
|
||||
{
|
||||
if (discordAuthedUser.User != null)
|
||||
{
|
||||
cleanupService.PurgeUser(discordAuthedUser.User, db);
|
||||
await cleanupService.PurgeUser(discordAuthedUser.User, db);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -508,6 +590,12 @@ public class DiscordBot : IHostedService
|
||||
vanityuid.WithDescription("Sets your Vanity UID.");
|
||||
vanityuid.AddOption("vanity_uid", ApplicationCommandOptionType.String, "Desired Vanity UID", isRequired: true);
|
||||
|
||||
var vanitygid = new SlashCommandBuilder();
|
||||
vanitygid.WithName("setsyncshellvanityid");
|
||||
vanitygid.WithDescription("Sets a Vanity GID for a Syncshell");
|
||||
vanitygid.AddOption("syncshell_id", ApplicationCommandOptionType.String, "Syncshell ID", isRequired: true);
|
||||
vanitygid.AddOption("vanity_syncshell_id", ApplicationCommandOptionType.String, "Desired Vanity Syncshell ID", isRequired: true);
|
||||
|
||||
var recover = new SlashCommandBuilder();
|
||||
recover.WithName("recover");
|
||||
recover.WithDescription("Allows you to recover your account by generating a new secret key");
|
||||
@@ -528,7 +616,6 @@ public class DiscordBot : IHostedService
|
||||
}
|
||||
if (!commands.Any(c => c.Name.Contains("setvanityuid")))
|
||||
{
|
||||
await guild.CreateApplicationCommandAsync(recover.Build()).ConfigureAwait(false);
|
||||
var vanityCommand = await guild.CreateApplicationCommandAsync(vanityuid.Build()).ConfigureAwait(false);
|
||||
vanityCommandId = vanityCommand.Id;
|
||||
}
|
||||
@@ -536,6 +623,10 @@ public class DiscordBot : IHostedService
|
||||
{
|
||||
vanityCommandId = commands.First(c => c.Name.Contains("setvanityuid")).Id;
|
||||
}
|
||||
if (!commands.Any(c => c.Name.Contains("setsyncshellvanityid")))
|
||||
{
|
||||
await guild.CreateApplicationCommandAsync(vanitygid.Build()).ConfigureAwait(false);
|
||||
}
|
||||
if (!commands.Any(c => c.Name.Contains("recover")))
|
||||
{
|
||||
await guild.CreateApplicationCommandAsync(recover.Build()).ConfigureAwait(false);
|
||||
@@ -651,6 +742,8 @@ public class DiscordBot : IHostedService
|
||||
{
|
||||
var aliasedUsers = db.LodeStoneAuth.Include("User")
|
||||
.Where(c => c.User != null && !string.IsNullOrEmpty(c.User.Alias));
|
||||
var aliasedGroups = db.Groups.Include(u => u.Owner)
|
||||
.Where(c => !string.IsNullOrEmpty(c.Alias));
|
||||
|
||||
foreach (var lodestoneAuth in aliasedUsers)
|
||||
{
|
||||
@@ -665,11 +758,32 @@ public class DiscordBot : IHostedService
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
foreach (var group in aliasedGroups)
|
||||
{
|
||||
var lodestoneUser = await db.LodeStoneAuth.Include(u => u.User).SingleOrDefaultAsync(f => f.User.UID == group.OwnerUID);
|
||||
RestGuildUser discordUser = null;
|
||||
if (lodestoneUser != null)
|
||||
{
|
||||
discordUser = await restGuild.GetUserAsync(lodestoneUser.DiscordId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
logger.LogInformation($"Checking Group: {group.GID}, owned by {lodestoneUser?.User?.UID ?? string.Empty} ({lodestoneUser?.User?.Alias ?? string.Empty}), User in Roles: {string.Join(", ", discordUser?.RoleIds ?? new List<ulong>())}");
|
||||
|
||||
if (lodestoneUser == null || discordUser == null || !discordUser.RoleIds.Any(u => allowedRoleIds.Contains(u)))
|
||||
{
|
||||
logger.LogInformation($"User {lodestoneUser.User.UID} not in allowed roles, deleting group alias");
|
||||
group.Alias = null;
|
||||
db.Update(group);
|
||||
}
|
||||
|
||||
await Task.Delay(100);
|
||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("No roles for command defined, no cleanup performed");
|
||||
@@ -690,7 +804,7 @@ public class DiscordBot : IHostedService
|
||||
updateStatusCts = new();
|
||||
while (!updateStatusCts.IsCancellationRequested)
|
||||
{
|
||||
var onlineUsers = clientService.GetOnlineUsers();
|
||||
var onlineUsers = await clientService.GetOnlineUsers();
|
||||
logger.LogInformation("Users online: " + onlineUsers);
|
||||
await discordClient.SetActivityAsync(new Game("Mare for " + onlineUsers + " Users")).ConfigureAwait(false);
|
||||
await Task.Delay(TimeSpan.FromSeconds(15)).ConfigureAwait(false);
|
||||
|
||||
@@ -5,8 +5,8 @@ using MareSynchronosShared.Protos;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosServices.Services
|
||||
{
|
||||
namespace MareSynchronosServices.Services;
|
||||
|
||||
public class AuthenticationService : AuthService.AuthServiceBase
|
||||
{
|
||||
private readonly ILogger<AuthenticationService> _logger;
|
||||
@@ -39,4 +39,3 @@ namespace MareSynchronosServices.Services
|
||||
return Task.FromResult(new Empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,14 @@
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using MareSynchronosServer;
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ISystemClock = Microsoft.AspNetCore.Authentication.ISystemClock;
|
||||
|
||||
namespace MareSynchronosShared.Authentication
|
||||
{
|
||||
namespace MareSynchronosShared.Authentication;
|
||||
|
||||
public class SecretKeyGrpcAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
@@ -60,4 +54,3 @@ namespace MareSynchronosShared.Authentication
|
||||
return AuthenticateResult.Success(ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,14 @@ public class MareDbContext : DbContext
|
||||
public DbSet<Auth> Auth { get; set; }
|
||||
public DbSet<LodeStoneAuth> LodeStoneAuth { get; set; }
|
||||
public DbSet<BannedRegistrations> BannedRegistrations { get; set; }
|
||||
public DbSet<Group> Groups { get; set; }
|
||||
public DbSet<GroupPair> GroupPairs { get; set; }
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Auth>().ToTable("auth");
|
||||
modelBuilder.Entity<User>().ToTable("users");
|
||||
//modelBuilder.Entity<User>().HasIndex(c => c.CharacterIdentification);
|
||||
modelBuilder.Entity<FileCache>().ToTable("file_caches");
|
||||
modelBuilder.Entity<FileCache>().HasIndex(c => c.UploaderUID);
|
||||
modelBuilder.Entity<ClientPair>().ToTable("client_pairs");
|
||||
@@ -56,5 +57,11 @@ public class MareDbContext : DbContext
|
||||
modelBuilder.Entity<Banned>().ToTable("banned_users");
|
||||
modelBuilder.Entity<LodeStoneAuth>().ToTable("lodestone_auth");
|
||||
modelBuilder.Entity<BannedRegistrations>().ToTable("banned_registrations");
|
||||
modelBuilder.Entity<Group>().ToTable("groups");
|
||||
modelBuilder.Entity<Group>().HasIndex(c => c.OwnerUID);
|
||||
modelBuilder.Entity<GroupPair>().ToTable("group_pairs");
|
||||
modelBuilder.Entity<GroupPair>().HasKey(u => new { u.GroupGID, u.GroupUserUID });
|
||||
modelBuilder.Entity<GroupPair>().HasIndex(c => c.GroupUserUID);
|
||||
modelBuilder.Entity<GroupPair>().HasIndex(c => c.GroupGID);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Linq;
|
||||
|
||||
namespace MareSynchronosServer
|
||||
{
|
||||
namespace MareSynchronosServer;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static string GetIpAddress(this IHttpContextAccessor accessor)
|
||||
@@ -27,4 +26,3 @@ namespace MareSynchronosServer
|
||||
return accessor.HttpContext.Connection.RemoteIpAddress.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="6.0.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore" Version="2.47.0" />
|
||||
@@ -33,6 +37,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="6.0.8" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.6" />
|
||||
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using MareSynchronosShared.Data;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Prometheus;
|
||||
using Prometheus;
|
||||
|
||||
namespace MareSynchronosShared.Metrics;
|
||||
|
||||
|
||||
@@ -19,4 +19,7 @@ public class MetricsAPI
|
||||
public const string CounterAuthenticationCacheHits = "mare_auth_requests_cachehit";
|
||||
public const string CounterAuthenticationFailures = "mare_auth_requests_fail";
|
||||
public const string CounterAuthenticationSuccesses = "mare_auth_requests_success";
|
||||
public const string GaugeGroups = "mare_groups";
|
||||
public const string GaugeGroupPairs = "mare_groups_pairs";
|
||||
public const string GaugeGroupPairsPaused = "mare_groups_pairs_paused";
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
||||
389
MareSynchronosServer/MareSynchronosShared/Migrations/20220917115233_Groups.Designer.cs
generated
Normal file
389
MareSynchronosServer/MareSynchronosShared/Migrations/20220917115233_Groups.Designer.cs
generated
Normal file
@@ -0,0 +1,389 @@
|
||||
// <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("20220917115233_Groups")]
|
||||
partial class Groups
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.8")
|
||||
.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<string>("UserUID")
|
||||
.HasColumnType("character varying(10)")
|
||||
.HasColumnName("user_uid");
|
||||
|
||||
b.HasKey("HashedKey")
|
||||
.HasName("pk_auth");
|
||||
|
||||
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>("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<byte[]>("Timestamp")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("timestamp");
|
||||
|
||||
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(14)
|
||||
.HasColumnType("character varying(14)")
|
||||
.HasColumnName("gid");
|
||||
|
||||
b.Property<string>("Alias")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)")
|
||||
.HasColumnName("alias");
|
||||
|
||||
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.GroupPair", b =>
|
||||
{
|
||||
b.Property<string>("GroupGID")
|
||||
.HasColumnType("character varying(14)")
|
||||
.HasColumnName("group_gid");
|
||||
|
||||
b.Property<string>("GroupUserUID")
|
||||
.HasColumnType("character varying(10)")
|
||||
.HasColumnName("group_user_uid");
|
||||
|
||||
b.Property<bool>("IsPaused")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_paused");
|
||||
|
||||
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.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(10)
|
||||
.HasColumnType("character varying(10)")
|
||||
.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.Auth", b =>
|
||||
{
|
||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserUID")
|
||||
.HasConstraintName("fk_auth_users_user_temp_id");
|
||||
|
||||
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_id1");
|
||||
|
||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserUID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_client_pairs_users_user_temp_id2");
|
||||
|
||||
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_id5");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
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_id");
|
||||
|
||||
b.HasOne("MareSynchronosShared.Models.User", "GroupUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("GroupUserUID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_group_pairs_users_group_user_temp_id4");
|
||||
|
||||
b.Navigation("Group");
|
||||
|
||||
b.Navigation("GroupUser");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MareSynchronosServer.Migrations
|
||||
{
|
||||
public partial class Groups : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_users_character_identification",
|
||||
table: "users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "character_identification",
|
||||
table: "users");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "alias",
|
||||
table: "users",
|
||||
type: "character varying(10)",
|
||||
maxLength: 10,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(100)",
|
||||
oldMaxLength: 100,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "groups",
|
||||
columns: table => new
|
||||
{
|
||||
gid = table.Column<string>(type: "character varying(14)", maxLength: 14, nullable: false),
|
||||
owner_uid = table.Column<string>(type: "character varying(10)", nullable: true),
|
||||
alias = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: true),
|
||||
invites_enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
hashed_password = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_groups", x => x.gid);
|
||||
table.ForeignKey(
|
||||
name: "fk_groups_users_owner_temp_id5",
|
||||
column: x => x.owner_uid,
|
||||
principalTable: "users",
|
||||
principalColumn: "uid");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "group_pairs",
|
||||
columns: table => new
|
||||
{
|
||||
group_gid = table.Column<string>(type: "character varying(14)", nullable: false),
|
||||
group_user_uid = table.Column<string>(type: "character varying(10)", nullable: false),
|
||||
is_paused = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_group_pairs", x => new { x.group_gid, x.group_user_uid });
|
||||
table.ForeignKey(
|
||||
name: "fk_group_pairs_groups_group_temp_id",
|
||||
column: x => x.group_gid,
|
||||
principalTable: "groups",
|
||||
principalColumn: "gid",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_group_pairs_users_group_user_temp_id4",
|
||||
column: x => x.group_user_uid,
|
||||
principalTable: "users",
|
||||
principalColumn: "uid",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_group_pairs_group_gid",
|
||||
table: "group_pairs",
|
||||
column: "group_gid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_group_pairs_group_user_uid",
|
||||
table: "group_pairs",
|
||||
column: "group_user_uid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_groups_owner_uid",
|
||||
table: "groups",
|
||||
column: "owner_uid");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "group_pairs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "groups");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "alias",
|
||||
table: "users",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(10)",
|
||||
oldMaxLength: 10,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "character_identification",
|
||||
table: "users",
|
||||
type: "character varying(100)",
|
||||
maxLength: 100,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_users_character_identification",
|
||||
table: "users",
|
||||
column: "character_identification");
|
||||
}
|
||||
}
|
||||
}
|
||||
389
MareSynchronosServer/MareSynchronosShared/Migrations/20220929150304_ChangeGidLength.Designer.cs
generated
Normal file
389
MareSynchronosServer/MareSynchronosShared/Migrations/20220929150304_ChangeGidLength.Designer.cs
generated
Normal file
@@ -0,0 +1,389 @@
|
||||
// <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("20220929150304_ChangeGidLength")]
|
||||
partial class ChangeGidLength
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.8")
|
||||
.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<string>("UserUID")
|
||||
.HasColumnType("character varying(10)")
|
||||
.HasColumnName("user_uid");
|
||||
|
||||
b.HasKey("HashedKey")
|
||||
.HasName("pk_auth");
|
||||
|
||||
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>("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<byte[]>("Timestamp")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("timestamp");
|
||||
|
||||
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<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.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>("IsPaused")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_paused");
|
||||
|
||||
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.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(10)
|
||||
.HasColumnType("character varying(10)")
|
||||
.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.Auth", b =>
|
||||
{
|
||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserUID")
|
||||
.HasConstraintName("fk_auth_users_user_temp_id");
|
||||
|
||||
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_id1");
|
||||
|
||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserUID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_client_pairs_users_user_temp_id2");
|
||||
|
||||
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_id5");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
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_id");
|
||||
|
||||
b.HasOne("MareSynchronosShared.Models.User", "GroupUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("GroupUserUID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_group_pairs_users_group_user_temp_id4");
|
||||
|
||||
b.Navigation("Group");
|
||||
|
||||
b.Navigation("GroupUser");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MareSynchronosServer.Migrations
|
||||
{
|
||||
public partial class ChangeGidLength : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "gid",
|
||||
table: "groups",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(14)",
|
||||
oldMaxLength: 14);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "group_gid",
|
||||
table: "group_pairs",
|
||||
type: "character varying(20)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(14)");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "gid",
|
||||
table: "groups",
|
||||
type: "character varying(14)",
|
||||
maxLength: 14,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(20)",
|
||||
oldMaxLength: 20);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "group_gid",
|
||||
table: "group_pairs",
|
||||
type: "character varying(14)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(20)");
|
||||
}
|
||||
}
|
||||
}
|
||||
393
MareSynchronosServer/MareSynchronosShared/Migrations/20221002105428_IsPinned.Designer.cs
generated
Normal file
393
MareSynchronosServer/MareSynchronosShared/Migrations/20221002105428_IsPinned.Designer.cs
generated
Normal file
@@ -0,0 +1,393 @@
|
||||
// <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("20221002105428_IsPinned")]
|
||||
partial class IsPinned
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.8")
|
||||
.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<string>("UserUID")
|
||||
.HasColumnType("character varying(10)")
|
||||
.HasColumnName("user_uid");
|
||||
|
||||
b.HasKey("HashedKey")
|
||||
.HasName("pk_auth");
|
||||
|
||||
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>("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<byte[]>("Timestamp")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("bytea")
|
||||
.HasColumnName("timestamp");
|
||||
|
||||
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<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.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>("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.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(10)
|
||||
.HasColumnType("character varying(10)")
|
||||
.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.Auth", b =>
|
||||
{
|
||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserUID")
|
||||
.HasConstraintName("fk_auth_users_user_temp_id");
|
||||
|
||||
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_id1");
|
||||
|
||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserUID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_client_pairs_users_user_temp_id2");
|
||||
|
||||
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_id5");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
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_id");
|
||||
|
||||
b.HasOne("MareSynchronosShared.Models.User", "GroupUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("GroupUserUID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_group_pairs_users_group_user_temp_id4");
|
||||
|
||||
b.Navigation("Group");
|
||||
|
||||
b.Navigation("GroupUser");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MareSynchronosServer.Migrations
|
||||
{
|
||||
public partial class IsPinned : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "is_pinned",
|
||||
table: "group_pairs",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "is_pinned",
|
||||
table: "group_pairs");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,6 +171,69 @@ namespace MareSynchronosServer.Migrations
|
||||
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<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.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>("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.LodeStoneAuth", b =>
|
||||
{
|
||||
b.Property<decimal>("DiscordId")
|
||||
@@ -213,15 +276,10 @@ namespace MareSynchronosServer.Migrations
|
||||
.HasColumnName("uid");
|
||||
|
||||
b.Property<string>("Alias")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)")
|
||||
.HasColumnName("alias");
|
||||
|
||||
b.Property<string>("CharacterIdentification")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasColumnName("character_identification");
|
||||
|
||||
b.Property<bool>("IsAdmin")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_admin");
|
||||
@@ -243,9 +301,6 @@ namespace MareSynchronosServer.Migrations
|
||||
b.HasKey("UID")
|
||||
.HasName("pk_users");
|
||||
|
||||
b.HasIndex("CharacterIdentification")
|
||||
.HasDatabaseName("ix_users_character_identification");
|
||||
|
||||
b.ToTable("users", (string)null);
|
||||
});
|
||||
|
||||
@@ -290,6 +345,37 @@ namespace MareSynchronosServer.Migrations
|
||||
b.Navigation("Uploader");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MareSynchronosShared.Models.Group", b =>
|
||||
{
|
||||
b.HasOne("MareSynchronosShared.Models.User", "Owner")
|
||||
.WithMany()
|
||||
.HasForeignKey("OwnerUID")
|
||||
.HasConstraintName("fk_groups_users_owner_temp_id5");
|
||||
|
||||
b.Navigation("Owner");
|
||||
});
|
||||
|
||||
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_id");
|
||||
|
||||
b.HasOne("MareSynchronosShared.Models.User", "GroupUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("GroupUserUID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_group_pairs_users_group_user_temp_id4");
|
||||
|
||||
b.Navigation("Group");
|
||||
|
||||
b.Navigation("GroupUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MareSynchronosShared.Models.LodeStoneAuth", b =>
|
||||
{
|
||||
b.HasOne("MareSynchronosShared.Models.User", "User")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class Auth
|
||||
{
|
||||
[Key]
|
||||
@@ -11,4 +11,3 @@ namespace MareSynchronosShared.Models
|
||||
public string UserUID { get; set; }
|
||||
public User User { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class Banned
|
||||
{
|
||||
[Key]
|
||||
@@ -11,4 +11,3 @@ namespace MareSynchronosShared.Models
|
||||
[Timestamp]
|
||||
public byte[] Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class BannedRegistrations
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(100)]
|
||||
public string DiscordIdOrLodestoneAuth { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class ClientPair
|
||||
{
|
||||
[MaxLength(10)]
|
||||
@@ -15,4 +15,3 @@ namespace MareSynchronosShared.Models
|
||||
[Timestamp]
|
||||
public byte[] Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class FileCache
|
||||
{
|
||||
[Key]
|
||||
@@ -14,4 +14,3 @@ namespace MareSynchronosShared.Models
|
||||
[Timestamp]
|
||||
public byte[] Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class ForbiddenUploadEntry
|
||||
{
|
||||
[Key]
|
||||
@@ -12,4 +12,3 @@ namespace MareSynchronosShared.Models
|
||||
[Timestamp]
|
||||
public byte[] Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
16
MareSynchronosServer/MareSynchronosShared/Models/Group.cs
Normal file
16
MareSynchronosServer/MareSynchronosShared/Models/Group.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class Group
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(20)]
|
||||
public string GID { get; set; }
|
||||
public string OwnerUID { get; set; }
|
||||
public User Owner { get; set; }
|
||||
[MaxLength(50)]
|
||||
public string Alias { get; set; }
|
||||
public bool InvitesEnabled { get; set; }
|
||||
public string HashedPassword { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class GroupPair
|
||||
{
|
||||
public string GroupGID { get; set; }
|
||||
public Group Group { get; set; }
|
||||
public string GroupUserUID { get; set; }
|
||||
public User GroupUser { get; set; }
|
||||
public bool IsPaused { get; set; }
|
||||
public bool IsPinned { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class LodeStoneAuth
|
||||
{
|
||||
[Key]
|
||||
@@ -13,4 +13,3 @@ namespace MareSynchronosShared.Models
|
||||
public User? User { get; set; }
|
||||
public DateTime? StartedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MareSynchronosShared.Models
|
||||
{
|
||||
namespace MareSynchronosShared.Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(10)]
|
||||
public string UID { get; set; }
|
||||
//[MaxLength(100)]
|
||||
//public string CharacterIdentification { get; set; }
|
||||
[Timestamp]
|
||||
public byte[] Timestamp { get; set; }
|
||||
|
||||
@@ -20,4 +18,3 @@ namespace MareSynchronosShared.Models
|
||||
[MaxLength(10)]
|
||||
public string Alias { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,40 +12,43 @@ public abstract class BaseClientIdentificationService : IClientIdentificationSer
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public virtual int GetOnlineUsers()
|
||||
public virtual Task<int> GetOnlineUsers()
|
||||
{
|
||||
return OnlineClients.Count;
|
||||
return Task.FromResult(OnlineClients.Count);
|
||||
}
|
||||
|
||||
public string? GetUidForCharacterIdent(string characterIdent)
|
||||
public Task<string?> GetUidForCharacterIdent(string characterIdent)
|
||||
{
|
||||
var result = OnlineClients.SingleOrDefault(u =>
|
||||
string.Compare(u.Value, characterIdent, StringComparison.InvariantCultureIgnoreCase) == 0);
|
||||
return result.Equals(new KeyValuePair<string, string>()) ? null : result.Key;
|
||||
return Task.FromResult(result.Equals(new KeyValuePair<string, string>()) ? null : result.Key);
|
||||
}
|
||||
|
||||
public virtual string? GetCharacterIdentForUid(string uid)
|
||||
public virtual Task<string?> GetCharacterIdentForUid(string uid)
|
||||
{
|
||||
if (!OnlineClients.TryGetValue(uid, out var result))
|
||||
{
|
||||
return null;
|
||||
return Task.FromResult((string?)null);
|
||||
}
|
||||
|
||||
return result;
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public virtual void MarkUserOnline(string uid, string charaIdent)
|
||||
public virtual Task MarkUserOnline(string uid, string charaIdent)
|
||||
{
|
||||
OnlineClients[uid] = charaIdent;
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual void MarkUserOffline(string uid)
|
||||
public virtual Task MarkUserOffline(string uid)
|
||||
{
|
||||
if (OnlineClients.TryRemove(uid, out _))
|
||||
{
|
||||
metrics.SetGaugeTo(MetricsAPI.GaugeAuthorizedConnections, OnlineClients.Count);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -21,14 +21,14 @@ public class DistributedClientIdentificationService : BaseClientIdentificationSe
|
||||
this.configuration = configuration.GetSection("MareSynchronos");
|
||||
}
|
||||
|
||||
public override int GetOnlineUsers()
|
||||
public override async Task<int> GetOnlineUsers()
|
||||
{
|
||||
try
|
||||
{
|
||||
var redis = configuration.GetValue<string>("RedisConnectionString");
|
||||
var conn = ConnectionMultiplexer.Connect(redis);
|
||||
var conn = await ConnectionMultiplexer.ConnectAsync(redis).ConfigureAwait(false);
|
||||
var endpoint = conn.GetEndPoints().First();
|
||||
return conn.GetServer(endpoint).Keys(pattern: "*" + RedisPrefix + "*").Count();
|
||||
return await conn.GetServer(endpoint).KeysAsync(pattern: "*" + RedisPrefix + "*").CountAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -37,27 +37,27 @@ public class DistributedClientIdentificationService : BaseClientIdentificationSe
|
||||
}
|
||||
}
|
||||
|
||||
public override string? GetCharacterIdentForUid(string uid)
|
||||
public override async Task<string?> GetCharacterIdentForUid(string uid)
|
||||
{
|
||||
var localIdent = base.GetCharacterIdentForUid(uid);
|
||||
var localIdent = await base.GetCharacterIdentForUid(uid).ConfigureAwait(false);
|
||||
if (localIdent != null) return localIdent;
|
||||
var cachedIdent = distributedCache.Get(RedisPrefix + uid);
|
||||
return cachedIdent == null ? null : Encoding.UTF8.GetString(cachedIdent);
|
||||
var cachedIdent = await distributedCache.GetStringAsync(RedisPrefix + uid).ConfigureAwait(false);
|
||||
return cachedIdent ?? null;
|
||||
}
|
||||
|
||||
public override void MarkUserOffline(string uid)
|
||||
public override async Task MarkUserOffline(string uid)
|
||||
{
|
||||
base.MarkUserOffline(uid);
|
||||
distributedCache.Remove(RedisPrefix + uid);
|
||||
await base.MarkUserOffline(uid).ConfigureAwait(false);
|
||||
await distributedCache.RemoveAsync(RedisPrefix + uid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override void MarkUserOnline(string uid, string charaIdent)
|
||||
public override async Task MarkUserOnline(string uid, string charaIdent)
|
||||
{
|
||||
base.MarkUserOnline(uid, charaIdent);
|
||||
distributedCache.Set(RedisPrefix + uid, Encoding.UTF8.GetBytes(charaIdent), new DistributedCacheEntryOptions()
|
||||
await base.MarkUserOnline(uid, charaIdent).ConfigureAwait(false);
|
||||
await distributedCache.SetAsync(RedisPrefix + uid, Encoding.UTF8.GetBytes(charaIdent), new DistributedCacheEntryOptions()
|
||||
{
|
||||
AbsoluteExpiration = DateTime.Now.AddDays(7)
|
||||
});
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
|
||||
@@ -4,9 +4,9 @@ namespace MareSynchronosShared.Services;
|
||||
|
||||
public interface IClientIdentificationService : IHostedService
|
||||
{
|
||||
int GetOnlineUsers();
|
||||
string? GetUidForCharacterIdent(string characterIdent);
|
||||
string? GetCharacterIdentForUid(string uid);
|
||||
void MarkUserOnline(string uid, string charaIdent);
|
||||
void MarkUserOffline(string uid);
|
||||
Task<int> GetOnlineUsers();
|
||||
Task<string?> GetUidForCharacterIdent(string characterIdent);
|
||||
Task<string?> GetCharacterIdentForUid(string uid);
|
||||
Task MarkUserOnline(string uid, string charaIdent);
|
||||
Task MarkUserOffline(string uid);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronosShared.Utils;
|
||||
|
||||
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)
|
||||
{
|
||||
groupHasMigrated = await TryMigrateGroup(context, group, potentialNewOwner.GroupUserUID, maxGroupsByUser).ConfigureAwait(false);
|
||||
|
||||
if (groupHasMigrated)
|
||||
{
|
||||
newOwner = potentialNewOwner.GroupUserUID;
|
||||
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)
|
||||
{
|
||||
var newOwnerOwnedGroups = await context.Groups.CountAsync(g => g.OwnerUID == potentialNewOwnerUid).ConfigureAwait(false);
|
||||
if (newOwnerOwnedGroups >= maxGroupsByUser)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
group.OwnerUID = potentialNewOwnerUid;
|
||||
group.Alias = null;
|
||||
await context.SaveChangesAsync().ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace MareSynchronosShared.Utils;
|
||||
|
||||
public static class StringUtils
|
||||
{
|
||||
public static string GenerateRandomString(int length, string? allowableChars = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(allowableChars))
|
||||
allowableChars = @"ABCDEFGHJKLMNPQRSTUVWXYZ0123456789";
|
||||
|
||||
// Generate random data
|
||||
var rnd = RandomNumberGenerator.GetBytes(length);
|
||||
|
||||
// Generate the output string
|
||||
var allowable = allowableChars.ToCharArray();
|
||||
var l = allowable.Length;
|
||||
var chars = new char[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
chars[i] = allowable[rnd[i] % l];
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
public static string Sha256String(string input)
|
||||
{
|
||||
using var sha256 = SHA256.Create();
|
||||
return BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(input))).Replace("-", "", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using MareSynchronosShared.Data;
|
||||
using MareSynchronosShared.Metrics;
|
||||
using MareSynchronosShared.Protos;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
Reference in New Issue
Block a user