PROJECT_GUIDELINES.md had drifted out of reality (podman-only rule while the stack runs docker; verifier scripts wired to Makefile targets nobody runs). Trashed it, its two verifier scripts, and the Makefile targets; the rules that still matched reality (docker runtime, schema in place until release, server binary only via Docker, services layer, integration-test pool rules) are absorbed into the new AGENTS.md. The library-visibility family-lens rule now has a real home: - AGENTS.md: the standing rule agents auto-discover (new media endpoints MUST thread the visible-library set; 403, never silently pass; history of the 2026-10-13 leak). - docs/developer/library-visibility.md: the full explanation (helpers, SQL pattern, per-item content gates, series per-library), linked from the developer docs index. Stale doc links updated (docs index, contributing, development).
74 lines
3.7 KiB
Markdown
74 lines
3.7 KiB
Markdown
# AGENTS.md — Bookhoard server
|
|
|
|
Standing rules for any agent or developer working in this repo.
|
|
Project-wide context lives in the sibling repos' AGENTS.md files
|
|
(../bookhoard-app/AGENTS.md is the Android client's).
|
|
|
|
## Library visibility (family-lens rule) — ALWAYS apply
|
|
|
|
Per-user library visibility (`library_visibility` table, default-ALLOW:
|
|
every user sees every library unless an admin hides one) must be
|
|
enforced on **every endpoint that returns media metadata or content**.
|
|
It is NOT automatic — enforcement is per-handler.
|
|
|
|
- **New endpoints that list/filter media or series** MUST filter by the
|
|
requesting user's visible-library set, and MUST reject an explicit
|
|
`library_id` outside that set with 403 (never silently pass).
|
|
- Helpers (do not hand-roll):
|
|
- `handlers`: `(*MediaHandler).visibleLibrarySet` /
|
|
`(*SeriesHandler).visibleLibrarySet` — membership map + id slice.
|
|
- `router`: `visibleLibraryIDsFor` — same for SSR routes.
|
|
- `resolveLibrary` (helpers.go) only honors requested library ids
|
|
inside the visible set.
|
|
- SQL pattern: `AND (sqlc.narg('visible_library_ids')::uuid[] IS NULL OR
|
|
library_id = ANY(sqlc.narg('visible_library_ids')::uuid[]))` — see
|
|
`ListMediaItems`, `GetDistinctSeries`, `SearchMediaItems`.
|
|
- Content endpoints (file bytes, page images) additionally gate per item
|
|
like `ReaderHandler.ensureItemVisible` / the download endpoint.
|
|
- Tests must pin visibility: hidden library → 403 on the endpoint, and
|
|
absent from unscoped aggregates.
|
|
|
|
History: 2026-10-13 (`431fec0`) the series/media listing surfaces
|
|
leaked hidden libraries to any authenticated account — series endpoints
|
|
were name-only across ALL libraries. Fixed; do not regress.
|
|
|
|
## Tests
|
|
|
|
- Use `setupTestServer()` from `cmd/server/tests/test_helpers_test.go`
|
|
(see the Conventions section below for the pool/cleanup rules).
|
|
- Integration tests run against the SHARED dev database: never write
|
|
destructive SQL without preserving the dev rows (libraries Manga/
|
|
Comics/Ebooks, testuser's annotations and progress are real data).
|
|
- Test-created users must use `@tests.bookhoard.internal` or
|
|
`@example.com` emails and "test"-named collections/libraries so the
|
|
harness cleanup can find them.
|
|
- **NEVER rename or edit the shared admin (`testuser`) in tests** — a
|
|
renamed admin escapes the cleanup preserve-list and cascades real
|
|
data (this wiped the dev annotations once; see user_test.go
|
|
TestUpdateProfileSelfEdit for the dedicated-user pattern).
|
|
|
|
## Timezones
|
|
|
|
- The server owns display conversion: clients send instants (RFC3339),
|
|
the server renders in the requesting user's timezone.
|
|
- Use `utils.FormatTimeInTimezone` / `utils.UserLocation` — never
|
|
`time.Now().Format(...)` bare (that is server-UTC).
|
|
- Analytics day-buckets must bucket in the user's timezone
|
|
(`CreatedAt.In(loc)`), and date-range end bounds must be end-of-day.
|
|
|
|
## Conventions (absorbed from the retired PROJECT_GUIDELINES.md —
|
|
## only the rules that still matched reality)
|
|
|
|
- **Runtime is Docker** (Makefile auto-detects docker, falls back to
|
|
podman). The old "podman only" rule is dead.
|
|
- **The server binary is only built inside the Docker build**
|
|
(`make rebuild-app`); host-side Go builds are for tests/vet only.
|
|
- **Schema changes go in place** — edit `database/schema/schema.sql`,
|
|
never create migration files, until a release splits them.
|
|
- **Business logic lives in services**, not handlers; handlers parse
|
|
requests and call services (all DB access goes through services).
|
|
- **Integration tests**: one `setupTestServer()` per test function;
|
|
test pools use `max_conns=1`; never create a `pgxpool` per subtest
|
|
(connection exhaustion); use `t.Cleanup()`-based helpers; table-driven
|
|
subtests over duplicate functions.
|