docs(api): sync mirrors with the retired rules CRUD and the chain-order endpoint

Three mirrors had drifted:

- The rules-endpoint retirement (4f71eda6) deleted the routes but not
  their mirrors: removed docs/developer/api/collections/
  {add_auto_assign_rule,remove_auto_assign_rule,test_rule,update_rule}
  .md + their bruno twins, and pruned /api/collections/test-rules,
  /{id}/rules, /{id}/rules/{ruleId} from the generated openapi spec
  (hand-edited — regenerating with -update would wipe hand-authored
  bruno suites that predate the docs mirrors).
- New endpoint PUT /api/collections/:id/view-settings gets a doc page
  + schema-registry entry + bruno mirror (repo convention: the route
  and its docs land together).
- 54 pre-existing TestDocsMatchRoutes failures: the media-item
  endpoints emit the 16 episode/download fields (source_feed_id,
  feed_position, download_state, enclosure_url, hosts/guests, …) and
  libraries emit purpose/allow_member_feeds, but the doc examples
  were never extended — the package only runs on the host, so the
  container suite never caught it. Examples now carry every key the
  handlers emit; internal/docs is fully green.
This commit is contained in:
John O'Keefe committed 2026-10-31 15:40:21 -04:00
1 parent 4410a994ef
commit 8aa66a30fc
21 files changed
+152 -476

No files matched your search

