# Architecture

```text
Browser / QR scanner
        │
        ▼
Cloudflare DNS, TLS, WAF, Turnstile, static caching
        │
        ▼
Apache + cPanel Application Manager + Passenger
        │
        ▼
Next.js 16 custom Node server (`app.js`)
       ┌┴───────────────────────────────┐
       ▼                                ▼
MySQL                                  Cloudflare R2
users, sessions, stories,              original photographs and
QR state, claims, permissions,         future audio/video/documents
audit and media metadata
```

## QR lifecycle

```text
unclaimed → reserved → claimed
     │          │          │
     │          └─ expires → unclaimed
     └─ admin may suspend/retire later
```

A claimed QR record is never assigned to another unrelated person. Replacement should create a new QR and retain the history of the old record.

## Account model

The first release is passwordless. A verified email creates or signs into an account. Sessions are opaque random tokens stored in an HttpOnly cookie; only a keyed hash is stored in MySQL.

## Media model

The application authorises the file and creates a short-lived presigned PUT URL. The browser uploads directly to R2, then calls the completion API. Large client files therefore do not pass through Apache/Passenger.
