# Data Flows

Sequence diagrams for the flows that cut across more than one service. If you want to understand why service X calls service Y, or where a particular piece of user state ends up, this is the page.

## 1. User Login (Email/Password)

A VR client or the website logs a user in, receives a JWT access token, and thereafter includes that token on every request.

```mermaid
sequenceDiagram
    autonumber
    actor U as User (client)
    participant API as apps/api :3009
    participant AA as AuthApi
    participant AD as AuthDatabase
    participant PG as Postgres

    U->>API: POST /auth/login<br/>{ email, password, appKey }
    API->>API: verify Authorization bearer (app key)
    API->>AA: handleEmailPasswordLogin
    AA->>AD: getAccountByEmail(email)
    AD->>PG: SELECT FROM accounts WHERE email=?
    PG-->>AD: row
    AD-->>AA: BigscreenAccount
    AA->>AA: Bcrypt.compare(password, hash)
    AA->>AA: sign JWT (access token)<br/>+ refresh token
    AA-->>API: tokens
    API-->>U: 200 { accessToken, refreshToken }<br/>via x-access-token / x-refresh-token headers
```

See [libraries/auth.md](./libraries/auth.md) for the full auth model (access-policy claims, token rotation, Steam/Oculus variants).

Files: [apps/api/api.ts](../apps/api/api.ts), [auth/AuthApi.ts](../auth/AuthApi.ts), [auth/AuthDatabase.ts](../auth/AuthDatabase.ts).

## 2. Room Creation

A VR client asks `cloud_api` to create a multiplayer room. `cloud_api` writes the room shell to Redis, asks a media server to provision a router, and the media server reports completion back via the `BigscreenCloud` SQS queue. `cloud_worker` processes that event and finalises room state in Redis.

```mermaid
sequenceDiagram
    autonumber
    actor U as User (VR)
    participant CAPI as cloud/cloud_api
    participant C as Cloud @bigscreen/cloud
    participant REDIS as Redis
    participant BCQ as SQS BigscreenCloud
    participant MS as media_server_next
    participant CW as cloud/cloud_worker

    U->>CAPI: POST /room { config }
    CAPI->>C: Cloud.createRoom(config)
    C->>REDIS: HSET room:<id> ...
    C->>BCQ: send MediaServerCommand.RoomCreationRequest
    BCQ-->>MS: poll → RoomCreationRequest
    MS->>MS: mediasoup.createRouter()
    MS->>BCQ: send BigscreenCloudCommand.RoomCreated
    BCQ-->>CW: poll → RoomCreated
    CW->>C: Cloud.onRoomCreated(payload)
    C->>REDIS: update room:<id> status=ready
    U->>CAPI: GET /room/:id
    CAPI->>REDIS: HGETALL room:<id>
    CAPI-->>U: 200 { room state }
```

Room mutations are Redlock-protected with `lock:room:<id>` (5 s TTL) so concurrent creates / deletes don't race.

Files: [cloud/cloud_api/cloud_api.ts](../cloud/cloud_api/cloud_api.ts), [cloud/src/Cloud.ts](../cloud/src/Cloud.ts), [cloud/src/MessageQueue.ts](../cloud/src/MessageQueue.ts), [cloud/cloud_worker/cloud_worker_v2.ts](../cloud/cloud_worker/cloud_worker_v2.ts), [cloud/media_server_next/media_server_next.ts](../cloud/media_server_next/media_server_next.ts).

## 3. Media-Server Heartbeat

Every media server beats at a fixed interval so `cloud` can detect dead servers and evacuate rooms from them. Because media servers may run outside AWS (where direct SQS access is impractical), the heartbeat is POSTed over HTTP through `cloud_api`, which forwards it to the SQS queue that `cloud_worker` drains.

```mermaid
sequenceDiagram
    autonumber
    participant MS as media_server_next
    participant CAPI as cloud/cloud_api
    participant BCQ as SQS BigscreenCloud
    participant CW as cloud/cloud_worker
    participant REDIS as Redis

    loop every N seconds
        MS->>CAPI: POST /api/media-server/message<br/>{ command: Heartbeat }
        CAPI->>BCQ: enqueue
        BCQ-->>CW: poll → Heartbeat
        CW->>REDIS: update mediaServers:<id>.lastHeartbeatAt
    end

    Note over CW: Every 20s, cloud_worker runs<br/>Cloud.removeOldMediaServers()<br/>to expire stale entries.
```

