116 lines
3.7 KiB
Markdown
116 lines
3.7 KiB
Markdown
# Cyber Hybrid Hub API
|
|
|
|
Postgres-backed profile API for the Flutter app.
|
|
|
|
## Setup
|
|
|
|
1. Create the database (once):
|
|
|
|
```bash
|
|
createdb cyberhybridhub
|
|
```
|
|
|
|
2. Copy and edit environment variables:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
3. Install dependencies and run (from this `server/` directory):
|
|
|
|
```bash
|
|
dart pub get
|
|
dart run bin/server.dart
|
|
```
|
|
|
|
The API listens on `http://localhost:3000` by default (`PORT` in `.env`).
|
|
|
|
## Endpoints
|
|
|
|
| Method | Path | Auth |
|
|
|--------|------|------|
|
|
| `GET` | `/v1/me/profile` | `Authorization: Bearer <Firebase ID token>` |
|
|
| `PUT` | `/v1/me/profile` | same |
|
|
| `POST` | `/v1/me/incoming-question` | same — pushes a question to the client via SignalR |
|
|
| `POST` | `/v1/me/questions/bootstrap` | ensure starter question at login |
|
|
| `GET` | `/v1/me/questions` | list unanswered questions (queue order) |
|
|
| `POST` | `/v1/me/questions/{id}/answer` | submit answer (`{"answer": 0}` default) |
|
|
| `POST` | `/v1/me/questions/{id}/defer` | move question to end of queue |
|
|
|
|
## SignalR — incoming questions
|
|
|
|
Hub URL: `http://localhost:3000/hubs/questions`
|
|
|
|
The Flutter app calls `POST /v1/me/questions/bootstrap` once at login to ensure a
|
|
starter question exists (random correct answer from -10 to 10) when the user has none.
|
|
After sign-in it connects to SignalR and listens for `ReceiveQuestion`. On each new
|
|
WebSocket connection the API only delivers an existing unanswered question — it does
|
|
not create new rows.
|
|
|
|
Client payload (correct answer is not sent):
|
|
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"assignedUserId": "firebase-uid",
|
|
"text": "...",
|
|
"sentAt": "...",
|
|
"unansweredCount": 2
|
|
}
|
|
```
|
|
|
|
`unansweredCount` is the number of unanswered rows for that user (shown in the app when greater than 1).
|
|
|
|
`questions` table: `id` (UUID), `assigned_user_id`, `question_text`, `user_response`
|
|
(nullable), `correct_answer`, `created_at`, `modified_at`.
|
|
|
|
## Background question pipeline
|
|
|
|
A background worker runs inside the API process (enabled by default). On each
|
|
interval it walks registered users, fetches data from public web APIs, and
|
|
enqueues pipeline questions when the user's queue is not full.
|
|
|
|
| Env var | Default | Purpose |
|
|
|---------|---------|---------|
|
|
| `QUESTION_WORKER_ENABLED` | `true` | Set to `false` to disable the worker |
|
|
| `QUESTION_WORKER_INTERVAL_SECONDS` | `60` | Seconds between maintenance cycles |
|
|
| `QUESTION_PIPELINE_TEST_MODE` | `false` | Use random -10..10 starter-style questions instead of API copy |
|
|
|
|
**External APIs used**
|
|
|
|
- [REST Countries](https://restcountries.com/) — population and capital facts
|
|
- [Open-Meteo](https://open-meteo.com/) — current temperature by city
|
|
|
|
**Branching flow**
|
|
|
|
1. **Track choice** — user swipes toward +10 (weather) or -10 (geography).
|
|
2. **Geography** — yes/no population threshold, then capital confirmation;
|
|
wrong population guess triggers a recovery question.
|
|
3. **Weather** — yes/no warm/cool for a random European city, then a follow-up
|
|
to continue weather or switch to geography.
|
|
|
|
When a user submits an answer (`POST .../answer`), the pipeline evaluates the
|
|
response and may immediately create the next branched question and push it over
|
|
SignalR.
|
|
|
|
Pipeline state is stored in `user_pipeline_state`; questions may include
|
|
`source_tag`, `pipeline_key`, and `pipeline_step` columns (migration
|
|
`003_question_pipeline.sql`).
|
|
|
|
Test from the shell (replace `ID_TOKEN`):
|
|
|
|
```bash
|
|
curl -s -X POST http://localhost:3000/v1/me/incoming-question \
|
|
-H "Authorization: Bearer ID_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"text":"What is your preferred contact method?"}'
|
|
```
|
|
|
|
## Flutter client
|
|
|
|
Run the app with the API URL (defaults to `http://localhost:3000`):
|
|
|
|
```bash
|
|
flutter run --dart-define=API_BASE_URL=http://localhost:3000
|
|
```
|