bruno: regenerate collection from canonical docs + permanent docs↔Bruno parity gate

WO4. The old collection was the drifted artifact AGENTS.md warned about:
bodies pre-truncated at their first {{var}}, routes renamed server-side
but not here (/api/notes/:media_id, /api/book-matching/search,
/api/collections/:id/rules/test), an orphan universal-progress suite for
routes that no longer exist.

scripts/gen_bruno_from_docs.py emits one OpenCollection request per
frontmatter'd endpoint doc (191 requests): gate-verified example-request
JSON as body, {{env}} params per the environment file, device-token
routes get an explicit Bearer {{device_token}} header, login/register/
refresh keep the token-capture after-response scripts that chain the
collection. Old 1:1 mirrors deleted (128); hand-authored NewDevDBSetup,
environments, and edge-case suites kept. 30 scenario files with
truncated bodies (the documented corruption) deleted — they could not
run.

internal/docs/drift_test.go gains TestBrunoMirrorsDocs: every
frontmatter'd doc must have a Bruno request with matching method+URL
shape — the collection can never silently drift from the docs again.
opencollection.yml README block replaced with the real run instructions.

make test green (9 pkgs, parity test included in internal/docs).
This commit is contained in:
John O'Keefe committed 2026-10-24 18:35:51 -04:00
1 parent 6fc501b35a
commit acd82fff0a
353 files changed
+5165 -5293

No files matched your search