Files: [cloud/src/RemoteMessageQueue.ts](../cloud/src/RemoteMessageQueue.ts), [cloud/cloud_worker/cloud_worker_v2.ts](../cloud/cloud_worker/cloud_worker_v2.ts):62.

## 4. WebSocket Message Delivery (Server → Client)

`cloud_api` or `cloud_worker` wants to push something to a specific user's VR client. It writes a message to the `WebsocketOutbox` queue keyed by the `websocketServerId` of the server that owns the user's connection. The correct `ws_server` instance polls that queue, locates the live `WebSocket`, and sends it.

```mermaid
sequenceDiagram
    autonumber
    participant CAPI as cloud/cloud_api<br/>(or cloud_worker)
    participant WOUT as SQS WebsocketOutbox
    participant WSS as cloud/ws_server
    actor U as User (VR)

    CAPI->>WOUT: enqueue<br/>{ userId, payload, websocketServerId }
    loop 100ms poll
        WSS->>WOUT: receiveMessages()
        WOUT-->>WSS: batch of messages
        WSS->>WSS: look up activeWebsockets[userId]
        WSS->>U: ws.send(payload)
    end

    Note over WSS: ws_server also owns the connection lifecycle:<br/>- ping every 10s<br/>- terminate after 5 unanswered pings<br/>- emit UpdateActiveConnections every 30s
```

Files: [cloud/ws_server/ws_server.ts](../cloud/ws_server/ws_server.ts), [cloud/src/MessageQueue.ts](../cloud/src/MessageQueue.ts).

## 5. Shopify Webhook → Order

A Shopify order fires a webhook to `apps/api/shopify/webhook`. `api.ts` bypasses its JSON parser for this path and hands the raw body to the Shopify integration, which verifies the HMAC signature before processing. Processed orders end up in the fabricator database, from which factory stations (Arda / CNC / Fusion via `apps/admin_api`) pick them up.

```mermaid
sequenceDiagram
    autonumber
    participant SHOP as Shopify
    participant API as apps/api :3009
    participant SH as Shopify handler<br/>(api/src/Shopify.ts)
    participant FD as FabricatorDatabase
    participant PG as Postgres
    participant ADMIN as apps/admin_api<br/>(for factory staff)
    participant ARDA as Arda SPA

    SHOP->>API: POST /shopify/webhook<br/>(raw body, HMAC header)
    API->>API: skip JSON parser for this path
    API->>SH: handle(raw, headers)
    SH->>SH: verify HMAC signature
    SH->>FD: upsert order + line items
    FD->>PG: INSERT / UPDATE fabricator tables
    SH-->>API: 200
    API-->>SHOP: 200 OK

    Note over ADMIN,ARDA: Later...
    ARDA->>ADMIN: GET /admin/order/...
    ADMIN->>FD: load order
    FD->>PG: SELECT
    FD-->>ADMIN: order
    ADMIN-->>ARDA: 200 { order }
```

Files: [apps/api/api.ts](../apps/api/api.ts):23 (webhook parser bypass), [api/src/Shopify.ts](../api/src/Shopify.ts), [api/src/fabricator/FabricatorDatabase.ts](../api/src/fabricator/FabricatorDatabase.ts).

## 6. Face Scan Submission

A customer scans their face in the iOS Scan Yourself app. The scan lands on the scan-yourself backend, is persisted, and eventually a factory worker triggers a toolpath generation job on `apps/admin_api`. The admin-API spawns a worker thread that talks to Fusion 360 to compute a CNC toolpath, which a CNC machine later pulls down.

```mermaid
sequenceDiagram
    autonumber
    actor CUST as Customer (iOS)
    participant SCAN as apps/scan_yourself
    participant LAMBDA as AWS Lambda
    participant S3 as S3 bucket
    participant ADMIN as apps/admin_api
    participant WT as Worker thread
    participant FUSION as Fusion 360 machine
    participant CNC as CNC machine

    CUST->>SCAN: upload scan data
    SCAN->>LAMBDA: preprocess (mesh ops, landmark detect)
    LAMBDA->>S3: store processed scan
    SCAN-->>CUST: accepted

    Note over ADMIN: Factory operator reviews in Arda, then:
    ADMIN->>WT: spawn Worker(task_runner.js, scanToolpath)
    WT->>FUSION: POST /fusion/job<br/>(bearer: fusion-api key)
    FUSION-->>WT: toolpath file
    WT->>S3: upload toolpath
    WT->>ADMIN: post completion (Discord + worker registry)

    CNC->>ADMIN: GET /cnc/toolpath/:id<br/>(bearer: cnc-machine-api key + access token)
    ADMIN->>S3: fetch toolpath
    ADMIN-->>CNC: toolpath file
```

