Files
snakkimo-API/README.md
2026-05-20 13:47:31 +02:00

7.4 KiB

snakkimo-API

REST API server for the snakkimo project. Connects to a PostgreSQL database and stores picture assets on Hetzner Object Storage.

  • Base URL: https://hyggecraftery.com/api/snakkimo
  • Auth: All /api/* routes require Authorization: Bearer <token>
  • Health: GET /health — public, no token needed

Authentication

Pass the token in every request:

Authorization: Bearer <token>

Tokens are configured via the API_TOKENS environment variable in Coolify (comma-separated for multiple tokens).


Environment Variables

Variable Description
DB_HOST PostgreSQL internal hostname (Coolify service UUID)
DB_PORT PostgreSQL port (default: 5432)
DB_NAME Database name
DB_USER Database user
DB_PASSWORD Database password
DB_SSL true or false
PORT API server port (default: 3000)
API_TOKENS Comma-separated Bearer tokens
S3_ACCESS_KEY Hetzner Object Storage access key
S3_SECRET_KEY Hetzner Object Storage secret key

Database

Host: Coolify internal network (same project as the API) Database: snakkimo Schema: public

Table: pictures

Stores AI-generated pictures with metadata.

Column Type Default Description
id UUID gen_random_uuid() Primary key, auto-generated
status VARCHAR(20) uploaded uploaded · published · blocked
blocked_reason VARCHAR(20) null regenerate · not_to_use
generation_prompt TEXT null Prompt used to generate the image
generation_timestamp TIMESTAMPTZ null When the generation was started
generation_duration_s NUMERIC(10,3) null How long generation took in seconds
published_timestamp TIMESTAMPTZ null Auto-set when status → published
blocked_timestamp TIMESTAMPTZ null Auto-set when status → blocked
blurhash TEXT null Blurhash string for image placeholder
picture_link TEXT null Public Hetzner URL — set automatically on upload
design TEXT null Design category (values TBD)
created_at TIMESTAMPTZ NOW() Auto-set on insert
updated_at TIMESTAMPTZ NOW() Auto-updated on every change (trigger)

Status flow:

uploaded → published
uploaded → blocked
published → blocked

Planned relations:

  • design → will become a FK to a designs table
  • words → M2M via junction table once words table exists

Storage

Provider: Hetzner Object Storage (S3-compatible) Endpoint: https://fsn1.your-objectstorage.com Bucket: snakkimo Public base URL: https://snakkimo.fsn1.your-objectstorage.com

Files are stored under pictures/<picture_id>/<uuid>.<ext>. Deleting a picture via the API also deletes the file from the bucket.


API Endpoints

Pictures

GET /api/pictures

List all pictures, newest first.

Query params:

Param Default Description
status Filter by status (uploaded, published, blocked)
limit 50 Max rows (max 500)
offset 0 Pagination offset
curl "$BASE/api/pictures" \
  -H "Authorization: Bearer $TOKEN"

# With filter
curl "$BASE/api/pictures?status=published&limit=10" \
  -H "Authorization: Bearer $TOKEN"

GET /api/pictures/:id

Get a single picture by UUID.

curl "$BASE/api/pictures/88602a2b-e608-429a-9e69-78a772128cb8" \
  -H "Authorization: Bearer $TOKEN"

POST /api/pictures

Create a new picture entry. Returns the new row with status uploaded.

Body (JSON):

Field Type Description
generation_prompt string Prompt text
generation_timestamp ISO 8601 When generation started
generation_duration_s number Duration in seconds
blurhash string Blurhash placeholder
design string Design category
curl -X POST "$BASE/api/pictures" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "generation_prompt": "A sunset over mountains",
    "generation_duration_s": 4.2
  }'

POST /api/pictures/:id/upload

Upload an image file to Hetzner and store the public URL in picture_link. Replaces any previously uploaded file for this entry.

Body: multipart/form-data, field name file, max 20 MB.

curl -X POST "$BASE/api/pictures/<ID>/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@/path/to/image.jpg"

Response: Updated picture row including picture_link.


PATCH /api/pictures/:id

Update one or more fields of a picture.

Writable fields: status, blocked_reason, generation_prompt, generation_timestamp, generation_duration_s, published_timestamp, blocked_timestamp, blurhash, picture_link, design

Auto-behavior:

  • Setting status: "published" auto-sets published_timestamp to now (if not provided)
  • Setting status: "blocked" auto-sets blocked_timestamp to now (if not provided)
# Publish a picture
curl -X PATCH "$BASE/api/pictures/<ID>" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "published"}'

# Block with reason
curl -X PATCH "$BASE/api/pictures/<ID>" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "blocked", "blocked_reason": "not_to_use"}'

DELETE /api/pictures/:id

Delete a picture entry from the database and remove the file from Hetzner Object Storage.

Returns 204 No Content on success.

curl -X DELETE "$BASE/api/pictures/<ID>" \
  -H "Authorization: Bearer $TOKEN" \
  -w "\nHTTP %{http_code}"

Utilities

GET /api/tables

List all tables in the public schema.

GET /api/tables/:table

Read rows from any table (?limit=100&offset=0).

POST /api/query

Execute raw SQL. Destructive statements (DROP, TRUNCATE, DELETE FROM) require the header X-Confirm-Destructive: yes.

curl -X POST "$BASE/api/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT count(*) FROM pictures"}'

Quick Start (Terminal)

# Set once
export TOKEN="dev_ccfd6fd149806a900311e253b0c3b5d1e107a68ab5619a5fde61f107ce9098ce"
export BASE="https://hyggecraftery.com/api/snakkimo"

# 1. Create entry
ID=$(curl -s -X POST "$BASE/api/pictures" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"generation_prompt": "test", "generation_duration_s": 1.5}' \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")

echo "Created: $ID"

# 2. Upload image
curl -X POST "$BASE/api/pictures/$ID/upload" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@/path/to/image.jpg"

# 3. Publish
curl -X PATCH "$BASE/api/pictures/$ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "published"}'

# 4. Delete (also removes from Hetzner)
curl -X DELETE "$BASE/api/pictures/$ID" \
  -H "Authorization: Bearer $TOKEN"

Deployment

  • Platform: Coolify (snakkimo project, production environment)
  • Gitea repo: https://git.hyggecraftery.com/admin/snakkimo-API
  • Docker: Node 20 Alpine, port 3000
  • Health check: Docker-native HEALTHCHECK on GET /health
  • Migrations: Run automatically on every server start (idempotent)