trymeapp/SYSTEM.md

83 lines
5.6 KiB
Markdown
Raw Permalink Normal View History

2026-11-11 22:06:26 +00:00
# TryMe Gaming App — System Architecture (`SYSTEM.md`)
## 1. High-Level Overview
**TryMe** is a web platform designed for students to author, share, vote on, favourite, and play multiple-choice and write-in quizzes (0–40 questions on the public free tier). It packages a **React 19 + TypeScript** single-page frontend and a **FastAPI + SQLAlchemy 2.0** backend into a single multi-stage container image, backed by either **PostgreSQL** or **SQLite**.
```mermaid
graph TB
Student["Student / Moderator Browser (SPA)"]
CLI["Automation / Study Script (X-API-Key)"]
Auth0["Auth0 Federated IdP (OIDC / JWKS)"]
subgraph Container["TryMe Container (:8000)"]
FastAPI["FastAPI HTTP & Static Layer (app/main.py)"]
AuthLayer["Session, Auth0 JWKS & API Key Guard (app/auth.py)"]
QuizSvc["Quiz Grading & Leaderboard Service (app/services/quiz_service.py)"]
SPA["Compiled React + Vite Assets (frontend/dist)"]
end
DB[("PostgreSQL 16 or SQLite + PVC")]
Student -->|"HTTPS / Cookie or Bearer JWT"| FastAPI
CLI -->|"HTTPS + X-API-Key"| FastAPI
FastAPI --> SPA
FastAPI --> AuthLayer
AuthLayer -->|"Verify OIDC / JWKS"| Auth0
AuthLayer --> QuizSvc
QuizSvc --> DB
```
---
## 2. CoreSubsystems
### 2.1 Authentication, Consent & API Keys (`app/auth.py`, `app/routers/auth_routes.py`, `app/routers/users.py`)
- **Auth0 Federated Login**: `/api/v1/auth/login` initiates an OAuth2/OIDC Authorization Code flow with signed anti-CSRF state (`itsdangerous`). `/api/v1/auth/callback` exchanges the code, fetches `/userinfo`, provisions or updates the `User` row, and issues an `HttpOnly` session JWT cookie (`tryme_session`). Direct Auth0 RS256 Bearer tokens are also verified via cached `/.well-known/jwks.json`.
- **Player Onboarding & Privacy**: Users select a stylized animal icon (`fox`, `owl`, `bear`, `otter`, `koala`, `tiger`, `frog`, `penguin`) and colorway (`avatar-1..8`), set a unique `nickname`, `first_name`, `last_name`, optional integer `age` (**never** storing a birthday), inclusive `gender` (`woman`, `man`, `non_binary`, `agender`, `genderqueer`, `two_spirit`, `other`, `prefer_not_to_say`), and explicitly confirm permission to use public websites (`public_website_consent`).
- **Personal API Keys**: Users generate `tm_live_<token>` keys stored strictly as SHA-256 hashes (`key_hash`) with configurable expiration and one-click revocation (`POST /api/v1/users/me/api-keys/{id}/expire`).
### 2.2 Quiz Engine, Leaderboards & Voting (`app/routers/quizzes.py`, `app/services/quiz_service.py`)
- **Free Tier Guardrails**: Validated via Pydantic (`max_length=40`) and route checks (`0 <= len(questions) <= 40`).
- **Question Formats**: Each question supports either `multiple_choice` (2–6 options) or `write_in` (whitespace-normalized, case-insensitive matching against `correct_answer` plus optional `accepted_answers` synonyms).
- **Score Aggregation**: Every quiz card and detail response computes three distinct metrics in bulk SQL queries:
1. **Global Best Score**: Highest `score_percent` across all attempts, paired with the top holder's `nickname` and animal icon (ties broken by earliest completion).
2. **User Latest Score**: The signed-in player's most chronological recent `QuizAttempt`.
3. **User All-Time Best Score**: The signed-in player's highest `QuizAttempt` on that quiz.
- **Thumbs Up / Down Voting**: Stored in `quiz_votes` with a `UNIQUE(quiz_id, user_id)` constraint (`+1` or `-1`, toggleable back to `0`).
- **Shareable URLs**: Every quiz receives a unique `share_slug` (e.g., `/quiz/solar-system-quick-ch-a1b2c3`) routable directly in the SPA and backend.
### 2.3 Right-Hand Favourites Pane & Recycle Bin (`app/routers/favourites.py`, `FavouritesPane.tsx`)
```mermaid
sequenceDiagram
participant Main as Explore Feed Card
participant Rail as Right Favourites Pane
participant Bin as Pop-up Recycle Bin
participant API as FastAPI /api/v1/favourites
Main->>Rail: Drag quiz card (application/x-tryme-quiz-id)
Rail->>API: POST /api/v1/favourites {quiz_id, position}
API-->>Rail: Ordered favourites list
Rail->>Rail: Drag existing favourite (summons Recycle Bin)
alt Reorder inside rail
Rail->>API: PUT /api/v1/favourites/reorder {quiz_ids}
else Drop onto Recycle Bin
Rail->>Bin: Drop favourite item
Bin->>API: DELETE /api/v1/favourites/{quiz_id}
Bin-->>Rail: Show 7-second Undo bar
end
```
### 2.4 Moderation (`app/routers/admin.py`, `ModeratorView.tsx`)
- Moderators can be designated via `ADMIN_USER` container env var (matched against `auth_sub`, `email`, or `nickname`) or claimed once at initial launch (`POST /api/v1/admin/claim`) when no moderator exists.
- Moderators can suspend/restore accounts (`is_banned = True` blocks quiz creation, submissions, and voting with `403 Forbidden`) and remove quizzes (`is_removed = True` hides the quiz from public feeds and cleans up dangling favourites).
---
## 3. UI Design System
The frontend uses the Hallmark **`Hum`** (`playful` genre, `--scope app`) design system in `frontend/src/styles/tokens.css` and `frontend/src/styles/app.css`:
- **Primary Brand Colors**: Warm festive orange `#fb723a` (`oklch(70.6% 0.178 41)`) paired with deep slate-navy `#253450` (`oklch(32.5% 0.058 258)`) over warm cream card stock (`oklch(97.2% 0.012 72)`).
- **Typography**: `Chillax` (`500`, `600`) for headings, body, and controls; `Geist Mono` exclusively for API key tokens; `tabular-nums` on all scores, counts, and votes.
- **Accessibility & Motion**: Instant `:focus-visible` rings, 44px touch targets, native `<dialog>` modals, keyboard reorder/delete fallbacks alongside HTML5 drag-and-drop, and `prefers-reduced-motion: reduce` support.