feat(safety): report, block and hide, MODULE_API 1.21.0
USER_SAFETY.md step 2 (core), with S11-S14 decided while building it. - Reports: POST /player/reports takes any target type, core's forum types or a module's registered ones. One resolver (model/safety/targets.js) answers "what is this item, for this reader" and 404s an item the reader cannot see. Every report stores content_reports.snapshot; the queue shows a module item from it. - Hide for everyone: POST /admin/moderation/reports/:id/hide hides the item through its own moderation (forum hide as staff, or the module's hide) and actions the report with actioned_with = 'hide'. - Blocks (user_blocks) name an item, never a person, and land on the author's account first (S12); an unlinked game identity is blocked as itself and becomes an account block once linked (S14). Staff cannot be blocked; a block on someone later promoted stops applying (S8). - Hide for me (user_hidden_items), /auth/me/blocks and /auth/me/hidden. - Forum reads filter blocked authors and hidden items on the server and carry canBlock per thread and post. - MODULE_API 1.21.0: api.registerReportTargets, api.registerGameIdentities, ctx.safety.forViewer and ctx.safety.staffAmong, so a module filters its own per-viewer feeds on the server (S11) and marks lines canBlock (S13). - Web: Hide for me / Block author in the forum menus, a Blocked section on both account pages, Hide for everyone in the Reports queue. - Tests, OpenAPI, route/guard/engagement manifests regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -346,6 +346,25 @@ function buildCtx(id, moduleRoot) {
|
||||
// configuration, so core answers it. A getter, not a captured string: the
|
||||
// value is read per call, so it cannot go stale against the env.
|
||||
site: { get baseUrl() { return (process.env.APP_BASE_URL || 'http://localhost:5173').replace(/\/+$/, '') } },
|
||||
// API 1.21.0 (USER_SAFETY.md §4.1, S11). READ only. What one reader has
|
||||
// blocked and hidden, so a module filters its own per-reader feeds on the
|
||||
// server — a chat feed is already read per viewer, so the website and the app
|
||||
// need no list of their own.
|
||||
//
|
||||
// `forViewer` is bound to the CALLING module: it answers the reader's account
|
||||
// blocks (with any game block whose identity is now linked already folded in,
|
||||
// S14, and staff dropped, S8), this module's own unlinked game blocks, and
|
||||
// the reader's hides on this module's report targets. Another module's game
|
||||
// blocks and hides are not its business. A signed-out reader has none.
|
||||
//
|
||||
// `staffAmong(userIds)` answers which of those accounts are staff, for a
|
||||
// module that marks lines `canBlock` (S13).
|
||||
safety: {
|
||||
// eslint-disable-next-line global-require
|
||||
forViewer: (userId) => require('../model/safety/userSafety.model').forModuleViewer(id, userId),
|
||||
// eslint-disable-next-line global-require
|
||||
staffAmong: (userIds) => require('../model/safety/userSafety.model').staffAmong(userIds),
|
||||
},
|
||||
// API 1.18.0 (DEMO_MODE.md §3.4, §3.5). True on the public demo. For the
|
||||
// locks a route list cannot express — a console command by its first word,
|
||||
// a config file by which plugin owns it — which the module enforces in its
|
||||
@@ -500,6 +519,18 @@ function buildApi(record) {
|
||||
once('registerEventTemplates')
|
||||
record.staged.registerEventTemplates(templates)
|
||||
},
|
||||
// API 1.21.0 (USER_SAFETY.md §4.1). What of the module's content a member
|
||||
// can report, hide for themself and block the author of, and the game
|
||||
// identity those authors are known by. Both STAGE, and `once` for the reason
|
||||
// every batch above takes it.
|
||||
registerReportTargets(targets) {
|
||||
once('registerReportTargets')
|
||||
record.staged.registerReportTargets(targets)
|
||||
},
|
||||
registerGameIdentities(identities) {
|
||||
once('registerGameIdentities')
|
||||
record.staged.registerGameIdentities(identities)
|
||||
},
|
||||
// The two lifecycle hooks (§2.5). Registered here, dispatched from
|
||||
// lifecycle.js — this file runs with no database and the hooks run with one.
|
||||
// Both are optional: a module with no warm-up and nothing to close simply
|
||||
|
||||
Reference in New Issue
Block a user