@@ -1,14 +0,0 @@
info:
name: Create Saved Filter
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/saved-filters'
auth: inherit
body:
type: json
data: "{\n \"name\": \"My Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"sort\": \"title ASC\",\n \"author_filter\": \"\"\n }\n}"
docs: "## Create Saved Filter\n\nCreates a new saved filter for the authenticated user.\n\n**Method:** POST\n\n**Endpoint:** /api/saved-filters\n\n**Authentication:** Required (Bearer token)\n\n**Request Body:**\n```json\n{\n \"name\": \"My Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"sort\": \"title ASC\",\n \"author_filter\": \"\",\n \"series_filter\": \"\"\n }\n}\n```\n\n**Required Fields:**\n- `name` (string, required): Filter name\n - Must be unique per user + resource type combination\n - Maximum 100 characters\n - Cannot be empty\n- `resource_type` (string, required): Type of resource to filter\n - Examples: \"media-items\", \"collections\", \"devices\"\n - Must match valid resource types\n- `filters` (object, required): Key-value pairs of filter criteria\n - Flexible structure - any valid filter fields\n - Serialized as JSONB in database\n\n**Common Filter Fields for media-items:**\n- `search`:\
\ General search term\n- `author_filter`: Filter by author name\n- `genre_filter`: Filter by genre\n- `series_filter`: Filter by series\n- `language_filter`: Filter by language\n- `year_min`: Minimum copyright year\n- `year_max`: Maximum copyright year\n- `has_cover`: Boolean for cover image\n- `sort`: Sort order (e.g., \"title ASC\", \"created_at DESC\")\n\n**Response:**\n```json\n{\n \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"name\": \"My Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"sort\": \"title ASC\"\n },\n \"created_at\": \"2024-03-20T12:00:00Z\",\n \"updated_at\": \"2024-03-20T12:00:00Z\"\n}\n```\n\n**Status Codes:**\n- 201: Created - Filter successfully created\n- 400: Bad Request - Invalid request body or missing required fields\n- 401: Unauthorized - Invalid or missing authentication token\n- 409: Conflict - Filter with this name already exists for this resource type\n\n**Error Response\
\ Examples:**\n\nDuplicate name error:\n```json\n{\n \"error\": \"filter with name 'My Sci-Fi Books' already exists for this resource type\"\n}\n```\n\n**Example Usage:**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"My Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"sort\": \"title ASC\"\n }\n }' \\\n \"{{base_url}}/api/saved-filters\"\n```\n\n**Validation Rules:**\n- Name must be unique per user + resource type\n- Name cannot be empty or whitespace only\n- Resource type must be valid\n- Filters object must be provided (can be empty object {})"
@@ -1,95 +0,0 @@
info:
name: Delete Saved Filter
type: http
seq: 4
http:
method: DELETE
url: '{{base_url}}/api/saved-filters/{{filter_id}}'
auth: inherit
docs: |-
## Delete Saved Filter
Permanently deletes a saved filter.
**Method:** DELETE
**Endpoint:** /api/saved-filters/{filter_id}
**Authentication:** Required (Bearer token)
**Path Parameters:**
- `filter_id` (string, required): UUID of the filter to delete
- Must be a valid UUID
- Must belong to the authenticated user
**Response:**
- 204 No Content (success)
- Empty response body
**Status Codes:**
- 204: No Content - Filter deleted successfully
- 400: Bad Request - Invalid filter ID format
- 401: Unauthorized - Invalid or missing authentication token
- 404: Not Found - Filter doesn't exist or doesn't belong to user
- 500: Internal Server Error - Database error
**Example Usage:**
```bash
curl -X DELETE \
-H "Authorization: Bearer YOUR_TOKEN" \
"{{base_url}}/api/saved-filters/{{filter_id}}"
```
**Important Notes:**
- You can only delete filters that belong to you
- Deletion is permanent - cannot be undone
- Returns 204 No Content on success (no response body)
- If filter doesn't exist or belongs to another user, returns 404
**Error Response Examples:**
Invalid filter ID:
```json
{
"error": "invalid filter ID"
}
```
Filter not found:
```json
{
"error": "failed to delete filter"
}
```
**Testing Deletion:**
```bash
# First, create a filter
CREATE_RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "To Be Deleted",
"resource_type": "media-items",
"filters": {}
}' \
"{{base_url}}/api/saved-filters")
# Extract the ID
FILTER_ID=$(echo $CREATE_RESPONSE | jq -r '.id')
# Delete the filter
curl -X DELETE \
-H "Authorization: Bearer YOUR_TOKEN" \
"{{base_url}}/api/saved-filters/$FILTER_ID"
# Verify it's gone
curl -H "Authorization: Bearer YOUR_TOKEN" \
"{{base_url}}/api/saved-filters?resource_type=media-items"
```
**Cascade Effects:**
- No cascade effects - saved filters are standalone records
- User deletion automatically cascades to delete their filters
- No foreign key dependencies on other tables
@@ -1,64 +0,0 @@
info:
name: Get Saved Filter By ID
type: http
seq: 4
http:
method: GET
url: "{{base_url}}/api/saved-filters/{{filter_id}}"
auth: inherit
docs: |-
## Get Saved Filter By ID
Retrieves a single saved filter by its ID.
**Method:** GET
**Endpoint:** /api/saved-filters/:id
**Authentication:** Required (Bearer token)
**URL Parameters:**
- `id` (UUID, required): The unique identifier of the saved filter
- Example: "550e8400-e29b-41d4-a716-446655440000"
- Must be a valid UUID format
**Response:**
Single saved filter object:
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Sci-Fi Books",
"resource_type": "media-items",
"filters": {
"genre_filter": "Science Fiction",
"sort": "title ASC"
},
"created_at": "2024-03-20T12:00:00Z",
"updated_at": "2024-03-20T12:00:00Z"
}
```
**Status Codes:**
- 200: Success - Returns the saved filter
- 400: Bad Request - Invalid filter ID format
- 401: Unauthorized - Invalid or missing authentication token
- 404: Not Found - Filter doesn't exist or doesn't belong to user
**Example Usage:**
```bash
# Get a specific saved filter
curl -H "Authorization: Bearer YOUR_TOKEN" \
"{{base_url}}/api/saved-filters/550e8400-e29b-41d4-a716-446655440000"
```
**Notes:**
- Filters are user-specific (ownership verified via JWT)
- Returns 404 if filter doesn't exist OR doesn't belong to authenticated user
- Use `GET /api/saved-filters?resource_type=X` to list all filters first
**Scenarios:**
- Get filter by ID from list response
- Load filter details for editing
- Verify filter exists before updating
- Mobile app on-demand filter loading
@@ -1,63 +0,0 @@
info:
name: List Saved Filters
type: http
seq: 1
http:
method: GET
url: '{{base_url}}/api/saved-filters?resource_type=media-items'
auth: inherit
docs: |-
## List Saved Filters
Retrieves all saved filters for the authenticated user and a specific resource type.
**Method:** GET
**Endpoint:** /api/saved-filters
**Authentication:** Required (Bearer token)
**Query Parameters:**
- `resource_type` (string, required): Filter by resource type
- Examples: "media-items", "collections", "devices"
- Required parameter - must be provided
**Response:**
Array of saved filter objects:
```json
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Sci-Fi Books",
"resource_type": "media-items",
"filters": {
"genre_filter": "Science Fiction",
"sort": "title ASC"
},
"created_at": "2024-03-20T12:00:00Z",
"updated_at": "2024-03-20T12:00:00Z"
}
]
```
**Status Codes:**
- 200: Success - Returns array of saved filters
- 400: Bad Request - Missing resource_type parameter
- 401: Unauthorized - Invalid or missing authentication token
**Example Usage:**
```bash
# Get all saved filters for media items
curl -H "Authorization: Bearer YOUR_TOKEN" \
"{{base_url}}/api/saved-filters?resource_type=media-items"
# Get all saved filters for collections
curl -H "Authorization: Bearer YOUR_TOKEN" \
"{{base_url}}/api/saved-filters?resource_type=collections"
```
**Notes:**
- Filters are specific to each user (user-scoped via JWT)
- Returns empty array [] if no filters exist for the resource type
- Results ordered by created_at DESC (newest first)
@@ -1,14 +0,0 @@
info:
name: Update Saved Filter
type: http
seq: 3
http:
method: PUT
url: '{{base_url}}/api/saved-filters/{{filter_id}}'
auth: inherit
body:
type: json
data: "{\n \"name\": \"Updated Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction Fantasy\",\n \"sort\": \"author ASC\",\n \"year_min\": \"2000\"\n }\n }"
docs: "## Update Saved Filter\n\nUpdates an existing saved filter's name and/or filter criteria.\n\n**Method:** PUT\n\n**Endpoint:** /api/saved-filters/{filter_id}\n\n**Authentication:** Required (Bearer token)\n\n**Path Parameters:**\n- `filter_id` (string, required): UUID of the filter to update\n - Must be a valid UUID\n - Must belong to the authenticated user\n\n**Request Body:**\n```json\n{\n \"name\": \"Updated Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction Fantasy\",\n \"sort\": \"author ASC\",\n \"year_min\": \"2000\",\n \"year_max\": \"2024\"\n }\n}\n```\n\n**Required Fields:**\n- `name` (string, required): New filter name\n - Must be unique per user + resource type (excluding current filter)\n - Maximum 100 characters\n- `resource_type` (string, required): Resource type (must match existing)\n - Cannot change resource type after creation\n- `filters` (object, required): Updated filter criteria\n\
\ - Replaces existing filters entirely\n\n**Response:**\n```json\n{\n \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"name\": \"Updated Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction Fantasy\",\n \"sort\": \"author ASC\",\n \"year_min\": \"2000\",\n \"year_max\": \"2024\"\n },\n \"created_at\": \"2024-03-20T12:00:00Z\",\n \"updated_at\": \"2024-03-20T15:30:00Z\"\n}\n```\n\n**Status Codes:**\n- 200: Success - Filter updated successfully\n- 400: Bad Request - Invalid request body or filter ID\n- 401: Unauthorized - Invalid or missing authentication token\n- 404: Not Found - Filter doesn't exist or doesn't belong to user\n- 409: Conflict - New name conflicts with existing filter\n\n**Error Response Examples:**\n\nFilter not found:\n```json\n{\n \"error\": \"filter not found or access denied\"\n}\n```\n\nDuplicate name:\n```json\n{\n \"error\": \"filter with name 'Updated Sci-Fi Books' already exists\
\ for this resource type\"\n}\n```\n\n**Example Usage:**\n```bash\ncurl -X PUT \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Updated Sci-Fi Books\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction Fantasy\",\n \"sort\": \"author ASC\",\n \"year_min\": \"2000\"\n }\n }' \\\n \"{{base_url}}/api/saved-filters/{{filter_id}}\"\n```\n\n**Important Notes:**\n- You can only update filters that belong to you\n- The `updated_at` timestamp is automatically updated\n- Cannot change the resource_type (it's immutable)\n- New name must not conflict with other filters you own for the same resource type\n- All filter fields are replaced entirely (partial updates not supported)"
@@ -0,0 +1,29 @@
info:
name: Create Saved Filter
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/saved-filters'
auth: inherit
body:
type: json
data: |-
{
"name": "My Filter",
"resource_type": "media-items",
"filters": {}
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Create Saved Filter
`POST /api/saved-filters` — auth: jwt.
Canonical reference: `docs/developer/api/saved-filters/create_saved_filter.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Delete Saved Filter
type: http
seq: 2
http:
method: DELETE
url: '{{base_url}}/api/saved-filters/{{media_item_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Delete Saved Filter
`DELETE /api/saved-filters/:id` — auth: jwt.
Canonical reference: `docs/developer/api/saved-filters/delete_saved_filter.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Saved Filter
type: http
seq: 3
http:
method: GET
url: '{{base_url}}/api/saved-filters/{{media_item_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Saved Filter
`GET /api/saved-filters/:id` — auth: jwt.
Canonical reference: `docs/developer/api/saved-filters/get_saved_filter.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: List Saved Filters
type: http
seq: 4
http:
method: GET
url: '{{base_url}}/api/saved-filters'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# List Saved Filters
`GET /api/saved-filters` — auth: jwt.
Canonical reference: `docs/developer/api/saved-filters/list_saved_filters.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,29 @@
info:
name: Update Saved Filter
type: http
seq: 5
http:
method: PUT
url: '{{base_url}}/api/saved-filters/{{media_item_id}}'
auth: inherit
body:
type: json
data: |-
{
"name": "Renamed Filter",
"resource_type": "media-items",
"filters": {}
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Update Saved Filter
`PUT /api/saved-filters/:id` — auth: jwt.
Canonical reference: `docs/developer/api/saved-filters/update_saved_filter.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.