ba423f32d8f4b163101040ce9a1051e58b687375
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7299e10b08 |
feat: cases (reports, reviews, requests, appeals) and notifications (#9)
Phase 3, part 1 (`docs/design.md`, "Next"): cases and notifications. File blocks, API tokens and two-factor sign-in come as separate PRs.
**Cases**
- One queue (Console → Cases) for everything someone has to decide. Each case has:
- a thread of notes, where internal notes are visible only to those deciding
- an assignee (moderators assign cases to moderators)
- a decision, Resolve or Decline, with a written resolution that the person who opened it sees
- **Reports:** anyone signed in can report a product or catalog from its page (the API also takes rooms and people).
- Limits: 10 an hour, one open report per person and thing, and you can't report yourself.
- Reports about a product go to the teams of the catalogs hosting it, as well as to moderators.
- Only the reporter and moderators know who reported. The audit entry for a report isn't tagged with what it's about, so a product's creator can't find out from their audit log either.
- **Rating reviews:** a creator lowering a rating from adult to general now opens a review instead of being refused. The rest of the edit still saves. Resolving the review lowers the rating, recorded in the product's history.
- **Removal requests:** a catalog's team can ask moderators to remove a product it hosts, everywhere. Resolving it removes the product.
- **Deletion requests:** a catalog's owner asks for deletion from the catalog's console page, and a moderator decides, as agreed. Resolving it marks the catalog `deleted`: its key stops working, nothing is available from it, and its changes feed says so.
- **Appeals:**
- From the audit log, the person an action affected can appeal it, once: their product removed, delisted or edited by someone else; their catalog suspended or rejected; a ban from a catalog or a declined request to join.
- Another moderator decides. An admin may decide an appeal of their own action, and the audit log flags it.
- Resolving records the decision; the moderator then undoes the action where it's done. For that, there's now a Restore for removed products (Console → Products, for moderators); the design mentioned one, but it was never built.
- The console shows a "Cases" tab, and a to-do with the number of open cases the person could decide.
**Notifications**
- A bell in the header with the unread count, and a notifications page with "Mark all as read".
- Each is also sent to the person's proxies over the realtime connection (`{"type": "notification"}`), so the client can show them later.
- They cover:
- a moderator edited or locked your product, or undid a change (with the reason)
- your product was removed or restored
- your case was decided
- someone replied on your case
- a catalog decided your membership
**Also changed**
- A product's creator and moderators can now see it when it's adult, even without an adult account. Before, a creator who made their own product adult couldn't open its page any more, and neither could moderators. Getting and wearing adult products still need an adult account.
**Not done yet** (in `docs/console.md`): email notifications (the hub sends no email yet), canned reasons, private moderator notes on people and products, and second approval for the most destructive actions.
**Migration** `0012_cases`: the `moderation_case`, `case_note` and `notification` tables. (`case` is a reserved word in SQL, hence the table's name.)
**Checks**
- 51 API tests pass. The 7 in the new `cases.test.ts` cover:
- who sees and decides each kind of case, and that the reporter stays private
- rate limits and duplicate reports
- the thread and internal notes, and assigning
- what deciding each kind of case does
- appeals by the person affected only, decided by another moderator
- restoring a removed product
- the notifications
- Type checks pass.
- On a local hub I went through every flow with the website's own forms: report from a product page; the catalog owner's note and decision; assigning, an internal note and a decision as admin; removal by a moderator, the creator's appeal from the audit log, the admin's decision and Restore; a creator's rating review and the decision lowering the rating; a deletion request and its decline; the notifications, the bell's count, and Mark all as read.
- Fixed along the way:
- The catalog's team could see who reported.
- A missing Resolve/Decline choice was treated as Decline; now it's refused.
- The Unassign button sent the typed name instead of nothing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Phase 3, part 1 (`docs/design.md`, "Next"): cases and notifications. File blocks, API tokens and two-factor sign-in come as separate PRs.
**Cases**
- One queue (Console → Cases) for everything someone has to decide. Each case has:
- a thread of notes, where internal notes are visible only to those deciding
- an assignee (moderators assign cases to moderators)
- a decision, Resolve or Decline, with a written resolution that the person who opened it sees
- **Reports:** anyone signed in can report a product or catalog from its page (the API also takes rooms and people).
- Limits: 10 an hour, one open report per person and thing, and you can't report yourself.
- Reports about a product go to the teams of the catalogs hosting it, as well as to moderators.
- Only the reporter and moderators know who reported. The audit entry for a report isn't tagged with what it's about, so a product's creator can't find out from their audit log either.
- **Rating reviews:** a creator lowering a rating from adult to general now opens a review instead of being refused. The rest of the edit still saves. Resolving the review lowers the rating, recorded in the product's history.
- **Removal requests:** a catalog's team can ask moderators to remove a product it hosts, everywhere. Resolving it removes the product.
- **Deletion requests:** a catalog's owner asks for deletion from the catalog's console page, and a moderator decides, as agreed. Resolving it marks the catalog `deleted`: its key stops working, nothing is available from it, and its changes feed says so.
- **Appeals:**
- From the audit log, the person an action affected can appeal it, once: their product removed, delisted or edited by someone else; their catalog suspended or rejected; a ban from a catalog or a declined request to join.
- Another moderator decides. An admin may decide an appeal of their own action, and the audit log flags it.
- Resolving records the decision; the moderator then undoes the action where it's done. For that, there's now a Restore for removed products (Console → Products, for moderators); the design mentioned one, but it was never built.
- The console shows a "Cases" tab, and a to-do with the number of open cases the person could decide.
**Notifications**
- A bell in the header with the unread count, and a notifications page with "Mark all as read".
- Each is also sent to the person's proxies over the realtime connection (`{"type": "notification"}`), so the client can show them later.
- They cover:
- a moderator edited or locked your product, or undid a change (with the reason)
- your product was removed or restored
- your case was decided
- someone replied on your case
- a catalog decided your membership
**Also changed**
- A product's creator and moderators can now see it when it's adult, even without an adult account. Before, a creator who made their own product adult couldn't open its page any more, and neither could moderators. Getting and wearing adult products still need an adult account.
**Not done yet** (in `docs/console.md`): email notifications (the hub sends no email yet), canned reasons, private moderator notes on people and products, and second approval for the most destructive actions.
**Migration** `0012_cases`: the `moderation_case`, `case_note` and `notification` tables. (`case` is a reserved word in SQL, hence the table's name.)
**Checks**
- 51 API tests pass. The 7 in the new `cases.test.ts` cover:
- who sees and decides each kind of case, and that the reporter stays private
- rate limits and duplicate reports
- the thread and internal notes, and assigning
- what deciding each kind of case does
- appeals by the person affected only, decided by another moderator
- restoring a removed product
- the notifications
- Type checks pass.
- On a local hub I went through every flow with the website's own forms: report from a product page; the catalog owner's note and decision; assigning, an internal note and a decision as admin; removal by a moderator, the creator's appeal from the audit log, the admin's decision and Restore; a creator's rating review and the decision lowering the rating; a deletion request and its decline; the notifications, the bell's count, and Mark all as read.
- Fixed along the way:
- The catalog's team could see who reported.
- A missing Resolve/Decline choice was treated as Decline; now it's refused.
- The Unassign button sent the typed name instead of nothing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: dhkatz <[email protected]>
|
||
|
|
2cbedd26fc |
feat: permissions, a scoped console, and catalog management (#7)
Phase 1 of `docs/console.md` (builds on #6, whose docs commit it includes; merge #6 first). **API** - `permissions.ts`: `resource:action` permissions, built-in roles as bundles at a scope (hub, catalog, product), `can`/`require`, "may give" checks. A missing permission answers 403 with `missing: {permission, scope}`. - Catalog management: settings (incl. rating ceiling, upload policy, mirroring), key rotation, operators, membership (open / approval / invite links, bans), delisting, suspension by moderators, and `GET /api/v1/catalog/changes` for catalogs. - Membership is enforced: getting a product (403 `members_only` with the catalogs to join), the library list, and outfits relayed to others. Seeing products still needs no membership. - Console for everyone who controls a catalog, product or hosted room; `/console/audit` (tagged entries, scoped by permission, with filters), `/console/permissions`; `/me` says whether the console is for them. - Migration 0010: `role_assignment`, `catalog_member`, `catalog_invite`, `catalog_change`, catalog settings, audit `tags` (backfilled from old entries). **Website** - Console overview by what you manage; audit log page with filters; catalog list scoped to your teams; a catalog management page (settings, key, team, members, invitations, products, moderation, recent changes). - Public catalog pages with join / ask / accept invitation / leave; product pages say "members only" and link the catalogs. **Checked**: 43 API tests pass (8 new in `catalogs.test.ts`); API, web and db type checks; every new page rendered against a local hub for owner, operator, member, outsider and admin; the Eden DELETE-with-query calls tested against the API. Server-action forms weren't clicked through in a browser. Not in this PR (later phases): catalog deletion requests (with cases), drafts and moderator product edits, notifications, API tokens, two-factor, imports, charts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Phase 1 of `docs/console.md` (builds on #6, whose docs commit it includes; merge #6 first). **API** - `permissions.ts`: `resource:action` permissions, built-in roles as bundles at a scope (hub, catalog, product), `can`/`require`, "may give" checks. A missing permission answers 403 with `missing: {permission, scope}`. - Catalog management: settings (incl. rating ceiling, upload policy, mirroring), key rotation, operators, membership (open / approval / invite links, bans), delisting, suspension by moderators, and `GET /api/v1/catalog/changes` for catalogs. - Membership is enforced: getting a product (403 `members_only` with the catalogs to join), the library list, and outfits relayed to others. Seeing products still needs no membership. - Console for everyone who controls a catalog, product or hosted room; `/console/audit` (tagged entries, scoped by permission, with filters), `/console/permissions`; `/me` says whether the console is for them. - Migration 0010: `role_assignment`, `catalog_member`, `catalog_invite`, `catalog_change`, catalog settings, audit `tags` (backfilled from old entries). **Website** - Console overview by what you manage; audit log page with filters; catalog list scoped to your teams; a catalog management page (settings, key, team, members, invitations, products, moderation, recent changes). - Public catalog pages with join / ask / accept invitation / leave; product pages say "members only" and link the catalogs. **Checked**: 43 API tests pass (8 new in `catalogs.test.ts`); API, web and db type checks; every new page rendered against a local hub for owner, operator, member, outsider and admin; the Eden DELETE-with-query calls tested against the API. Server-action forms weren't clicked through in a browser. Not in this PR (later phases): catalog deletion requests (with cases), drafts and moderator product edits, notifications, API tokens, two-factor, imports, charts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: dhkatz <[email protected]> |
||
|
|
efd5d52198 |
feat(web): Download link to t5de.dev in the header (#2)
A Download link in the header, beside the account menu or Sign in, to t5de.dev/download: T5DE's website with the latest setup, its version and checksum. Gitea's release pages are confusing for people new to it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) A Download link in the header, beside the account menu or Sign in, to t5de.dev/download: T5DE's website with the latest setup, its version and checksum. Gitea's release pages are confusing for people new to it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: dhkatz <[email protected]> |
||
|
|
07bbc11565 |
Website header: the T5DE logo, four links, and a profile menu
CI / test (push) Successful in 1m11s
The header shows T5DE's logo (the client's, also the tab icon) and Online, Rooms, Products and Catalogs; the person's pages (Account, Library, Console for moderators) and signing out are in a menu under their picture: their first linked IMVU account's avatar picture, which /me now gives (kept an hour), or their initial. Creating a product moves to Products (New product, /products/new; /create redirects there), as creating a room is on Rooms. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
51d5bb5ff3 |
Website: products, library, catalogs and their console page
/products (search), product pages with revisions, getting and staying on a revision, retracting; /products/:id/edit; /create and new revisions upload from the browser straight to the catalog with a token from the hub; /library; /catalogs (directory, registering with a once-shown key); /console/catalogs to approve or reject. Every page now sends a nonce-based CSP (proxy.ts), so product descriptions can't run scripts. buttonVariants now merges classes, so outline links get their border. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
6ba4d83317 |
Room channel and rooms list
CI / test (push) Successful in 6m38s
The realtime connection now carries the IMVU chats a client is in: the hub tells each proxy in a chat which other T5DE users are there and what community products they wear (adult products only to adult accounts), on joining and on every change. Rooms T5DE users enter are looked up on IMVU; those that are public, listed and whose owner turned on "List my public rooms" appear on the new Rooms page, with the T5DE users in them who show they're online. AP rooms are shown to adults only. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
64c552330b |
Website: Next.js app for accounts, linking, who's online, device sign-in and the console
apps/web: Next.js 16 (App Router), Tailwind 4 and shadcn/ui, light and dark from the system. Pages render on the server and call the API with Eden Treaty (typed by @t5de/api), forwarding the visitor's cookie; forms use server actions; sign-up, sign-in and sign-out use Better Auth's React client. /api is rewritten to the API so its cookies are first-party. Who's online follows the API's event stream (Next's gzip is off: it held the stream back). Standalone output with a Dockerfile; deploys to Vercel as is. Co-Authored-By: Claude Opus 5.5 <[email protected]> |