From 933c7eb51ccbb07d82627dca0e6b33d456b99846 Mon Sep 17 00:00:00 2001 From: Alexa <53471801+Alexa-RR@users.noreply.github.com> Date: Tue, 28 Jul 2026 09:19:35 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20close=20the=20last=20route=20gaps=20?= =?UTF-8?q?=E2=80=94=20100%=20client=20route+verb=20coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every HTTP request the 2023-03-21 client can issue now reaches a handler under the verb it actually uses: 522/522, with an empty KnownGaps allowlist. The coverage test asserts it, so a regression fails the build rather than becoming a 404 in the wild. The six routes left after the previous pass all needed storage that did not exist, so they are done here as one schema change rather than stubbed: - ClubRolePermissionEntity (ClubId, MembershipType, six bools) backs PUT club/{id}/permissions/{role}. A club with no stored row falls back to the derived default policy, so every club that predates the table behaves exactly as before. Partial writes keep the values they currently resolve to, so a client that omits a field cannot silently revoke a permission. - ClubAdditionalImageEntity (ClubId, Slot, ImageName) backs the gallery. The details envelope now emits real {ImageName, Slot} rows instead of an empty array, and DELETE clears a slot. - ClubEntity.MinLevel and ClubEntity.ClubChatEnabled back their setters and are read back by hasDisabledClubChat and the Club wire shape, which previously reported hardcoded defaults. - ChatMessageEntity.ModerationState backs PUT thread/message/{id}/moderate, gated so an author can retract their own message but only club moderators and admins can action anyone else's. - TestCaseEntity.CommentsJson backs the QA comment endpoint, appending rather than overwriting so a second tester's note cannot erase the first. Also corrects the Joinability comment on ClubEntity, which documented 1=RequestToJoin / 2=InviteOnly. The real enum is Open=0, InviteOnly=1, AskToJoin=2 — the inverted reading the requesttojoin handler was already fixed away from, left behind where it could mislead the next change. --- .../ClientRouteCoverageTests.cs | 15 +- .../TestCaseManagementController.cs | 68 + .../Controllers/Chat/ChatController.cs | 54 + .../Controllers/Clubs/ClubsController.cs | 250 +- DorkNet.Server/Data/DorkNetDbContext.cs | 17 + .../Data/Entities/ChatMessageEntity.cs | 6 + DorkNet.Server/Data/Entities/ClubEntity.cs | 13 +- .../Data/Entities/ClubRolePermissionEntity.cs | 65 + .../Data/Entities/TestCaseEntities.cs | 4 + ...ermissionsGalleryAndModeration.Designer.cs | 3654 +++++++++++++++++ ...658_ClubPermissionsGalleryAndModeration.cs | 118 + .../DorkNetDbContextModelSnapshot.cs | 82 + 12 files changed, 4307 insertions(+), 39 deletions(-) create mode 100644 DorkNet.Server/Data/Entities/ClubRolePermissionEntity.cs create mode 100644 DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.Designer.cs create mode 100644 DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.cs diff --git a/DorkNet.Server.Tests/ClientRouteCoverageTests.cs b/DorkNet.Server.Tests/ClientRouteCoverageTests.cs index 39496a9..1103de7 100644 --- a/DorkNet.Server.Tests/ClientRouteCoverageTests.cs +++ b/DorkNet.Server.Tests/ClientRouteCoverageTests.cs @@ -20,11 +20,16 @@ namespace DorkNet.Server.Tests; public sealed class ClientRouteCoverageTests { /// Client routes with no handler yet. Each entry is - /// VERB route exactly as it appears in the inventory file. - private static readonly HashSet KnownGaps = new(StringComparer.OrdinalIgnoreCase) - { - // (populated from the current gap report; remove as they are implemented) - }; + /// VERB route exactly as it appears in the inventory file. + /// + /// This is EMPTY, and that is the point: every request the 2023-03-21 + /// client can issue reaches a handler under the verb it actually uses. Do + /// not add an entry to make a red build green — an entry here is a promise + /// that the client gets a 404 or 405 in the wild. Implement the route, or + /// if the inventory itself is wrong (a literal that is really a cache key, + /// a deeplink, or a base the client concatenates onto), fix the inventory + /// so the claim stays honest. + private static readonly HashSet KnownGaps = new(StringComparer.OrdinalIgnoreCase); [Fact] public void Every_client_route_has_a_handler() diff --git a/DorkNet.Server/Controllers/API/TestCaseManagement/TestCaseManagementController.cs b/DorkNet.Server/Controllers/API/TestCaseManagement/TestCaseManagementController.cs index c33a086..34b5f99 100644 --- a/DorkNet.Server/Controllers/API/TestCaseManagement/TestCaseManagementController.cs +++ b/DorkNet.Server/Controllers/API/TestCaseManagement/TestCaseManagementController.cs @@ -141,6 +141,74 @@ public class TestCaseManagementController(DorkNetDbContext db) : ControllerBase return Ok(ToWireCase(tc)); } + /// POST api/testcasemanagement/v1/testcase/{id}/comment — + /// attach a QA note to a test case. + /// + /// The client builds the URL by concatenating + /// "api/testcasemanagement/v1/testcase/" + id + "/comment" + /// (RecNet.Runtime/FLODOBMEJAO.txt:660, :658) and posts it with verb + /// ordinal 2 (Move rdx, 2 at :673). The issuing method is + /// LDGADANDBIO KNDOKAKBLCC(String, String) (:575) — a status-only + /// promise, so the client parses no response DTO and only needs a 2xx. + /// + /// Comments append to a JSON list on the case rather than overwriting, so + /// a second tester's note never erases the first. + [HttpPost("api/testcasemanagement/v1/testcase/{id}/comment")] + [Authorize] + [Consumes("application/json", "application/x-www-form-urlencoded", "multipart/form-data")] + public async Task AddComment(string id) + { + var tc = await db.TestCases.FirstOrDefaultAsync(c => c.Id == id); + if (tc is null) return NotFound(); + + var text = (await ReadCommentAsync())?.Trim(); + if (string.IsNullOrEmpty(text)) return BadRequest("missing comment"); + if (text.Length > 2000) text = text[..2000]; + + var comments = ParseStringList(tc.CommentsJson); + comments.Add($"{this.RequireCurrentPlayerId()}|{DateTime.UtcNow:O}|{text.Replace('|', '/')}"); + tc.CommentsJson = JsonSerializer.Serialize(comments); + tc.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + return Ok(); + } + + /// The comment text, from a form field, the query string, a JSON + /// object, or a bare JSON string body — the exact spelling is not + /// recoverable from the ISIL, so every plausible carrier is accepted. + private async Task ReadCommentAsync() + { + string[] names = ["comment", "Comment", "text", "Text", "body", "Body", "message", "Message"]; + + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + foreach (var n in names) + if (form.TryGetValue(n, out var v) && v.Count > 0) return v.ToString(); + } + foreach (var n in names) + if (Request.Query.TryGetValue(n, out var q) && q.Count > 0) return q.ToString(); + + if (!Request.HasFormContentType) + { + try + { + Request.EnableBuffering(); + Request.Body.Position = 0; + using var doc = await JsonDocument.ParseAsync(Request.Body); + Request.Body.Position = 0; + var root = doc.RootElement; + if (root.ValueKind == JsonValueKind.String) return root.GetString(); + if (root.ValueKind == JsonValueKind.Object) + foreach (var n in names) + if (root.TryGetProperty(n, out var v) && v.ValueKind == JsonValueKind.String) + return v.GetString(); + } + catch (JsonException) { /* no usable body */ } + } + return null; + } + // ── Helpers ────────────────────────────────────────────────────────── private static List ParseIntList(string? json) diff --git a/DorkNet.Server/Controllers/Chat/ChatController.cs b/DorkNet.Server/Controllers/Chat/ChatController.cs index 34deafc..54889ba 100644 --- a/DorkNet.Server/Controllers/Chat/ChatController.cs +++ b/DorkNet.Server/Controllers/Chat/ChatController.cs @@ -475,6 +475,60 @@ public class ChatController( /// that client a 405 (and a 415 even if it had used PUT). The /// response is consumed by an Action<MGGNHLHPHOF> /// (instr 162) — a BARE integer body, not a wrapper object. + /// PUT thread/message/{messageId}/moderate — a moderator + /// actions one message. + /// + /// Binary evidence: DLDKCILCKNA.txt method + /// FGLDKEJLAKB<MGGNHLHPHOF> PLBCHNGAEML(RecNet.ChatMessage, MOFGOGFEHNN) + /// (:8365); route literal at :8600 with verb constant + /// Move rdx, 3 (= PUT) at :8610 and chat host r8 = 8; the + /// single form field is moderationState (:8615). The response is the + /// same BARE MGGNHLHPHOF integer every other chat mutation returns. + /// + /// Gated to club/room moderators and admins — a plain thread member must + /// not be able to rewrite other people's messages. + [HttpPut("/thread/message/{messageId:long}/moderate")] + [HttpPost("/thread/message/{messageId:long}/moderate")] + public async Task ModerateMessage(long messageId) + { + var message = await db.ChatMessages.FirstOrDefaultAsync(m => m.Id == messageId); + if (message is null) return Ok(ChatThreadNotFound); + + var raw = Request.HasFormContentType + ? (await Request.ReadFormAsync())["moderationState"].FirstOrDefault() + : Request.Query["moderationState"].FirstOrDefault(); + if (!int.TryParse(raw, out var state)) return Ok(ChatInvalidArguments); + + // The message author can retract their own message; anyone else needs + // moderator standing on the club whose thread it belongs to. + var isAuthor = message.SenderPlayerId == Me; + if (!isAuthor) + { + var clubId = ClubIdFromKey(message.ThreadKey); + var allowed = false; + if (clubId != 0) + { + var membership = await db.ClubMemberships + .FirstOrDefaultAsync(m => m.ClubId == clubId && m.PlayerId == Me); + allowed = membership is not null && + ClubService.MembershipTypeFromPerms(membership.Permissions) + >= ClubService.MembershipTypeModerator; + } + if (!allowed) + { + allowed = await db.Players + .Where(p => p.Id == Me) + .Select(p => p.IsAdmin || p.IsCommunityTeam) + .FirstOrDefaultAsync(); + } + if (!allowed) return Ok(ChatMembershipNotFound); + } + + message.ModerationState = state; + await db.SaveChangesAsync(); + return Ok(ChatSuccess); + } + [HttpPut("/thread/{chatThreadId}/rename")] [HttpPost("/thread/{chatThreadId}/rename")] public async Task Rename(string chatThreadId, diff --git a/DorkNet.Server/Controllers/Clubs/ClubsController.cs b/DorkNet.Server/Controllers/Clubs/ClubsController.cs index 8639b1a..5f29214 100644 --- a/DorkNet.Server/Controllers/Clubs/ClubsController.cs +++ b/DorkNet.Server/Controllers/Clubs/ClubsController.cs @@ -394,18 +394,93 @@ public class ClubsController(ClubService clubs, DorkNetDbContext db) : Controlle /// so the body is a BARE JSON boolean — not an object, not a wrapper. /// /// The answer is the negation of the Club wire field - /// ClubChatEnabled that emits. That field - /// has no column on yet and its setter - /// (PUT club/{id}/clubChatEnabled) is therefore not implemented, so - /// no club can currently be in the disabled state and false is the - /// true answer for every club. Both sites must flip together when the - /// column lands. + /// ClubChatEnabled, which is now a real column set by + /// PUT club/{id}/clubChatEnabled. [HttpGet("/club/{clubId:long}/hasDisabledClubChat")] public async Task ClubHasDisabledChat(long clubId) { var club = await clubs.GetByIdAsync(clubId); if (club is null) return NotFound(); - return Ok(false); + return Content(club.ClubChatEnabled ? "false" : "true", "application/json"); + } + + /// PUT /club/{id}/clubChatEnabled — toggle the club's chat + /// channel (IKMMOCKDKAF.txt:25779). Form field + /// clubChatEnabled; responds with the refreshed details + /// envelope. + [HttpPut("/club/{clubId:long}/clubChatEnabled")] + [HttpPost("/club/{clubId:long}/clubChatEnabled")] + [Authorize] + public async Task SetClubChatEnabled(long clubId) + { + var value = await ReadBoolFieldAsync("clubChatEnabled", "ClubChatEnabled", "enabled"); + if (value is not bool enabled) return BadRequest(new { error = "missing_value" }); + try + { + var updated = await clubs.ModifyAsync(clubId, Me, c => c.ClubChatEnabled = enabled); + if (updated is null) return NotFound(); + } + catch (UnauthorizedAccessException) { return Forbid(); } + return await BuildDetailsResponseAsync(clubId); + } + + /// PUT /club/{id}/minlevel — minimum account level required + /// to join (IKMMOCKDKAF.txt:16561). Form field minLevel; + /// responds with the refreshed details envelope. + [HttpPut("/club/{clubId:long}/minlevel")] + [HttpPost("/club/{clubId:long}/minlevel")] + [Authorize] + public async Task SetClubMinLevel(long clubId) + { + var raw = await ReadFieldAsync("minLevel", "MinLevel", "level"); + if (!int.TryParse(raw, out var minLevel)) return BadRequest(new { error = "missing_value" }); + minLevel = Math.Clamp(minLevel, 0, 100); + try + { + var updated = await clubs.ModifyAsync(clubId, Me, c => c.MinLevel = minLevel); + if (updated is null) return NotFound(); + } + catch (UnauthorizedAccessException) { return Forbid(); } + return await BuildDetailsResponseAsync(clubId); + } + + /// First value present under any of the given names, from the + /// form, the query string, or a JSON body. Null means "not sent". + private async Task ReadFieldAsync(params string[] names) + { + if (Request.HasFormContentType) + { + var form = await Request.ReadFormAsync(); + foreach (var n in names) + if (form.TryGetValue(n, out var v) && v.Count > 0) return v.ToString(); + } + foreach (var n in names) + if (Request.Query.TryGetValue(n, out var q) && q.Count > 0) return q.ToString(); + + if (!Request.HasFormContentType) + { + try + { + Request.EnableBuffering(); + Request.Body.Position = 0; + using var doc = await JsonDocument.ParseAsync(Request.Body); + Request.Body.Position = 0; + if (doc.RootElement.ValueKind == JsonValueKind.Object) + foreach (var n in names) + if (doc.RootElement.TryGetProperty(n, out var v) && + v.ValueKind != JsonValueKind.Null) return v.ToString(); + } + catch (JsonException) { /* no usable body */ } + } + return null; + } + + private async Task ReadBoolFieldAsync(params string[] names) + { + var raw = await ReadFieldAsync(names); + if (raw is null) return null; + if (bool.TryParse(raw, out var b)) return b; + return raw == "1" ? true : raw == "0" ? false : null; } /// GET /club/{id}/details — the central club page @@ -468,7 +543,50 @@ public class ClubsController(ClubService clubs, DorkNetDbContext db) : Controlle { var club = await clubs.GetByIdAsync(clubId); if (club is null) return NotFound(); - return Ok(PermissionsForRole(clubId, role)); + return Ok(await PermissionsForRoleAsync(clubId, role)); + } + + /// PUT /club/{id}/permissions/{role} — rewrite what one + /// role may do. Route and verb confirmed at + /// IKMMOCKDKAF.txt:17685 (route) and :17709 + /// (Move rdx, 3 = PUT); the six form booleans are + /// editDetails, approveMember, createEvent, + /// postAnnouncement, editPermissionSettings and + /// banUnban (IKMMOCKDKAF_NestedType_BOIMHOCCOEI.txt:173, + /// :189, :205, :221, :237, :253). + /// + /// Upserts one row per (club, role). Any field the client omits keeps the + /// value it currently resolves to, so a partial write cannot silently + /// revoke a permission. Gated on EditPermissionSettings for the caller's + /// own role, which is what the client's UI gate checks. + [HttpPut("/club/{clubId:long}/permissions/{role:int}")] + [HttpPost("/club/{clubId:long}/permissions/{role:int}")] + [Authorize] + public async Task SetClubPermissions(long clubId, int role) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + if (!await clubs.CanManageAsync(clubId, Me, club)) return Forbid(); + + var current = await ResolveRolePermissionsAsync(clubId, role); + var row = await db.ClubRolePermissions + .FirstOrDefaultAsync(p => p.ClubId == clubId && p.MembershipType == role); + if (row is null) + { + row = new ClubRolePermissionEntity { ClubId = clubId, MembershipType = role }; + db.ClubRolePermissions.Add(row); + } + + row.EditDetails = await ReadBoolFieldAsync("editDetails", "EditDetails") ?? current.EditDetails; + row.ApproveMember = await ReadBoolFieldAsync("approveMember", "ApproveMember") ?? current.ApproveMember; + row.CreateEvent = await ReadBoolFieldAsync("createEvent", "CreateEvent") ?? current.CreateEvent; + row.PostAnnouncement = await ReadBoolFieldAsync("postAnnouncement", "PostAnnouncement") ?? current.PostAnnouncement; + row.EditPermissionSettings = await ReadBoolFieldAsync("editPermissionSettings", "EditPermissionSettings") ?? current.EditPermissionSettings; + row.BanUnban = await ReadBoolFieldAsync("banUnban", "BanUnban") ?? current.BanUnban; + row.UpdatedAt = DateTime.UtcNow; + await db.SaveChangesAsync(); + + return await BuildDetailsResponseAsync(clubId); } // ── Account / created lists ────────────────────────────────────── @@ -627,14 +745,11 @@ public class ClubsController(ClubService clubs, DorkNetDbContext db) : Controlle /// POST/PUT /club/{id}/additionalimage/{slot} — the club /// gallery's per-slot image setter (IKMMOCKDKAF.txt:16214). /// - /// The request now binds (the client sends form-urlencoded - /// imageName, which [FromBody] used to reject with 415), but - /// the slot itself STILL is not persisted: there is no additional-image - /// table, and the envelope's AdditionalImages is the client's - /// HIKCHBLAMLP — {ImageName, Slot} — with nowhere to come from. So - /// the call is accepted and permission-checked, the details envelope comes - /// back refreshed, and the gallery renders blank until a - /// ClubAdditionalImageEntity lands. + /// The slot is persisted as a row + /// and read back by the details envelope's AdditionalImages + /// (HIKCHBLAMLP = {ImageName, Slot}), so the gallery renders what + /// the creator set. An empty imageName clears the slot, same as the + /// DELETE twin. [HttpPost("/club/{clubId:long}/additionalimage/{slot:int}")] [HttpPut("/club/{clubId:long}/additionalimage/{slot:int}")] [Authorize] @@ -643,6 +758,50 @@ public class ClubsController(ClubService clubs, DorkNetDbContext db) : Controlle var club = await clubs.GetByIdAsync(clubId); if (club is null) return NotFound(); if (!await clubs.CanManageAsync(clubId, Me, club)) return Forbid(); + + var imageName = (req?.ImageName ?? string.Empty).Trim(); + var row = await db.ClubAdditionalImages + .FirstOrDefaultAsync(i => i.ClubId == clubId && i.Slot == slot); + if (imageName.Length == 0) + { + if (row is not null) db.ClubAdditionalImages.Remove(row); + } + else if (row is null) + { + db.ClubAdditionalImages.Add(new ClubAdditionalImageEntity + { + ClubId = clubId, + Slot = slot, + ImageName = imageName.Length > 256 ? imageName[..256] : imageName, + }); + } + else + { + row.ImageName = imageName.Length > 256 ? imageName[..256] : imageName; + row.UpdatedAt = DateTime.UtcNow; + } + await db.SaveChangesAsync(); + return await BuildDetailsResponseAsync(clubId); + } + + /// DELETE /club/{id}/additionalimage/{slot} — clear one + /// gallery slot. Same permission gate as the setter; idempotent, so + /// clearing an empty slot is a success. + [HttpDelete("/club/{clubId:long}/additionalimage/{slot:int}")] + [Authorize] + public async Task ClubAdditionalImageDelete(long clubId, int slot) + { + var club = await clubs.GetByIdAsync(clubId); + if (club is null) return NotFound(); + if (!await clubs.CanManageAsync(clubId, Me, club)) return Forbid(); + + var row = await db.ClubAdditionalImages + .FirstOrDefaultAsync(i => i.ClubId == clubId && i.Slot == slot); + if (row is not null) + { + db.ClubAdditionalImages.Remove(row); + await db.SaveChangesAsync(); + } return await BuildDetailsResponseAsync(clubId); } @@ -1456,10 +1615,14 @@ public class ClubsController(ClubService clubs, DorkNetDbContext db) : Controlle // gallery slots get a table; see ClubAdditionalImage. Club = ToWireClub(club, memberCount), CustomTags = await CustomTagsAsync(clubId), - AdditionalImages = Array.Empty(), - CoownerPermissions = PermissionsForRole(clubId, ClubService.MembershipTypeCoOwner), - ModeratorPermissions = PermissionsForRole(clubId, ClubService.MembershipTypeModerator), - MemberPermissions = PermissionsForRole(clubId, ClubService.MembershipTypeMember), + AdditionalImages = await db.ClubAdditionalImages + .Where(i => i.ClubId == clubId) + .OrderBy(i => i.Slot) + .Select(i => new { i.ImageName, i.Slot }) + .ToListAsync(), + CoownerPermissions = await PermissionsForRoleAsync(clubId, ClubService.MembershipTypeCoOwner), + ModeratorPermissions = await PermissionsForRoleAsync(clubId, ClubService.MembershipTypeModerator), + MemberPermissions = await PermissionsForRoleAsync(clubId, ClubService.MembershipTypeMember), MyMembershipType = myType, }); } @@ -1536,21 +1699,43 @@ public class ClubsController(ClubService clubs, DorkNetDbContext db) : Controlle /// Owner/CoOwner do everything, Moderator moderates but cannot re-write the /// permission settings, Member and below are read-only. /// - private static object PermissionsForRole(long clubId, int membershipType) + private readonly record struct RolePermissions( + bool EditDetails, bool ApproveMember, bool CreateEvent, + bool PostAnnouncement, bool EditPermissionSettings, bool BanUnban); + + /// The stored permission row for a role, or the derived default + /// when the club has never customised it — Owner/CoOwner do everything, + /// Moderator moderates but cannot rewrite permissions, Member and below are + /// read-only. Falling back rather than defaulting to false keeps every club + /// that predates the table behaving exactly as it did. + private async Task ResolveRolePermissionsAsync(long clubId, int membershipType) { + var stored = await db.ClubRolePermissions + .FirstOrDefaultAsync(p => p.ClubId == clubId && p.MembershipType == membershipType); + if (stored is not null) + return new RolePermissions( + stored.EditDetails, stored.ApproveMember, stored.CreateEvent, + stored.PostAnnouncement, stored.EditPermissionSettings, stored.BanUnban); + var admin = membershipType is ClubService.MembershipTypeOwner or ClubService.MembershipTypeCoOwner; var moderator = admin || membershipType == ClubService.MembershipTypeModerator; + return new RolePermissions(admin, moderator, moderator, moderator, admin, moderator); + } + + private async Task PermissionsForRoleAsync(long clubId, int membershipType) + { + var p = await ResolveRolePermissionsAsync(clubId, membershipType); return new { ClubId = clubId, Type = membershipType, - EditDetails = admin, - ApproveMember = moderator, - CreateEvent = moderator, - PostAnnouncement = moderator, - EditPermissionSettings = admin, - BanUnban = moderator, + p.EditDetails, + p.ApproveMember, + p.CreateEvent, + p.PostAnnouncement, + p.EditPermissionSettings, + p.BanUnban, }; } @@ -1594,12 +1779,11 @@ public class ClubsController(ClubService clubs, DorkNetDbContext db) : Controlle MemberCount = memberCount, // MinLevel + ClubChatEnabled are registered by the 2023 Club reader // (BLIBJIHOENF.txt:1234-1322) and were never emitted, so every club came - // back with chat disabled on the profile Clubs tab. Neither has a column - // on ClubEntity yet — the client-facing setters are club/{0}/minlevel - // and club/{0}/clubChatEnabled (IKMMOCKDKAF.txt:16561,25779) — so they - // report the permissive defaults until the schema catches up. - MinLevel = 0, - ClubChatEnabled = true, + // back with chat disabled on the profile Clubs tab. Both are real + // columns now, written by club/{0}/minlevel and + // club/{0}/clubChatEnabled (IKMMOCKDKAF.txt:16561, :25779). + c.MinLevel, + c.ClubChatEnabled, IsRRO = c.IsRRO, ClubhouseRoomId = c.ClubhouseRoomId, ClubType = c.ClubType, diff --git a/DorkNet.Server/Data/DorkNetDbContext.cs b/DorkNet.Server/Data/DorkNetDbContext.cs index 9295088..7b92952 100644 --- a/DorkNet.Server/Data/DorkNetDbContext.cs +++ b/DorkNet.Server/Data/DorkNetDbContext.cs @@ -38,6 +38,8 @@ public class DorkNetDbContext(DbContextOptions options) : DbCo public DbSet BugReports => Set(); public DbSet Clubs => Set(); public DbSet ClubMemberships => Set(); + public DbSet ClubRolePermissions => Set(); + public DbSet ClubAdditionalImages => Set(); public DbSet RoyaleMatches => Set(); public DbSet RoyaleMatchPlayers => Set(); public DbSet RoyalePlayerProgress => Set(); @@ -123,6 +125,21 @@ public class DorkNetDbContext(DbContextOptions options) : DbCo e.HasIndex(s => new { s.PlayerId, s.Key }).IsUnique(); }); + // One permission row per (club, role) and one image per (club, slot); + // both are upserted by their PUT handlers, so the uniqueness is what + // keeps a repeated write from stacking duplicate rows. + modelBuilder.Entity(e => + { + e.HasKey(p => p.Id); + e.HasIndex(p => new { p.ClubId, p.MembershipType }).IsUnique(); + }); + + modelBuilder.Entity(e => + { + e.HasKey(i => i.Id); + e.HasIndex(i => new { i.ClubId, i.Slot }).IsUnique(); + }); + modelBuilder.Entity(e => { e.HasKey(r => r.Id); diff --git a/DorkNet.Server/Data/Entities/ChatMessageEntity.cs b/DorkNet.Server/Data/Entities/ChatMessageEntity.cs index 29ae890..f00ad8f 100644 --- a/DorkNet.Server/Data/Entities/ChatMessageEntity.cs +++ b/DorkNet.Server/Data/Entities/ChatMessageEntity.cs @@ -30,6 +30,12 @@ public class ChatMessageEntity public DateTime SentAt { get; set; } = DateTime.UtcNow; + /// Per-message moderation state, set by + /// PUT thread/message/{id}/moderate (form field + /// moderationState). 0 = normal; a non-zero value means a moderator + /// has actioned the message and it should not be re-served verbatim. + public int ModerationState { get; set; } = 0; + /// Build the canonical DM thread key for two players. /// Order-independent so both sides hit the same row. public static string DmKey(long a, long b) => diff --git a/DorkNet.Server/Data/Entities/ClubEntity.cs b/DorkNet.Server/Data/Entities/ClubEntity.cs index a782e42..19d51a1 100644 --- a/DorkNet.Server/Data/Entities/ClubEntity.cs +++ b/DorkNet.Server/Data/Entities/ClubEntity.cs @@ -61,7 +61,9 @@ public class ClubEntity public int Visibility { get; set; } = 0; /// Club joinability enum on the wire (Joinability). - /// 0=Open, 1=RequestToJoin, 2=InviteOnly. + /// 0=Open, 1=InviteOnly, 2=AskToJoin — confirmed from the 2023 dump + /// (enum FEHIHCMDOLN). This comment previously had 1 and 2 swapped, which + /// is the reading the requesttojoin handler was fixed away from. public int Joinability { get; set; } = 0; /// Drives the wire AllowJuniors bool. True means @@ -83,6 +85,15 @@ public class ClubEntity /// browse. public int ClubType { get; set; } = 0; + /// Minimum account level required to join, surfaced as the wire + /// MinLevel and set by PUT club/{id}/minlevel. 0 = no gate. + public int MinLevel { get; set; } = 0; + + /// Whether the club's chat channel is available. Drives the wire + /// ClubChatEnabled and is the negation of what + /// GET club/{id}/hasDisabledClubChat answers. + public bool ClubChatEnabled { get; set; } = true; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; } diff --git a/DorkNet.Server/Data/Entities/ClubRolePermissionEntity.cs b/DorkNet.Server/Data/Entities/ClubRolePermissionEntity.cs new file mode 100644 index 0000000..057321a --- /dev/null +++ b/DorkNet.Server/Data/Entities/ClubRolePermissionEntity.cs @@ -0,0 +1,65 @@ +using System.ComponentModel.DataAnnotations; + +namespace DorkNet.Server.Data.Entities; + +/// +/// What one club role is allowed to do, as set by +/// PUT club/{clubId}/permissions/{membershipType}. +/// +/// The 2023-03-21 client sends six booleans for a given role +/// (RecNet.Runtime/IKMMOCKDKAF_NestedType_BOIMHOCCOEI.txt:173, :189, :205, +/// :221, :237, :253) and reads them back on the club details envelope as the +/// CoownerPermissions / ModeratorPermissions / +/// MemberPermissions objects. +/// +/// One row per (club, role). A club with no row for a role falls back to the +/// derived default policy in ClubsController.PermissionsForRole, so +/// existing clubs keep behaving exactly as they did before this table existed. +/// +/// is the WIRE enum value (Member=10, +/// Moderator=20, CoOwner=30), not the stored permission bitmask — the client +/// addresses roles by the wire value and so does the route. +/// +public class ClubRolePermissionEntity +{ + public long Id { get; set; } + + public long ClubId { get; set; } + + /// Wire membership-type enum: 10=Member, 20=Moderator, 30=CoOwner. + public int MembershipType { get; set; } + + public bool EditDetails { get; set; } + public bool ApproveMember { get; set; } + public bool CreateEvent { get; set; } + public bool PostAnnouncement { get; set; } + public bool EditPermissionSettings { get; set; } + public bool BanUnban { get; set; } + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} + +/// +/// One image in a club's gallery, addressed by slot. +/// +/// The client sets a slot with PUT club/{clubId}/additionalimage/{slot} +/// and clears it with the DELETE twin, then reads the set back as the details +/// envelope's AdditionalImages rows +/// ({ImageName, Slot} — recnet-runtime-decomp/HIKCHBLAMLP.cs:9, :27). +/// Without this table the envelope could only ever send an empty array, so the +/// gallery rendered blank no matter what the creator uploaded. +/// +public class ClubAdditionalImageEntity +{ + public long Id { get; set; } + + public long ClubId { get; set; } + + /// Gallery position, taken from the route. + public int Slot { get; set; } + + [MaxLength(256)] + public string ImageName { get; set; } = string.Empty; + + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} diff --git a/DorkNet.Server/Data/Entities/TestCaseEntities.cs b/DorkNet.Server/Data/Entities/TestCaseEntities.cs index 28fe4ba..840d1ce 100644 --- a/DorkNet.Server/Data/Entities/TestCaseEntities.cs +++ b/DorkNet.Server/Data/Entities/TestCaseEntities.cs @@ -64,6 +64,10 @@ public class TestCaseEntity /// case belongs to (null = orphan / standalone). public uint? TestPassId { get; set; } + /// Append-only JSON array of QA comments posted through + /// POST api/testcasemanagement/v1/testcase/{id}/comment. + public string CommentsJson { get; set; } = "[]"; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; } diff --git a/DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.Designer.cs b/DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.Designer.cs new file mode 100644 index 0000000..c6e8968 --- /dev/null +++ b/DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.Designer.cs @@ -0,0 +1,3654 @@ +// +using System; +using DorkNet.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DorkNet.Server.Migrations +{ + [DbContext(typeof(DorkNetDbContext))] + [Migration("20260728061658_ClubPermissionsGalleryAndModeration")] + partial class ClubPermissionsGalleryAndModeration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.4"); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AdminActionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("AdminPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("TargetId") + .HasColumnType("INTEGER"); + + b.Property("TargetType") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AdminPlayerId"); + + b.HasIndex("Timestamp"); + + b.HasIndex("TargetType", "TargetId"); + + b.ToTable("AdminActions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AvatarEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CustomAvatarItemsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("EquippedItemsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FaceFeatures") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("TEXT"); + + b.Property("HairColor") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("InventoryJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("OutfitSelections") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("OutfitSelectionsV2") + .IsRequired() + .HasMaxLength(8192) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SavedOutfitsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SkinColor") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId") + .IsUnique(); + + b.ToTable("Avatars"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.BugReportEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ClientVersion") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GameSessionId") + .HasColumnType("INTEGER"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.Property("ReporterPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ReadAt"); + + b.HasIndex("ReporterPlayerId"); + + b.ToTable("BugReports"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CardEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActionUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Priority") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ExpiresAt"); + + b.HasIndex("PlayerId"); + + b.ToTable("Cards"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CharadesWordListEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("IsBuiltIn") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WordsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CharadesWordLists"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatMessageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ModerationState") + .HasColumnType("INTEGER"); + + b.Property("SenderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ThreadKey", "SentAt"); + + b.ToTable("ChatMessages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatThreadEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasMaxLength(96) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ThreadKey") + .IsUnique(); + + b.ToTable("ChatThreads"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ChatThreadMemberEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("LastReadMessageId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SnoozeUntil") + .HasColumnType("TEXT"); + + b.Property("ThreadKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ThreadKey", "PlayerId") + .IsUnique(); + + b.ToTable("ChatThreadMembers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CheerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheeredAt") + .HasColumnType("TEXT"); + + b.Property("FromPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetInventionId") + .HasColumnType("INTEGER"); + + b.Property("TargetPhotoId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetRoomId") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("TargetPhotoId"); + + b.HasIndex("TargetPlayerId"); + + b.HasIndex("TargetRoomId"); + + b.HasIndex("FromPlayerId", "TargetPlayerId", "TargetRoomId", "TargetPhotoId", "Type") + .IsUnique(); + + b.ToTable("Cheers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAdditionalImageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId", "Slot") + .IsUnique(); + + b.ToTable("ClubAdditionalImages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAnnouncementEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AuthorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId", "CreatedAt"); + + b.ToTable("ClubAnnouncements"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAnnouncementReadEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AnnouncementId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("AnnouncementId", "PlayerId") + .IsUnique(); + + b.ToTable("ClubAnnouncementReads"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubCategoryAssignmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CategoryTagId") + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CategoryTagId"); + + b.HasIndex("ClubId", "CategoryTagId") + .IsUnique(); + + b.ToTable("ClubCategoryAssignments"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubCategoryTagEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("ClubCategoryTags"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowJuniors") + .HasColumnType("INTEGER"); + + b.Property("BanStatus") + .HasColumnType("INTEGER"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("ClubChatEnabled") + .HasColumnType("INTEGER"); + + b.Property("ClubType") + .HasColumnType("INTEGER"); + + b.Property("ClubhouseRoomId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsRRO") + .HasColumnType("INTEGER"); + + b.Property("Joinability") + .HasColumnType("INTEGER"); + + b.Property("MinLevel") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Visibility") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Clubs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubMembershipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("JoinedAt") + .HasColumnType("TEXT"); + + b.Property("Permissions") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("ClubId", "PlayerId") + .IsUnique(); + + b.ToTable("ClubMemberships"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubRolePermissionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApproveMember") + .HasColumnType("INTEGER"); + + b.Property("BanUnban") + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("CreateEvent") + .HasColumnType("INTEGER"); + + b.Property("EditDetails") + .HasColumnType("INTEGER"); + + b.Property("EditPermissionSettings") + .HasColumnType("INTEGER"); + + b.Property("MembershipType") + .HasColumnType("INTEGER"); + + b.Property("PostAnnouncement") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId", "MembershipType") + .IsUnique(); + + b.ToTable("ClubRolePermissions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubSubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("SubscribedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId"); + + b.HasIndex("PlayerId", "ClubId") + .IsUnique(); + + b.ToTable("ClubSubscriptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CohortAssignmentEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssignedAt") + .HasColumnType("TEXT"); + + b.Property("CohortKey") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Variant") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "CohortKey") + .IsUnique(); + + b.ToTable("CohortAssignments"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CommunityBoardEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Json") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CommunityBoardRows"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CouponEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("MaxRedemptions") + .HasColumnType("INTEGER"); + + b.Property("RedemptionCount") + .HasColumnType("INTEGER"); + + b.Property("RewardAmount") + .HasColumnType("INTEGER"); + + b.Property("RewardCurrencyType") + .HasColumnType("INTEGER"); + + b.Property("RewardItemSlug") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("Coupons"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CouponRedemptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CouponId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RedeemedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CouponId", "PlayerId") + .IsUnique(); + + b.ToTable("CouponRedemptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CurrencyBalanceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Balance") + .HasColumnType("INTEGER"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "CurrencyType") + .IsUnique(); + + b.ToTable("CurrencyBalances"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CustomAvatarItemEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssetName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("BaseAvatarItemId") + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("Color") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsFeatured") + .HasColumnType("INTEGER"); + + b.Property("IsPublic") + .HasColumnType("INTEGER"); + + b.Property("ItemType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("PublicId") + .HasColumnType("TEXT"); + + b.Property("PurchaseCount") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("IsFeatured"); + + b.HasIndex("IsPublic"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.ToTable("CustomAvatarItems"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.CustomAvatarItemOwnershipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AcquiredAt") + .HasColumnType("TEXT"); + + b.Property("CustomAvatarItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CustomAvatarItemId"); + + b.HasIndex("PlayerId", "CustomAvatarItemId") + .IsUnique(); + + b.ToTable("CustomAvatarItemOwnership"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.DormStateEntity", b => + { + b.Property("PlayerId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CurrentDataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("PlayerId"); + + b.ToTable("DormStates"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GameRewardSelectionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GiftContext") + .HasColumnType("INTEGER"); + + b.Property("GrantedItemId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Offer1ItemId") + .HasColumnType("INTEGER"); + + b.Property("Offer2ItemId") + .HasColumnType("INTEGER"); + + b.Property("Offer3ItemId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RewardType") + .HasColumnType("INTEGER"); + + b.Property("SelectedAt") + .HasColumnType("TEXT"); + + b.Property("SelectedGiftDropId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "SelectedAt"); + + b.ToTable("GameRewardSelections"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GameSessionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ActivityLevelId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("MaxCapacity") + .HasColumnType("INTEGER"); + + b.Property("PhotonRoomName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerCount") + .HasColumnType("INTEGER"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId", "Region"); + + b.ToTable("GameSessions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.GiftPackageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvatarItemDescOrHairDyeDesc") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("AvatarItemType") + .HasColumnType("INTEGER"); + + b.Property("ConsumableItemDesc") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Consumed") + .HasColumnType("INTEGER"); + + b.Property("ConsumedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("INTEGER"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("EquipmentModificationGuid") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("EquipmentPrefabName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("FromPlayerId") + .HasColumnType("INTEGER"); + + b.Property("GiftContext") + .HasColumnType("INTEGER"); + + b.Property("GiftRarity") + .HasColumnType("INTEGER"); + + b.Property("IsGifted") + .HasColumnType("INTEGER"); + + b.Property("IsValid") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("PackageMaterial") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PackageVariant") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.Property("RecipientPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SourceStoreItemId") + .HasColumnType("INTEGER"); + + b.Property("SupportsCurrentPlatform") + .HasColumnType("INTEGER"); + + b.Property("Xp") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RecipientPlayerId", "Consumed"); + + b.ToTable("GiftPackages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.InventionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreationRoomId") + .HasColumnType("INTEGER"); + + b.Property("CreatorPermission") + .HasColumnType("INTEGER"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrentBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CurrentVersionNumber") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("FirstPublishedAt") + .HasColumnType("TEXT"); + + b.Property("GeneralPermission") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAgInvention") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsPublished") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("NumPlayersHaveUsedInRoom") + .HasColumnType("INTEGER"); + + b.Property("Permission") + .HasColumnType("INTEGER"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("SpawnCount") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CheerCount"); + + b.HasIndex("CreatorPlayerId"); + + b.ToTable("Inventions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.InventionVersionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("InstantiationCost") + .HasColumnType("INTEGER"); + + b.Property("InventionId") + .HasColumnType("INTEGER"); + + b.Property("LightsCost") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("VersionNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InventionId", "VersionNumber") + .IsUnique(); + + b.ToTable("InventionVersions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.IpBanEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedAt") + .HasColumnType("TEXT"); + + b.Property("BannedByAdminId") + .HasColumnType("INTEGER"); + + b.Property("Cidr") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Until") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Cidr"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ItemWishlistEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ItemKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ItemType") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "ItemKey", "ItemType") + .IsUnique(); + + b.ToTable("ItemWishlists"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.KeepsakeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("EarnedAt") + .HasColumnType("TEXT"); + + b.Property("EventKey") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlayerId", "EventKey"); + + b.ToTable("Keepsakes"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LeaderboardChannelMetaEntity", b => + { + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("Channel") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LowerIsBetter") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("ValueFormat") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("RoomId", "Channel"); + + b.HasIndex("RoomId"); + + b.ToTable("LeaderboardChannelMeta"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LeaderboardStatEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("StatChannel") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomId", "PlayerId", "StatChannel") + .IsUnique(); + + b.HasIndex("RoomId", "StatChannel", "Value"); + + b.ToTable("LeaderboardStats"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.LoadingScreenTipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Context") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("PlatformMask") + .HasColumnType("INTEGER"); + + b.Property("RoomNamesCsv") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("LoadingScreenTips"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.MessageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Body") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ReadAt") + .HasColumnType("TEXT"); + + b.Property("RecipientPlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SenderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("SentAt") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RecipientPlayerId", "SentAt"); + + b.HasIndex("SenderPlayerId", "SentAt"); + + b.ToTable("Messages"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.NotificationPrefsEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAll") + .HasColumnType("INTEGER"); + + b.Property("AllowAnnouncements") + .HasColumnType("INTEGER"); + + b.Property("AllowEventInvite") + .HasColumnType("INTEGER"); + + b.Property("AllowFriendRequest") + .HasColumnType("INTEGER"); + + b.Property("AllowMessage") + .HasColumnType("INTEGER"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Platform") + .IsUnique(); + + b.ToTable("NotificationPrefs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ObjectiveProgressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClearedAt") + .HasColumnType("TEXT"); + + b.Property("IsCompleted") + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Key") + .IsUnique(); + + b.ToTable("ObjectiveProgress"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PendingDeviceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DeviceId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FirstSeenAt") + .HasColumnType("TEXT"); + + b.Property("LastIp") + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.Property("PlatformId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PendingDevices"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PhotoEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Caption") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DeletedAt") + .HasColumnType("TEXT"); + + b.Property("IsPublic") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("TaggedPlayerIdsCsv") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("UploaderPlayerId") + .HasColumnType("INTEGER"); + + b.Property("ViewCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BlobName"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("RoomId"); + + b.HasIndex("UploaderPlayerId"); + + b.ToTable("Photos"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlatformIgnoreEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.Property("PlatformUserId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Platform", "PlatformUserId") + .IsUnique(); + + b.ToTable("PlatformIgnores"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerDeviceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("FirstSeen") + .HasColumnType("TEXT"); + + b.Property("IsBanned") + .HasColumnType("INTEGER"); + + b.Property("LastSeen") + .HasColumnType("TEXT"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DeviceId"); + + b.HasIndex("PlayerId"); + + b.ToTable("PlayerDevices"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEloEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Elo") + .HasColumnType("INTEGER"); + + b.Property("GameMode") + .HasColumnType("INTEGER"); + + b.Property("Losses") + .HasColumnType("INTEGER"); + + b.Property("MatchesPlayed") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Wins") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GameMode", "Elo"); + + b.HasIndex("PlayerId", "GameMode") + .IsUnique(); + + b.ToTable("PlayerElo"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BannedUntil") + .HasColumnType("TEXT"); + + b.Property("Bio") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Birthday") + .HasColumnType("TEXT"); + + b.Property("CanReceiveInvites") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedFromDeviceId") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("IsCommunityTeam") + .HasColumnType("INTEGER"); + + b.Property("IsDeveloper") + .HasColumnType("INTEGER"); + + b.Property("IsJunior") + .HasColumnType("INTEGER"); + + b.Property("IsVerified") + .HasColumnType("INTEGER"); + + b.Property("LastIp") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("LastPlatform") + .HasColumnType("INTEGER"); + + b.Property("LastPlatformId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("LastSeenAt") + .HasColumnType("TEXT"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Phone") + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("ProfileImageName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Reputation") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("XP") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Username") + .IsUnique(); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEventEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Capacity") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndsAt") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("StartsAt") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("StartsAt"); + + b.ToTable("PlayerEvents"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEventResponseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("EventId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Response") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("EventId", "PlayerId") + .IsUnique(); + + b.ToTable("PlayerEventResponses"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerInventoryEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AcquiredAt") + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("ItemSlug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Quantity") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlayerId", "ItemSlug") + .IsUnique(); + + b.ToTable("PlayerInventory"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerSettingEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId", "Key") + .IsUnique(); + + b.ToTable("PlayerSettings"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Accessibility") + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CustomWarning") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsCurated") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("SupportsJuniors") + .HasColumnType("INTEGER"); + + b.Property("SupportsLevelVoting") + .HasColumnType("INTEGER"); + + b.Property("SupportsScreens") + .HasColumnType("INTEGER"); + + b.Property("SupportsTeleportVR") + .HasColumnType("INTEGER"); + + b.Property("SupportsWalkVR") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.Property("VisitorCount") + .HasColumnType("INTEGER"); + + b.Property("WarningMask") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("IsCurated"); + + b.HasIndex("Name"); + + b.ToTable("Playlists"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistInteractionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Cheered") + .HasColumnType("INTEGER"); + + b.Property("Favorited") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlaylistId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlaylistId", "PlayerId") + .IsUnique(); + + b.ToTable("PlaylistInteractions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlaylistRoomEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("PlaylistId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlaylistId"); + + b.HasIndex("RoomId"); + + b.HasIndex("PlaylistId", "OrderIndex"); + + b.ToTable("PlaylistRooms"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PrivateInstanceEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DataBlob") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("MaxCapacity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("OwnerPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PhotonRegion") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PhotonRoomId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("RoomCode") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SubRoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OwnerPlayerId"); + + b.HasIndex("RoomId", "SubRoomId"); + + b.ToTable("PrivateInstances"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PrivateInstanceInviteeEntity", b => + { + b.Property("PrivateInstanceId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("InvitedAt") + .HasColumnType("TEXT"); + + b.Property("LatestInviteMessageId") + .HasColumnType("INTEGER"); + + b.HasKey("PrivateInstanceId", "PlayerId"); + + b.HasIndex("PlayerId"); + + b.ToTable("PrivateInstanceInvitees"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PushTokenEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(16) + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Token") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Token"); + + b.HasIndex("PlayerId", "Platform") + .IsUnique(); + + b.ToTable("PushTokens"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RelationshipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Favorited") + .HasColumnType("INTEGER"); + + b.Property("Ignored") + .HasColumnType("INTEGER"); + + b.Property("Muted") + .HasColumnType("INTEGER"); + + b.Property("RequesterId") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TargetId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RequesterId", "TargetId") + .IsUnique(); + + b.ToTable("Relationships"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ReportEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Category") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("GameSessionId") + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("ReporterPlayerId") + .HasColumnType("INTEGER"); + + b.Property("ResolutionNote") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ResolvedAt") + .HasColumnType("TEXT"); + + b.Property("ResolverAdminId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("TargetEventId") + .HasColumnType("INTEGER"); + + b.Property("TargetInventionId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetRoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ReporterPlayerId"); + + b.HasIndex("ResolvedAt"); + + b.HasIndex("TargetPlayerId"); + + b.ToTable("Reports"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomBanEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BanType") + .HasColumnType("INTEGER"); + + b.Property("BannedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("BannedPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("Until") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("BannedPlayerId"); + + b.HasIndex("RoomId", "BannedPlayerId"); + + b.ToTable("RoomBans"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomBookmarkEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("PlayerId", "RoomId") + .IsUnique(); + + b.ToTable("RoomBookmarks"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomConsumableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrencyId") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("PublicId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("RoomConsumables"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomConsumableOwnershipEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyCode") + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("ModifiedAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomConsumableId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomConsumableId"); + + b.HasIndex("PlayerId", "RoomConsumableId") + .IsUnique(); + + b.ToTable("RoomConsumableOwnership"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomCurrencyBalanceEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Balance") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomCurrencyId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomCurrencyId"); + + b.HasIndex("PlayerId", "RoomCurrencyId") + .IsUnique(); + + b.ToTable("RoomCurrencyBalances"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomCurrencyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("DailyLimit") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("PublicId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("RoomCurrencies"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomCurrencyPurchaseOfferEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Amount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("PublicId") + .HasColumnType("TEXT"); + + b.Property("RoomCurrencyId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.HasIndex("RoomCurrencyId"); + + b.ToTable("RoomCurrencyPurchaseOffers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomDataBlobEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Bytes") + .HasColumnType("BLOB"); + + b.Property("ReferencedFilenamesCsv") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SubRoomId") + .HasColumnType("INTEGER"); + + b.Property("UploadedAt") + .HasColumnType("TEXT"); + + b.Property("UploadedByPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("BlobName") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("RoomDataBlobs"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Accessibility") + .HasColumnType("INTEGER"); + + b.Property("AllowNewUsers") + .HasColumnType("INTEGER"); + + b.Property("AllowsJuniors") + .HasColumnType("INTEGER"); + + b.Property("CheerCount") + .HasColumnType("INTEGER"); + + b.Property("CloningAllowed") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrentDataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("CustomRoomWarning") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("DisableMicAutoMute") + .HasColumnType("INTEGER"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("HiddenFromBrowse") + .HasColumnType("INTEGER"); + + b.Property("HotScore") + .HasColumnType("REAL"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsAGRoom") + .HasColumnType("INTEGER"); + + b.Property("IsDormRoom") + .HasColumnType("INTEGER"); + + b.Property("IsRoomLinkedToRecRoomStudio") + .HasColumnType("INTEGER"); + + b.Property("IsStudioRoom") + .HasColumnType("INTEGER"); + + b.Property("LoadScreensJson") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT"); + + b.Property("LocationReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("MaxCapacity") + .HasColumnType("INTEGER"); + + b.Property("MaxPlayerCalculationMode") + .HasColumnType("INTEGER"); + + b.Property("MinLevel") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("PromoExternalContentJson") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT"); + + b.Property("PromoImagesJson") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT"); + + b.Property("RoomWarningMask") + .HasColumnType("INTEGER"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("StudioSessionId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("SupportsLevelVoting") + .HasColumnType("INTEGER"); + + b.Property("SupportsMobile") + .HasColumnType("INTEGER"); + + b.Property("SupportsScreens") + .HasColumnType("INTEGER"); + + b.Property("SupportsTeleportVR") + .HasColumnType("INTEGER"); + + b.Property("SupportsVRLow") + .HasColumnType("INTEGER"); + + b.Property("SupportsWalkVR") + .HasColumnType("INTEGER"); + + b.Property("TagsCsv") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.Property("VisitorCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("HotScore"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomKeyEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(174) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("ReplicationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoomId", "Name"); + + b.ToTable("RoomKeys"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomKeyPurchaseEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("PaidPrice") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("PurchasedAt") + .HasColumnType("TEXT"); + + b.Property("RoomKeyId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("RoomKeyId", "PlayerId") + .IsUnique(); + + b.ToTable("RoomKeyPurchases"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomRoleEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Accepted") + .HasColumnType("INTEGER"); + + b.Property("GrantedAt") + .HasColumnType("TEXT"); + + b.Property("GrantedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoomId"); + + b.HasIndex("RoomId", "PlayerId", "Role") + .IsUnique(); + + b.ToTable("RoomRoles"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomSceneEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CanMatchmakeInto") + .HasColumnType("INTEGER"); + + b.Property("DataBlobName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("DataModifiedAt") + .HasColumnType("TEXT"); + + b.Property("IsSandbox") + .HasColumnType("INTEGER"); + + b.Property("MaxPlayers") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT") + .UseCollation("NOCASE"); + + b.Property("OrderIndex") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("RoomSceneLocationId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("StudioAssetBundleNamesCsv") + .IsRequired() + .HasMaxLength(4096) + .HasColumnType("TEXT"); + + b.Property("StudioSubRoomDataSaveId") + .HasColumnType("INTEGER"); + + b.Property("StudioUnityAssetId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoomId", "Name"); + + b.HasIndex("RoomId", "OrderIndex") + .IsUnique(); + + b.ToTable("RoomScenes"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoomVisitEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FirstVisitAt") + .HasColumnType("TEXT"); + + b.Property("LastVisitAt") + .HasColumnType("TEXT"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("VisitCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("RoomId", "LastVisitAt"); + + b.HasIndex("RoomId", "PlayerId") + .IsUnique(); + + b.ToTable("RoomVisits"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyaleMatchEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChestsOpened") + .HasColumnType("INTEGER"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CustomGame") + .HasColumnType("INTEGER"); + + b.Property("HealthPotionsConsumed") + .HasColumnType("INTEGER"); + + b.Property("NumEliminations") + .HasColumnType("INTEGER"); + + b.Property("Rank") + .HasColumnType("INTEGER"); + + b.Property("SecondsAlive") + .HasColumnType("INTEGER"); + + b.Property("SecondsInAir") + .HasColumnType("INTEGER"); + + b.Property("ShieldPotionsConsumed") + .HasColumnType("INTEGER"); + + b.Property("WalkGame") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompletedAt"); + + b.ToTable("RoyaleMatches"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyaleMatchPlayerEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MatchId") + .HasColumnType("INTEGER"); + + b.Property("NumEliminations") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("Rank") + .HasColumnType("INTEGER"); + + b.Property("SecondsAlive") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId"); + + b.HasIndex("MatchId", "PlayerId"); + + b.ToTable("RoyaleMatchPlayers"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RoyalePlayerProgressEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CurrentLevelXPThreshold") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("NextLevelAcornReward") + .HasColumnType("INTEGER"); + + b.Property("NextLevelXPThreshold") + .HasColumnType("INTEGER"); + + b.Property("PlayerId") + .HasColumnType("INTEGER"); + + b.Property("RankIdx") + .HasColumnType("INTEGER"); + + b.Property("RankName") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("TotalXP") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlayerId") + .IsUnique(); + + b.ToTable("RoyalePlayerProgress"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.ServerSettingsEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("AllAvatarItemsOwned") + .HasColumnType("INTEGER"); + + b.Property("BaseRoomNamesJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CharadesSlotBindingsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DiscoveredGameConfigsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("GlobalFriendsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PlayMenuTagsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("ProfanityFilterDisabled") + .HasColumnType("INTEGER"); + + b.Property("RecCenterDoorsJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("RoomBlobVersionClampDisabled") + .HasColumnType("INTEGER"); + + b.Property("SignupsDisabled") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WeeklyChallengeRewardJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WeeklyChallengesCompletedRequired") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("WeeklyChallengesJson") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.HasKey("Id"); + + b.ToTable("ServerSettings"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.SignupCodeEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Code") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Descriptor") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAt") + .HasColumnType("TEXT"); + + b.Property("RedeemedAt") + .HasColumnType("TEXT"); + + b.Property("RedeemedByPlayerId") + .HasColumnType("INTEGER"); + + b.Property("Revoked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("SignupCodes"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.StoreItemEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvailableUntil") + .HasColumnType("TEXT"); + + b.Property("Category") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsLimitedTime") + .HasColumnType("INTEGER"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Storefront") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("Storefront"); + + b.HasIndex("IsActive", "Category"); + + b.ToTable("StoreItems"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.SubscriptionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("SubscriberPlayerId") + .HasColumnType("INTEGER"); + + b.Property("TargetPlayerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SubscriberPlayerId"); + + b.HasIndex("TargetPlayerId"); + + b.HasIndex("SubscriberPlayerId", "TargetPlayerId") + .IsUnique(); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.TestCaseEntity", b => + { + b.Property("Pk") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AssignedPlayerIdsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("AssignedPlayerNamesJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CommentsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("JiraBugUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("JiraUrl") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("MinNumAssignedPlayers") + .HasColumnType("INTEGER"); + + b.Property("RoomName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TestPassId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Pk"); + + b.HasIndex("Id") + .IsUnique(); + + b.HasIndex("Status"); + + b.HasIndex("TestPassId"); + + b.ToTable("TestCases"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.TestPassEntity", b => + { + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WasManuallyClosed") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TestPasses"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.UgcPurchasableEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatorPlayerId") + .HasColumnType("INTEGER"); + + b.Property("CurrencyType") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1024) + .HasColumnType("TEXT"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER"); + + b.Property("IsFeatured") + .HasColumnType("INTEGER"); + + b.Property("ItemType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("INTEGER"); + + b.Property("PublicId") + .HasColumnType("TEXT"); + + b.Property("RoomId") + .HasColumnType("INTEGER"); + + b.Property("SortOrder") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatorPlayerId"); + + b.HasIndex("PublicId") + .IsUnique(); + + b.HasIndex("RoomId"); + + b.ToTable("UgcPurchasables"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.AvatarEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Player") + .WithOne("Avatar") + .HasForeignKey("DorkNet.Server.Data.Entities.AvatarEntity", "PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerSettingEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Player") + .WithMany("Settings") + .HasForeignKey("PlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Player"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.RelationshipEntity", b => + { + b.HasOne("DorkNet.Server.Data.Entities.PlayerEntity", "Requester") + .WithMany("Relationships") + .HasForeignKey("RequesterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Requester"); + }); + + modelBuilder.Entity("DorkNet.Server.Data.Entities.PlayerEntity", b => + { + b.Navigation("Avatar"); + + b.Navigation("Relationships"); + + b.Navigation("Settings"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.cs b/DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.cs new file mode 100644 index 0000000..c8f0987 --- /dev/null +++ b/DorkNet.Server/Migrations/20260728061658_ClubPermissionsGalleryAndModeration.cs @@ -0,0 +1,118 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DorkNet.Server.Migrations +{ + /// + public partial class ClubPermissionsGalleryAndModeration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CommentsJson", + table: "TestCases", + type: "TEXT", + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "ClubChatEnabled", + table: "Clubs", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "MinLevel", + table: "Clubs", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ModerationState", + table: "ChatMessages", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + name: "ClubAdditionalImages", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClubId = table.Column(type: "INTEGER", nullable: false), + Slot = table.Column(type: "INTEGER", nullable: false), + ImageName = table.Column(type: "TEXT", maxLength: 256, nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubAdditionalImages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClubRolePermissions", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ClubId = table.Column(type: "INTEGER", nullable: false), + MembershipType = table.Column(type: "INTEGER", nullable: false), + EditDetails = table.Column(type: "INTEGER", nullable: false), + ApproveMember = table.Column(type: "INTEGER", nullable: false), + CreateEvent = table.Column(type: "INTEGER", nullable: false), + PostAnnouncement = table.Column(type: "INTEGER", nullable: false), + EditPermissionSettings = table.Column(type: "INTEGER", nullable: false), + BanUnban = table.Column(type: "INTEGER", nullable: false), + UpdatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClubRolePermissions", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_ClubAdditionalImages_ClubId_Slot", + table: "ClubAdditionalImages", + columns: new[] { "ClubId", "Slot" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClubRolePermissions_ClubId_MembershipType", + table: "ClubRolePermissions", + columns: new[] { "ClubId", "MembershipType" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ClubAdditionalImages"); + + migrationBuilder.DropTable( + name: "ClubRolePermissions"); + + migrationBuilder.DropColumn( + name: "CommentsJson", + table: "TestCases"); + + migrationBuilder.DropColumn( + name: "ClubChatEnabled", + table: "Clubs"); + + migrationBuilder.DropColumn( + name: "MinLevel", + table: "Clubs"); + + migrationBuilder.DropColumn( + name: "ModerationState", + table: "ChatMessages"); + } + } +} diff --git a/DorkNet.Server/Migrations/DorkNetDbContextModelSnapshot.cs b/DorkNet.Server/Migrations/DorkNetDbContextModelSnapshot.cs index ce99e67..59d7102 100644 --- a/DorkNet.Server/Migrations/DorkNetDbContextModelSnapshot.cs +++ b/DorkNet.Server/Migrations/DorkNetDbContextModelSnapshot.cs @@ -262,6 +262,9 @@ namespace DorkNet.Server.Migrations .HasMaxLength(2000) .HasColumnType("TEXT"); + b.Property("ModerationState") + .HasColumnType("INTEGER"); + b.Property("SenderPlayerId") .HasColumnType("INTEGER"); @@ -386,6 +389,34 @@ namespace DorkNet.Server.Migrations b.ToTable("Cheers"); }); + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAdditionalImageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("ImageName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("Slot") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId", "Slot") + .IsUnique(); + + b.ToTable("ClubAdditionalImages"); + }); + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubAnnouncementEntity", b => { b.Property("Id") @@ -514,6 +545,9 @@ namespace DorkNet.Server.Migrations .HasMaxLength(64) .HasColumnType("TEXT"); + b.Property("ClubChatEnabled") + .HasColumnType("INTEGER"); + b.Property("ClubType") .HasColumnType("INTEGER"); @@ -542,6 +576,9 @@ namespace DorkNet.Server.Migrations b.Property("Joinability") .HasColumnType("INTEGER"); + b.Property("MinLevel") + .HasColumnType("INTEGER"); + b.Property("Name") .IsRequired() .HasMaxLength(128) @@ -595,6 +632,47 @@ namespace DorkNet.Server.Migrations b.ToTable("ClubMemberships"); }); + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubRolePermissionEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApproveMember") + .HasColumnType("INTEGER"); + + b.Property("BanUnban") + .HasColumnType("INTEGER"); + + b.Property("ClubId") + .HasColumnType("INTEGER"); + + b.Property("CreateEvent") + .HasColumnType("INTEGER"); + + b.Property("EditDetails") + .HasColumnType("INTEGER"); + + b.Property("EditPermissionSettings") + .HasColumnType("INTEGER"); + + b.Property("MembershipType") + .HasColumnType("INTEGER"); + + b.Property("PostAnnouncement") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ClubId", "MembershipType") + .IsUnique(); + + b.ToTable("ClubRolePermissions"); + }); + modelBuilder.Entity("DorkNet.Server.Data.Entities.ClubSubscriptionEntity", b => { b.Property("Id") @@ -3358,6 +3436,10 @@ namespace DorkNet.Server.Migrations .IsRequired() .HasColumnType("TEXT"); + b.Property("CommentsJson") + .IsRequired() + .HasColumnType("TEXT"); + b.Property("CreatedAt") .HasColumnType("TEXT");