From b77eab7ffda9f48c7562d17c2cb6aa5048e6d20a Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 24 Oct 2026 16:18:48 -0400 Subject: [PATCH] =?UTF-8?q?docs(libraries):=20Beam=20WO5=20batch=205=20?= =?UTF-8?q?=E2=80=94=2011=20endpoints=20backfilled=20+=20pilot=20recovery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beam backfilled 11 library docs (CRUD, folders, visibility, browse, stats, media-items) and re-attached frontmatter to create_collection.md — which had silently lost its pilot version in cc893f75: the negative-test 'git checkout --' restored the OLD file because the pilot was never staged, and a frontmatter-less doc is invisible to the gate. Claude restored the full corrected pilot content (priority higher-wins, time.String() note, 409). Claude registry: 11 entries + one inverted catch — DELETE /:id/folders DOES bind {folder_path} (handler reuses AddLibraryFolderRequest); registry first draft said bodyless. GET /:id/visibility returns joined GetUserVisibleLibrariesRow (type_name etc.), not bare Libraries. Gate-driven merges fixed create/get library response shapes (sqlc row keys: created_by_admin_id, library_type_id, updated_at; dropped invented type/ folders/is_visible). Fixed invented request fields (user_id in set-visibility; library_type_id in update). make test 9/9. --- .../api/collections/create_collection.md | 127 ++++++++---------- .../api/libraries/add_library_folder.md | 12 +- .../developer/api/libraries/browse-folders.md | 10 +- .../developer/api/libraries/create_library.md | 18 ++- .../developer/api/libraries/delete_library.md | 8 ++ .../api/libraries/delete_library_folder.md | 10 +- docs/developer/api/libraries/get_library.md | 25 ++-- .../api/libraries/get_library_media_items.md | 23 ++-- .../api/libraries/get_library_stats.md | 24 ++-- .../api/libraries/get_visible_libraries.md | 10 +- .../api/libraries/set_library_visibility.md | 18 ++- .../developer/api/libraries/update_library.md | 18 ++- internal/docs/schema.go | 41 ++++++ 13 files changed, 223 insertions(+), 121 deletions(-) diff --git a/docs/developer/api/collections/create_collection.md b/docs/developer/api/collections/create_collection.md index ef3803cd..934ec709 100644 --- a/docs/developer/api/collections/create_collection.md +++ b/docs/developer/api/collections/create_collection.md @@ -1,96 +1,81 @@ +--- +method: POST +path: /api/collections +auth: jwt +area: collections +--- + + # Create Collection -Create a new 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. -**Endpoint**: `POST /api/collections` -**Auth**: Required -**Content-Type**: `application/json` +Creating a collection whose name already exists for the user returns **409**. -## Request Body +## Example request -| Field | Type | Required | Description | -| ----------------- | ------ | -------- | -------------------------------- | -| name | string | Yes | Collection name (max 255 chars) | -| description | string | No | Collection description | -| color | string | No | Hex color code (e.g., "#FF5733") | -| icon | string | No | Emoji icon (e.g., "🚀", "📖") | -| auto_assign_rules | array | No | Array of rule objects | -| view_settings | object | No | Per-device display preferences | - -### Auto-Assign Rule Object - -| Field | Type | Required | Description | -| -------- | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | -| field | string | Yes | Field to match on (genre, author, series, language, publisher, copyright_year, tags) | -| operator | string | Yes | Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than) | -| value | string/number | Yes | Value to compare against | -| priority | integer | No | Rule priority (1 = highest, default: 1) | -| enabled | boolean | No | Whether rule is active (default: true) | - -### Example Request - -```json +```json example-request { "name": "To Read", "description": "Books I want to read soon", "color": "#00FF00", "icon": "📖", - "auto_assign_rules": [ - { - "field": "tags", - "operator": "contains", - "value": "to-read", - "priority": 1, - "enabled": true - } - ], - "view_settings": { - "kobo": { - "view_mode": "grid" - }, - "koreader": { - "view_mode": "list" - } - } + "collection_type": "static", + "auto_assign_rules": [], + "view_settings": { "kobo": { "view_mode": "grid" } } } ``` -## Response (201 Created) +## Example response -```json +```json 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": "📖", - "auto_assign_rules": [ - { - "field": "tags", - "operator": "contains", - "value": "to-read", - "priority": 1, - "enabled": true - } - ], - "view_settings": { - "kobo": { - "view_mode": "grid" - }, - "koreader": { - "view_mode": "list" - } - }, - "created_at": "2026-02-01T10:00:00Z" + "collection_type": "static", + "auto_assign_rules": null, + "view_settings": { "kobo": { "view_mode": "grid" } }, + "book_count": 0, + "created_at": "2026-10-24 13:57:01.81234 +0000 UTC" } ``` -## Error Responses +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. -| Code | Description | -| ---- | ----------------------------------- | -| 400 | Invalid request (validation failed) | -| 401 | Authentication required | -| 500 | Internal server error | +## Request fields -## Try It Out +| 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 non-empty `auto_assign_rules` | +| auto_assign_rules | array | no | rule objects, see below | +| view_settings | object | no | per-device display preferences | + +### Auto-assign rule object + +| Field | Type | Required | Notes | +| ----- | ---- | -------- | ----- | +| id | string | no | rule id | +| field | string | yes | one of `genre`, `series`, `author`, `language`, `publisher`, `copyright_year`, `tags` | +| operator | string | yes | one of `equals`, `not_equals`, `contains`, `not_contains`, `starts_with`, `ends_with`, `greater_than`, `less_than` | +| value | string | yes | value to compare against | +| priority | number | no | 1-10; higher values take precedence | +| enabled | boolean | no | absent = enabled (rules written before this field existed keep matching) | + +## 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 | diff --git a/docs/developer/api/libraries/add_library_folder.md b/docs/developer/api/libraries/add_library_folder.md index 90f7969e..ad51043c 100644 --- a/docs/developer/api/libraries/add_library_folder.md +++ b/docs/developer/api/libraries/add_library_folder.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/libraries/:id/folders +auth: jwt+admin +area: libraries +--- + + # Add Library Folder Add a folder to an existing library (Admin only). @@ -20,7 +28,7 @@ Add a folder to an existing library (Admin only). ### Example Request -```json +```json example-request { "folder_path": "/path/to/comics" } @@ -28,7 +36,7 @@ Add a folder to an existing library (Admin only). ## Response (201 Created) -```json +```json example-response { "id": "uuid", "library_id": "uuid", diff --git a/docs/developer/api/libraries/browse-folders.md b/docs/developer/api/libraries/browse-folders.md index 4bb9921a..431a2190 100644 --- a/docs/developer/api/libraries/browse-folders.md +++ b/docs/developer/api/libraries/browse-folders.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/libraries/browse +auth: jwt+admin +area: libraries +--- + + # Browse Library Folders Browse server directories for folder selection in library management. @@ -26,7 +34,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "current_path": "/home/user", "parent_path": "/home", diff --git a/docs/developer/api/libraries/create_library.md b/docs/developer/api/libraries/create_library.md index 373574e7..84800146 100644 --- a/docs/developer/api/libraries/create_library.md +++ b/docs/developer/api/libraries/create_library.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/libraries +auth: jwt+admin +area: libraries +--- + + # Create Library Create a new library (Admin only). @@ -16,7 +24,7 @@ Create a new library (Admin only). ### Example Request -```json +```json example-request { "name": "Comics Collection", "description": "Digital comics", @@ -26,13 +34,15 @@ Create a new library (Admin only). ## Response (201 Created) -```json +```json example-response { "id": "uuid", "name": "Comics Collection", "description": "Digital comics", - "type": "comics", - "created_at": "2026-01-31T10:00:00Z" + "created_at": "2026-01-31T10:00:00Z", + "created_by_admin_id": null, + "library_type_id": null, + "updated_at": null } ``` diff --git a/docs/developer/api/libraries/delete_library.md b/docs/developer/api/libraries/delete_library.md index ed477c85..c15244f9 100644 --- a/docs/developer/api/libraries/delete_library.md +++ b/docs/developer/api/libraries/delete_library.md @@ -1,3 +1,11 @@ +--- +method: DELETE +path: /api/libraries/:id +auth: jwt+admin +area: libraries +--- + + # Delete Library Delete a library and all associated data. diff --git a/docs/developer/api/libraries/delete_library_folder.md b/docs/developer/api/libraries/delete_library_folder.md index 18f6cb82..cf189bf2 100644 --- a/docs/developer/api/libraries/delete_library_folder.md +++ b/docs/developer/api/libraries/delete_library_folder.md @@ -1,3 +1,11 @@ +--- +method: DELETE +path: /api/libraries/:id/folders +auth: jwt+admin +area: libraries +--- + + # Delete Library Folder Delete a folder from a library. @@ -20,7 +28,7 @@ Delete a folder from a library. ### Example Request -```json +```json example-request { "folder_path": "/path/to/library/folder" } diff --git a/docs/developer/api/libraries/get_library.md b/docs/developer/api/libraries/get_library.md index 3f2866dc..79ab1035 100644 --- a/docs/developer/api/libraries/get_library.md +++ b/docs/developer/api/libraries/get_library.md @@ -1,4 +1,13 @@ -# Get Library Details +--- +method: GET +path: /api/libraries/:id +auth: jwt+admin +area: libraries +--- + + +# Get Library Details:<|end|><|start|> assistant to=edit<|channel|>analysis <|constrain|>xml<|message|> +/home/nymusicman/Code/bookhoard/docs/developer/api/libraries/get_library_media_items.md Retrieve details of a specific library. @@ -26,19 +35,15 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "id": "uuid", "name": "My Ebooks", "description": "Ebook collection", - "type": "ebooks", - "folders": [ - { - "id": "uuid", - "folder_path": "/path/to/ebooks" - } - ], - "is_visible": true + "created_at": null, + "created_by_admin_id": null, + "library_type_id": null, + "updated_at": null } ``` diff --git a/docs/developer/api/libraries/get_library_media_items.md b/docs/developer/api/libraries/get_library_media_items.md index a4ef04ee..625f0302 100644 --- a/docs/developer/api/libraries/get_library_media_items.md +++ b/docs/developer/api/libraries/get_library_media_items.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/libraries/:id/media-items +auth: jwt+admin +area: libraries +--- + + # Get Library Media Items Get all media items in a specific library. @@ -35,20 +43,9 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { - "items": [ - { - "id": "uuid", - "title": "Sample Book", - "authors": ["Author Name"], - "tags": ["fiction", "science-fiction"], - "created_at": "2026-02-08T10:00:00Z" - } - ], - "total": 1523, - "limit": 20, - "offset": 0 + "data": null } ``` diff --git a/docs/developer/api/libraries/get_library_stats.md b/docs/developer/api/libraries/get_library_stats.md index 1effb785..39518181 100644 --- a/docs/developer/api/libraries/get_library_stats.md +++ b/docs/developer/api/libraries/get_library_stats.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/libraries/:id/stats +auth: jwt+admin +area: libraries +--- + + # Get Library Statistics Get statistics for a specific library. @@ -26,18 +34,14 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "library_id": "uuid", - "total_media_items": 1523, - "total_size_bytes": 5368709120, - "last_scan": "2026-02-08T10:00:00Z", - "formats": { - "epub": 1234, - "kepub": 189, - "mobi": 50, - "pdf": 50 - } + "books": null, + "minutes": null, + "name": null, + "pages": null, + "sessions": null } ``` diff --git a/docs/developer/api/libraries/get_visible_libraries.md b/docs/developer/api/libraries/get_visible_libraries.md index 6c30ab50..dd91a76f 100644 --- a/docs/developer/api/libraries/get_visible_libraries.md +++ b/docs/developer/api/libraries/get_visible_libraries.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/libraries/visibility +auth: jwt +area: libraries +--- + + # Get Visible Libraries Retrieve all libraries visible to the current user. @@ -22,7 +30,7 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... A top-level JSON **array** of library rows: -```json +```json example-response [ { "id": "uuid", diff --git a/docs/developer/api/libraries/set_library_visibility.md b/docs/developer/api/libraries/set_library_visibility.md index 3897f7c2..da1bb895 100644 --- a/docs/developer/api/libraries/set_library_visibility.md +++ b/docs/developer/api/libraries/set_library_visibility.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/libraries/visibility +auth: jwt +area: libraries +--- + + # Set Library Visibility Set library visibility for a specific user (Admin only). @@ -16,9 +24,8 @@ Set library visibility for a specific user (Admin only). ### Example Request -```json +```json example-request { - "user_id": "user-uuid", "library_id": "library-uuid", "is_visible": true } @@ -26,11 +33,14 @@ Set library visibility for a specific user (Admin only). ## Response (200 OK) -```json +```json example-response { "user_id": "user-uuid", "library_id": "library-uuid", - "is_visible": true + "is_visible": true, + "created_at": null, + "id": null, + "updated_at": null } ``` diff --git a/docs/developer/api/libraries/update_library.md b/docs/developer/api/libraries/update_library.md index 2dd8da5a..4060dfc4 100644 --- a/docs/developer/api/libraries/update_library.md +++ b/docs/developer/api/libraries/update_library.md @@ -1,3 +1,11 @@ +--- +method: PUT +path: /api/libraries/:id +auth: jwt+admin +area: libraries +--- + + # Update Library Update a library's information. @@ -21,22 +29,24 @@ Update a library's information. ### Example Request -```json +```json example-request { "name": "My Updated Library", - "library_type_id": 1 + "description": "Updated description" } ``` ## Response (200 OK) -```json +```json example-response { "id": "uuid", "name": "My Updated Library", "library_type_id": 1, "created_at": "2026-02-08T10:00:00Z", - "updated_at": "2026-02-08T11:00:00Z" + "updated_at": "2026-02-08T11:00:00Z", + "created_by_admin_id": null, + "description": null } ``` diff --git a/internal/docs/schema.go b/internal/docs/schema.go index 3e25e7cb..5d8c50b9 100644 --- a/internal/docs/schema.go +++ b/internal/docs/schema.go @@ -511,4 +511,45 @@ var Schemas = map[string]Schema{ Request: sync.SaveProgressRequest{}, Response: database.ReadingProgress{}, }, + + // --- Beam batch 5: libraries --- + "POST /api/libraries": { + Request: handlers.CreateLibraryRequest{}, + Response: database.Libraries{}, + }, + "GET /api/libraries/:id": { + Response: database.Libraries{}, + }, + "PUT /api/libraries/:id": { + Request: handlers.UpdateLibraryRequest{}, + Response: database.Libraries{}, + }, + "DELETE /api/libraries/:id": {}, + "POST /api/libraries/:id/folders": { + Request: handlers.AddLibraryFolderRequest{}, + Response: database.LibraryFolders{}, + }, + "DELETE /api/libraries/:id/folders": { + // Handler REUSES AddLibraryFolderRequest ({folder_path}) on delete. + Request: handlers.AddLibraryFolderRequest{}, + }, + "GET /api/libraries/visibility": { + Response: []database.GetUserVisibleLibrariesRow{}, + }, + "POST /api/libraries/visibility": { + Request: handlers.SetLibraryVisibilityRequest{}, + Response: database.LibraryVisibility{}, + }, + "GET /api/libraries/browse": { + Response: json.RawMessage(`{ + "current_path": "", "parent_path": "", "directories": [{}] + }`), + }, + "GET /api/libraries/:id/stats": { + Response: services.LibraryStats{}, + }, + "GET /api/libraries/:id/media-items": { + // Delegates to ListMediaItems with library_id injected. + Response: json.RawMessage(`{"data": [{}]}`), + }, }