mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
feat: close the last route gaps — 100% client route+verb coverage
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.
This commit is contained in:
@@ -30,6 +30,12 @@ public class ChatMessageEntity
|
||||
|
||||
public DateTime SentAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Per-message moderation state, set by
|
||||
/// <c>PUT thread/message/{id}/moderate</c> (form field
|
||||
/// <c>moderationState</c>). 0 = normal; a non-zero value means a moderator
|
||||
/// has actioned the message and it should not be re-served verbatim.</summary>
|
||||
public int ModerationState { get; set; } = 0;
|
||||
|
||||
/// <summary>Build the canonical DM thread key for two players.
|
||||
/// Order-independent so both sides hit the same row.</summary>
|
||||
public static string DmKey(long a, long b) =>
|
||||
|
||||
@@ -61,7 +61,9 @@ public class ClubEntity
|
||||
public int Visibility { get; set; } = 0;
|
||||
|
||||
/// <summary>Club joinability enum on the wire (<c>Joinability</c>).
|
||||
/// 0=Open, 1=RequestToJoin, 2=InviteOnly.</summary>
|
||||
/// 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.</summary>
|
||||
public int Joinability { get; set; } = 0;
|
||||
|
||||
/// <summary>Drives the wire <c>AllowJuniors</c> bool. True means
|
||||
@@ -83,6 +85,15 @@ public class ClubEntity
|
||||
/// browse.</summary>
|
||||
public int ClubType { get; set; } = 0;
|
||||
|
||||
/// <summary>Minimum account level required to join, surfaced as the wire
|
||||
/// <c>MinLevel</c> and set by <c>PUT club/{id}/minlevel</c>. 0 = no gate.</summary>
|
||||
public int MinLevel { get; set; } = 0;
|
||||
|
||||
/// <summary>Whether the club's chat channel is available. Drives the wire
|
||||
/// <c>ClubChatEnabled</c> and is the negation of what
|
||||
/// <c>GET club/{id}/hasDisabledClubChat</c> answers.</summary>
|
||||
public bool ClubChatEnabled { get; set; } = true;
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace DorkNet.Server.Data.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// What one club role is allowed to do, as set by
|
||||
/// <c>PUT club/{clubId}/permissions/{membershipType}</c>.
|
||||
///
|
||||
/// 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
|
||||
/// <c>CoownerPermissions</c> / <c>ModeratorPermissions</c> /
|
||||
/// <c>MemberPermissions</c> objects.
|
||||
///
|
||||
/// One row per (club, role). A club with no row for a role falls back to the
|
||||
/// derived default policy in <c>ClubsController.PermissionsForRole</c>, so
|
||||
/// existing clubs keep behaving exactly as they did before this table existed.
|
||||
///
|
||||
/// <see cref="MembershipType"/> 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.
|
||||
/// </summary>
|
||||
public class ClubRolePermissionEntity
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public long ClubId { get; set; }
|
||||
|
||||
/// <summary>Wire membership-type enum: 10=Member, 20=Moderator, 30=CoOwner.</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One image in a club's gallery, addressed by slot.
|
||||
///
|
||||
/// The client sets a slot with <c>PUT club/{clubId}/additionalimage/{slot}</c>
|
||||
/// and clears it with the DELETE twin, then reads the set back as the details
|
||||
/// envelope's <c>AdditionalImages</c> rows
|
||||
/// (<c>{ImageName, Slot}</c> — 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.
|
||||
/// </summary>
|
||||
public class ClubAdditionalImageEntity
|
||||
{
|
||||
public long Id { get; set; }
|
||||
|
||||
public long ClubId { get; set; }
|
||||
|
||||
/// <summary>Gallery position, taken from the route.</summary>
|
||||
public int Slot { get; set; }
|
||||
|
||||
[MaxLength(256)]
|
||||
public string ImageName { get; set; } = string.Empty;
|
||||
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
@@ -64,6 +64,10 @@ public class TestCaseEntity
|
||||
/// case belongs to (null = orphan / standalone).</summary>
|
||||
public uint? TestPassId { get; set; }
|
||||
|
||||
/// <summary>Append-only JSON array of QA comments posted through
|
||||
/// <c>POST api/testcasemanagement/v1/testcase/{id}/comment</c>.</summary>
|
||||
public string CommentsJson { get; set; } = "[]";
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user