trymeapp/SYSTEM.md

83 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.