using System.Text.Json; using RelayServer.Models; using RelayServer.Services.Chat; using RelayServer.Services.Crypto; using RelayShared.Services; using SurrealDb.Net; namespace RelayServer.Services.Core; public sealed class ServerBootstrapService { private readonly SurrealDbClient _db; private readonly CoreClientService _coreClient; private readonly ChannelCryptoService _cryptoService; public ServerBootstrapService( SurrealDbClient db, CoreClientService coreClient, ChannelCryptoService cryptoService) { _db = db; _coreClient = coreClient; _cryptoService = cryptoService; } public async Task InitializeAsync() { var keeper = await _coreClient.GetUserByUsernameAsync("Keeper317"); var kira = await _coreClient.GetUserByUsernameAsync("Ru_Kira"); var test = await _coreClient.GetUserByUsernameAsync("Test"); if (keeper is null || kira is null || test is null) throw new InvalidOperationException("One or more required users do not exist in RelayCore."); if (!keeper.Licensed || !kira.Licensed || !test.Licensed) throw new InvalidOperationException("One or more required users are not licensed."); Console.WriteLine($"Core verified: {keeper.Username}, {kira.Username}, {test.Username}"); var server = await GetServerByNameAsync("Test Server"); if (server is null) { server = await _db.Create("servers", new Servers { Name = "Test Server", OwnerUserId = keeper.Id, CreatedAt = DateTime.UtcNow }); Console.WriteLine($"Server created: {ToJson(server)}"); } else { Console.WriteLine($"Server already exists: {server.Name}"); } await EnsureServerMemberAsync(keeper.Id, isOwner: true); await EnsureServerMemberAsync(kira.Id, isOwner: false); await EnsureServerMemberAsync(test.Id, isOwner: false); Console.WriteLine("Server members ensured."); var tBase = new DateTime(2024, 1, 1, 0, 0, 0, DateTimeKind.Utc); var chWelcome = await EnsureChannelAsync("welcome", ChannelType.Text, group: "General", isReadOnly: true, createdAt: tBase); var chGeneral = await EnsureChannelAsync("general", ChannelType.Text, group: "General", isReadOnly: false, createdAt: tBase.AddHours(1)); var chFiles = await EnsureChannelAsync("files", ChannelType.File, group: "General", isReadOnly: true, createdAt: tBase.AddHours(2)); var chVoice = await EnsureChannelAsync("voice-general", ChannelType.Voice, group: "General", isReadOnly: false, createdAt: tBase.AddHours(3)); Console.WriteLine($"Channels: {GetRecordId(chWelcome.Id)} | {GetRecordId(chGeneral.Id)} | {GetRecordId(chFiles.Id)} | {GetRecordId(chVoice.Id)}"); await EnsureFileChannelLinkAsync(chGeneral, GetRecordId(chFiles.Id)); var adminRole = await EnsureRoleAsync("Admin", PermissionFlags.Administrator, priority: 0); var modRole = await EnsureRoleAsync("Moderator", PermissionFlags.ReadMessages | PermissionFlags.SendMessages | PermissionFlags.ManageMessages, priority: 1); var memberRole = await EnsureRoleAsync("Member", PermissionFlags.ReadMessages | PermissionFlags.SendMessages, priority: 2); Console.WriteLine($"Roles ensured: Admin={GetRecordId(adminRole.Id)}, Mod={GetRecordId(modRole.Id)}, Member={GetRecordId(memberRole.Id)}"); await SetUserRoleAsync(keeper.Id, GetRecordId(adminRole.Id)); await SetUserRoleAsync(kira.Id, GetRecordId(modRole.Id)); await SetUserRoleAsync(test.Id, GetRecordId(memberRole.Id)); Console.WriteLine("User roles set."); await EnsureChannelPermissionAsync(GetRecordId(chWelcome.Id), GetRecordId(memberRole.Id), allow: PermissionFlags.ReadMessages, deny: PermissionFlags.SendMessages); await EnsureChannelPermissionAsync(GetRecordId(chFiles.Id), GetRecordId(memberRole.Id), allow: PermissionFlags.ReadMessages, deny: PermissionFlags.SendMessages); Console.WriteLine("Channel permissions ensured."); var existingKey = await GetLatestServerEncryptionKeyAsync(); if (existingKey is null) { var keyBase64 = _cryptoService.GenerateKey(); var serverKeys = E2EeHelper.GenerateRsaKeyPair(); existingKey = await _db.Create("server_encryption_keys", new ServerEncryptionKeys { KeyBase64 = keyBase64, PublicKey = serverKeys.publicKey, PrivateKey = serverKeys.privateKey, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }); Console.WriteLine("Server encryption key created."); } else { Console.WriteLine("Server encryption key already exists."); } ChatSocketBehavior.ServerPublicKey = existingKey.PublicKey; ChatSocketBehavior.ServerPrivateKey = existingKey.PrivateKey; ChatSocketBehavior.ChannelDbKey = existingKey.KeyBase64; } private async Task EnsureServerMemberAsync(string userId, bool isOwner) { var members = await _db.Select("server_members"); var existing = members.FirstOrDefault(m => m.UserId == userId); if (existing is not null) { if (existing.IsOwner != isOwner) { existing.IsOwner = isOwner; await _db.Merge(existing); Console.WriteLine($"Member IsOwner updated: {userId} → {isOwner}"); } else { Console.WriteLine($"Member already correct: {userId}"); } return; } await _db.Create("server_members", new ServerMembers { UserId = userId, JoinedAt = DateTime.UtcNow, IsOwner = isOwner }); Console.WriteLine($"Member created: {userId} (IsOwner={isOwner})"); } private async Task EnsureChannelAsync( string name, ChannelType type, string group, bool isReadOnly, DateTime createdAt) { var channels = await _db.Select("channels"); var existing = channels.FirstOrDefault(c => c.Name == name); if (existing is not null) { bool dirty = existing.Type != type || existing.Group != group || existing.IsReadOnly != isReadOnly; if (dirty) { existing.Type = type; existing.Group = group; existing.IsReadOnly = isReadOnly; await _db.Merge(existing); Console.WriteLine($"Channel updated: {name}"); } else { Console.WriteLine($"Channel already correct: {name}"); } return existing; } var channel = await _db.Create("channels", new Channels { Name = name, Type = type, Group = group, IsReadOnly = isReadOnly, CreatedAt = createdAt }); Console.WriteLine($"Channel created: {name} ({type})"); return channel; } private async Task EnsureFileChannelLinkAsync(Channels channel, string fileChannelId) { if (channel.LinkedFileChannelId == fileChannelId) { Console.WriteLine($"File link already correct: {channel.Name} → {fileChannelId}"); return; } channel.LinkedFileChannelId = fileChannelId; await _db.Merge(channel); Console.WriteLine($"File link set: {channel.Name} → {fileChannelId}"); } private async Task EnsureRoleAsync(string name, PermissionFlags permissions, int priority) { var roles = await _db.Select("roles"); var existing = roles.FirstOrDefault(r => r.Name == name); if (existing is not null) { Console.WriteLine($"Role already exists: {name}"); return existing; } var role = await _db.Create("roles", new Roles { Name = name, Permissions = permissions, Priority = priority, CreatedAt = DateTime.UtcNow }); Console.WriteLine($"Role created: {name}"); return role; } private async Task SetUserRoleAsync(string userId, string roleId) { var userRoles = await _db.Select("user_roles"); var existing = userRoles .Where(ur => string.Equals(ur.UserId, userId, StringComparison.OrdinalIgnoreCase)) .ToList(); bool alreadyCorrect = existing.Count == 1 && existing[0].RoleId == roleId; if (alreadyCorrect) { Console.WriteLine($"UserRole already correct: {userId} → {roleId}"); return; } foreach (var stale in existing) { if (stale.Id is not null) await _db.Delete(stale.Id); } await _db.Create("user_roles", new UserRoles { UserId = userId, RoleId = roleId, AssignedAt = DateTime.UtcNow }); Console.WriteLine($"UserRole set: {userId} → {roleId}"); } private async Task EnsureChannelPermissionAsync( string channelId, string roleId, PermissionFlags allow, PermissionFlags deny) { var perms = await _db.Select("channel_permissions"); if (perms.Any(cp => cp.ChannelId == channelId && cp.RoleId == roleId)) { Console.WriteLine($"ChannelPermission already exists: {channelId} → {roleId}"); return; } await _db.Create("channel_permissions", new ChannelPermissions { ChannelId = channelId, RoleId = roleId, Allow = allow, Deny = deny }); Console.WriteLine($"ChannelPermission created: {channelId} → {roleId} | allow={allow}, deny={deny}"); } private async Task GetServerByNameAsync(string name) { var servers = await _db.Select("servers"); return servers.FirstOrDefault(x => x.Name == name); } private async Task GetLatestServerEncryptionKeyAsync() { var keys = await _db.Select("server_encryption_keys"); return keys.OrderByDescending(x => x.CreatedAt).FirstOrDefault(); } private static string GetRecordId(object? id) { if (id is null) return string.Empty; var json = JsonSerializer.Serialize(id); using var doc = JsonDocument.Parse(json); var root = doc.RootElement; return $"{root.GetProperty("Table").GetString()}:{root.GetProperty("Id").GetString()}"; } private static string ToJson(object? obj) => JsonSerializer.Serialize(obj, new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }); }