Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a63b10c73 |
@@ -69,8 +69,7 @@ def upgrade() -> None:
|
||||
sa.Column(
|
||||
"visibility",
|
||||
postgresql.ENUM(
|
||||
"public", "private", "friends_only", name="world_visibility",
|
||||
create_type=False,
|
||||
"public", "private", "friends_only", name="world_visibility"
|
||||
),
|
||||
nullable=True,
|
||||
),
|
||||
@@ -90,7 +89,7 @@ def upgrade() -> None:
|
||||
"asset_type",
|
||||
postgresql.ENUM(
|
||||
"image", "audio", "video", "document", "model_3d", "other",
|
||||
name="asset_type", create_type=False,
|
||||
name="asset_type",
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
|
||||
@@ -8,7 +8,7 @@ services:
|
||||
POSTGRES_PASSWORD: agp_password
|
||||
POSTGRES_DB: agp_db
|
||||
ports:
|
||||
- "${POSTGRES_HOST_PORT:-5432}:5432"
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -20,7 +20,7 @@ services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "${REDIS_HOST_PORT:-6379}:6379"
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# MVP Vertical Slice: Client-Server Wiring — Plan
|
||||
|
||||
## Goal
|
||||
Prove the real Godot client and the real backend can talk: **a user can register/log in
|
||||
and see a list of worlds from the game client**. Nothing more. Gameplay, join-a-world,
|
||||
chat, avatar, Discovery search/tags/trending, Social/Economy/Trust & Safety are all
|
||||
explicitly out of scope.
|
||||
|
||||
## Verified backend contract (as of this writing)
|
||||
- `POST /api/auth/register` — JSON body `{username, email, password, date_of_birth}`.
|
||||
`date_of_birth` is a `YYYY-MM-DD` string. Returns `201 {access_token, token_type}`.
|
||||
Rejects under-18 with `400`.
|
||||
- `POST /api/auth/login` — OAuth2 **form-encoded** (`application/x-www-form-urlencoded`)
|
||||
`username` + `password` (this is NOT a JSON endpoint). Returns `{access_token, token_type}`.
|
||||
- `GET /api/worlds` — **requires auth** (`CurrentUser`), returns a JSON array of
|
||||
`{id, name, description, visibility, owner_id, created_at}`. There is no unauthenticated
|
||||
public-worlds path, so the worlds list screen can only be shown after login/register.
|
||||
- `GET /api/health` — unauthenticated, `{status, app}`.
|
||||
- Base URL: uvicorn default `http://127.0.0.1:8000` (must be configurable in the client).
|
||||
- CORS is irrelevant to the native Godot `HTTPRequest` client (no browser origin).
|
||||
|
||||
## Task breakdown
|
||||
| # | Type | Task | Depends on |
|
||||
|---|------|------|-----------|
|
||||
| 1 | architecture | `ApiClient` autoload: HTTP + JWT storage + error contract | — |
|
||||
| 2 | implementation | Main-menu **login** wiring (form-encoded) | 1 |
|
||||
| 3 | implementation | Main-menu **register** wiring (JSON + age gate) | 2 |
|
||||
| 4 | implementation | **World list** screen consuming `GET /api/worlds` | 2 |
|
||||
| 5 | testing | End-to-end headless smoke test + manual verify | 1,3,4 |
|
||||
|
||||
## Design decisions captured (resolved in task 1)
|
||||
- Token storage: in-memory singleton property for the MVP; a `save_to_disk` seam is left
|
||||
open but not required yet (no re-login on restart in this slice).
|
||||
- Error contract: every call returns a discriminated result (`ok`/`error`) so callers
|
||||
never crash on network failure; non-2xx surfaces `{status, detail}` from the backend.
|
||||
- Base URL: read from a ProjectSetting, defaulting to `http://127.0.0.1:8000`.
|
||||
- Login is form-encoded; register is JSON — the client helper must support both content types.
|
||||
@@ -1,182 +0,0 @@
|
||||
# Creator Studio — Extended Authoring Tools
|
||||
|
||||
> **Feature:** Creator Studio — Extended Authoring Tools
|
||||
> **Status:** Planning
|
||||
> **Owner:** Platform team
|
||||
|
||||
## Feature Overview
|
||||
|
||||
Extends the in-game world editor (M6) toward a real authoring tool with:
|
||||
1. **Asset import pipeline** (glTF models/textures)
|
||||
2. **Script editor** with sandboxed API surface exposed clearly
|
||||
3. **Experience publishing flow** (draft → review → live)
|
||||
4. **Basic analytics** for creators (plays, session length)
|
||||
|
||||
## User Stories
|
||||
|
||||
### US-1: Asset Import Pipeline
|
||||
_As a creator, I can import glTF models and textures into my experience so that I can use custom 3D assets._
|
||||
|
||||
### US-2: Script Editor with Sandboxed API
|
||||
_As a creator, I can write and test scripts in the editor with autocomplete/docs for the available API surface._
|
||||
|
||||
### US-3: Experience Publishing Flow
|
||||
_As a creator, I can publish my experience through a draft → review → live pipeline so that players can discover and play it._
|
||||
|
||||
### US-4: Creator Analytics
|
||||
_As a creator, I can see how many plays and average session length my experiences receive so that I can understand player engagement._
|
||||
|
||||
---
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### US-1: Asset Import Pipeline
|
||||
|
||||
#### T1.1: Backend — glTF Asset Upload with Metadata Extraction
|
||||
- **Type:** implementation
|
||||
- **Files:** `backend/app/routers/assets.py` (extend upload endpoint), `backend/app/models/asset.py` (add glTF-specific metadata fields)
|
||||
- **Scope:** Extend the existing `upload_asset_file` endpoint in `assets.py` to detect `.glb`/`.gltf` files and extract glTF metadata (mesh_count, vertex_count, has_animation, texture_refs, bounding_box) from the embedded JSON. Store extracted metadata in `metadata_json`. Add a `has_gltf_metadata` computed property. The Asset model already has `asset_type = MODEL_3D` and `metadata_json` — this task adds the extraction logic.
|
||||
- **Acceptance:** POST `/api/assets/{id}/upload` with a `.glb` file returns asset with `metadata_json` containing `mesh_count`, `vertex_count`, `has_animation`, `texture_refs`. GET lists include these fields. File stored on disk. Non-glTF files are unaffected.
|
||||
- **Dependencies:** None (builds on existing asset infrastructure)
|
||||
|
||||
#### T1.2: Backend — Public Asset Catalog Endpoint
|
||||
- **Type:** implementation
|
||||
- **Files:** `backend/app/routers/assets.py` (new endpoint)
|
||||
- **Scope:** Add `GET /api/assets/catalog` endpoint that returns all public glTF assets with their metadata (name, thumbnail_url, mesh_count, vertex_count, tags). Add filtering by tags and pagination. This is what the editor's asset palette queries. Uses the existing `Asset.is_public` field.
|
||||
- **Acceptance:** Catalog endpoint returns paginated list of public assets; filtering by tag works; pagination params respected; returns 401 without auth.
|
||||
- **Dependencies:** T1.1
|
||||
|
||||
#### T1.3: Godot — glTF Import Utility (Editor-Side)
|
||||
- **Type:** implementation
|
||||
- **Files:** `scripts/gltf_importer.gd` (new), `scripts/asset_catalog.gd` (new)
|
||||
- **Scope:** Godot-side utility that connects to the catalog API, downloads glTF files, and converts them to Godot-compatible resources (`.res` files with embedded mesh data). Provides `import_gltf(url: String) -> Resource` function. Handles texture extraction and mesh merging. Uses the ApiClient autoload (from client-server-wiring plan) for HTTP.
|
||||
- **Acceptance:** `import_gltf()` downloads a glTF from the catalog, converts mesh to `Mesh` resource, returns a ready-to-use Godot resource. Texture files stored in `user://assets/`.
|
||||
- **Dependencies:** T1.2, client-server-wiring ApiClient (task 1 from that plan)
|
||||
|
||||
### US-2: Script Editor with Sandboxed API
|
||||
|
||||
> **Design note from `scripting_sandbox.md`:** Godot 4 has no built-in GDScript sandbox. The recommended approach is a **data-driven DSL** (JSON/Resource) interpreted by trusted GDScript, not arbitrary code execution. This task follows that recommendation.
|
||||
|
||||
#### T2.1: Backend — Script Model & CRUD Endpoints
|
||||
- **Type:** architecture
|
||||
- **Files:** `backend/app/models/script.py` (new), `backend/app/routers/scripts.py` (new), `backend/app/main.py` (register router), `backend/app/models/__init__.py` (export)
|
||||
- **Scope:** Create `Script` model (id, name, world_id, owner_id, source_code TEXT, version INT, script_type ENUM[gdscript, dsl], dsl_payload JSON, created_at, updated_at). Add full CRUD endpoints: `POST /api/scripts/` (create), `GET /api/scripts/` (list), `GET /api/scripts/{id}` (get), `PUT /api/scripts/{id}` (update), `DELETE /api/scripts/{id}` (delete). Scripts are owned by users and associated with a world. `dsl_payload` stores the JSON/Resource data for the DSL approach; `source_code` stores raw GDScript if used.
|
||||
- **Acceptance:** All CRUD endpoints work; scripts are owned by creator; list endpoint filters by owner/world; update increments version; DSL and gdscript types supported.
|
||||
- **Dependencies:** None (new model + router)
|
||||
|
||||
#### T2.2: Godot — Script API Surface Definition
|
||||
- **Type:** architecture
|
||||
- **Files:** `scripts/editor_script_api.gd` (new)
|
||||
- **Scope:** Define the public API surface available to creator scripts. This is a `RefCounted` class that wraps the Godot engine's capabilities into a safe, documented interface. Includes: `WorldAPI` (get_tile, get_object, set_property), `PlayerAPI` (get_position, get_health), `AudioAPI` (play_sound, play_music), `NetworkAPI` (send_event, broadcast). Each method has documented parameters and return types. Self-documenting via `##` doc comments for autocomplete.
|
||||
- **Acceptance:** `EditorScriptAPI` class exposes all documented methods; methods are callable from user scripts; API surface is self-documenting.
|
||||
- **Dependencies:** None (standalone definition)
|
||||
|
||||
#### T2.3: Godot — In-Game Script Editor UI
|
||||
- **Type:** implementation
|
||||
- **Files:** `scenes/editor_script_editor.tscn` (new), `scripts/editor_script_editor.gd` (new)
|
||||
- **Scope:** A `Control`-based UI panel that provides a script editor within the game. Features: code editor with syntax highlighting (basic), autocomplete dropdown, run button (calls backend `/run` endpoint), output console (shows stdout/stderr), API reference panel (shows available methods). Integrates with the existing editor toolbar.
|
||||
- **Acceptance:** Editor panel opens from toolbar; code editor accepts input; autocomplete shows API methods; run button executes script and shows output; API reference panel is browsable.
|
||||
- **Dependencies:** T2.1, T2.2
|
||||
|
||||
### US-3: Experience Publishing Flow
|
||||
|
||||
#### T3.1: Backend — Experience Model & Status Transitions
|
||||
- **Type:** architecture
|
||||
- **Files:** `backend/app/models/experience.py` (new), `backend/app/routers/experiences.py` (new), `backend/app/main.py` (register router), `backend/app/models/__init__.py` (export)
|
||||
- **Scope:** Create `Experience` model (id, world_id FK→worlds, title, description, status ENUM[DRAFT,SUBMITTED,IN_REVIEW,APPROVED,REJECTED,LIVE], published_at, review_notes, reviewer_id FK→users). Status transitions enforced: DRAFT→SUBMITTED→IN_REVIEW→APPROVED→LIVE; REJECTED→DRAFT (resubmit). Add endpoints: `POST /api/experiences/` (create draft), `POST /api/experiences/{id}/submit` (submit for review), `POST /api/experiences/{id}/approve` (approve), `POST /api/experiences/{id}/reject` (reject), `GET /api/experiences/` (list with status filter). An experience is a **publishing wrapper** around a World — the world's visibility is set to PUBLIC when experience goes LIVE.
|
||||
- **Acceptance:** All CRUD + transition endpoints work; status transitions enforced; rejected can be resubmitted; list filters by status; approve sets world visibility to PUBLIC; only world owner can submit; only moderators can approve/reject.
|
||||
- **Dependencies:** None (new model + router)
|
||||
|
||||
#### T3.2: Backend — Review Queue & Moderation Integration
|
||||
- **Type:** implementation
|
||||
- **Files:** `backend/app/routers/experiences.py` (add endpoints)
|
||||
- **Scope:** Add review queue endpoint `GET /api/experiences/review-queue` (returns experiences in IN_REVIEW status). Add `POST /api/experiences/{id}/review` endpoint for moderators to add review notes and change status. Integrates with content moderation: when an experience is submitted, its world's content flags are computed and attached to the review.
|
||||
- **Acceptance:** Review queue returns IN_REVIEW experiences; review endpoint accepts notes and status change; content flags attached on submit; only moderators can review.
|
||||
- **Dependencies:** T3.1
|
||||
|
||||
#### T3.3: Godot — Publishing UI Panel
|
||||
- **Type:** implementation
|
||||
- **Files:** `scenes/editor_publish_panel.tscn` (new), `scripts/editor_publish_panel.gd` (new)
|
||||
- **Scope:** A `Control`-based panel in the editor that lets creators manage their experience's publishing state. Features: title/description editor, status badge (DRAFT/SUBMITTED/LIVE), submit button, review notes display, publish history timeline. Integrates with the existing editor toolbar.
|
||||
- **Acceptance:** Panel shows current status; submit button calls backend; review notes displayed; publish history shown; status badge updates on refresh.
|
||||
- **Dependencies:** T3.1, T3.2
|
||||
|
||||
#### T3.4: Godot — Experience List & Discovery
|
||||
- **Type:** implementation
|
||||
- **Files:** `scenes/experience_list.tscn` (new), `scripts/experience_list.gd` (new)
|
||||
- **Scope:** A UI panel that lists all experiences available to the player (LIVE status). Features: search/filter, experience cards (title, description, creator, play count), join button. Queries `GET /api/experiences/?status=LIVE` and displays results.
|
||||
- **Acceptance:** Lists LIVE experiences; search filters by title/description; cards show metadata; join button loads the experience's world.
|
||||
- **Dependencies:** T3.1
|
||||
|
||||
### US-4: Creator Analytics
|
||||
|
||||
#### T4.1: Backend — Session & Play Tracking Models
|
||||
- **Type:** architecture
|
||||
- **Files:** `backend/app/models/analytics.py` (new), `backend/app/routers/analytics.py` (new), `backend/app/main.py` (register router), `backend/app/models/__init__.py` (export)
|
||||
- **Scope:** Create `PlaySession` model (id, experience_id FK→experiences, player_id FK→users, started_at, ended_at, duration_seconds, chunk_loads INT, interactions_count INT). Create `PlayEvent` model (id, experience_id FK→experiences, player_id FK→users, event_type, payload JSON, created_at). Add endpoints: `POST /api/analytics/session/start` (start session), `POST /api/analytics/session/end` (end session), `POST /api/analytics/event` (log event). Note: `sessions` table is taken by auth tokens; this uses `play_sessions` table name.
|
||||
- **Acceptance:** Session start/end endpoints work; events logged; duration calculated correctly; endpoints accept valid experience/player IDs.
|
||||
- **Dependencies:** None (new models + routers)
|
||||
|
||||
#### T4.2: Backend — Creator Dashboard Analytics Endpoint
|
||||
- **Type:** implementation
|
||||
- **Files:** `backend/app/routers/analytics.py` (add endpoint)
|
||||
- **Scope:** Add `GET /api/analytics/experience/{id}` endpoint that returns aggregated analytics for an experience: total_plays, unique_players, avg_session_length, total_play_time, plays_over_time (daily breakdown), top_chunks_loaded, top_interactions. Data is aggregated from PlaySession and PlayEvent tables.
|
||||
- **Acceptance:** Returns all aggregated metrics; daily breakdown covers last 30 days; only owner or moderator can view; empty experience returns zeros.
|
||||
- **Dependencies:** T4.1
|
||||
|
||||
#### T4.3: Godot — Analytics Tracking in Game
|
||||
- **Type:** implementation
|
||||
- **Files:** `scripts/analytics_tracker.gd` (new)
|
||||
- **Scope:** A singleton-like service that tracks player sessions and events during gameplay. Automatically starts session on world load, tracks chunk loads, interactions, and time spent. Sends periodic heartbeat events to backend. Handles session end on world unload or disconnect.
|
||||
- **Acceptance:** Session starts on world load; events logged during play; heartbeat sends every 60s; session ends on world unload; handles disconnect gracefully.
|
||||
- **Dependencies:** T4.1
|
||||
|
||||
#### T4.4: Godot — Creator Analytics Dashboard UI
|
||||
- **Type:** implementation
|
||||
- **Files:** `scenes/creator_analytics_panel.tscn` (new), `scripts/creator_analytics_panel.gd` (new)
|
||||
- **Scope:** A `Control`-based panel in the editor that displays analytics for the current experience. Features: total plays counter, avg session length, plays over time chart (simple bar chart), top chunks, top interactions. Queries `GET /api/analytics/experience/{id}` and renders data.
|
||||
- **Acceptance:** Panel shows all metrics; chart renders bar chart of daily plays; data refreshes on button press; handles empty data gracefully.
|
||||
- **Dependencies:** T4.2, T4.3
|
||||
|
||||
---
|
||||
|
||||
## Database Migration Plan
|
||||
|
||||
New tables needed (in order):
|
||||
1. `scripts` — script storage
|
||||
2. `experiences` — publishing state (wraps worlds)
|
||||
3. `play_sessions` — session tracking (note: `sessions` table already exists for auth tokens)
|
||||
4. `play_events` — granular events
|
||||
|
||||
Migration file: `backend/alembic/versions/0003_creator_studio.py`
|
||||
|
||||
---
|
||||
|
||||
## Task Dependencies Summary
|
||||
|
||||
```
|
||||
T1.1 (glTF upload) ──→ T1.2 (catalog API) ──→ T1.3 (Godot importer)
|
||||
T2.1 (script model) ──→ T2.3 (editor UI)
|
||||
T2.2 (API surface) ──→ T2.3 (editor UI)
|
||||
T3.1 (experience model) ──→ T3.2 (review queue) ──→ T3.3 (publish UI)
|
||||
T3.1 ──→ T3.4 (discovery list)
|
||||
T4.1 (tracking model) ──→ T4.2 (dashboard endpoint) ──→ T4.4 (analytics UI)
|
||||
T4.1 ──→ T4.3 (game tracker) ──→ T4.4 (analytics UI)
|
||||
```
|
||||
|
||||
## Implementation Order Recommendation
|
||||
|
||||
1. **T1.1** — Foundation: asset upload with glTF metadata extraction
|
||||
2. **T2.1** — Foundation: script model + CRUD
|
||||
3. **T3.1** — Foundation: experience model + status transitions
|
||||
4. **T4.1** — Foundation: analytics tracking model
|
||||
5. **T1.2** — Asset catalog for editor
|
||||
6. **T2.2** — Script API surface definition
|
||||
7. **T3.2** — Review queue integration
|
||||
8. **T4.2** — Analytics dashboard endpoint
|
||||
9. **T1.3** — Godot glTF importer (depends on ApiClient from client-server-wiring)
|
||||
10. **T2.3** — Script editor UI
|
||||
11. **T3.3** — Publishing UI panel
|
||||
12. **T3.4** — Experience discovery list
|
||||
13. **T4.3** — Analytics tracker in game
|
||||
14. **T4.4** — Analytics dashboard UI
|
||||
@@ -0,0 +1,160 @@
|
||||
# Trust & Safety Operations — Plan
|
||||
|
||||
> **Status:** Planned
|
||||
> **Owner:** Platform team
|
||||
> **Feature ID:** `trust_safety`
|
||||
> **Godot version:** 4.6.2 stable
|
||||
> **Backend:** FastAPI + SQLAlchemy (async) + PostgreSQL (+ Redis available, unused)
|
||||
> **Related:** `content_moderation.md` (M5 — content rating/flagging at upload time),
|
||||
> `social.md` (chat + report-message), `app/dependencies.py` (JWT auth)
|
||||
|
||||
## 1. Overview
|
||||
|
||||
This feature is the **enforcement workflow** for content/behavior that is
|
||||
**already live** and has been reported by a real user. M5
|
||||
(`content_moderation.md`) covers rating/flagging at authoring/upload time; this
|
||||
feature covers what happens **after** something is live and someone reports it:
|
||||
|
||||
1. **Reporting** — report a user, an experience (world/asset), or a chat
|
||||
message with a reason and optional detail.
|
||||
2. **Moderation queue** — staff review incoming reports, see report + target
|
||||
context, and resolve them.
|
||||
3. **Ban / suspension** — apply a temporary suspension or permanent ban to a
|
||||
user, with enforcement at the auth boundary.
|
||||
4. **Appeals** — a sanctioned user can appeal; staff can uphold or overturn.
|
||||
|
||||
The userbase being 18+ does **not** shrink the moderation need — harassment,
|
||||
hate speech, spam, griefing, and CSAM/policy violations still require real
|
||||
enforcement. Safety is a **blocking requirement**, not an add-on.
|
||||
|
||||
## 2. Existing constraints (do not re-build)
|
||||
|
||||
- **Auth:** JWT bearer; `get_current_user` / `CurrentUser` in
|
||||
`app/dependencies.py`. Every new endpoint reuses it.
|
||||
- **DB:** async SQLAlchemy + Alembic. Current head is `0002_add_date_of_birth_to_users`.
|
||||
- **Models:** `User`, `World` (owner_id), `Asset` (owner_id, is_public). There
|
||||
is no `role` field yet, and no chat/message table yet (social.md is planned,
|
||||
not built).
|
||||
- **Redis:** configured (`REDIS_URL`) but unused; not required by this feature.
|
||||
- **Client:** Godot 4.6.2, minimal `main_menu.gd`; no report/chat UI yet.
|
||||
|
||||
## 3. Key design decisions
|
||||
|
||||
| Concern | Decision |
|
||||
|---|---|
|
||||
| Report target addressing | Generic `target_type` + `target_id` (string) so user/world/asset/message all flow through one report path; router resolves target existence via a small registry |
|
||||
| Report reasons | Fixed enum `reason_category` (harassment, hate_speech, sexual_content, violence, spam, self_harm, other) + free-text `details` |
|
||||
| Staff roles | Add `role` enum on `User` (`user` / `moderator` / `admin`); `RequireStaff` dependency gates the queue/action/appeal-review endpoints |
|
||||
| Enforcement | Denormalized `banned_until` / `suspended_until` timestamps on `User` (kept in sync by moderation actions); `get_current_user` rejects sanctioned users with `403` so enforcement applies to **all** existing endpoints |
|
||||
| Ban vs. suspension | Suspension = temporary (`expires_at`); ban = permanent (`expires_at IS NULL`). Both are rows in `moderation_actions` for auditability |
|
||||
| Appeals | Appeal references the specific `moderation_action`; approving an appeal revokes the action and clears the user's sanction |
|
||||
| Audit | Every moderation action is an append-only row with `moderator_id`, `reason`, `created_at`, `revoked_at`; reports record `resolved_by` + `resolution` |
|
||||
|
||||
## 4. Data model (target shape)
|
||||
|
||||
```
|
||||
users
|
||||
+ role ENUM(user, moderator, admin) default 'user'
|
||||
+ banned_until TIMESTAMPTZ NULL
|
||||
+ suspended_until TIMESTAMPTZ NULL
|
||||
|
||||
reports
|
||||
id, reporter_id FK users, target_type ENUM(user,world,asset,chat_message),
|
||||
target_id VARCHAR(64), reason_category ENUM(...), details TEXT,
|
||||
status ENUM(open,in_review,resolved,dismissed) default 'open',
|
||||
resolved_by FK users NULL, resolution ENUM(...) NULL,
|
||||
created_at, updated_at, resolved_at
|
||||
|
||||
moderation_actions
|
||||
id, moderator_id FK users, target_user_id FK users,
|
||||
target_type ENUM(user,world,asset,chat_message), target_id VARCHAR(64),
|
||||
action ENUM(warn,suspend,ban,restrict,remove,restore),
|
||||
reason TEXT, report_id FK reports NULL,
|
||||
expires_at TIMESTAMPTZ NULL, revoked_at TIMESTAMPTZ NULL, created_at
|
||||
|
||||
appeals
|
||||
id, moderation_action_id FK moderation_actions,
|
||||
user_id FK users, reason TEXT,
|
||||
status ENUM(pending,approved,rejected) default 'pending',
|
||||
reviewed_by FK users NULL, reviewed_at TIMESTAMPTZ NULL, created_at
|
||||
```
|
||||
|
||||
## 5. User stories
|
||||
|
||||
### US-1 Reporting
|
||||
As a player, I can report a user, an experience (world/asset), or a chat
|
||||
message, choosing a reason and adding optional detail.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Report requires a valid target of the matching type (404 otherwise) and a
|
||||
reason from the fixed category set.
|
||||
- Duplicate reports from the same reporter for the same target are coalesced
|
||||
(409) rather than creating unbounded duplicates.
|
||||
- A report cannot be filed by a banned/suspended user (enforced by existing auth).
|
||||
- Each report stores reporter, target, reason, and created_at; status starts `open`.
|
||||
|
||||
### US-2 Moderation queue & review
|
||||
As a moderator, I can list open reports (filterable by type/status/reason),
|
||||
view a report with its target, and resolve it with an action.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Queue is staff-only (`RequireStaff`); non-staff get 403.
|
||||
- List supports status/type/reason filters + pagination (offset/limit).
|
||||
- Resolving sets `status`, `resolution`, `resolved_by`, `resolved_at` and
|
||||
(where applicable) records a `moderation_actions` row.
|
||||
|
||||
### US-3 Ban / suspension enforcement
|
||||
As a moderator, I can suspend a user temporarily or ban them permanently, and
|
||||
the sanction blocks the user from authenticated endpoints immediately.
|
||||
|
||||
**Acceptance criteria**
|
||||
- Suspension sets `suspended_until`; ban sets `banned_until` (permanent). Warn
|
||||
records an action without blocking.
|
||||
- `get_current_user` returns `403` for any user past a live sanction.
|
||||
- Expired suspensions no longer block (time-based, no cron required).
|
||||
- Removing a ban/suspension is itself a recorded `restore` action.
|
||||
|
||||
### US-4 Appeals
|
||||
As a sanctioned user, I can appeal an active ban/suspension; as a moderator I
|
||||
can approve (overturn) or reject (uphold) it.
|
||||
|
||||
**Acceptance criteria**
|
||||
- A user can submit exactly one open appeal per sanction.
|
||||
- Appeal is tied to the specific `moderation_action`.
|
||||
- Approving revokes the action and clears the user's sanction; rejecting
|
||||
upholds it. Both record reviewer + timestamp.
|
||||
|
||||
## 6. Task breakdown
|
||||
|
||||
Right-sized to ~1–3 files / one coherent piece of functionality. Task IDs are
|
||||
dependency order.
|
||||
|
||||
| # | Task | Story | Depends on |
|
||||
|---|------|-------|-----------|
|
||||
| 1 | Moderation data model + Alembic migration (reports, moderation_actions, appeals, user `role`/`banned_until`/`suspended_until`) | US-1..4 | — |
|
||||
| 2 | Staff authorization + sanction enforcement dependency (`RequireStaff`; extend `get_current_user` to reject sanctioned users) | US-2, US-3 | 1 |
|
||||
| 3 | Report submission endpoints (report user / experience / chat message) | US-1 | 1 |
|
||||
| 4 | Moderation queue + review endpoints (list, detail, resolve→action) | US-2 | 1, 2 |
|
||||
| 5 | Ban/suspend/restore action application + user-sanction sync | US-3 | 2, 4 |
|
||||
| 6 | Appeals endpoints (submit, list, review → uphold/overturn) | US-4 | 1, 5 |
|
||||
| 7 | Integration tests: report → review → ban → appeal → overturn | US-1..4 | 3, 4, 5, 6 |
|
||||
|
||||
## 7. Phasing
|
||||
|
||||
- **Phase 1 (data + enforcement core):** tasks 1–2. Land the schema and make
|
||||
sanctioned users actually unable to act — highest value.
|
||||
- **Phase 2 (report → review → action):** tasks 3–5. The moderation loop.
|
||||
- **Phase 3 (appeals):** task 6.
|
||||
- **Phase 4 (verification):** task 7 integration tests.
|
||||
|
||||
## 8. Out of scope for this feature
|
||||
|
||||
- **Client (Godot) report/appeal UI** — no chat/social UI exists yet; report
|
||||
endpoints are consumed once `social.md` US-6 lands. Client is a follow-up.
|
||||
- Automated content scanning / hash detection (covered by M5 /
|
||||
`content_moderation.md`).
|
||||
- Third-party moderation/CSAM-scanning vendors.
|
||||
- Rate limiting of report submission (Redis is available; add a token-bucket
|
||||
dependency later if abuse is observed).
|
||||
- Voice chat moderation.
|
||||
- Per-jurisdiction legal-takedown workflows (DMCA etc.).
|
||||
Reference in New Issue
Block a user