# Abuse Registration API POC A stripped-down, in-memory API proof-of-concept. It keeps the important authentication shape from the original API while removing backend/database business logic. The demo data is synthetic, stored in memory, and reset to the base dataset every 10 minutes. The base dataset contains 546 rows with `registered_at` dates from 1988 through 1991. The HTML template, CSS, JavaScript, and favicon are embedded with Go `embed`. A built binary can be copied and run by itself; it does not need a neighboring `static/`, `templates/`, or `favicon.ico` file. The JSON model is intentionally small: ```json { "id": "bb8d39a6-8fef-48af-9f9c-27a41c8f8baf", "registered_at": "1989-04-13T15:22:00Z", "gender": "female", "location": "Tórshavn", "abuse_type": "psychological", "status": "new" } ``` `id` is generated by the server as a UUID. `registered_at` defaults to `time.Now().UTC()` when omitted on create or update. ## Run locally ```bash go run ./cmd/api ``` Or with Task: ```bash task run ``` Then open: ```text http://localhost:8080/ ``` Optional environment variables: ```bash PORT=:9999 JWT_SECRET=change-me RESET_INTERVAL=10m go run ./cmd/api ``` ## Build The Taskfile follows the FLÓ-style build version format: ```text version [-/-]. git is [clean|dirty]. ``` `build/words.txt` is copied from the FLÓ website project. ```bash task build ./bin/abuse-registration-poc ``` The resulting `./bin/abuse-registration-poc` is self-contained. You can copy just that file to another Linux host and run it. ## Project layout The POC is intentionally small, but it is no longer a single-file prototype. The code is split in the same broad shape as the original API: ```text cmd/api/main.go binary entry point and flags internal/config environment/default configuration internal/models user and registration models plus allowed values internal/auth static POC users replacing the auth database internal/utils JWT and UUID helpers internal/store in-memory registration store and reset logic internal/handlers HTTP handlers for login, health, page, and data internal/middlewares authentication, authorization, and CORS internal/routes route registration and protected route wiring internal/web/templates/index.html single HTML page template, embedded into the binary internal/web/static embedded CSS and browser JavaScript internal/web/favicon.ico embedded favicon ``` The real database/repository layer is deliberately replaced by `internal/store`, but the login response shape, raw `Authorization` JWT usage, role split, and route-permission middleware are kept close to the original API. Web assets are separate source files, but are compiled into the binary at build time. ## Demo users | User | Password | Role | Access | |---|---|---|---| | `reader` | `reader-password` | `Reader` | Read protected endpoints | | `admin` | `admin-password` | `Admin` | Full create/read/update/delete and manual reset | ## Auth workflow The site does not contain a login form and does not acquire a token in the browser. Use curl or PowerShell only. Reader with curl: ```bash READER_TOKEN=$(curl -s -X POST http://localhost:8080/login \ -H 'Content-Type: application/json' \ -d '{"user_name":"reader","password":"reader-password"}' \ | sed -n 's/.*"token":"\([^"]*\)".*/\1/p') curl -s 'http://localhost:8080/api/v1/registrations?location=Tórshavn&limit=5' \ -H "Authorization: $READER_TOKEN" curl -s 'http://localhost:8080/api/v1/registrations?gender=female&abuse_type=psychological&status=open&limit=10' \ -H "Authorization: $READER_TOKEN" curl -s 'http://localhost:8080/api/v1/registrations?from=1989-01-01&to=1990-01-01&offset=20&limit=10' \ -H "Authorization: $READER_TOKEN" ``` Reader with PowerShell: ```powershell $reader = Invoke-RestMethod ` -Method Post ` -Uri "http://localhost:8080/login" ` -ContentType "application/json" ` -Body '{"user_name":"reader","password":"reader-password"}' $READER_TOKEN = $reader.token Invoke-RestMethod ` -Uri "http://localhost:8080/api/v1/registrations?location=Tórshavn&limit=5" ` -Headers @{Authorization=$READER_TOKEN} ``` Admin workflow: ```bash ADMIN_TOKEN=$(curl -s -X POST http://localhost:8080/login \ -H 'Content-Type: application/json' \ -d '{"user_name":"admin","password":"admin-password"}' \ | sed -n 's/.*"token":"\([^"]*\)".*/\1/p') CREATED_ID=$(curl -s -X POST http://localhost:8080/api/v1/registrations \ -H "Authorization: $ADMIN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"gender":"unknown","location":"Tórshavn","abuse_type":"psychological","status":"new"}' \ | sed -n 's/.*"id":"\([^"]*\)".*/\1/p') curl -s -X PUT "http://localhost:8080/api/v1/registrations/$CREATED_ID" \ -H "Authorization: $ADMIN_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"gender":"female","location":"Skopun","abuse_type":"digital","status":"referred"}' curl -s -X DELETE "http://localhost:8080/api/v1/registrations/$CREATED_ID" \ -H "Authorization: $ADMIN_TOKEN" ``` ## Endpoint behavior Public: - `GET /` returns the single HTML page only. It does not embed all 546 registrations into the HTML. - `GET /health` - `POST /login` - `GET /demo/registrations` is the public demo snapshot used by the page JSON viewer. Protected read: - `GET /api/v1/categories` - `GET /api/v1/locations` - `GET /api/v1/registrations` - `GET /api/v1/registrations/{uuid}` Admin create/read/update/delete: - `POST /api/v1/registrations` - `PUT /api/v1/registrations/{uuid}` - `DELETE /api/v1/registrations/{uuid}` - `POST /api/v1/reset` Unknown normal pages return a plain text message. Unknown `/api/...` routes return JSON. ## Allowed values Allowed `gender` values: - `female` - `male` - `non_binary` - `unknown` Allowed `abuse_type` values: - `physical` - `psychological` - `sexual` - `economic` - `material` - `digital` - `stalking` - `threats` - `honor_related` Allowed `location` values are fixed to the included Faroese town/village list in `internal/models/registration.go`. The browser filter dropdown uses this same list, and create/update rejects anything else. Filters for `GET /api/v1/registrations` and `/demo/registrations`: - `abuse_type` - `gender` - `location` — exact match against the fixed list - `status` - `from` and `to`, matched against `registered_at`, accepting `YYYY-MM-DD` or RFC3339 values - `search` - `limit` - `offset`