Files: [apps/scan_yourself/](../apps/scan_yourself), [apps/admin_api/admin_api.ts](../apps/admin_api/admin_api.ts):71-130 (fusion and CNC auth handlers), [apps/admin_api/worker_tasks.ts](../apps/admin_api/worker_tasks.ts).

## 7. OAuth Authorization-Code Flow (PKCE)

A third-party OAuth client wants an admin's consent to call `apps/admin_api` on their behalf. The flow crosses three services — the consent UI lives on arda (so the admin is already authenticated via the arda session cookie), the code mint lives on admin_api (so it writes to the same Postgres tables that back client CRUD), and the token exchange lives on apps/api (so the provider has a public internet-facing endpoint separate from the IP-restricted admin_api).

```mermaid
sequenceDiagram
    autonumber
    actor U as Admin (browser)
    participant C as Third-party client
    participant ARDA as webapps/arda :3010
    participant ADMIN as apps/admin_api :3999
    participant API as apps/api :3009
    participant REDIS as Redis
    participant PG as Postgres

    C->>U: 302 → /oauth/authorize<br/>?client_id&redirect_uri&code_challenge&scope&state
    U->>ARDA: GET /oauth/authorize
    ARDA->>ADMIN: GET /admin/oauth/lookup_client
    ADMIN->>PG: SELECT oauth_clients
    PG-->>ARDA: safe client metadata
    ARDA-->>U: render oauth_consent.pug

    U->>ARDA: POST /oauth/authorize/decision<br/>(allow + csrf)
    ARDA->>ADMIN: POST /admin/oauth/my_grant
    ADMIN->>PG: UPSERT oauth_grants
    ARDA->>ADMIN: POST /admin/oauth/codes
    ADMIN->>REDIS: SET oauth:code:<code> NX EX 60
    ADMIN-->>ARDA: { code }
    ARDA-->>U: 302 redirect_uri?code=...&state=...

    U->>C: browser lands on redirect_uri
    C->>API: POST /oauth/token<br/>(code, verifier, client_id, secret)
    API->>REDIS: GETDEL oauth:code:<code>
    REDIS-->>API: payload (once)
    API->>API: verify PKCE S256
    API->>API: mint RS256 access + refresh
    API-->>C: { access_token, refresh_token, scope }

    C->>ADMIN: Bearer <access_token>
    ADMIN->>ADMIN: handleOAuthBearer<br/>(verify + grant + scope)
    ADMIN-->>C: 200 (admin endpoint)
```

The code is bound to `(clientId, userId, redirectUri, scope, codeChallenge)` and is single-use via `GETDEL`. Refresh tokens rotate with chain reuse detection — a replay of a previously-rotated refresh burns the whole chain.

Gated on three per-service env vars that must all be on: `OAUTH_ENABLED` (api), `OAUTH_ADMIN_API_ENABLED` (admin_api), `OAUTH_AUTHORIZE_ENABLED` (arda).

Files: [apps/api/api.ts](../apps/api/api.ts):155-168 (token endpoint wiring + bypass), [api/src/OAuthApi.ts](../api/src/OAuthApi.ts), [api/src/OAuthClientsApi.ts](../api/src/OAuthClientsApi.ts), [apps/admin_api/admin_api.ts](../apps/admin_api/admin_api.ts):128-164 (OAuth bearer admission), [webapps/src/server/oauth.js](../webapps/src/server/oauth.js), [auth/OAuthTokens.ts](../auth/OAuthTokens.ts), [auth/OAuthCodes.ts](../auth/OAuthCodes.ts). Reference: [services/oauth.md](./services/oauth.md).

## Further reading

- [services/cloud-api.md](./services/cloud-api.md) for the full cloud-api endpoint picture
- [services/cloud-worker.md](./services/cloud-worker.md) for the full inbound / outbound command list
- [services/ws-server.md](./services/ws-server.md) for the WebSocket connection lifecycle
- [libraries/auth.md](./libraries/auth.md) for token rotation, replay protection, access-policy semantics
- [services/oauth.md](./services/oauth.md) for scope semantics, refresh-chain rotation, and DB schema
- [external-services.md](./external-services.md) for what each queue / table / bucket is used for