@@ -1,89 +0,0 @@
---
method: POST
path: /api/collections/:id/rules
auth: jwt
area: collections
---
<!-- source: internal/handlers/collections.go:877 CollectionHandler.AddRule -->
# Add Auto-Assign Rule
Add an automatic book assignment rule to a collection.
**Endpoint**: `POST /api/collections/{id}/rules`
**Auth**: Required
**Content-Type**: `application/json`
## Path Parameters
| Parameter | Type | Required | Description |
| --------- | ------------- | -------- | --------------- |
| id | string (UUID) | Yes | Collection UUID |
## Request Body
| 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) |
### Supported Fields
| Field | Type | Description |
| -------------- | ------ | ------------------------------------- |
| genre | string | Book genre |
| author | string | Book author |
| series | string | Book series name |
| language | string | Book language |
| publisher | string | Publisher name |
| copyright_year | number | Publication year (numeric comparison) |
| tags | string | Book tags |
### Supported Operators
| Operator | Type | Description |
| ------------ | ------ | ------------------------------------- |
| equals | all | Exact match |
| not_equals | all | Not equal |
| contains | string | Contains substring (case-insensitive) |
| not_contains | string | Does not contain |
| starts_with | string | Starts with (case-insensitive) |
| ends_with | string | Ends with (case-insensitive) |
| greater_than | number | Greater than |
| less_than | number | Less than |
### Example Request
```json example-request
{
"field": "genre",
"operator": "equals",
"value": "Science Fiction",
"priority": 1,
"enabled": true
}
```
## Response (201 Created)
```json example-response
{
"collection_id": null,
"is_smart": null,
"rule": null
}
```
## Error Responses
| Code | Description |
| ---- | ----------------------------------- |
| 400 | Invalid request (validation failed) |
| 401 | Authentication required |
| 404 | Collection not found |
| 500 | Internal server error |
## Try It Out
@@ -1,46 +0,0 @@
---
method: DELETE
path: /api/collections/:id/rules/:ruleId
auth: jwt
area: collections
---
<!-- source: internal/handlers/collections.go:960 CollectionHandler.RemoveRule -->
# Remove Auto-Assign Rule
Remove an automatic book assignment rule from a collection.
**Endpoint**: `DELETE /api/collections/{collectionId}/rules/{ruleId}`
**Auth**: Required
**Content-Type**: `application/json`
## Path Parameters
| Parameter | Type | Required | Description |
| ------------ | ------------- | -------- | --------------- |
| collectionId | string (UUID) | Yes | Collection UUID |
| ruleId | string (UUID) | Yes | Rule UUID |
## Response (204 No Content)
Rule deleted successfully. No response body.
```json example-response
{
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
"is_smart": true
}
```
`is_smart` reflects the collection state AFTER removal — removing the
last rule converts a smart collection back to static.
## Error Responses
| Code | Description |
| ---- | ---------------------------- |
| 401 | Authentication required |
| 404 | Collection or rule not found |
| 500 | Internal server error |
## Try It Out
@@ -0,0 +1,48 @@
---
method: PUT
path: /api/collections/:id/view-settings
auth: jwt
area: collections
---
<!-- source: internal/handlers/collections.go CollectionHandler.SetCollectionChainOrder -->
# Set Collection Chain Order
Sets the curated listening order for the collection's episode sequence
(plan §10 R2): the chain producer (`buildSmartCollectionSequenceContext`)
plays `Play as Sequence` in this order. The key merges into the
collection's `view_settings` JSONB — playlist toggles that share the
document survive the write. Owner-only.
## Example request
```json example-request
{
"chain_order": "newest"
}
```
## Example response
```json example-response
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"chain_order": "newest",
"view_settings": { "chain_order": "newest" }
}
```
## Fields
| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| chain_order | string | Yes | One of `native`, `newest`, `oldest`, `reverse`; `native`/empty removes the stored key |
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "invalid collection id"}` | malformed id |
| 400 | `{"error": "chain_order must be one of: native, newest, oldest, reverse"}` | unknown order |
| 404 | `{"error": "collection not found"}` | unknown id |
| 401 / 403 | — | missing JWT / not the owner |
@@ -1,79 +0,0 @@
---
method: POST
path: /api/collections/test-rules
auth: jwt
area: collections
---
<!-- source: internal/handlers/collections.go:996 CollectionHandler.TestRules -->
# Test Rule
Test which books would match given rules without saving.
**Endpoint**: `POST /api/collections/test-rules`
**Auth**: Required
**Content-Type**: `application/json`
## Request Body
| Field | Type | Required | Description |
| ----- | ----- | -------- | ----------------------------- |
| rules | array | Yes | Array of rule objects to test |
### 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 |
### Example Request
```json example-request
{
"rules": [
{
"field": "genre",
"operator": "equals",
"value": "Science Fiction"
},
{
"field": "author",
"operator": "contains",
"value": "Asimov"
}
]
}
```
## Response (200 OK)
```json example-response
{
"matches": [
{
"media_item_id": "660e8400-e29b-41d4-a716-446655440000",
"title": "Foundation",
"author": "Isaac Asimov",
"cover_image_path": "/covers/foundation.jpg",
"match_reason": "Matched rule: genre equals Science Fiction"
}
],
"total": 42
}
```
## Use Case
Test rules before creating a collection to verify correct book matching. This endpoint shows which books would be added without actually modifying any collections.
## Error Responses
| Code | Description |
| ---- | ----------------------------------- |
| 400 | Invalid request (validation failed) |
| 401 | Authentication required |
| 500 | Internal server error |
## Try It Out
@@ -1,45 +0,0 @@
---
method: PUT
path: /api/collections/:id/rules/:ruleId
auth: jwt
area: collections
---
<!-- source: internal/handlers/collections.go:922 CollectionHandler.UpdateRule (SetRuleEnabled) -->
# Update Collection Rule
Despite the route name, this endpoint **toggles a rule's enabled state only**.
It does not update the rule's field/operator/value — rules are replaced wholesale
via [Create Collection](create_collection.md) (`auto_assign_rules`) or added via
the rules endpoint.
## Example request
```json example-request
{
"enabled": false
}
```
## Example response
```json example-response
{
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
"is_smart": true
}
```
## Request fields
| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| enabled | boolean | yes | toggles the rule on/off |
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "invalid collection id"}` / `{"error": "rule id required"}` / `{"error": "invalid request"}` | malformed |
| 404 | `{"error": "rule not found"}` | no such rule |
| 500 | `{"error": "..."}` | unexpected server error |