docs(libraries): Beam WO5 batch 5 — 11 endpoints backfilled + pilot recovery

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.
This commit is contained in:
John O'Keefe committed 2026-10-24 16:18:48 -04:00
1 parent 6f13dc2b63
commit b77eab7ffd
13 files changed
+223 -121

No files matched your search

@@ -1,96 +1,81 @@
---
method: POST
path: /api/collections
auth: jwt
area: collections
---
<!-- source: internal/handlers/collections.go:94 CollectionHandler.CreateCollection -->
# 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 |
@@ -1,3 +1,11 @@
---
method: POST
path: /api/libraries/:id/folders
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:167 LibraryHandler.AddLibraryFolder -->
# 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",
@@ -1,3 +1,11 @@
---
method: GET
path: /api/libraries/browse
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:254 LibraryHandler.BrowseDirectories -->
# 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",
+14 -4
View File
@@ -1,3 +1,11 @@
---
method: POST
path: /api/libraries
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:60 LibraryHandler.CreateLibrary -->
# 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
}
```
@@ -1,3 +1,11 @@
---
method: DELETE
path: /api/libraries/:id
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:152 LibraryHandler.DeleteLibrary -->
# Delete Library
Delete a library and all associated data.
@@ -1,3 +1,11 @@
---
method: DELETE
path: /api/libraries/:id/folders
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:231 LibraryHandler.DeleteLibraryFolder -->
# 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"
}
+15 -10
View File
@@ -1,4 +1,13 @@
# Get Library Details
---
method: GET
path: /api/libraries/:id
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:87 LibraryHandler.GetLibrary -->
# Get Library Details:<|end|><|start|> assistant to=edit<|channel|>analysis <|constrain|>xml<|message|><rfl:parameter name="path">
/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
}
```
@@ -1,3 +1,11 @@
---
method: GET
path: /api/libraries/:id/media-items
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/media.go:757 MediaHandler.ListMediaItems -->
# 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
}
```
@@ -1,3 +1,11 @@
---
method: GET
path: /api/libraries/:id/stats
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:303 LibraryHandler.GetLibraryStats -->
# 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
}
```
@@ -1,3 +1,11 @@
---
method: GET
path: /api/libraries/visibility
auth: jwt
area: libraries
---
<!-- source: internal/handlers/library.go:112 LibraryHandler.GetUserVisibleLibraries -->
# 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",
@@ -1,3 +1,11 @@
---
method: POST
path: /api/libraries/visibility
auth: jwt
area: libraries
---
<!-- source: internal/handlers/library.go:273 LibraryHandler.SetLibraryVisibility -->
# 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
}
```
+14 -4
View File
@@ -1,3 +1,11 @@
---
method: PUT
path: /api/libraries/:id
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:124 LibraryHandler.UpdateLibrary -->
# 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
}
```
+41
View File
@@ -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": [{}]}`),
},
}