From 791b5bca292241ed308b76c5d39e18acdbfb2a04 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:54:32 +0000 Subject: [PATCH 1/2] docs(provincesystem): new table names, code session sweep and status checks codes, sessions and submissions are now login_codes, code_sessions and skin_submissions; character creation, war declaration and player notification tables are renamed too. drink_textures.cmd is custom_model_data, view-as Profile codes keep the view's session in view_session_id, status and scope columns carry CHECK constraints, LuckPerms node tables have primary keys and the hourly sweep deletes expired code sessions. Companion to TF-Minecraft/ProvinceSystem#268. Co-Authored-By: Claude Opus 5.5 --- projects/ProvinceSystem/DEPLOY.md | 2 +- projects/ProvinceSystem/docs/architecture.md | 7 +++++-- projects/ProvinceSystem/docs/cosmetics/drinks.md | 2 +- projects/ProvinceSystem/docs/cosmetics/skins.md | 9 +++++---- projects/ProvinceSystem/docs/identity/auth-security.md | 7 ++++--- projects/ProvinceSystem/docs/identity/tfmcweb.md | 2 +- projects/ProvinceSystem/docs/integrations/luckperms.md | 4 +++- projects/ProvinceSystem/docs/ops/postgresql.md | 6 +++++- projects/RPCharacters/docs/realm-wipe.md | 4 ++-- 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/projects/ProvinceSystem/DEPLOY.md b/projects/ProvinceSystem/DEPLOY.md index 45e7000..b4fb97e 100644 --- a/projects/ProvinceSystem/DEPLOY.md +++ b/projects/ProvinceSystem/DEPLOY.md @@ -51,7 +51,7 @@ the `ps-preview-router` Caddy container (`127.0.0.1:8090`, behind the `*.tfminec creation and privileged restore use trusted Dev tooling; branch code takes no part. The schema follows Dev's migrations, so once Dev has a new migration, a preview of a branch cut before it fails its migration check until the branch is rebased onto `main`. -- **Scrub:** before startup, the helper clears sessions, user sessions, Discord OAuth/link state, preview sign-in +- **Scrub:** before startup, the helper clears code sessions, user sessions, Discord OAuth/link state, preview sign-in tickets, Microsoft link state, and Patreon tokens, OAuth and pending-link state, including creator credentials. It revokes redemption and war declaration codes and clears redemption-code plaintext, retaining rows needed by foreign-key-linked content. diff --git a/projects/ProvinceSystem/docs/architecture.md b/projects/ProvinceSystem/docs/architecture.md index b4aed95..34ffb70 100644 --- a/projects/ProvinceSystem/docs/architecture.md +++ b/projects/ProvinceSystem/docs/architecture.md @@ -158,10 +158,13 @@ Configuration, migrations, backups and restores: [PostgreSQL storage](ops/postgr | Table family | Purpose | |--------------|---------| -| `codes`, `submissions` | Skins workflow | +| `login_codes`, `code_sessions` | In-game codes and the sessions they open | +| `skin_submissions` | Skins workflow | | `drink_submissions`, `drink_textures`, `drink_catalog` | Drinks workflow | | `discord_links`, `discord_link_codes` | Identity | -| Character sessions, roster, wardrobe, wills and catalog | Characters API | +| `player_notifications` | Bot outbox for Discord DMs to players: staff warnings and bird mail | +| `war_declaration_codes` | War declaration codes | +| Character sessions, roster, creation requests (`character_creation_requests`, `character_creation_wardrobe`), wardrobe, wills and catalog | Characters API | | `web_entitlements` | Per-realm rank entitlements from TFMCWeb: character limit, name colours, wardrobe slots, skin kinds, drink perks, permission flags | | `character_account_meta` | Per-account data from RPCharacters' roster push: 18+ answer, account creation time, kit cooldowns | | `users`, `user_sessions`, OAuth states | Website accounts and sign-in | diff --git a/projects/ProvinceSystem/docs/cosmetics/drinks.md b/projects/ProvinceSystem/docs/cosmetics/drinks.md index 0a79207..34268bd 100644 --- a/projects/ProvinceSystem/docs/cosmetics/drinks.md +++ b/projects/ProvinceSystem/docs/cosmetics/drinks.md @@ -64,7 +64,7 @@ sequenceDiagram ## Data (ProvinceSystem) - `drink_submissions` - recipe JSON, status, player UUID, optional texture -- `drink_textures` - owner UUID, CMD, IA id, refcount, png path +- `drink_textures` - owner UUID, `custom_model_data` (DrinkBuilder's route and payload call it `cmd`), IA id, refcount, png path - `drink_catalog` - ingredients allowlist + category labels + effects blacklist - `drink_notifications` - staff/bot outbox diff --git a/projects/ProvinceSystem/docs/cosmetics/skins.md b/projects/ProvinceSystem/docs/cosmetics/skins.md index 252584a..16d8720 100644 --- a/projects/ProvinceSystem/docs/cosmetics/skins.md +++ b/projects/ProvinceSystem/docs/cosmetics/skins.md @@ -10,7 +10,7 @@ End-to-end design for donator texture submissions on **ProvinceSystem** (store + - Players start a skin from the Profile wardrobe ([below](#profile-wardrobe)) or with a TFMCWeb code (`/token create skin` or **`/token create skin staff`**); either is bound to the player UUID. - **Player:** staff approve/deny in Discord; ArmourShop writes **`tfmc_submissions`** + `ps_*` + LP. - **Staff curated:** auto-approve (no bot); category + scroll on upload; writes **`tfmc_armorshop`** into real ArmourShop categories. -- **Kit item customise** (character creator) also creates `submissions` rows via an internal `lore_upload` code per player — not a donator mint token. Staff review and ArmourShop apply use the same pipeline as Profile skin uploads. +- **Kit item customise** (character creator) also creates `skin_submissions` rows via an internal `lore_upload` code per player — not a donator mint token. Staff review and ArmourShop apply use the same pipeline as Profile skin uploads. ## Upload kinds @@ -109,8 +109,9 @@ Player DMs: submitted (outbox poll); approved / denied from cog after staff API Site records share `DATABASE_URL`; see [database configuration and migrations](../ops/postgresql.md). -- `codes` - hashed codes, player UUID, expiry, redeemed_at on submit -- `submissions` - kind, display_name, status, paths, discord_user_id, tiers, base_set, grip_preset, name colours/styles +- `login_codes` - hashed codes, player UUID, scope, expiry, redeemed_at on submit, `minted_via` +- `code_sessions` - the sessions redeemed codes open; the hourly housekeeping sweep deletes expired ones +- `skin_submissions` - kind, display_name, status, paths, discord_user_id, tiers, base_set, grip_preset, name colours/styles - `discord_links` / `discord_link_codes` - identity bind ### Disk @@ -212,7 +213,7 @@ the `/token create` rules: A refusal is a 409 whose `detail` is `{ "reason", "next_at" }`. An unused, unexpired code of the same scope and realm is reused, whether Profile or `/token create` made it. Otherwise the start records a new code with no plaintext -and `codes.minted_via = 'site'`, which starts the shared cooldown. A skin or drink +and `login_codes.minted_via = 'site'`, which starts the shared cooldown. A skin or drink code that expires without a submission stops counting once no session from it can still upload, whether Profile or `/token create` made it, so a code that lapses unused costs nothing. A used code keeps counting after it expires. Source: [`codes.py`](https://github.com/TF-Minecraft/ProvinceSystem/blob/main/backend/src/skins/codes.py). diff --git a/projects/ProvinceSystem/docs/identity/auth-security.md b/projects/ProvinceSystem/docs/identity/auth-security.md index b2703c0..31d996c 100644 --- a/projects/ProvinceSystem/docs/identity/auth-security.md +++ b/projects/ProvinceSystem/docs/identity/auth-security.md @@ -178,7 +178,7 @@ answers every `POST`, `PUT`, `PATCH` and `DELETE` sent with the view's session cookie with **403** `viewing_as_player`. Only leaving the view, signing out and opening or closing Profile get through, and Profile's Linked accounts card shows the player's accounts with its actions disabled. Profile opened during a view records its code -with `minted_via = staff_view:`, ends with the view, and the guard +with `minted_via = 'staff_view'` and the view's user session in `view_session_id`, ends with the view, and the guard refuses its Bearer writes the same way. The view has no staff rights even if the player's role changes, and dev will not hand it to a branch preview. Signing out during a view also signs the admin out, and signing in with Discord @@ -201,10 +201,11 @@ their views and their Profile sessions; the role change or revocation is the aud | Profile / map staff | `profile` scope session from `/profile` redeem or a linked Discord account ([Profile without a code](#profile-without-a-code)); carries `player_uuid` and `realm_id` for permission checks | | No website passwords | Codes are UUID-bound and not shareable by design | -Codes are **hashed at rest** (SHA-256). Plaintext shown once in-game at mint. The `codes` row keeps the +Codes are **hashed at rest** (SHA-256). Plaintext shown once in-game at mint. The `login_codes` row keeps the plaintext only while the code is open, for the plugin list of active codes (`GET /skins/plugin/codes/active`). Redeeming a profile code, using a skin or drink code for a submission, and revoking a code clear it in the same -update. The backend's hourly housekeeping loop clears it once a code expires unused. +update. The backend's hourly housekeeping loop clears it once a code expires unused, and deletes expired +`code_sessions` rows. ### Scope enforcement diff --git a/projects/ProvinceSystem/docs/identity/tfmcweb.md b/projects/ProvinceSystem/docs/identity/tfmcweb.md index 28583f8..670281e 100644 --- a/projects/ProvinceSystem/docs/identity/tfmcweb.md +++ b/projects/ProvinceSystem/docs/identity/tfmcweb.md @@ -86,7 +86,7 @@ Link tables live in the site PostgreSQL database (`discord_links`, `discord_link ## War declare codes -One-time codes that gate an in-game war declaration. Staff mint one in Discord (`tfmc_bot` `factions` cog, `/warcode`), the attacking leader types it in SimpleFactions, and it pins the war goal. Rows live in `war_declare_codes`, realm-scoped, hashed with **no plaintext column**: staff see the code once and a lost code is revoked and reminted. +One-time codes that gate an in-game war declaration. Staff mint one in Discord (`tfmc_bot` `factions` cog, `/warcode`), the attacking leader types it in SimpleFactions, and it pins the war goal. Rows live in `war_declaration_codes`, realm-scoped, hashed with **no plaintext column**: staff see the code once and a lost code is revoked and reminted. | Route | Auth | Role | |-------|------|------| diff --git a/projects/ProvinceSystem/docs/integrations/luckperms.md b/projects/ProvinceSystem/docs/integrations/luckperms.md index ab6c721..bae46db 100644 --- a/projects/ProvinceSystem/docs/integrations/luckperms.md +++ b/projects/ProvinceSystem/docs/integrations/luckperms.md @@ -55,7 +55,9 @@ All routes need the primary `X-Plugin-Key`; secondary keys are refused. `NODE` is `{"key": "group.staff", "value": true, "contexts": {"server": ["main"]}, "expiry": 0}`; `expiry` is unix seconds, 0 for permanent; `contexts` maps each key to its values (empty object for global). `users` holds every user with at least one stored node (`UserManager.searchAll` with an empty key prefix). -The site replaces its whole mirror in one transaction and answers `{"ok": true}`. +The site replaces its whole mirror in one transaction and answers `{"ok": true}`. The node tables +`lp_user_nodes` and `lp_group_nodes` hold one row per subject, key, context set and expiry, so a timed and a +permanent copy of one group are two rows. `revision` orders everything the bridge reports: it strictly increases across snapshots and results, also across restarts (the bridge uses `max(last + 1, current time in ms)`). The site diff --git a/projects/ProvinceSystem/docs/ops/postgresql.md b/projects/ProvinceSystem/docs/ops/postgresql.md index 5669b50..96de2c1 100644 --- a/projects/ProvinceSystem/docs/ops/postgresql.md +++ b/projects/ProvinceSystem/docs/ops/postgresql.md @@ -34,7 +34,7 @@ settings bound queries and locks. Domain advisory locks coordinate checks that s absence of a row; file guards coordinate wardrobe and map assets across workers. An hourly housekeeping loop in the backend, which also runs at startup, clears data that has outlived its use. -It currently clears the plaintext of login codes that expired unused +It clears the plaintext of login codes that expired unused and deletes expired code sessions ([opaque Bearer sessions](../identity/auth-security.md#opaque-bearer-sessions)). ## Schema migrations @@ -47,6 +47,10 @@ revision that lacks a migration the database already has, because its code expec ([deploy steps](#deploy-steps)). Add a new numbered migration for a schema change; do not edit an applied file. To undo a change, add another migration. +CHECK constraints limit login code scopes and `minted_via`, skin and drink submission statuses, character +creation request statuses and lore item states to the values the code writes or still reads, so an edit in +Database Studio must use one of those values. + Precedent and patch-note tables have their own migrations and keep their data. ## Deploy steps diff --git a/projects/RPCharacters/docs/realm-wipe.md b/projects/RPCharacters/docs/realm-wipe.md index 93f76c7..debf716 100644 --- a/projects/RPCharacters/docs/realm-wipe.md +++ b/projects/RPCharacters/docs/realm-wipe.md @@ -40,9 +40,9 @@ Usage / errors: `Usage: /rpcharacter wipe website [confirm]`. `Nothing to confir Delete: - `character_roster` -- `character_creates` +- `character_creation_requests` - `character_wardrobe_slots` -- `character_create_wardrobe` +- `character_creation_wardrobe` - `lore_item_customisations` Do not delete player rank/age meta. From a57558ff6bf8095747b56c9a939fcc014622d7d5 Mon Sep 17 00:00:00 2001 From: Ryan Barlow <7389646+ryanbarlow97@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:00:26 +0000 Subject: [PATCH 2/2] docs(provincesystem): drop the LuckPerms node key note ProvinceSystem#268 no longer adds primary keys to lp_user_nodes and lp_group_nodes. Co-Authored-By: Claude Opus 5.5 --- projects/ProvinceSystem/docs/integrations/luckperms.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/projects/ProvinceSystem/docs/integrations/luckperms.md b/projects/ProvinceSystem/docs/integrations/luckperms.md index bae46db..ab6c721 100644 --- a/projects/ProvinceSystem/docs/integrations/luckperms.md +++ b/projects/ProvinceSystem/docs/integrations/luckperms.md @@ -55,9 +55,7 @@ All routes need the primary `X-Plugin-Key`; secondary keys are refused. `NODE` is `{"key": "group.staff", "value": true, "contexts": {"server": ["main"]}, "expiry": 0}`; `expiry` is unix seconds, 0 for permanent; `contexts` maps each key to its values (empty object for global). `users` holds every user with at least one stored node (`UserManager.searchAll` with an empty key prefix). -The site replaces its whole mirror in one transaction and answers `{"ok": true}`. The node tables -`lp_user_nodes` and `lp_group_nodes` hold one row per subject, key, context set and expiry, so a timed and a -permanent copy of one group are two rows. +The site replaces its whole mirror in one transaction and answers `{"ok": true}`. `revision` orders everything the bridge reports: it strictly increases across snapshots and results, also across restarts (the bridge uses `max(last + 1, current time in ms)`). The site