favoritter/CLAUDE.md
Ole-Morten Duesund 1fc42bf1b2 feat: add packaging, deployment, error pages, and project docs
Phase 7 — Polish:
- Error page template with styled 404/403/500 pages
- Error rendering helper on Renderer

Phase 8 — Packaging & Deployment:
- Containerfile: multi-stage build, non-root user, health check,
  OCI labels with build date and git revision
- Makefile: build, test, cross-compile, deb, rpm, container,
  tarballs, checksums targets
- nfpm.yaml: .deb and .rpm package config
- systemd service: hardened with NoNewPrivileges, ProtectSystem,
  ProtectHome, PrivateTmp, RestrictSUIDSGID
- Default environment file with commented examples
- postinstall/preremove scripts (shellcheck validated)
- compose.yaml: example Podman/Docker Compose
- Caddyfile.example: subdomain, subpath, and remote proxy configs
- CHANGELOG.md for release notes
- CLAUDE.md with architecture, conventions, and quick reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 16:34:32 +02:00

3.2 KiB

Favoritter — Project Guide

Quick Reference

go build ./cmd/favoritter           # Build
go test ./...                        # Test
FAVORITTER_DEV_MODE=true \
  FAVORITTER_ADMIN_USERNAME=admin \
  FAVORITTER_ADMIN_PASSWORD=dev \
  go run ./cmd/favoritter            # Run (dev mode)
make build                           # Build with version info
make test                            # Run tests
make container                       # Build container image

Architecture

Single Go binary serving HTML (server-rendered templates + HTMX) and a JSON API. SQLite for storage, filesystem for uploaded images. All templates and static assets are embedded via go:embed.

Directory Layout

  • cmd/favoritter/ — Entry point, wiring, graceful shutdown
  • internal/config/ — Environment variable configuration
  • internal/database/ — SQLite connection, PRAGMAs, migration runner
  • internal/model/ — Domain types (no logic, no DB)
  • internal/store/ — Data access layer (one file per entity, plain SQL)
  • internal/handler/ — HTTP handlers for web UI
  • internal/handler/api/ — JSON REST API handlers
  • internal/middleware/ — HTTP middleware (auth, CSRF, rate limit, etc.)
  • internal/render/ — Template rendering with layout support
  • internal/image/ — Image upload processing (validate, resize, strip EXIF)
  • web/templates/ — HTML templates (layouts, pages, partials)
  • web/static/ — CSS, JS, vendored Pico CSS + HTMX
  • dist/ — Packaging artifacts (systemd, env file, install scripts)

Key Design Decisions

  • Go 1.22+ stdlib router — no framework, http.ServeMux with method routing
  • 3 external dependencies — modernc.org/sqlite (pure Go), golang.org/x/crypto (Argon2id), gorilla/feeds
  • SetMaxOpenConns(1) — SQLite works best with a single writer; PRAGMAs are set once on the single connection
  • Templates embedded in binary//go:embed for single-binary deployment; dev mode reads from disk for live reload
  • Middleware chain order matters — Recovery → SecurityHeaders → BasePath → RealIP → Logger → SessionLoader → CSRF → MustResetGuard

Database

SQLite with WAL mode. Migrations are embedded SQL files in internal/database/migrations/, applied sequentially on startup. Forward-only — no down migrations.

Conventions

  • Norwegian Bokmål for all user-facing text (templates, flash messages, error text)
  • SPDX license headers on all source files
  • Argon2id for password hashing (never bcrypt)
  • UUID filenames for all uploads — never use user-provided filenames
  • Errors: log with slog.Error at the handler level, return generic messages to users — never leak internal errors
  • Tests: use real in-memory SQLite (:memory:), set fast Argon2 params (Memory=1024, Time=1) in tests
  • Session cookie name: use middleware.SessionCookieName constant, never hardcode "session"
  • CSRF: auto-included in HTMX requests via htmx:configRequest JS hook

Hosting

  • Hosted on Forgejo at kode.naiv.no — use fj CLI, not gh
  • Supports deployment as: standalone binary, .deb package, .rpm package, or container
  • Reverse proxy (Caddy) may be on a different machine — always use EXTERNAL_URL and TRUSTED_PROXIES