Files
bookhoard/docs/developer/api/collections/create_collection.md
T
John O'Keefe 7c7e282e79 feat(dashboard): custom-section builder saves again — the flagged tree migration
Archaeology, prompted by the user asking when it broke: the builder's
save NEVER worked. The page was born (2026-02-19, a92bf99a) posting
auto_assign_rules as a JSON-encoded STRING into an endpoint that has
bound []Rule since its creation (2026-01-31, 6495cc2c), plus fields
(manual_book_ids, match_type, library_id, show_on_dashboard) the
endpoint never accepted. The preview button hit a real endpoint, so
the page looked alive while Save always failed at request binding.
Verified live before this change: the page's exact payload 400'd
"invalid request" against the unmodified server.

The fix adapts the builder's payload at write time — the legacy rules
vocabulary converts to a filter tree (RulesToFilterTree restored from
the retirement commit as the single write path, unit test restored
with it):

- match_type seeds the converted tree's root mode (any default).
- manual_book_ids attach to the created collection after create
  (unparsable ids skipped — the AddBooks leniency).
- show_on_dashboard persists (new create field).
- Converted trees validate under ParseFilterTree: an unhonorable rule
  is a 400 at write naming the problem, never a stored 500 at render.

Dashboard sections: smart user sections now evaluate their tree LIVE —
GetUserCollectionItems merges stored membership with the composed
engine's matches (deduped, library-scoped, capped at the section
limit). This exposed a REAL production wiring bug: every production
NewDashboardService call omitted the search service, so tree
evaluation through the dashboard could never have worked (the test
harness wired it correctly — the gap was production-only and latent,
because nothing in the dashboard evaluated trees before this
migration). The dashboard handler and both inline sites now receive
the search service, and evaluation failures log instead of silently
emptying a section.

Supersedes the same-day loud-400 stance for the builder's payload
(contract_pins migration entry); custom-section-builder.md documents
the save contract; the create_collection API doc gains the adapted
fields. New TestCustomSectionBuilderSave/BadRule pin the payload →
tree → dashboard-render path end to end. Full container suite green
(155s).
2026-10-31 19:40:43 -04:00

2.5 KiB

method, path, auth, area
method path auth area
POST /api/collections jwt collections

Create Collection

Creates a new collection for the signed-in user. If any rules are present (or collection_type is "smart"), the collection is a smart collection whose membership is evaluated live; otherwise it is static.

Creating a collection whose name already exists for the user returns 409.

Example request

{
  "name": "To Read",
  "description": "Books I want to read soon",
  "color": "#00FF00",
  "icon": "📖",
  "collection_type": "static",
  "view_settings": { "kobo": { "view_mode": "grid" } }
}

Example response

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "user_id": "8f14e45f-ceea-467f-a429-0b21bfc0b1a2",
  "name": "To Read",
  "description": "Books I want to read soon",
  "color": "#00FF00",
  "icon": "📖",
  "collection_type": "static",
  "view_settings": { "kobo": { "view_mode": "grid" } },
  "book_count": 0,
  "created_at": "2026-10-24 13:57:01.81234 +0000 UTC"
}

Note: created_at is serialized with Go's time.String() format, not RFC3339. Treat it as display text; do not parse it as an ISO timestamp.

Request fields

Field Type Required Notes
name string yes unique per user
description string no
color string no hex color, e.g. "#FF5733"
icon string no emoji
collection_type string no "static" (default) or "smart"; also implied by a filter_tree
view_settings object no per-device display preferences
filter_tree object no the unified filter tree (qf DSL); presence makes the collection smart
auto_assign_rules string | array no custom-section-builder payload — converted to filter_tree at write (see custom-section-builder doc)
match_type string no builder payload: any (default) or all — seeds the converted tree's root mode
manual_book_ids string[] no builder payload: attached to the collection after create
show_on_dashboard boolean no pins the collection as a dashboard section

Errors

Status Body When
400 {"error": "..."} invalid JSON or validation failure
409 {"error": "a collection with this name already exists"} name taken
500 {"error": "..."} unexpected server error