# Favoritter — Project Guide ## Quick Reference ```bash 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`