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:
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.
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user