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:
{
"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
go run ./cmd/api
Or with Task:
task run
Then open:
http://localhost:8080/
Optional environment variables:
PORT=:9999 JWT_SECRET=change-me RESET_INTERVAL=10m go run ./cmd/api
Build
The Taskfile follows the FLÓ-style build version format:
version [<git-sha>-<branch>/<random-word>-<random-word>]. git is [clean|dirty].
build/words.txt is copied from the FLÓ website project.
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:
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:
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:
$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:
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 /healthPOST /loginGET /demo/registrationsis the public demo snapshot used by the page JSON viewer.
Protected read:
GET /api/v1/categoriesGET /api/v1/locationsGET /api/v1/registrationsGET /api/v1/registrations/{uuid}
Admin create/read/update/delete:
POST /api/v1/registrationsPUT /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:
femalemalenon_binaryunknown
Allowed abuse_type values:
physicalpsychologicalsexualeconomicmaterialdigitalstalkingthreatshonor_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_typegenderlocation— exact match against the fixed liststatusfromandto, matched againstregistered_at, acceptingYYYY-MM-DDor RFC3339 valuessearchlimitoffset