diff --git a/AGENTS.md b/AGENTS.md index 871d9bd..10fae0c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,16 +9,18 @@ collections. A small server-rendered web UI (templ + Tailwind + htmx) at ## Build, test, lint ```bash -make build # go build -o bin/davserver ./cmd/server +make build # go build -o bin/davserver ./cmd/server + ./bin/nidusctl make run # build + ./bin/davserver -config config.yaml make test # go test ./... -v -race -make lint # golangci-lint run ./... +make lint # golangci-lint run ./... (no config file; uses defaults) make tidy # go mod tidy -go test ./internal/store/ -run TestStoreRoundTrip -v # single test +make web-deps # install Tailwind CLI + TypeScript (needed once, or after web/package.json changes) make templ-generate # regenerate *_templ.go after editing internal/web/templates/*.templ -make web-css # templ-generate + rebuild web/static/app.css (needs `make web-deps` once, Node.js/npm) +make web-css # templ-generate + rebuild web/static/app.css make web-ts # compile web/ts/*.ts to web/static/*.js make web-assets # web-css + web-ts (everything under web/static/) +make nidusctl # build + run nidusctl with ARGS (e.g. `make nidusctl ARGS="user list"`) +go run ./tools/migrate -config config.yaml # restructure data directory ``` Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, @@ -51,14 +53,15 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, if it's nil. `auth.NewContext(ctx, p)` is the test-only inverse, used to build authenticated contexts without a real Basic Auth handshake. - `internal/store` — the single source of truth for all persisted data. - A thin filesystem KV abstraction: `///`. + A thin filesystem KV abstraction: `//[calendars|addressbooks|files]/`. All collection/object names pass through `sanitize()` (via `filepath.Base` + strip `..`) to prevent path traversal — preserve this when adding new store methods. Writes use temp-file + rename for atomicity (`PutObject`). Locking is sharded per-user (`lockFor(user)`, a `map[string]*sync.RWMutex` guarded by its own mutex) rather than one global lock, so different users' requests don't serialize against each - other. + other. `Migrate()` restructures data from the old format (`cal-`, + `card-`) to the new unified layout and is idempotent. - `internal/caldav` and `internal/carddav` — implement the `caldav.Backend`/`carddav.Backend` interfaces from `github.com/emersion/go-webdav` on top of `store.Store`. Calendars are stored as collections prefixed @@ -100,6 +103,12 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, shared data is never copied — it's read/written directly under the owner's own `store.Store` namespace, just addressed via the synthetic name from the grantee's requests. + **ICSSubscriptions**: read-only calendars backed by remote ICS/webcal + URLs (see `db.CreateICSSubscription`, `internal/db/ics.go`) are exposed + alongside real calendars under `/cal/home//` (shared namespace; + see `internal/caldav/ics.go`). `Birthdays` is a computed calendar + synthesized from contacts' BDAY fields (see `internal/caldav/birthdays.go`, + `internal/birthdays/birthdays.go`). - `internal/db` — a small `database/sql` wrapper around `modernc.org/sqlite` (pure Go, no CGO) at `/nidus.db`. Only one open connection is used (`SetMaxOpenConns(1)`) since SQLite allows a @@ -118,6 +127,9 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, cleans those up in a transaction. `modernc.org/sqlite` has no typed unique-constraint error, so `isUniqueConstraintErr()` string-matches the driver's error message. + **Additional tables**: `birthday_calendars` (per-user display color for + the computed Birthdays calendar), `ics_subscriptions` (remote ICS/webcal + calendar subscriptions), `web_sessions` (server-side session tokens). - `internal/webdav` — plain-file WebDAV via `golang.org/x/net/webdav`, mounted at the single fixed URL `/files/` for all users (no username in the path either). `NewHandler` caches one `*xwebdav.Handler` per @@ -128,6 +140,10 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, - `tools/hashpwd` — standalone CLI (`go run ./tools/hashpwd `) to generate bcrypt hashes for ad-hoc testing (no longer needed for normal user setup — see `nidusctl user create` below). +- `tools/migrate` — restructures data directory from old format (`cal-`, + `card-`) to unified layout (`calendars/`, `addressbooks/`, + `files/`). Run automatically on server startup; invoke manually with + `go run ./tools/migrate -config config.yaml`. - `tools/nidusctl` — standalone admin CLI (`go run ./tools/nidusctl -config config.yaml ...`) — the only way to create/delete users, calendars, and address books, plus manage sharing @@ -190,6 +206,14 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, no runtime dependency on Node.js or the `web/` directory being present — Node/npm are only needed when actually changing templates/styles/TS (`make web-assets` rebuilds everything under `web/static/`). + **Web UI features**: dashboard (calendars/address books/ICS subscriptions + management), login (cookie-based sessions), share management (create/update/ + revoke share grants via htmx), files browser (inline preview, upload, download), + contacts manager (vCard import/export, edit fields), calendar view (month/week, + per-calendar colors), account settings (name/email/password). The + synthetic `Birthdays` calendar (computed from contacts' BDAY fields) and + ICS/webcal subscriptions are managed via the web UI just like real + calendars. ## Conventions @@ -211,3 +235,5 @@ Test files: `internal/store/store_test.go`, `internal/webdav/handler_test.go`, - Logging uses `log/slog` structured fields (e.g. `logger.Warn("...", "user", u, "error", err)`), passed down explicitly to every constructor (`NewBackend`, `NewHandler`, `NewMiddleware`) rather than a global logger. - Config module path is `github.com/yourusername/caldav-server` (go.mod name predates the `nidus` repo rename) — import paths still use this, not `nidus`. +- **Docker/CI**: Docker image pushed to `git.arnef.de/arnef/nidus` on tags (`v*`) or releases (`.github/workflows/docker-release.yml`), multi-arch (`linux/amd64`, `linux/arm64`). Use `docker-compose` for local dev with healthcheck on `/healthz`. +- **Database schema** lives in `internal/db/db.go` `migrate()` — all tables are created with `CREATE TABLE IF NOT EXISTS` and schema changes are applied via `ALTER TABLE` in `migrateAddColumns()`. No external migration framework.