From 09b66935051d82ed84eb82dbaa8e90c46e7f43bb Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 17 Oct 2026 12:22:35 -0400 Subject: [PATCH] =?UTF-8?q?fix(bruno):=20convert=20drifted=20body=20keys?= =?UTF-8?q?=20to=20body.data=20=E2=80=94=20CLI=20scenario=20runs=20now=20c?= =?UTF-8?q?arry=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the sweep route audit, correcting its diagnosis: the CLI DOES read .yml bodies โ€” via OpenCollection's `body.data` string form (the NewDevDBSetup script always worked because it used it). The failing scenario POSTs used `body.json` (structured) / `body.jsonBody` (string) โ€” keys the CLI silently ignores, sending Content-Length: 0 with form-urlencoded content-type, so every server bind failed. - 81 bodies converted to data-form (json maps serialized; jsonBody strings kept verbatim, {{vars}} intact). - 6 pre-existing TRUNCATED payloads repaired: the original generator cut bodies at the first {{var}} (every SHA-256-identified scenario plus Sync Progress/Sync Bookmarks shipped mid-JSON and 400'd from day one). Rewritten whole, faithful to each file's documented purpose; book_sha256/book_uuid added to the environment. - environments: opds_base_url correction from the sweep retained. Verified live against the rebuilt dev container: the full koreader scenario tree runs green through `bru run` โ€” 14/14 (202s across immediate/checkpoint/SHA-256 progress, 200s bookmarks, highlights, notes; SHA-256 book identification exercised against a real file_sha256). --- bruno/NewDevDBSetup/SetBaseUrl.yml | 19 +- bruno/books/Bulk Delete Books.yml | 39 +-- bruno/books/Bulk Update Books.yml | 45 +-- bruno/collections/Add Books to Collection.yml | 30 +- bruno/collections/Create Collection.yml | 40 +-- bruno/collections/Create Device Mapping.yml | 31 +-- bruno/collections/Update Collection.yml | 38 +-- bruno/collections/Update Device Mapping.yml | 32 +-- .../create-custom-section-manual.yml | 2 +- .../create-custom-section-missing-fields.yml | 2 +- .../create-custom-section-rules.yml | 2 +- bruno/collections/preview-collection.yml | 2 +- .../Bulk Add Books to Collections.yml | 41 +-- .../Bulk Remove Books - All Books.yml | 12 +- .../Bulk Remove Books - Empty List.yml | 14 +- .../Bulk Remove Books - Invalid IDs.yml | 18 +- .../Bulk Remove Books - Single Book.yml | 14 +- ...est Collection Rules - Author Contains.yml | 17 +- ...on Rules - Copyright Year Greater Than.yml | 19 +- ...t Collection Rules - Empty Rules Array.yml | 16 +- .../Test Collection Rules - No Matches.yml | 21 +- .../scenarios/Test Collection Rules.yml | 55 +--- bruno/conflicts/Resolve Conflict.yml | 14 +- .../scenarios/Bulk Dismiss Conflicts.yml | 36 +-- .../scenarios/Bulk Resolve Conflicts.yml | 43 +-- .../Bulk Resolve Highest Progress.yml | 34 +-- bruno/dashboard/restore-system-collection.yml | 37 +-- bruno/dashboard/update-preferences.yml | 38 +-- bruno/devices/kobo/Get Analytics Tests.yml | 91 +----- .../kobo/scenarios/Auto Link Books.yml | 32 +-- .../kobo/scenarios/Bulk Link Books.yml | 34 +-- .../kobo/scenarios/Server Sync to Kobo.yml | 43 +-- .../Sync Books from Server to Kobo.yml | 101 +------ .../Sync Multiple Books Progress.yml | 87 +----- .../Sync Progress with Bookmarks.yml | 80 +----- .../kobo/scenarios/Sync Reading Progress.yml | 81 +----- .../kobo/scenarios/Sync Single Bookmark.yml | 89 +----- .../kobo/scenarios/Sync with Annotations.yml | 102 +------ .../devices/kobo/scenarios/bookmark-sync.yml | 34 +-- bruno/devices/kobo/scenarios/link-book.yml | 32 +-- .../scenarios/sync-bookmark-url-token.yml | 14 +- .../kobo/scenarios/sync-markup-url-token.yml | 15 +- .../koreader/scenarios/Checkpoint Sync.yml | 12 +- .../scenarios/Immediate Sync - Page Turn.yml | 8 +- .../Sync Annotations (Per-Book SHA-256).yml | 2 +- .../scenarios/Sync Bookmarks (SHA-256).yml | 2 +- .../koreader/scenarios/Sync Bookmarks.yml | 14 +- .../koreader/scenarios/Sync Highlights.yml | 5 +- .../devices/koreader/scenarios/Sync Notes.yml | 2 +- .../Sync Progress (SHA-256 Only).yml | 2 +- .../scenarios/Sync Progress (SHA-256).yml | 2 +- .../Sync Progress - Multiple Books.yml | 22 +- .../scenarios/Sync Progress - Single Book.yml | 14 +- .../Sync Progress - With Bookmarks.yml | 20 +- ...c Progress - With Highlights and Notes.yml | 23 +- .../koreader/scenarios/Sync Progress.yml | 2 +- .../scenarios/Add Books to Kobo Shelf.yml | 12 +- .../scenarios/Check Pending Registration.yml | 46 +--- .../scenarios/Check Registration Status.yml | 29 +- .../scenarios/Create Device File Alias.yml | 42 +-- .../devices/scenarios/Disable Device Sync.yml | 42 ++- .../Initiate Device Registration.yml | 31 +-- bruno/devices/scenarios/Register Device.yml | 34 ++- .../scenarios/Register KOReader Device.yml | 33 ++- .../devices/scenarios/Register Web Device.yml | 34 ++- .../scenarios/Update Device Settings.yml | 45 ++- bruno/devices/scenarios/Update Device.yml | 34 +-- .../scenarios/Update Sync Frequency.yml | 49 +++- bruno/environments/Bookhoard.yml | 4 + bruno/saved-filters/Create Saved Filter.yml | 108 +------- bruno/saved-filters/Update Saved Filter.yml | 111 +------- .../scenarios/Complete CRUD Workflow.yml | 243 +---------------- .../scenarios/Duplicate Name Validation.yml | 69 +---- .../Filter Validation - Edge Cases.yml | 258 +----------------- bruno/scanner/Start Watch Mode.yml | 24 +- bruno/scanner/Stop Watch Mode.yml | 24 +- bruno/sidecar/Update System Configuration.yml | 31 +-- bruno/system/update-scan-settings.yml | 20 +- bruno/user/admin/Reset User Password.yml | 28 +- bruno/user/admin/Update User.yml | 34 +-- bruno/user/profile/Update Password.yml | 29 +- bruno/user/profile/Update Profile.yml | 37 +-- bruno/user/profile/Update Theme.yml | 28 +- 83 files changed, 386 insertions(+), 2769 deletions(-) diff --git a/bruno/NewDevDBSetup/SetBaseUrl.yml b/bruno/NewDevDBSetup/SetBaseUrl.yml index 27ba77d7..7fa37b0d 100644 --- a/bruno/NewDevDBSetup/SetBaseUrl.yml +++ b/bruno/NewDevDBSetup/SetBaseUrl.yml @@ -2,37 +2,36 @@ info: name: SetBaseUrl type: http seq: 3 - http: method: PUT url: '{{base_url}}/api/system/config' auth: inherit body: type: json - jsonBody: |- - { - "base_url": "http://localhost:8765" - } + data: "{\n \"base_url\": \"http://localhost:8765\"\n}" headers: - key: Authorization value: Bearer {{token}} - key: Content-Type value: application/json - settings: encodeUrl: true timeout: 0 followRedirects: true maxRedirects: 5 +docs: '## Set Base URL -docs: |- - ## Set Base URL - Configures the server's base_url during initial dev database setup. + Configures the server''s base_url during initial dev database setup. + Must be run after RegisterUser (which provides the auth token) and before + any library/device creation (which require setup to be complete). + **Method:** PUT + **Endpoint:** /api/system/config - **Auth:** Bearer token (from RegisterUser) + + **Auth:** Bearer token (from RegisterUser)' diff --git a/bruno/books/Bulk Delete Books.yml b/bruno/books/Bulk Delete Books.yml index 447cb6e3..cd7d3d8a 100644 --- a/bruno/books/Bulk Delete Books.yml +++ b/bruno/books/Bulk Delete Books.yml @@ -8,43 +8,8 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"media_item_ids\": [\n \"{{bookId1" + data: "{\n \"media_item_ids\": [\n \"{{bookId1" headers: - key: Content-Type value: application/json - -docs: |- - ## Bulk Delete Media Items - - Deletes multiple media items in a single request. - - **Method:** POST - - **Endpoint:** /api/media-items/bulk-delete - - **Authentication:** Required (Bearer token) - - **Request Body:** - - `media_item_ids` (array of strings): Array of media item UUIDs to delete - - **Response:** - - `results` (array): Results for each deletion attempt - - `total` (number): Total number of media items processed - - `deleted` (number): Number of successfully deleted media items - - `failed` (number): Number of failed deletions - - **Status Codes:** - - 200: Success (with partial results if some failed) - - 400: Invalid request data - - 401: Unauthorized - - 403: Forbidden - - 500: Internal server error - - **Example:** - ```json - { - "media_item_ids": [ - "uuid-1", - "uuid-2", - "uuid-3" - ] +docs: "## Bulk Delete Media Items\n\n Deletes multiple media items in a single request.\n\n **Method:** POST\n\n **Endpoint:** /api/media-items/bulk-delete\n\n **Authentication:** Required (Bearer token)\n\n **Request Body:**\n - `media_item_ids` (array of strings): Array of media item UUIDs to delete\n\n **Response:**\n - `results` (array): Results for each deletion attempt\n - `total` (number): Total number of media items processed\n - `deleted` (number): Number of successfully deleted media items\n - `failed` (number): Number of failed deletions\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"media_item_ids\": [\n \"uuid-1\",\n \"uuid-2\",\n \"uuid-3\"\n ]" diff --git a/bruno/books/Bulk Update Books.yml b/bruno/books/Bulk Update Books.yml index e7f53073..64e0a02b 100644 --- a/bruno/books/Bulk Update Books.yml +++ b/bruno/books/Bulk Update Books.yml @@ -8,48 +8,9 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"media_item_updates\": [\n {\n \"media_item_id\"\ - : \"{{bookId1" + data: "{\n \"media_item_updates\": [\n {\n \"media_item_id\": \"{{bookId1" headers: - key: Content-Type value: application/json - -docs: |- - ## Bulk Update Media Items - - Updates multiple media items in a single request with different fields for each item. - - **Method:** POST - - **Endpoint:** /api/media-items/bulk-update - - **Authentication:** Required (Bearer token) - - **Request Body:** - - `media_item_updates` (array): Array of update objects - - `media_item_id` (string): Media item UUID to update - - `updates` (object): Fields to update (can include title, author, genre, tags, contributors, etc.) - - **Response:** - - `results` (array): Results for each update attempt - - `total` (number): Total number of media items processed - - `updated` (number): Number of successfully updated media items - - `failed` (number): Number of failed updates - - **Status Codes:** - - 200: Success (with partial results if some failed) - - 400: Invalid request data - - 401: Unauthorized - - 403: Forbidden - - 500: Internal server error - - **Example:** - ```json - { - "media_item_updates": [ - { - "media_item_id": "uuid-1", - "updates": { - "title": "New Title", - "genre": "Fiction", - "tags": ["fiction", "adventure"] +docs: "## Bulk Update Media Items\n\n Updates multiple media items in a single request with different fields for each item.\n\n **Method:** POST\n\n **Endpoint:** /api/media-items/bulk-update\n\n **Authentication:** Required (Bearer token)\n\n **Request Body:**\n - `media_item_updates` (array): Array of update objects\n - `media_item_id` (string): Media item UUID to update\n - `updates` (object): Fields to update (can include title, author, genre, tags, contributors, etc.)\n\n **Response:**\n - `results` (array): Results for each update attempt\n - `total` (number): Total number of media items processed\n - `updated` (number): Number of successfully updated media items\n - `failed` (number): Number of failed updates\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"media_item_updates\": [\n {\n\ + \ \"media_item_id\": \"uuid-1\",\n \"updates\": {\n \"title\": \"New Title\",\n \"genre\": \"Fiction\",\n \"tags\": [\"fiction\", \"adventure\"]" diff --git a/bruno/collections/Add Books to Collection.yml b/bruno/collections/Add Books to Collection.yml index b895dede..73e26d8f 100644 --- a/bruno/collections/Add Books to Collection.yml +++ b/bruno/collections/Add Books to Collection.yml @@ -8,31 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"book_ids\": [\n \"{{book_id_1}}" - -docs: |- - ## Add Books to Collection - - Adds multiple books to an existing collection. - - **Method:** POST - - **Endpoint:** /api/collections/{collection_id}/books - - **Authentication:** Required (Bearer token) - - **Path Parameters:** - - `collection_id` (string): Collection ID - - **Request Body:** - - `book_ids` (array of strings, required): List of book IDs to add - - **Response:** - - `message` (string): Success message - - `added_count` (integer): Number of books added - - **Status Codes:** - - 200: Success - - 400: Invalid request - - 401: Unauthorized - - 404: Collection not found + data: "{\n \"book_ids\": [\n \"{{book_id_1}}" +docs: "## Add Books to Collection\n\n Adds multiple books to an existing collection.\n \n **Method:** POST\n \n **Endpoint:** /api/collections/{collection_id}/books\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `collection_id` (string): Collection ID\n \n **Request Body:**\n - `book_ids` (array of strings, required): List of book IDs to add\n \n **Response:**\n - `message` (string): Success message\n - `added_count` (integer): Number of books added\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Collection not found" diff --git a/bruno/collections/Create Collection.yml b/bruno/collections/Create Collection.yml index 098be29e..59b51f63 100644 --- a/bruno/collections/Create Collection.yml +++ b/bruno/collections/Create Collection.yml @@ -8,40 +8,6 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"name\": \"Science Fiction\",\n \"description\": \"My favorite\ - \ sci-fi books\",\n \"color\": \"#ff0000\",\n \"icon\": \"\U0001F680\"\ - ,\n \"auto_assign_rules\": [\n {\n \"id\": \"rule-1\",\n \ - \ \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\"\ - : \"Science Fiction\",\n \"priority\": 8" - -docs: |- - ## Create Collection - - Creates a new collection for organizing books. - - **Method:** POST - - **Endpoint:** /api/collections - - **Authentication:** Required (Bearer token) - - **Request Body:** - - `name` (string, required): Collection name (max 100 chars) - - `description` (string, optional): Collection description (max 500 chars) - - `color` (string, optional): Hex color code (default #3498db) - - `icon` (string, optional): Emoji icon (default ๐Ÿ“š) - - `auto_assign_rules` (array, optional): Auto-assignment rules - - `id` (string): Rule ID - - `field` (string): Field to match (genre, author, series, etc.) - - `operator` (string): Comparison operator (equals, contains, startsWith) - - `value` (string): Value to match - - `priority` (integer): Rule priority (1-10) - - **Response:** - - Collection object with generated ID - - **Status Codes:** - - 201: Created - - 400: Invalid request - - 401: Unauthorized - - 409: Collection name already exists + data: "{\n \"name\": \"Science Fiction\",\n \"description\": \"My favorite sci-fi books\",\n \"color\": \"#ff0000\",\n \"icon\": \"\U0001F680\",\n \"auto_assign_rules\": [\n {\n \"id\": \"rule-1\",\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"Science Fiction\",\n \"priority\": 8" +docs: "## Create Collection\n\n Creates a new collection for organizing books.\n \n **Method:** POST\n \n **Endpoint:** /api/collections\n \n **Authentication:** Required (Bearer token)\n \n **Request Body:**\n - `name` (string, required): Collection name (max 100 chars)\n - `description` (string, optional): Collection description (max 500 chars)\n - `color` (string, optional): Hex color code (default #3498db)\n - `icon` (string, optional): Emoji icon (default \U0001F4DA)\n - `auto_assign_rules` (array, optional): Auto-assignment rules\n - `id` (string): Rule ID\n - `field` (string): Field to match (genre, author, series, etc.)\n - `operator` (string): Comparison operator (equals, contains, startsWith)\n - `value` (string): Value to match\n - `priority` (integer): Rule priority (1-10)\n \n **Response:**\n - Collection object with generated ID\n \n **Status Codes:**\n - 201: Created\n - 400: Invalid request\n - 401: Unauthorized\n - 409: Collection name\ + \ already exists" diff --git a/bruno/collections/Create Device Mapping.yml b/bruno/collections/Create Device Mapping.yml index d0498fee..56ee66a7 100644 --- a/bruno/collections/Create Device Mapping.yml +++ b/bruno/collections/Create Device Mapping.yml @@ -8,32 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"collection_id\": \"{{collection_id}}" - -docs: |- - ## Create Device Mapping - - Maps a collection to a device shelf for synchronization. - - **Method:** POST - - **Endpoint:** /api/devices/{device_id}/collections - - **Authentication:** Required (Bearer token) - - **Path Parameters:** - - `device_id` (string): Device ID - - **Request Body:** - - `collection_id` (string, required): Collection ID to map - - `device_shelf_name` (string, optional): Custom shelf name on device - - `sync_direction` (string, optional): sync direction (book_to_device, device_to_book, bidirectional) - - **Response:** - - Mapping object with generated ID - - **Status Codes:** - - 201: Created - - 400: Invalid request - - 401: Unauthorized - - 404: Device or collection not found + data: "{\n \"collection_id\": \"{{collection_id}}" +docs: "## Create Device Mapping\n\n Maps a collection to a device shelf for synchronization.\n \n **Method:** POST\n \n **Endpoint:** /api/devices/{device_id}/collections\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `device_id` (string): Device ID\n \n **Request Body:**\n - `collection_id` (string, required): Collection ID to map\n - `device_shelf_name` (string, optional): Custom shelf name on device\n - `sync_direction` (string, optional): sync direction (book_to_device, device_to_book, bidirectional)\n \n **Response:**\n - Mapping object with generated ID\n \n **Status Codes:**\n - 201: Created\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Device or collection not found" diff --git a/bruno/collections/Update Collection.yml b/bruno/collections/Update Collection.yml index 46142bd4..d945d590 100644 --- a/bruno/collections/Update Collection.yml +++ b/bruno/collections/Update Collection.yml @@ -8,39 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"name\": \"Sci-Fi Favorites\",\n \"description\": \"Updated\ - \ description\",\n \"color\": \"#00ff00\",\n \"icon\": \"โญ\",\n \"\ - auto_assign_rules\": [\n {\n \"id\": \"rule-2\",\n \"field\"\ - : \"series\",\n \"operator\": \"equals\",\n \"value\": \"Foundation\"\ - ,\n \"priority\": 9" - -docs: |- - ## Update Collection - - Updates an existing collection's properties. - - **Method:** PUT - - **Endpoint:** /api/collections/{collection_id} - - **Authentication:** Required (Bearer token) - - **Path Parameters:** - - `collection_id` (string): Collection ID - - **Request Body:** - - `name` (string, optional): New collection name - - `description` (string, optional): New description - - `color` (string, optional): New hex color code - - `icon` (string, optional): New emoji icon - - `auto_assign_rules` (array, optional): Updated auto-assignment rules - - **Response:** - - Updated collection object - - **Status Codes:** - - 200: Success - - 400: Invalid request - - 401: Unauthorized - - 404: Collection not found - - 409: Collection name already exists + data: "{\n \"name\": \"Sci-Fi Favorites\",\n \"description\": \"Updated description\",\n \"color\": \"#00ff00\",\n \"icon\": \"โญ\",\n \"auto_assign_rules\": [\n {\n \"id\": \"rule-2\",\n \"field\": \"series\",\n \"operator\": \"equals\",\n \"value\": \"Foundation\",\n \"priority\": 9" +docs: "## Update Collection\n\n Updates an existing collection's properties.\n \n **Method:** PUT\n \n **Endpoint:** /api/collections/{collection_id}\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `collection_id` (string): Collection ID\n \n **Request Body:**\n - `name` (string, optional): New collection name\n - `description` (string, optional): New description\n - `color` (string, optional): New hex color code\n - `icon` (string, optional): New emoji icon\n - `auto_assign_rules` (array, optional): Updated auto-assignment rules\n \n **Response:**\n - Updated collection object\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Collection not found\n - 409: Collection name already exists" diff --git a/bruno/collections/Update Device Mapping.yml b/bruno/collections/Update Device Mapping.yml index 0b1a50e8..f0a7eea5 100644 --- a/bruno/collections/Update Device Mapping.yml +++ b/bruno/collections/Update Device Mapping.yml @@ -8,33 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"device_shelf_name\": \"Science Fiction\",\n \"sync_direction\"\ - : \"book_to_device\"" - -docs: |- - ## Update Device Mapping - - Updates settings for a collection-to-device mapping. - - **Method:** PUT - - **Endpoint:** /api/devices/{device_id}/collections/{mapping_id} - - **Authentication:** Required (Bearer token) - - **Path Parameters:** - - `device_id` (string): Device ID - - `mapping_id` (string): Mapping ID - - **Request Body:** - - `device_shelf_name` (string, optional): Custom shelf name on device - - `sync_direction` (string, optional): Sync direction (book_to_device, device_to_book, bidirectional) - - **Response:** - - Updated mapping object - - **Status Codes:** - - 200: Success - - 400: Invalid request - - 401: Unauthorized - - 404: Device or mapping not found + data: "{\n \"device_shelf_name\": \"Science Fiction\",\n \"sync_direction\": \"book_to_device\"" +docs: "## Update Device Mapping\n\n Updates settings for a collection-to-device mapping.\n \n **Method:** PUT\n \n **Endpoint:** /api/devices/{device_id}/collections/{mapping_id}\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `device_id` (string): Device ID\n - `mapping_id` (string): Mapping ID\n \n **Request Body:**\n - `device_shelf_name` (string, optional): Custom shelf name on device\n - `sync_direction` (string, optional): Sync direction (book_to_device, device_to_book, bidirectional)\n \n **Response:**\n - Updated mapping object\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Device or mapping not found" diff --git a/bruno/collections/create-custom-section-manual.yml b/bruno/collections/create-custom-section-manual.yml index c40acb93..3d549307 100644 --- a/bruno/collections/create-custom-section-manual.yml +++ b/bruno/collections/create-custom-section-manual.yml @@ -8,7 +8,7 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"My Favorites\",\n \"icon\": \"โญ\",\n \"description\": \"My favorite books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"\",\n \"manual_book_ids\": [\"{{media_item_id}}\"],\n \"match_type\": \"all\"\n}" + data: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"My Favorites\",\n \"icon\": \"โญ\",\n \"description\": \"My favorite books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"\",\n \"manual_book_ids\": [\"{{media_item_id}}\"],\n \"match_type\": \"all\"\n}" runtime: scripts: - type: after-response diff --git a/bruno/collections/create-custom-section-missing-fields.yml b/bruno/collections/create-custom-section-missing-fields.yml index b1f1e2e6..524d61bb 100644 --- a/bruno/collections/create-custom-section-missing-fields.yml +++ b/bruno/collections/create-custom-section-missing-fields.yml @@ -8,7 +8,7 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"icon\": \"\U0001F4DA\"\n}" + data: "{\n \"library_id\": \"{{library_id}}\",\n \"icon\": \"\U0001F4DA\"\n}" runtime: scripts: - type: after-response diff --git a/bruno/collections/create-custom-section-rules.yml b/bruno/collections/create-custom-section-rules.yml index 6c4b3234..e5e64f32 100644 --- a/bruno/collections/create-custom-section-rules.yml +++ b/bruno/collections/create-custom-section-rules.yml @@ -8,7 +8,7 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"Science Fiction Books\",\n \"icon\": \"\U0001F680\",\n \"description\": \"All my sci-fi books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"{\\\"rules\\\":[{\\\"id\\\":\\\"rule1\\\",\\\"field\\\":\\\"genre\\\",\\\"operator\\\":\\\"equals\\\",\\\"value\\\":\\\"Sci-Fi\\\",\\\"priority\\\":1}]}\",\n \"manual_book_ids\": [],\n \"match_type\": \"all\"\n}" + data: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"Science Fiction Books\",\n \"icon\": \"\U0001F680\",\n \"description\": \"All my sci-fi books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"{\\\"rules\\\":[{\\\"id\\\":\\\"rule1\\\",\\\"field\\\":\\\"genre\\\",\\\"operator\\\":\\\"equals\\\",\\\"value\\\":\\\"Sci-Fi\\\",\\\"priority\\\":1}]}\",\n \"manual_book_ids\": [],\n \"match_type\": \"all\"\n}" runtime: scripts: - type: after-response diff --git a/bruno/collections/preview-collection.yml b/bruno/collections/preview-collection.yml index 8a8232a7..dc0c864f 100644 --- a/bruno/collections/preview-collection.yml +++ b/bruno/collections/preview-collection.yml @@ -8,7 +8,7 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"rules\": [\n{\n \"id\": \"rule1\",\n \"field\": \"genre\",\n\"operator\": \"equals\",\n \"value\": \"Fiction\",\n\"priority\": 1\n }\n ],\n \"manual_book_ids\": [],\n \"limit\": 20\n}" + data: "{\n \"library_id\": \"{{library_id}}\",\n \"rules\": [\n{\n \"id\": \"rule1\",\n \"field\": \"genre\",\n\"operator\": \"equals\",\n \"value\": \"Fiction\",\n\"priority\": 1\n }\n ],\n \"manual_book_ids\": [],\n \"limit\": 20\n}" runtime: scripts: - type: after-response diff --git a/bruno/collections/scenarios/Bulk Add Books to Collections.yml b/bruno/collections/scenarios/Bulk Add Books to Collections.yml index 365991f1..c39d4198 100644 --- a/bruno/collections/scenarios/Bulk Add Books to Collections.yml +++ b/bruno/collections/scenarios/Bulk Add Books to Collections.yml @@ -8,46 +8,11 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"operations\": [\n {\n \"collection_id\": \"{{collectionId1" + data: "{\n \"operations\": [\n {\n \"collection_id\": \"{{collectionId1" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Add Books to Collections - - Adds multiple books to multiple collections in a single request. Each operation specifies a collection and a list of books to add. - - **Method:** POST - - **Endpoint:** /api/collections/bulk-add-books - - **Authentication:** Bearer token - - **Request Body:** - - `operations` (array): Array of collection-book operations - - `collection_id` (string): Collection UUID - - `book_ids` (array): Array of book UUIDs to add to the collection - - **Response:** - - `results` (array): Results for each operation - - `total` (number): Total number of operations - - `success` (number): Number of successful operations - - `failed` (number): Number of failed operations - - **Status Codes:** - - 200: Success (with partial results if some failed) - - 400: Invalid request data - - 401: Unauthorized - - 403: Forbidden - - 500: Internal server error - - **Example:** - ```json - { - "operations": [ - { - "collection_id": "collection-uuid-1", - "book_ids": ["book-1", "book-2"] +docs: "## Bulk Add Books to Collections\n\n Adds multiple books to multiple collections in a single request. Each operation specifies a collection and a list of books to add.\n\n **Method:** POST\n\n **Endpoint:** /api/collections/bulk-add-books\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `operations` (array): Array of collection-book operations\n - `collection_id` (string): Collection UUID\n - `book_ids` (array): Array of book UUIDs to add to the collection\n\n **Response:**\n - `results` (array): Results for each operation\n - `total` (number): Total number of operations\n - `success` (number): Number of successful operations\n - `failed` (number): Number of failed operations\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"operations\": [\n {\n \"collection_id\":\ + \ \"collection-uuid-1\",\n \"book_ids\": [\"book-1\", \"book-2\"]" diff --git a/bruno/collections/scenarios/Bulk Remove Books - All Books.yml b/bruno/collections/scenarios/Bulk Remove Books - All Books.yml index e8826d83..6ecd70d4 100644 --- a/bruno/collections/scenarios/Bulk Remove Books - All Books.yml +++ b/bruno/collections/scenarios/Bulk Remove Books - All Books.yml @@ -8,16 +8,8 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"book_ids\": [\n \"{{bookId1" + data: "{\n \"book_ids\": [\n \"{{bookId1" headers: - key: Content-Type value: application/json - -docs: |- - ## Bulk Remove Books from Collection - - Removes multiple books from a collection in a single request. - - **Method:** POST - - **Endpoint:** /api/collections/{collection_id +docs: "## Bulk Remove Books from Collection\n\n Removes multiple books from a collection in a single request.\n\n **Method:** POST\n\n **Endpoint:** /api/collections/{collection_id" diff --git a/bruno/collections/scenarios/Bulk Remove Books - Empty List.yml b/bruno/collections/scenarios/Bulk Remove Books - Empty List.yml index f0638e33..997264eb 100644 --- a/bruno/collections/scenarios/Bulk Remove Books - Empty List.yml +++ b/bruno/collections/scenarios/Bulk Remove Books - Empty List.yml @@ -8,20 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"book_ids\": []" + data: "{\n \"book_ids\": []" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Remove Books - Empty List Validation - - Tests validation behavior when providing an empty book_ids array. - - **Expected Result:** 400 Bad Request - - **Validation Rule:** book_ids array must contain at least one book UUID. - - **Purpose:** Ensures the API properly validates input and rejects empty removal requests. +docs: "## Bulk Remove Books - Empty List Validation\n\n Tests validation behavior when providing an empty book_ids array.\n\n **Expected Result:** 400 Bad Request\n\n **Validation Rule:** book_ids array must contain at least one book UUID.\n\n **Purpose:** Ensures the API properly validates input and rejects empty removal requests." diff --git a/bruno/collections/scenarios/Bulk Remove Books - Invalid IDs.yml b/bruno/collections/scenarios/Bulk Remove Books - Invalid IDs.yml index 48229911..dcd3350a 100644 --- a/bruno/collections/scenarios/Bulk Remove Books - Invalid IDs.yml +++ b/bruno/collections/scenarios/Bulk Remove Books - Invalid IDs.yml @@ -8,24 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"book_ids\": [\n \"{{bookId1" + data: "{\n \"book_ids\": [\n \"{{bookId1" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Remove Books - Invalid IDs - - Tests behavior when the book_ids array contains invalid UUID formats or non-existent books. - - **Expected Result:** 200 OK with partial success - - **Purpose:** Verifies that: - - Invalid UUID formats don't crash the endpoint - - Non-existent book IDs are handled gracefully - - Valid IDs in the same request are still processed - - Response includes detailed results showing which succeeded/failed - - **Note:** The endpoint should process all valid IDs and report failures for invalid ones, allowing clients to handle partial failures appropriately. +docs: "## Bulk Remove Books - Invalid IDs\n\n Tests behavior when the book_ids array contains invalid UUID formats or non-existent books.\n\n **Expected Result:** 200 OK with partial success\n\n **Purpose:** Verifies that:\n - Invalid UUID formats don't crash the endpoint\n - Non-existent book IDs are handled gracefully\n - Valid IDs in the same request are still processed\n - Response includes detailed results showing which succeeded/failed\n\n **Note:** The endpoint should process all valid IDs and report failures for invalid ones, allowing clients to handle partial failures appropriately." diff --git a/bruno/collections/scenarios/Bulk Remove Books - Single Book.yml b/bruno/collections/scenarios/Bulk Remove Books - Single Book.yml index 1fb1df90..b81960ba 100644 --- a/bruno/collections/scenarios/Bulk Remove Books - Single Book.yml +++ b/bruno/collections/scenarios/Bulk Remove Books - Single Book.yml @@ -8,20 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"book_ids\": [\n \"{{bookId1" + data: "{\n \"book_ids\": [\n \"{{bookId1" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Remove Books - Single Book - - Tests that bulk remove endpoint works correctly with a single book. - - **Expected Result:** 200 OK with removed: 1, total: 1 - - **Purpose:** Verifies the bulk remove endpoint handles single-item arrays correctly, providing flexibility for clients to use the same endpoint for both single and multiple removals. - - **Note:** Using bulk remove for a single book is functionally equivalent to the single remove endpoint but allows for consistent error handling and response format. +docs: "## Bulk Remove Books - Single Book\n\n Tests that bulk remove endpoint works correctly with a single book.\n\n **Expected Result:** 200 OK with removed: 1, total: 1\n\n **Purpose:** Verifies the bulk remove endpoint handles single-item arrays correctly, providing flexibility for clients to use the same endpoint for both single and multiple removals.\n\n **Note:** Using bulk remove for a single book is functionally equivalent to the single remove endpoint but allows for consistent error handling and response format." diff --git a/bruno/collections/scenarios/Test Collection Rules - Author Contains.yml b/bruno/collections/scenarios/Test Collection Rules - Author Contains.yml index 3d1b3ed3..85c984ce 100644 --- a/bruno/collections/scenarios/Test Collection Rules - Author Contains.yml +++ b/bruno/collections/scenarios/Test Collection Rules - Author Contains.yml @@ -8,23 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"rules\": [\n {\n \"field\": \"author\",\n \ - \ \"operator\": \"contains\",\n \"value\": \"Asimov\"" + data: "{\n \"rules\": [\n {\n \"field\": \"author\",\n \"operator\": \"contains\",\n \"value\": \"Asimov\"" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Test Collection Rules - Author Contains - - Tests the "contains" operator on the author field to find books by a specific author (partial match). - - **Example Use Case:** Finding all books by an author whose name contains "Asimov" (e.g., "Isaac Asimov"). - - **Operator:** `contains` - Matches if the field contains the specified value as a substring (case-insensitive typically). - - **Expected Result:** Returns all books where the author field contains "Asimov". - - **Purpose:** Demonstrates text-based partial matching for author searches, useful when you don't need the exact author name or want to find books by authors with similar names. +docs: "## Test Collection Rules - Author Contains\n\n Tests the \"contains\" operator on the author field to find books by a specific author (partial match).\n\n **Example Use Case:** Finding all books by an author whose name contains \"Asimov\" (e.g., \"Isaac Asimov\").\n\n **Operator:** `contains` - Matches if the field contains the specified value as a substring (case-insensitive typically).\n\n **Expected Result:** Returns all books where the author field contains \"Asimov\".\n\n **Purpose:** Demonstrates text-based partial matching for author searches, useful when you don't need the exact author name or want to find books by authors with similar names." diff --git a/bruno/collections/scenarios/Test Collection Rules - Copyright Year Greater Than.yml b/bruno/collections/scenarios/Test Collection Rules - Copyright Year Greater Than.yml index e61e353f..27ee2ec4 100644 --- a/bruno/collections/scenarios/Test Collection Rules - Copyright Year Greater Than.yml +++ b/bruno/collections/scenarios/Test Collection Rules - Copyright Year Greater Than.yml @@ -8,25 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"rules\": [\n {\n \"field\": \"copyright_year\"\ - ,\n \"operator\": \"greater_than\",\n \"value\": \"2000\"" + data: "{\n \"rules\": [\n {\n \"field\": \"copyright_year\",\n \"operator\": \"greater_than\",\n \"value\": \"2000\"" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Test Collection Rules - Copyright Year Greater Than - - Tests the "greater_than" operator on the copyright_year field to find books published after a specific year. - - **Example Use Case:** Creating a "Modern Books" collection with books published after 2000. - - **Operator:** `greater_than` - Matches if the field value is greater than the specified value (numeric comparison). - - **Field:** `copyright_year` - The year the book was copyrighted/published. - - **Expected Result:** Returns all books with copyright_year greater than 2000 (i.e., published in 2001 or later). - - **Purpose:** Demonstrates numeric comparison operators for creating date-based collections, useful for organizing books by publication era. +docs: "## Test Collection Rules - Copyright Year Greater Than\n\n Tests the \"greater_than\" operator on the copyright_year field to find books published after a specific year.\n\n **Example Use Case:** Creating a \"Modern Books\" collection with books published after 2000.\n\n **Operator:** `greater_than` - Matches if the field value is greater than the specified value (numeric comparison).\n\n **Field:** `copyright_year` - The year the book was copyrighted/published.\n\n **Expected Result:** Returns all books with copyright_year greater than 2000 (i.e., published in 2001 or later).\n\n **Purpose:** Demonstrates numeric comparison operators for creating date-based collections, useful for organizing books by publication era." diff --git a/bruno/collections/scenarios/Test Collection Rules - Empty Rules Array.yml b/bruno/collections/scenarios/Test Collection Rules - Empty Rules Array.yml index eb37ac06..9ba18073 100644 --- a/bruno/collections/scenarios/Test Collection Rules - Empty Rules Array.yml +++ b/bruno/collections/scenarios/Test Collection Rules - Empty Rules Array.yml @@ -8,22 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"rules\": []" + data: "{\n \"rules\": []" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Test Collection Rules - Empty Rules Array - - Tests validation behavior when providing an empty rules array. - - **Expected Result:** 400 Bad Request - - **Validation Rule:** rules array must contain at least one rule object. - - **Purpose:** Ensures the API properly validates input and rejects empty rule sets, preventing accidental queries that would return all books or cause performance issues. - - **Use Case:** Client-side validation should prevent sending empty rules, but the API should also validate to catch malformed requests. +docs: "## Test Collection Rules - Empty Rules Array\n\n Tests validation behavior when providing an empty rules array.\n\n **Expected Result:** 400 Bad Request\n\n **Validation Rule:** rules array must contain at least one rule object.\n\n **Purpose:** Ensures the API properly validates input and rejects empty rule sets, preventing accidental queries that would return all books or cause performance issues.\n\n **Use Case:** Client-side validation should prevent sending empty rules, but the API should also validate to catch malformed requests." diff --git a/bruno/collections/scenarios/Test Collection Rules - No Matches.yml b/bruno/collections/scenarios/Test Collection Rules - No Matches.yml index 79eeb429..dd16b427 100644 --- a/bruno/collections/scenarios/Test Collection Rules - No Matches.yml +++ b/bruno/collections/scenarios/Test Collection Rules - No Matches.yml @@ -8,27 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"rules\": [\n {\n \"field\": \"genre\",\n \ - \ \"operator\": \"equals\",\n \"value\": \"NonExistentGenre123456\"" + data: "{\n \"rules\": [\n {\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"NonExistentGenre123456\"" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Test Collection Rules - No Matches - - Tests behavior when collection rules don't match any books in the library. - - **Example Use Case:** Validating that a new genre name doesn't exist before creating a collection for it, or testing edge cases. - - **Expected Result:** 200 OK with empty matches array and total: 0 - - **Purpose:** Verifies that the API handles zero-match scenarios gracefully: - - Returns 200 (success) not 404 - - Returns empty array, not null - - Returns total: 0 for clarity - - No errors thrown for no results - - **Note:** An empty result set is a valid response and doesn't indicate an error. This allows users to test rules confidently before creating collections. +docs: "## Test Collection Rules - No Matches\n\n Tests behavior when collection rules don't match any books in the library.\n\n **Example Use Case:** Validating that a new genre name doesn't exist before creating a collection for it, or testing edge cases.\n\n **Expected Result:** 200 OK with empty matches array and total: 0\n\n **Purpose:** Verifies that the API handles zero-match scenarios gracefully:\n - Returns 200 (success) not 404\n - Returns empty array, not null\n - Returns total: 0 for clarity\n - No errors thrown for no results\n\n **Note:** An empty result set is a valid response and doesn't indicate an error. This allows users to test rules confidently before creating collections." diff --git a/bruno/collections/scenarios/Test Collection Rules.yml b/bruno/collections/scenarios/Test Collection Rules.yml index 752d0aea..f95fe571 100644 --- a/bruno/collections/scenarios/Test Collection Rules.yml +++ b/bruno/collections/scenarios/Test Collection Rules.yml @@ -8,60 +8,11 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"rules\": [\n {\n \"field\": \"genre\",\n \ - \ \"operator\": \"equals\",\n \"value\": \"Science Fiction\"" + data: "{\n \"rules\": [\n {\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"Science Fiction\"" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Test Collection Rules - - Tests collection rules against the library to see which books match, without creating a collection. Useful for previewing what books would be included in a collection with specific rules. - - **Method:** POST - - **Endpoint:** /api/collections/test-rules - - **Authentication:** Bearer token - - **Request Body:** - - `rules` (array): Array of rule objects to test - - `field` (string): Field to test (genre, author, copyright_year, tags, etc.) - - `operator` (string): Comparison operator - - `equals`: Exact match - - `contains`: Contains substring (for text fields) - - `greater_than`: Greater than (for numeric fields) - - `less_than`: Less than (for numeric fields) - - `not_equals`: Not equal to - - `starts_with`: Starts with - - `ends_with`: Ends with - - `is_empty`: Field is empty or null - - `is_not_empty`: Field is not empty and not null - - `value` (string): Value to compare against (not required for is_empty/is_not_empty) - - **Response:** - - `matches` (array): Array of matching books - - `id` (string): Book UUID - - `title` (string): Book title - - `author` (string): Book author - - `genre` (string): Book genre - - Additional book metadata - - `total` (number): Total number of matching books - - **Status Codes:** - - 200: Success - returns matching books - - 400: Invalid request (empty rules array, invalid field/operator) - - 401: Unauthorized - - 500: Internal server error - - **Example Request:** - ```json - { - "rules": [ - { - "field": "genre", - "operator": "equals", - "value": "Science Fiction" +docs: "## Test Collection Rules\n\n Tests collection rules against the library to see which books match, without creating a collection. Useful for previewing what books would be included in a collection with specific rules.\n\n **Method:** POST\n\n **Endpoint:** /api/collections/test-rules\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `rules` (array): Array of rule objects to test\n - `field` (string): Field to test (genre, author, copyright_year, tags, etc.)\n - `operator` (string): Comparison operator\n - `equals`: Exact match\n - `contains`: Contains substring (for text fields)\n - `greater_than`: Greater than (for numeric fields)\n - `less_than`: Less than (for numeric fields)\n - `not_equals`: Not equal to\n - `starts_with`: Starts with\n - `ends_with`: Ends with\n - `is_empty`: Field is empty or null\n - `is_not_empty`: Field is not empty and not null\n - `value` (string): Value to compare against (not required\ + \ for is_empty/is_not_empty)\n\n **Response:**\n - `matches` (array): Array of matching books\n - `id` (string): Book UUID\n - `title` (string): Book title\n - `author` (string): Book author\n - `genre` (string): Book genre\n - Additional book metadata\n - `total` (number): Total number of matching books\n\n **Status Codes:**\n - 200: Success - returns matching books\n - 400: Invalid request (empty rules array, invalid field/operator)\n - 401: Unauthorized\n - 500: Internal server error\n\n **Example Request:**\n ```json\n {\n \"rules\": [\n {\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"Science Fiction\"" diff --git a/bruno/conflicts/Resolve Conflict.yml b/bruno/conflicts/Resolve Conflict.yml index d0226165..040fa785 100644 --- a/bruno/conflicts/Resolve Conflict.yml +++ b/bruno/conflicts/Resolve Conflict.yml @@ -8,18 +8,8 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"winner\": \"koreader\",\n \"manual_data\": null,\n \"\ - apply_to_all_future_conflicts\": false,\n \"reason\": \"User chose more recent\ - \ progress\"" + data: "{\n \"winner\": \"koreader\",\n \"manual_data\": null,\n \"apply_to_all_future_conflicts\": false,\n \"reason\": \"User chose more recent progress\"" headers: - key: Authorization value: Bearer {{token - -docs: |- - ## Resolve Conflict - - Resolves a sync conflict by choosing which source to use for the conflicting data. - - **Method:** POST - - **Endpoint:** /api/conflicts/{conflict_id +docs: "## Resolve Conflict\n\n Resolves a sync conflict by choosing which source to use for the conflicting data.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/{conflict_id" diff --git a/bruno/conflicts/scenarios/Bulk Dismiss Conflicts.yml b/bruno/conflicts/scenarios/Bulk Dismiss Conflicts.yml index 47269ec5..a55d0746 100644 --- a/bruno/conflicts/scenarios/Bulk Dismiss Conflicts.yml +++ b/bruno/conflicts/scenarios/Bulk Dismiss Conflicts.yml @@ -8,42 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"conflict_ids\": [\n \"{{conflictId1" + data: "{\n \"conflict_ids\": [\n \"{{conflictId1" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Dismiss Conflicts - - Dismisses multiple sync conflicts without resolving them. This removes them from the conflict list while leaving the data unchanged. - - **Method:** POST - - **Endpoint:** /api/conflicts/bulk-dismiss - - **Authentication:** Bearer token - - **Request Body:** - - `conflict_ids` (array): Array of conflict UUIDs to dismiss - - **Response:** - - `results` (array): Results for each dismissal - - `total` (number): Total number of conflicts processed - - `success` (number): Number of successfully dismissed conflicts - - `failed` (number): Number of failed dismissals - - **Status Codes:** - - 200: Success - - 400: Invalid request data - - 401: Unauthorized - - 403: Forbidden - - 404: One or more conflicts not found - - 500: Internal server error - - **Example:** - ```json - { - "conflict_ids": ["uuid-1", "uuid-2"] +docs: "## Bulk Dismiss Conflicts\n\n Dismisses multiple sync conflicts without resolving them. This removes them from the conflict list while leaving the data unchanged.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/bulk-dismiss\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `conflict_ids` (array): Array of conflict UUIDs to dismiss\n\n **Response:**\n - `results` (array): Results for each dismissal\n - `total` (number): Total number of conflicts processed\n - `success` (number): Number of successfully dismissed conflicts\n - `failed` (number): Number of failed dismissals\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 404: One or more conflicts not found\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"conflict_ids\": [\"uuid-1\", \"uuid-2\"]" diff --git a/bruno/conflicts/scenarios/Bulk Resolve Conflicts.yml b/bruno/conflicts/scenarios/Bulk Resolve Conflicts.yml index 433505e1..d17e89a3 100644 --- a/bruno/conflicts/scenarios/Bulk Resolve Conflicts.yml +++ b/bruno/conflicts/scenarios/Bulk Resolve Conflicts.yml @@ -8,48 +8,11 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"conflict_ids\": [\n \"{{conflictId1" + data: "{\n \"conflict_ids\": [\n \"{{conflictId1" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Resolve Conflicts - - Resolves multiple sync conflicts in a single request using a specified resolution strategy. - - **Method:** POST - - **Endpoint:** /api/conflicts/bulk-resolve - - **Authentication:** Bearer token - - **Request Body:** - - `conflict_ids` (array): Array of conflict UUIDs to resolve - - `strategy` (string): Resolution strategy - - `most_recent`: Use the most recently updated progress - - `highest_progress`: Use the reading progress with the highest percent read - - `server`: Always prefer server-side data - - `device`: Always prefer device-side data - - **Response:** - - `results` (array): Results for each conflict resolution - - `total` (number): Total number of conflicts processed - - `success` (number): Number of successfully resolved conflicts - - `failed` (number): Number of failed resolutions - - **Status Codes:** - - 200: Success (with partial results if some failed) - - 400: Invalid request data - - 401: Unauthorized - - 403: Forbidden - - 404: One or more conflicts not found - - 500: Internal server error - - **Example:** - ```json - { - "conflict_ids": ["uuid-1", "uuid-2", "uuid-3"], - "strategy": "most_recent" +docs: "## Bulk Resolve Conflicts\n\n Resolves multiple sync conflicts in a single request using a specified resolution strategy.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/bulk-resolve\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `conflict_ids` (array): Array of conflict UUIDs to resolve\n - `strategy` (string): Resolution strategy\n - `most_recent`: Use the most recently updated progress\n - `highest_progress`: Use the reading progress with the highest percent read\n - `server`: Always prefer server-side data\n - `device`: Always prefer device-side data\n\n **Response:**\n - `results` (array): Results for each conflict resolution\n - `total` (number): Total number of conflicts processed\n - `success` (number): Number of successfully resolved conflicts\n - `failed` (number): Number of failed resolutions\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n\ + \ - 403: Forbidden\n - 404: One or more conflicts not found\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"conflict_ids\": [\"uuid-1\", \"uuid-2\", \"uuid-3\"],\n \"strategy\": \"most_recent\"" diff --git a/bruno/conflicts/scenarios/Bulk Resolve Highest Progress.yml b/bruno/conflicts/scenarios/Bulk Resolve Highest Progress.yml index aff45fb6..16b6315c 100644 --- a/bruno/conflicts/scenarios/Bulk Resolve Highest Progress.yml +++ b/bruno/conflicts/scenarios/Bulk Resolve Highest Progress.yml @@ -8,39 +8,11 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"conflict_ids\": [\n \"{{conflictId1" + data: "{\n \"conflict_ids\": [\n \"{{conflictId1" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Resolve with Highest Progress Strategy - - Resolves multiple sync conflicts using the "highest_progress" strategy, which keeps the reading progress with the highest percentage read. - - **Method:** POST - - **Endpoint:** /api/conflicts/bulk-resolve - - **Authentication:** Bearer token - - **Request Body:** - - `conflict_ids` (array): Array of conflict UUIDs to resolve - - `strategy` (string): Must be "highest_progress" - - **Response:** - - `results` (array): Results for each conflict resolution - - `total` (number): Total number of conflicts processed - - `success` (number): Number of successfully resolved conflicts - - `failed` (number): Number of failed resolutions - - **Status Codes:** - - 200: Success - - 400: Invalid request data - - 401: Unauthorized - - 403: Forbidden - - 500: Internal server error - - **Note:** The highest progress strategy is ideal when you want to preserve the most reading progress across devices. Use this when you've been reading on multiple devices and want to keep the furthest position. +docs: "## Bulk Resolve with Highest Progress Strategy\n\n Resolves multiple sync conflicts using the \"highest_progress\" strategy, which keeps the reading progress with the highest percentage read.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/bulk-resolve\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `conflict_ids` (array): Array of conflict UUIDs to resolve\n - `strategy` (string): Must be \"highest_progress\"\n\n **Response:**\n - `results` (array): Results for each conflict resolution\n - `total` (number): Total number of conflicts processed\n - `success` (number): Number of successfully resolved conflicts\n - `failed` (number): Number of failed resolutions\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Note:** The highest progress strategy is ideal when you want to preserve the most reading progress across devices. Use this when you've been\ + \ reading on multiple devices and want to keep the furthest position." diff --git a/bruno/dashboard/restore-system-collection.yml b/bruno/dashboard/restore-system-collection.yml index 7d6f73c4..65d8ee3b 100644 --- a/bruno/dashboard/restore-system-collection.yml +++ b/bruno/dashboard/restore-system-collection.yml @@ -8,40 +8,9 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"collection_name\": \"continue-reading\"\n}" + data: "{\n \"collection_name\": \"continue-reading\"\n}" runtime: scripts: - type: tests - code: "test(\"status must be 200 with valid collection\", function() {\n expect(res.status).to.eql(200);\n\ - });\n\ntest(\"response has success message\", function() {\n expect(res.body.message).to.exist;\n\ - });" - -docs: |- - Restore a system collection to its default state. - - **Endpoint**: POST /api/dashboard/restore-system-collection - **Auth**: Required (Bearer token) - - ## Request Body - - | Field | Type | Required | Description | - |-------|------|----------|-------------| - | collection_name | string | Yes | Name of system collection to restore | - - Valid collection names: - - `continue-reading`: Books you're currently reading (0 < progress < 1) - - `recently-added`: Newly added items to this library - - `recently-read`: Books you've finished (progress >= 1) - - `not-started`: Books you haven't read yet (progress = 0 or no record) - - ## Example Request - - ```json - { - "collection_name": "continue-reading" - } - ``` - - ## Response - - Returns success message on restore. + code: "test(\"status must be 200 with valid collection\", function() {\n expect(res.status).to.eql(200);\n});\n\ntest(\"response has success message\", function() {\n expect(res.body.message).to.exist;\n});" +docs: "Restore a system collection to its default state.\n\n**Endpoint**: POST /api/dashboard/restore-system-collection\n**Auth**: Required (Bearer token)\n\n## Request Body\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| collection_name | string | Yes | Name of system collection to restore |\n\nValid collection names:\n- `continue-reading`: Books you're currently reading (0 < progress < 1)\n- `recently-added`: Newly added items to this library\n- `recently-read`: Books you've finished (progress >= 1)\n- `not-started`: Books you haven't read yet (progress = 0 or no record)\n\n## Example Request\n\n```json\n{\n \"collection_name\": \"continue-reading\"\n}\n```\n\n## Response\n\nReturns success message on restore." diff --git a/bruno/dashboard/update-preferences.yml b/bruno/dashboard/update-preferences.yml index 2c892ba6..6c1f541e 100644 --- a/bruno/dashboard/update-preferences.yml +++ b/bruno/dashboard/update-preferences.yml @@ -8,41 +8,9 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"hidden_collections\"\ - : [\"not-started\"],\n \"collection_order\": [\"recently-added\", \"continue-reading\"\ - , \"recently-read\"],\n \"items_per_section\": 20\n}" + data: "{\n \"library_id\": \"{{library_id}}\",\n \"hidden_collections\": [\"not-started\"],\n \"collection_order\": [\"recently-added\", \"continue-reading\", \"recently-read\"],\n \"items_per_section\": 20\n}" runtime: scripts: - type: tests - code: "test(\"status must be 200 with valid request\", function() {\n expect(res.status).to.eql(200);\n\ - });" - -docs: |- - Update dashboard preferences for the authenticated user. - - **Endpoint**: PUT /api/dashboard/preferences - **Auth**: Required (Bearer token) - - ## Request Body - - | Field | Type | Required | Description | - |-------|------|----------|-------------| - | library_id | string | Yes | Library UUID | - | hidden_collections | array | No | Collection names to hide from dashboard | - | collection_order | array | No | Collection names in custom display order | - | items_per_section | int | No | Items to show per section (default: 20) | - - ## Example Request - - ```json - { - "library_id": "cc23c3a7-f8fb-451a-a78d-2a16df1b725a", - "hidden_collections": ["not-started"], - "collection_order": ["recently-added", "continue-reading", "recently-read"], - "items_per_section": 20 - } - ``` - - ## Response - - Returns updated preferences object on success. + code: "test(\"status must be 200 with valid request\", function() {\n expect(res.status).to.eql(200);\n});" +docs: "Update dashboard preferences for the authenticated user.\n\n**Endpoint**: PUT /api/dashboard/preferences\n**Auth**: Required (Bearer token)\n\n## Request Body\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| library_id | string | Yes | Library UUID |\n| hidden_collections | array | No | Collection names to hide from dashboard |\n| collection_order | array | No | Collection names in custom display order |\n| items_per_section | int | No | Items to show per section (default: 20) |\n\n## Example Request\n\n```json\n{\n \"library_id\": \"cc23c3a7-f8fb-451a-a78d-2a16df1b725a\",\n \"hidden_collections\": [\"not-started\"],\n \"collection_order\": [\"recently-added\", \"continue-reading\", \"recently-read\"],\n \"items_per_section\": 20\n}\n```\n\n## Response\n\nReturns updated preferences object on success." diff --git a/bruno/devices/kobo/Get Analytics Tests.yml b/bruno/devices/kobo/Get Analytics Tests.yml index 32735586..ce261904 100644 --- a/bruno/devices/kobo/Get Analytics Tests.yml +++ b/bruno/devices/kobo/Get Analytics Tests.yml @@ -2,95 +2,16 @@ info: name: Get Analytics Tests type: http seq: 7 - http: method: POST url: '{{base_url}}/api/sync/kobo/{{kobo_device_token}}/v1/analytics/gettests' auth: inherit headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - platform: android - firmware_version: 4.30.19023 - -docs: |- - ## Get Kobo Analytics Tests - - Retrieves A/B testing configuration and feature flags for Kobo device. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/v1/analytics/gettests - - **Authentication:** Bearer token - - **Request Body:** - - `platform` (string): Device platform - - `android`: Kobo Android app - - `kobo`: Native Kobo firmware - - `firmware_version` (string): Firmware version (e.g., "4.30.19023") - - `device_model` (string, optional): Device model identifier - - `locale` (string, optional): Device locale (e.g., "en_US") - - **Response:** - - `tests` (array): Active A/B tests - - `test_name` (string): Test identifier - - `variant` (string): Assigned variant (A, B, C, etc.) - - `enabled` (boolean): Whether test is active - - `parameters` (object): Test-specific parameters - - `features` (object): Feature flags - - `feature_name` (boolean/string): Feature state - - `configuration` (object): Device configuration - - `sync_interval_minutes` (integer): Recommended sync frequency - - `batch_size` (integer): Max items per batch sync - - `timeout_seconds` (integer): Request timeout - - `version` (string): Configuration version - - **Status Codes:** - - 200: Success - - 401: Unauthorized - - 400: Invalid request parameters - - **Analytics Testing Purpose:** - - Kobo uses A/B testing for UX features - - Feature flags for gradual rollout - - Performance monitoring configuration - - Sync behavior optimization - - Device-specific tuning - - **Common Tests:** - - Sync frequency optimization - - UI/UX variations - - Network usage patterns - - Battery life improvements - - Feature set variations by model - - **Feature Flags:** - - New sync features - - Beta functionality - - Platform-specific capabilities - - Experimental features - - **Configuration Parameters:** - - Optimal sync intervals for device - - Batch size limits based on device capabilities - - Timeout values for network conditions - - Retry logic configuration - - Cache policy settings - - **Usage:** - - Called during device initialization - - Refreshed daily or weekly - - Cached on device - - Affects sync behavior - - Can be overridden by server - - **Use Cases:** - - Device initialization - - Feature rollout testing - - Performance optimization - - UX experiment participation - - Configuration management + data: "{\n \"platform\": \"android\",\n \"firmware_version\": \"4.30.19023\"\n}" +docs: "## Get Kobo Analytics Tests\n\nRetrieves A/B testing configuration and feature flags for Kobo device.\n\n**Method:** POST\n\n**Endpoint:** /api/sync/kobo/v1/analytics/gettests\n\n**Authentication:** Bearer token\n\n**Request Body:**\n- `platform` (string): Device platform\n - `android`: Kobo Android app\n - `kobo`: Native Kobo firmware\n- `firmware_version` (string): Firmware version (e.g., \"4.30.19023\")\n- `device_model` (string, optional): Device model identifier\n- `locale` (string, optional): Device locale (e.g., \"en_US\")\n\n**Response:**\n- `tests` (array): Active A/B tests\n - `test_name` (string): Test identifier\n - `variant` (string): Assigned variant (A, B, C, etc.)\n - `enabled` (boolean): Whether test is active\n - `parameters` (object): Test-specific parameters\n- `features` (object): Feature flags\n - `feature_name` (boolean/string): Feature state\n- `configuration` (object): Device configuration\n - `sync_interval_minutes` (integer): Recommended sync frequency\n\ + \ - `batch_size` (integer): Max items per batch sync\n - `timeout_seconds` (integer): Request timeout\n- `version` (string): Configuration version\n\n**Status Codes:**\n- 200: Success\n- 401: Unauthorized\n- 400: Invalid request parameters\n\n**Analytics Testing Purpose:**\n- Kobo uses A/B testing for UX features\n- Feature flags for gradual rollout\n- Performance monitoring configuration\n- Sync behavior optimization\n- Device-specific tuning\n\n**Common Tests:**\n- Sync frequency optimization\n- UI/UX variations\n- Network usage patterns\n- Battery life improvements\n- Feature set variations by model\n\n**Feature Flags:**\n- New sync features\n- Beta functionality\n- Platform-specific capabilities\n- Experimental features\n\n**Configuration Parameters:**\n- Optimal sync intervals for device\n- Batch size limits based on device capabilities\n- Timeout values for network conditions\n- Retry logic configuration\n- Cache policy settings\n\n**Usage:**\n- Called during device initialization\n\ + - Refreshed daily or weekly\n- Cached on device\n- Affects sync behavior\n- Can be overridden by server\n\n**Use Cases:**\n- Device initialization\n- Feature rollout testing\n- Performance optimization\n- UX experiment participation\n- Configuration management" diff --git a/bruno/devices/kobo/scenarios/Auto Link Books.yml b/bruno/devices/kobo/scenarios/Auto Link Books.yml index ecf36901..4446afb8 100644 --- a/bruno/devices/kobo/scenarios/Auto Link Books.yml +++ b/bruno/devices/kobo/scenarios/Auto Link Books.yml @@ -8,38 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"confidence_threshold\": 0.8,\n \"limit\": 50" + data: "{\n \"confidence_threshold\": 0.8,\n \"limit\": 50" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Auto-Link Unlinked Books - - Automatically links unlinked books to media items based on title and author matching with a configurable confidence threshold. - - **Method:** POST - - **Endpoint:** /sync/auto-link-books - - **Authentication:** Bearer token - - **Request Body:** - - `confidence_threshold` (number, optional): Minimum confidence score for auto-linking (0-1, default: 0.8) - - `limit` (number, optional): Maximum number of books to auto-link (default: 50) - - **Response:** - - `results` (array): Results for each auto-link attempt - - `total` (number): Total number of books processed - - `success` (number): Number of successful links - - `failed` (number): Number of failed links - - **Status Codes:** - - 200: Success - - 400: Invalid request data - - 401: Unauthorized - - 500: Internal server error - - **Note:** Higher confidence thresholds produce fewer but more accurate matches. Consider the tradeoff between automation and accuracy. +docs: "## Auto-Link Unlinked Books\n\n Automatically links unlinked books to media items based on title and author matching with a configurable confidence threshold.\n\n **Method:** POST\n\n **Endpoint:** /sync/auto-link-books\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `confidence_threshold` (number, optional): Minimum confidence score for auto-linking (0-1, default: 0.8)\n - `limit` (number, optional): Maximum number of books to auto-link (default: 50)\n\n **Response:**\n - `results` (array): Results for each auto-link attempt\n - `total` (number): Total number of books processed\n - `success` (number): Number of successful links\n - `failed` (number): Number of failed links\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid request data\n - 401: Unauthorized\n - 500: Internal server error\n\n **Note:** Higher confidence thresholds produce fewer but more accurate matches. Consider the tradeoff between automation and accuracy." diff --git a/bruno/devices/kobo/scenarios/Bulk Link Books.yml b/bruno/devices/kobo/scenarios/Bulk Link Books.yml index dc20edde..84950dd1 100644 --- a/bruno/devices/kobo/scenarios/Bulk Link Books.yml +++ b/bruno/devices/kobo/scenarios/Bulk Link Books.yml @@ -8,40 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"links\": [\n {\n \"unlinked_book_id\": \"{{unlinkedBookId1" + data: "{\n \"links\": [\n {\n \"unlinked_book_id\": \"{{unlinkedBookId1" headers: - key: Content-Type value: application/json - key: Authorization value: Bearer {{authToken - -docs: |- - ## Bulk Link Unlinked Books - - Links multiple unlinked books to media items in a single request. - - **Method:** POST - - **Endpoint:** /sync/bulk-link-books - - **Authentication:** Bearer token - - **Request Body:** - - `links` (array): Array of link objects - - `unlinked_book_id` (string): Unlinked book UUID - - `media_item_id` (string): Media item UUID to link to - - `confidence_score` (number): Match confidence (0-1) - - **Response:** - - `results` (array): Results for each link attempt - - `total` (number): Total number of links processed - - `success` (number): Number of successful links - - `failed` (number): Number of failed links - - **Status Codes:** - - 200: Success - - 400: Invalid request data - - 401: Unauthorized - - 500: Internal server error - - **Note:** Use this endpoint after reviewing suggestions from the Get Unlinked Book Suggestions endpoint. +docs: "## Bulk Link Unlinked Books\n\n Links multiple unlinked books to media items in a single request.\n\n **Method:** POST\n\n **Endpoint:** /sync/bulk-link-books\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `links` (array): Array of link objects\n - `unlinked_book_id` (string): Unlinked book UUID\n - `media_item_id` (string): Media item UUID to link to\n - `confidence_score` (number): Match confidence (0-1)\n\n **Response:**\n - `results` (array): Results for each link attempt\n - `total` (number): Total number of links processed\n - `success` (number): Number of successful links\n - `failed` (number): Number of failed links\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid request data\n - 401: Unauthorized\n - 500: Internal server error\n\n **Note:** Use this endpoint after reviewing suggestions from the Get Unlinked Book Suggestions endpoint." diff --git a/bruno/devices/kobo/scenarios/Server Sync to Kobo.yml b/bruno/devices/kobo/scenarios/Server Sync to Kobo.yml index c2323f3d..68f07bc3 100644 --- a/bruno/devices/kobo/scenarios/Server Sync to Kobo.yml +++ b/bruno/devices/kobo/scenarios/Server Sync to Kobo.yml @@ -8,46 +8,9 @@ http: auth: inherit body: type: json - jsonBody: "[\n {\n \"ContentId\": \"{{bookUUID" + data: "[\n {\n \"ContentId\": \"{{bookUUID" headers: - key: Authorization value: Bearer {{koboToken - -docs: |- - ## Sync from Bookhoard to Kobo - - Server-initiated sync pushing progress, bookmarks, and highlights from Bookhoard to Kobo device. Two-way sync endpoint. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/sync-from-server - - **Authentication:** Bearer token with Kobo device identification - - **Headers:** - - `x-kobo-device` (string): JSON string containing Kobo device info - - `DeviceId`: Kobo device ID - - `Model`: Kobo device model - - `SerialNumber`: Kobo device serial number - - **Request Body:** Array of sync data objects - - `ContentId` (string): Book UUID - - `PercentRead` (number): Reading progress percentage (0-100) - - `LastModified` (string): ISO 8601 timestamp - - `Bookmarks` (array, optional): Array of bookmark objects - - `BookmarkId`: Unique bookmark ID - - `ContentId`: Book UUID - - `BookmarkText`: Bookmark text/note - - `BookmarkType`: Type (bookmark, annotation, etc.) - - `BookmarkTitle`: Bookmark title - - `Highlights` (array, optional): Array of highlight objects (same structure as bookmarks) - - **Response:** - - Sync result confirmation - - **Status Codes:** - - 200: Success - - 401: Unauthorized - - 500: Internal server error - - **Note:** Allows Bookhoard server to push updates to Kobo device, including reading progress, bookmarks, and highlights. +docs: "## Sync from Bookhoard to Kobo\n\n Server-initiated sync pushing progress, bookmarks, and highlights from Bookhoard to Kobo device. Two-way sync endpoint.\n\n **Method:** POST\n\n **Endpoint:** /api/sync/kobo/sync-from-server\n\n **Authentication:** Bearer token with Kobo device identification\n\n **Headers:**\n - `x-kobo-device` (string): JSON string containing Kobo device info\n - `DeviceId`: Kobo device ID\n - `Model`: Kobo device model\n - `SerialNumber`: Kobo device serial number\n\n **Request Body:** Array of sync data objects\n - `ContentId` (string): Book UUID\n - `PercentRead` (number): Reading progress percentage (0-100)\n - `LastModified` (string): ISO 8601 timestamp\n - `Bookmarks` (array, optional): Array of bookmark objects\n - `BookmarkId`: Unique bookmark ID\n - `ContentId`: Book UUID\n - `BookmarkText`: Bookmark text/note\n - `BookmarkType`: Type (bookmark, annotation, etc.)\n - `BookmarkTitle`: Bookmark title\n - `Highlights` (array,\ + \ optional): Array of highlight objects (same structure as bookmarks)\n\n **Response:**\n - Sync result confirmation\n\n **Status Codes:**\n - 200: Success\n - 401: Unauthorized\n - 500: Internal server error\n\n **Note:** Allows Bookhoard server to push updates to Kobo device, including reading progress, bookmarks, and highlights." diff --git a/bruno/devices/kobo/scenarios/Sync Books from Server to Kobo.yml b/bruno/devices/kobo/scenarios/Sync Books from Server to Kobo.yml index 0aec3189..f44ad7eb 100644 --- a/bruno/devices/kobo/scenarios/Sync Books from Server to Kobo.yml +++ b/bruno/devices/kobo/scenarios/Sync Books from Server to Kobo.yml @@ -2,105 +2,16 @@ info: name: Sync Books from Server to Kobo type: http seq: 9 - http: method: POST url: '{{base_url}}/api/sync/kobo/{{kobo_device_token}}/sync-from-server' auth: inherit headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - force_sync: true - books: - - book-uuid-1 - - book-uuid-2 - -docs: |- - ## Sync Books from Server to Kobo - - Pulls reading progress, annotations, and bookmarks from server to Kobo device. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/sync-from-server - - **Authentication:** Bearer token - - **Request Body:** - - `force_sync` (boolean): Force full sync (ignore last sync time) - - `books` (array, optional): List of book UUIDs to sync - - If empty, syncs all books with server data - - If specified, only syncs listed books - - `sync_options` (object, optional): - - `since_date` (string): ISO 8601 timestamp for incremental sync - - `include_annotations` (boolean): Include highlights/notes - - `include_progress` (boolean): Include reading progress - - `conflict_resolution` (string): `most_recent`, `server_wins`, `client_wins` - - **Response:** - - `synced` (integer): Number of books synced - - `skipped` (integer): Books with no server changes - - `failed` (integer): Books that failed to sync - - `results` (array): Per-book sync results - - `book_id` (string): Book UUID - - `status` (string): `synced`, `skipped`, `failed` - - `progress_pulled` (boolean): Whether progress was downloaded - - `annotations_pulled` (integer): Number of annotations downloaded - - `error` (string, optional): Error message if failed - - `server_timestamp` (string): Server timestamp of sync - - **Status Codes:** - - 200: Sync completed - - 207: Multi-status (some succeeded, some failed) - - 401: Unauthorized - - 400: Invalid request - - **Sync Direction:** - - **Server โ†’ Device** (this endpoint) - - Device โ†’ Server: Use `/sync/kobo/markup` endpoint - - Bidirectional sync achieved by calling both - - **Pull Sync Use Cases:** - - New device setup (download all progress) - - Device replacement (restore from server) - - Multi-device sync (pull changes from other devices) - - Conflict resolution (server wins) - - Manual "Download from server" operation - - **Conflict Resolution:** - - `most_recent`: Latest timestamp wins (default) - - `server_wins`: Server data always used - - `client_wins`: Device data preserved - - Applied per-book, per-item - - **Sync Optimization:** - - Incremental sync by default (since last sync) - - Force sync does full comparison - - Book-level batching (10 books per batch) - - Delta transfer (only changed items) - - Compression for large annotation sets - - **Kobo Device Behavior:** - - Device updates local database - - Progress reflected in reading view - - Annotations appear in Notebook - - Bookmarks updated in navigation - - Conflict warnings shown to user - - Sync progress displayed on screen - - **Performance:** - - Small sync (1-10 books): 2-5 seconds - - Medium sync (10-50 books): 5-15 seconds - - Large sync (50-200 books): 15-45 seconds - - Timeout: 120 seconds - - **Use Cases:** - - Initial device sync - - After firmware update - - From another device's changes - - Manual sync request - - Conflict recovery - - Data restoration + data: "{\n \"force_sync\": true,\n \"books\": [\n \"book-uuid-1\",\n \"book-uuid-2\"\n ]\n}" +docs: "## Sync Books from Server to Kobo\n\nPulls reading progress, annotations, and bookmarks from server to Kobo device.\n\n**Method:** POST\n\n**Endpoint:** /api/sync/kobo/sync-from-server\n\n**Authentication:** Bearer token\n\n**Request Body:**\n- `force_sync` (boolean): Force full sync (ignore last sync time)\n- `books` (array, optional): List of book UUIDs to sync\n - If empty, syncs all books with server data\n - If specified, only syncs listed books\n- `sync_options` (object, optional):\n - `since_date` (string): ISO 8601 timestamp for incremental sync\n - `include_annotations` (boolean): Include highlights/notes\n - `include_progress` (boolean): Include reading progress\n - `conflict_resolution` (string): `most_recent`, `server_wins`, `client_wins`\n\n**Response:**\n- `synced` (integer): Number of books synced\n- `skipped` (integer): Books with no server changes\n- `failed` (integer): Books that failed to sync\n- `results` (array): Per-book sync results\n - `book_id` (string):\ + \ Book UUID\n - `status` (string): `synced`, `skipped`, `failed`\n - `progress_pulled` (boolean): Whether progress was downloaded\n - `annotations_pulled` (integer): Number of annotations downloaded\n - `error` (string, optional): Error message if failed\n- `server_timestamp` (string): Server timestamp of sync\n\n**Status Codes:**\n- 200: Sync completed\n- 207: Multi-status (some succeeded, some failed)\n- 401: Unauthorized\n- 400: Invalid request\n\n**Sync Direction:**\n- **Server โ†’ Device** (this endpoint)\n- Device โ†’ Server: Use `/sync/kobo/markup` endpoint\n- Bidirectional sync achieved by calling both\n\n**Pull Sync Use Cases:**\n- New device setup (download all progress)\n- Device replacement (restore from server)\n- Multi-device sync (pull changes from other devices)\n- Conflict resolution (server wins)\n- Manual \"Download from server\" operation\n\n**Conflict Resolution:**\n- `most_recent`: Latest timestamp wins (default)\n- `server_wins`: Server data always used\n- `client_wins`:\ + \ Device data preserved\n- Applied per-book, per-item\n\n**Sync Optimization:**\n- Incremental sync by default (since last sync)\n- Force sync does full comparison\n- Book-level batching (10 books per batch)\n- Delta transfer (only changed items)\n- Compression for large annotation sets\n\n**Kobo Device Behavior:**\n- Device updates local database\n- Progress reflected in reading view\n- Annotations appear in Notebook\n- Bookmarks updated in navigation\n- Conflict warnings shown to user\n- Sync progress displayed on screen\n\n**Performance:**\n- Small sync (1-10 books): 2-5 seconds\n- Medium sync (10-50 books): 5-15 seconds\n- Large sync (50-200 books): 15-45 seconds\n- Timeout: 120 seconds\n\n**Use Cases:**\n- Initial device sync\n- After firmware update\n- From another device's changes\n- Manual sync request\n- Conflict recovery\n- Data restoration" diff --git a/bruno/devices/kobo/scenarios/Sync Multiple Books Progress.yml b/bruno/devices/kobo/scenarios/Sync Multiple Books Progress.yml index 8bdf926d..bf4958e1 100644 --- a/bruno/devices/kobo/scenarios/Sync Multiple Books Progress.yml +++ b/bruno/devices/kobo/scenarios/Sync Multiple Books Progress.yml @@ -2,90 +2,17 @@ info: name: Sync Multiple Books Progress type: http seq: 3 - http: method: POST url: '{{base_url}}/api/sync/kobo/{{kobo_device_token}}/markup' auth: inherit headers: - - key: Content-Type - value: application/json - - key: x-kobo-device - value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Aura"}' + - key: Content-Type + value: application/json + - key: x-kobo-device + value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Aura"}' body: type: json - json: - ReadingSync: - - ContentId: book-1-uuid - PercentRead: 25.0 - EntitlementId: entitlement-1 - RemainingTimeMinutes: 240 - FirstReadTime: '2026-01-25T10:00:00Z' - LastModified: '2026-01-30T18:00:00Z' - - ContentId: book-2-uuid - PercentRead: 78.5 - EntitlementId: entitlement-2 - RemainingTimeMinutes: 45 - FirstReadTime: '2026-01-25T14:00:00Z' - LastModified: '2026-01-30T20:00:00Z' - BookmarkSync: [] - -docs: |- - ## Sync Multiple Books Progress - - Synchronizes reading progress for multiple books in a single request. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/markup - - **Authentication:** Bearer token - - **Headers:** - - `Authorization`: Bearer {{kobo_device_token}} - - `x-kobo-device`: Device information (Model: "Kobo Aura") - - **Request Body:** - - `ReadingSync` (array): Multiple progress items - - Each item contains: ContentId, PercentRead, EntitlementId, etc. - - `BookmarkSync` (array): Empty for progress-only batch - - **Response:** - - `total` (integer): Total items in request - - `synced` (integer): Successfully synced - - `failed` (integer): Failed items - - `results` (array): Per-item results - - `ContentId` (string): Book UUID - - `status` (string): `synced`, `failed`, `skipped` - - `error` (string, optional): Error message if failed - - **Status Codes:** - - 200: Batch sync completed - - 207: Multi-status (some succeeded, some failed) - - 401: Unauthorized - - 413: Payload too large (>1MB) - - **Batch Sync Advantages:** - - Efficient sync of entire library - - Reduces HTTP overhead - - Faster for devices with many books - - Atomic operation (all or nothing by default) - - **Kobo Batch Sync Behavior:** - - Triggered when device connects after being offline - - Occurs during manual "Sync now" operation - - Limited to 100 books per request - - Progress updates shown on device - - Failed items retried individually - - **Performance:** - - Typical batch: 10-50 books in 1-3 seconds - - Large batch: 50-100 books in 3-8 seconds - - Timeout: 30 seconds - - Rate limit: 10 batches per minute per device - - **Use Cases:** - - Initial device sync after registration - - Catch-up sync after extended offline period - - Library-wide progress update - - Pre-sync before device firmware update + data: "{\n \"ReadingSync\": [\n {\n \"ContentId\": \"book-1-uuid\",\n \"PercentRead\": 25.0,\n \"EntitlementId\": \"entitlement-1\",\n \"RemainingTimeMinutes\": 240,\n \"FirstReadTime\": \"2026-01-25T10:00:00Z\",\n \"LastModified\": \"2026-01-30T18:00:00Z\"\n },\n {\n \"ContentId\": \"book-2-uuid\",\n \"PercentRead\": 78.5,\n \"EntitlementId\": \"entitlement-2\",\n \"RemainingTimeMinutes\": 45,\n \"FirstReadTime\": \"2026-01-25T14:00:00Z\",\n \"LastModified\": \"2026-01-30T20:00:00Z\"\n }\n ],\n \"BookmarkSync\": []\n}" +docs: "## Sync Multiple Books Progress\n\nSynchronizes reading progress for multiple books in a single request.\n\n**Method:** POST\n\n**Endpoint:** /api/sync/kobo/markup\n\n**Authentication:** Bearer token\n\n**Headers:**\n- `Authorization`: Bearer {{kobo_device_token}}\n- `x-kobo-device`: Device information (Model: \"Kobo Aura\")\n\n**Request Body:**\n- `ReadingSync` (array): Multiple progress items\n - Each item contains: ContentId, PercentRead, EntitlementId, etc.\n- `BookmarkSync` (array): Empty for progress-only batch\n\n**Response:**\n- `total` (integer): Total items in request\n- `synced` (integer): Successfully synced\n- `failed` (integer): Failed items\n- `results` (array): Per-item results\n - `ContentId` (string): Book UUID\n - `status` (string): `synced`, `failed`, `skipped`\n - `error` (string, optional): Error message if failed\n\n**Status Codes:**\n- 200: Batch sync completed\n- 207: Multi-status (some succeeded, some failed)\n- 401: Unauthorized\n- 413: Payload too large\ + \ (>1MB)\n\n**Batch Sync Advantages:**\n- Efficient sync of entire library\n- Reduces HTTP overhead\n- Faster for devices with many books\n- Atomic operation (all or nothing by default)\n\n**Kobo Batch Sync Behavior:**\n- Triggered when device connects after being offline\n- Occurs during manual \"Sync now\" operation\n- Limited to 100 books per request\n- Progress updates shown on device\n- Failed items retried individually\n\n**Performance:**\n- Typical batch: 10-50 books in 1-3 seconds\n- Large batch: 50-100 books in 3-8 seconds\n- Timeout: 30 seconds\n- Rate limit: 10 batches per minute per device\n\n**Use Cases:**\n- Initial device sync after registration\n- Catch-up sync after extended offline period\n- Library-wide progress update\n- Pre-sync before device firmware update" diff --git a/bruno/devices/kobo/scenarios/Sync Progress with Bookmarks.yml b/bruno/devices/kobo/scenarios/Sync Progress with Bookmarks.yml index 87780f8d..78932df4 100644 --- a/bruno/devices/kobo/scenarios/Sync Progress with Bookmarks.yml +++ b/bruno/devices/kobo/scenarios/Sync Progress with Bookmarks.yml @@ -2,83 +2,17 @@ info: name: Sync Progress with Bookmarks type: http seq: 2 - http: method: POST url: '{{base_url}}/api/sync/kobo/{{kobo_device_token}}/markup' auth: inherit headers: - - key: Content-Type - value: application/json - - key: x-kobo-device - value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Clara"}' + - key: Content-Type + value: application/json + - key: x-kobo-device + value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Clara"}' body: type: json - json: - ReadingSync: - - ContentId: book-uuid - PercentRead: 42.3 - EntitlementId: entitlement-id - RemainingTimeMinutes: 138 - FirstReadTime: '2026-01-25T10:00:00Z' - LastModified: '2026-01-30T20:00:00Z' - BookmarkSync: - - ContentId: book-uuid - BookmarkText: highlighted text passage - BookmarkType: annotation - BookmarkTitle: Chapter 3 - -docs: |- - ## Sync Progress with Bookmarks - - Synchronizes reading progress and highlights/annotations from Kobo device. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/markup - - **Authentication:** Bearer token - - **Headers:** - - `Authorization`: Bearer {{kobo_device_token}} - - `x-kobo-device`: Device information JSON - - **Request Body:** - - `ReadingSync` (array): Progress items (see Sync Reading Progress) - - `BookmarkSync` (array): Highlights and annotations - - `ContentId` (string): Book UUID - - `BookmarkText` (string): Highlighted text or note content - - `BookmarkType` (string): Type of bookmark - - `annotation`: Highlighted text - - `note`: Personal note - - `bookmark`: Location bookmark - - `BookmarkTitle` (string): Reference (e.g., chapter name) - - `ChapterID` (string, optional): Chapter identifier - - `DateCreated` (string, optional): Creation timestamp - - **Response:** - - `progress_synced` (integer): Progress items synced - - `bookmarks_synced` (integer): Bookmark items synced - - `conflicts_resolved` (integer): Number of conflicts auto-resolved - - `timestamp` (string): Sync timestamp - - `details` (object): Sync breakdown by type - - **Status Codes:** - - 200: Successful sync - - 401: Unauthorized - - 400: Invalid data - - **Kobo Highlight Features:** - - 5 highlight colors (yellow, green, blue, pink, orange) - - Chapter-based organization - - Linked to reading progress - - Appears in Kobo "Notebook" view - - Can be exported from device - - Syncs across all user devices - - **Annotation Sync:** - - Highlight text preserved exactly - - Color mapped to system colors - - Chapter reference maintained - - Location data converted to standard format - - Notes attached to highlights synced separately + data: "{\n \"ReadingSync\": [\n {\n \"ContentId\": \"book-uuid\",\n \"PercentRead\": 42.3,\n \"EntitlementId\": \"entitlement-id\",\n \"RemainingTimeMinutes\": 138,\n \"FirstReadTime\": \"2026-01-25T10:00:00Z\",\n \"LastModified\": \"2026-01-30T20:00:00Z\"\n }\n ],\n \"BookmarkSync\": [\n {\n \"ContentId\": \"book-uuid\",\n \"BookmarkText\": \"highlighted text passage\",\n \"BookmarkType\": \"annotation\",\n \"BookmarkTitle\": \"Chapter 3\"\n }\n ]\n}" +docs: "## Sync Progress with Bookmarks\n\nSynchronizes reading progress and highlights/annotations from Kobo device.\n\n**Method:** POST\n\n**Endpoint:** /api/sync/kobo/markup\n\n**Authentication:** Bearer token\n\n**Headers:**\n- `Authorization`: Bearer {{kobo_device_token}}\n- `x-kobo-device`: Device information JSON\n\n**Request Body:**\n- `ReadingSync` (array): Progress items (see Sync Reading Progress)\n- `BookmarkSync` (array): Highlights and annotations\n - `ContentId` (string): Book UUID\n - `BookmarkText` (string): Highlighted text or note content\n - `BookmarkType` (string): Type of bookmark\n - `annotation`: Highlighted text\n - `note`: Personal note\n - `bookmark`: Location bookmark\n - `BookmarkTitle` (string): Reference (e.g., chapter name)\n - `ChapterID` (string, optional): Chapter identifier\n - `DateCreated` (string, optional): Creation timestamp\n\n**Response:**\n- `progress_synced` (integer): Progress items synced\n- `bookmarks_synced` (integer): Bookmark\ + \ items synced\n- `conflicts_resolved` (integer): Number of conflicts auto-resolved\n- `timestamp` (string): Sync timestamp\n- `details` (object): Sync breakdown by type\n\n**Status Codes:**\n- 200: Successful sync\n- 401: Unauthorized\n- 400: Invalid data\n\n**Kobo Highlight Features:**\n- 5 highlight colors (yellow, green, blue, pink, orange)\n- Chapter-based organization\n- Linked to reading progress\n- Appears in Kobo \"Notebook\" view\n- Can be exported from device\n- Syncs across all user devices\n\n**Annotation Sync:**\n- Highlight text preserved exactly\n- Color mapped to system colors\n- Chapter reference maintained\n- Location data converted to standard format\n- Notes attached to highlights synced separately" diff --git a/bruno/devices/kobo/scenarios/Sync Reading Progress.yml b/bruno/devices/kobo/scenarios/Sync Reading Progress.yml index c8003ecb..f57bbe4f 100644 --- a/bruno/devices/kobo/scenarios/Sync Reading Progress.yml +++ b/bruno/devices/kobo/scenarios/Sync Reading Progress.yml @@ -2,83 +2,18 @@ info: name: Sync Reading Progress type: http seq: 1 - http: method: POST url: '{{base_url}}/api/sync/kobo/{{kobo_device_token}}/markup' auth: inherit headers: - - key: Content-Type - value: application/json - - key: x-kobo-device - value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Clara"}' + - key: Content-Type + value: application/json + - key: x-kobo-device + value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Clara"}' body: type: json - json: - ReadingSync: - - ContentId: book-uuid-here - PercentRead: 45.6 - EntitlementId: entitlement-id-here - RemainingTimeMinutes: 120 - FirstReadTime: '2026-01-25T10:00:00Z' - LastModified: '2026-01-30T20:00:00Z' - BookmarkSync: [] - -docs: |- - ## Sync Kobo Reading Progress - - Synchronizes reading progress from a Kobo device to the server using Bearer token authentication. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/markup - - **Authentication:** Bearer token (in Authorization header) - - **Headers:** - - `Authorization`: Bearer {{kobo_device_token}} - - `x-kobo-device`: JSON-encoded device info - - `DeviceId`: Kobo device identifier - - `Model`: Device model (e.g., "Kobo Clara", "Kobo Libra", "Kobo Aura") - - **Request Body:** - - `ReadingSync` (array): Reading progress items - - `ContentId` (string): Book/Content UUID - - `PercentRead` (number): Reading progress 0-100 - - `EntitlementId` (string): Kobo entitlement ID - - `RemainingTimeMinutes` (integer): Estimated reading time remaining - - `FirstReadTime` (string): ISO 8601 timestamp when first opened - - `LastModified` (string): ISO 8601 timestamp of last progress update - - `BookmarkSync` (array): Empty array for progress-only sync - - **Response:** - - `synced` (integer): Number of items synced - - `failed` (integer): Number of items that failed to sync - - `timestamp` (string): Server timestamp of sync - - `books` (array): Synced book data - - `ContentId` (string): Book UUID - - `status` (string): `synced`, `failed`, `skipped` - - `server_percent` (number): Server-side progress (for conflict detection) - - **Status Codes:** - - 200: Sync successful - - 401: Invalid device token - - 403: Device not approved - - 400: Invalid request format - - **Authentication Methods:** - This endpoint uses **Bearer token authentication** (token in Authorization header). - Alternative: Use `/sync/kobo/{token}/markup` with token in URL path. - - **Kobo Sync Features:** - - Native Kobo sync protocol - - Supports Kobo Clara, Libra, Aura, Forma, Sage, Elipsa - - Progress percentage tracking - - Reading time estimation - - Cross-device synchronization - - Automatic conflict resolution (most recent wins) - - **Sync Frequency:** - - Kobo devices auto-sync every 15-30 minutes when connected to WiFi - - Manual sync available from device settings - - Sync triggers on: device wake, book close, WiFi connection + data: "{\n \"ReadingSync\": [\n {\n \"ContentId\": \"book-uuid-here\",\n \"PercentRead\": 45.6,\n \"EntitlementId\": \"entitlement-id-here\",\n \"RemainingTimeMinutes\": 120,\n \"FirstReadTime\": \"2026-01-25T10:00:00Z\",\n \"LastModified\": \"2026-01-30T20:00:00Z\"\n }\n ],\n \"BookmarkSync\": []\n}" +docs: "## Sync Kobo Reading Progress\n\nSynchronizes reading progress from a Kobo device to the server using Bearer token authentication.\n\n**Method:** POST\n\n**Endpoint:** /api/sync/kobo/markup\n\n**Authentication:** Bearer token (in Authorization header)\n\n**Headers:**\n- `Authorization`: Bearer {{kobo_device_token}}\n- `x-kobo-device`: JSON-encoded device info\n - `DeviceId`: Kobo device identifier\n - `Model`: Device model (e.g., \"Kobo Clara\", \"Kobo Libra\", \"Kobo Aura\")\n\n**Request Body:**\n- `ReadingSync` (array): Reading progress items\n - `ContentId` (string): Book/Content UUID\n - `PercentRead` (number): Reading progress 0-100\n - `EntitlementId` (string): Kobo entitlement ID\n - `RemainingTimeMinutes` (integer): Estimated reading time remaining\n - `FirstReadTime` (string): ISO 8601 timestamp when first opened\n - `LastModified` (string): ISO 8601 timestamp of last progress update\n- `BookmarkSync` (array): Empty array for progress-only sync\n\n**Response:**\n\ + - `synced` (integer): Number of items synced\n- `failed` (integer): Number of items that failed to sync\n- `timestamp` (string): Server timestamp of sync\n- `books` (array): Synced book data\n - `ContentId` (string): Book UUID\n - `status` (string): `synced`, `failed`, `skipped`\n - `server_percent` (number): Server-side progress (for conflict detection)\n\n**Status Codes:**\n- 200: Sync successful\n- 401: Invalid device token\n- 403: Device not approved\n- 400: Invalid request format\n\n**Authentication Methods:**\nThis endpoint uses **Bearer token authentication** (token in Authorization header).\nAlternative: Use `/sync/kobo/{token}/markup` with token in URL path.\n\n**Kobo Sync Features:**\n- Native Kobo sync protocol\n- Supports Kobo Clara, Libra, Aura, Forma, Sage, Elipsa\n- Progress percentage tracking\n- Reading time estimation\n- Cross-device synchronization\n- Automatic conflict resolution (most recent wins)\n\n**Sync Frequency:**\n- Kobo devices auto-sync every 15-30 minutes\ + \ when connected to WiFi\n- Manual sync available from device settings\n- Sync triggers on: device wake, book close, WiFi connection" diff --git a/bruno/devices/kobo/scenarios/Sync Single Bookmark.yml b/bruno/devices/kobo/scenarios/Sync Single Bookmark.yml index e404311f..cb8e8438 100644 --- a/bruno/devices/kobo/scenarios/Sync Single Bookmark.yml +++ b/bruno/devices/kobo/scenarios/Sync Single Bookmark.yml @@ -2,91 +2,18 @@ info: name: Sync Single Bookmark type: http seq: 5 - http: method: POST url: '{{base_url}}/api/sync/kobo/{{kobo_device_token}}/bookmark' auth: inherit headers: - - key: Content-Type - value: application/json - - key: x-kobo-device - value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Clara"}' + - key: Content-Type + value: application/json + - key: x-kobo-device + value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Clara"}' body: type: json - json: - ContentId: book-uuid - BookmarkText: Bookmarked passage - BookmarkType: annotation - BookmarkTitle: Chapter 3 - -docs: |- - ## Sync Single Bookmark - - Synchronizes an individual bookmark/highlight from Kobo device. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/bookmark - - **Authentication:** Bearer token - - **Headers:** - - `Authorization`: Bearer {{kobo_device_token}} - - `x-kobo-device`: Device information JSON - - **Request Body:** - - `ContentId` (string): Book UUID - - `BookmarkText` (string): Highlighted text or bookmark description - - `BookmarkType` (string): Type of bookmark - - `annotation`: Highlighted text passage - - `note`: Personal note - - `bookmark`: Location marker - - `BookmarkTitle` (string): Reference title (e.g., chapter name) - - `ChapterID` (string, optional): Chapter identifier - - `DateCreated` (string, optional): ISO 8601 timestamp - - `highlight_color` (string, optional): Color name (yellow, green, blue, pink, orange) - - **Response:** - - `id` (string): Server bookmark ID - - `ContentId` (string): Associated book UUID - - `status` (string): `created`, `updated`, `skipped` (duplicate) - - `timestamp` (string): Server timestamp - - `url` (string): API URL to retrieve bookmark - - **Status Codes:** - - 201: Bookmark created - - 200: Bookmark updated (duplicate found) - - 409: Duplicate bookmark (unchanged) - - 401: Unauthorized - - 400: Invalid bookmark data - - **Single Bookmark Sync vs Batch:** - - **Single bookmark endpoint:** Real-time, immediate sync - - **Batch markup endpoint:** Deferred, periodic sync - - Use single when user explicitly creates highlight - - Use batch for periodic background sync - - **Kobo Trigger:** - - User highlights text โ†’ immediate single sync - - User adds note โ†’ immediate single sync - - Device goes online โ†’ batch sync of all changes - - **Duplicate Detection:** - - Same ContentId + similar BookmarkText + same location - - Updates existing if text modified - - Skips if identical bookmark exists - - Preserves creation date of original - - **Use Cases:** - - Real-time highlight sync - - Instant note backup - - Immediate annotation sharing - - Quick single annotation update - - Testing annotation sync - - **Notes:** - - Much faster than full markup sync - - Lower bandwidth usage - - Ideal for intermittent connectivity - - Can be called multiple times safely + data: "{\n \"ContentId\": \"book-uuid\",\n \"BookmarkText\": \"Bookmarked passage\",\n \"BookmarkType\": \"annotation\",\n \"BookmarkTitle\": \"Chapter 3\"\n}" +docs: "## Sync Single Bookmark\n\nSynchronizes an individual bookmark/highlight from Kobo device.\n\n**Method:** POST\n\n**Endpoint:** /api/sync/kobo/bookmark\n\n**Authentication:** Bearer token\n\n**Headers:**\n- `Authorization`: Bearer {{kobo_device_token}}\n- `x-kobo-device`: Device information JSON\n\n**Request Body:**\n- `ContentId` (string): Book UUID\n- `BookmarkText` (string): Highlighted text or bookmark description\n- `BookmarkType` (string): Type of bookmark\n - `annotation`: Highlighted text passage\n - `note`: Personal note\n - `bookmark`: Location marker\n- `BookmarkTitle` (string): Reference title (e.g., chapter name)\n- `ChapterID` (string, optional): Chapter identifier\n- `DateCreated` (string, optional): ISO 8601 timestamp\n- `highlight_color` (string, optional): Color name (yellow, green, blue, pink, orange)\n\n**Response:**\n- `id` (string): Server bookmark ID\n- `ContentId` (string): Associated book UUID\n- `status` (string): `created`, `updated`, `skipped` (duplicate)\n\ + - `timestamp` (string): Server timestamp\n- `url` (string): API URL to retrieve bookmark\n\n**Status Codes:**\n- 201: Bookmark created\n- 200: Bookmark updated (duplicate found)\n- 409: Duplicate bookmark (unchanged)\n- 401: Unauthorized\n- 400: Invalid bookmark data\n\n**Single Bookmark Sync vs Batch:**\n- **Single bookmark endpoint:** Real-time, immediate sync\n- **Batch markup endpoint:** Deferred, periodic sync\n- Use single when user explicitly creates highlight\n- Use batch for periodic background sync\n\n**Kobo Trigger:**\n- User highlights text โ†’ immediate single sync\n- User adds note โ†’ immediate single sync\n- Device goes online โ†’ batch sync of all changes\n\n**Duplicate Detection:**\n- Same ContentId + similar BookmarkText + same location\n- Updates existing if text modified\n- Skips if identical bookmark exists\n- Preserves creation date of original\n\n**Use Cases:**\n- Real-time highlight sync\n- Instant note backup\n- Immediate annotation sharing\n- Quick single annotation\ + \ update\n- Testing annotation sync\n\n**Notes:**\n- Much faster than full markup sync\n- Lower bandwidth usage\n- Ideal for intermittent connectivity\n- Can be called multiple times safely" diff --git a/bruno/devices/kobo/scenarios/Sync with Annotations.yml b/bruno/devices/kobo/scenarios/Sync with Annotations.yml index fead3b28..03d2a093 100644 --- a/bruno/devices/kobo/scenarios/Sync with Annotations.yml +++ b/bruno/devices/kobo/scenarios/Sync with Annotations.yml @@ -2,105 +2,17 @@ info: name: Sync with Annotations type: http seq: 4 - http: method: POST url: '{{base_url}}/api/sync/kobo/{{kobo_device_token}}/markup' auth: inherit headers: - - key: Content-Type - value: application/json - - key: x-kobo-device - value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Libra"}' + - key: Content-Type + value: application/json + - key: x-kobo-device + value: '{"DeviceId":"{{kobo_device_id}}","Model":"Kobo Libra"}' body: type: json - json: - ReadingSync: - - ContentId: book-uuid - PercentRead: 55.0 - EntitlementId: entitlement-id - RemainingTimeMinutes: 120 - FirstReadTime: '2026-01-25T10:00:00Z' - LastModified: '2026-01-30T20:00:00Z' - BookmarkSync: - - ContentId: book-uuid - BookmarkText: Important quote - BookmarkType: annotation - BookmarkTitle: Chapter 4 - The Truth - - ContentId: book-uuid - BookmarkText: Another quote - BookmarkType: annotation - BookmarkTitle: Chapter 5 - - ContentId: book-uuid - BookmarkText: Note to myself - BookmarkType: note - BookmarkTitle: Personal note - -docs: |- - ## Sync with Multiple Annotations - - Synchronizes reading progress with multiple highlights and notes. - - **Method:** POST - - **Endpoint:** /api/sync/kobo/markup - - **Authentication:** Bearer token - - **Headers:** - - `Authorization`: Bearer {{kobo_device_token}} - - `x-kobo-device`: Device info (Model: "Kobo Libra") - - **Request Body:** - - `ReadingSync` (array): Single book progress - - `BookmarkSync` (array): Multiple annotations - - Can include highlights (annotation type) - - Can include notes (note type) - - Each has: ContentId, BookmarkText, BookmarkType, BookmarkTitle - - **Response:** - - `progress_synced` (boolean): Progress update status - - `annotations_synced` (integer): Number of annotations synced - - `highlights_count` (integer): Highlights synced - - `notes_count` (integer): Notes synced - - `conflicts` (array): Any annotation conflicts resolved - - `timestamp` (string): Sync completion time - - **Status Codes:** - - 200: Successful sync - - 401: Unauthorized - - 400: Invalid annotation data - - **Kobo Annotation Types:** - - **Highlights:** Selected text passages - - 5 preset colors available - - Can have chapter titles - - Exportable to PDF/Mobile - - **Notes:** Personal annotations - - Free-form text - - Can be attached to highlights - - Separate from highlights - - **Bookmarks:** Location markers - - Chapter positions - - Quick navigation - - **Sync Behavior:** - - Duplicates detected by content matching - - Most recent edit wins conflicts - - Annotations linked to book content - - Chapter references preserved - - Order maintained from device - - **Kobo Notebook Export:** - - All annotations appear in Kobo "Notebook" - - Can be exported to PDF - - Can be exported to Mobile (text) - - Organized by book - - Shows highlight context - - **Use Cases:** - - Study and research - - Book club discussion prep - - Content review - - Sharing insights - - Personal learning archive + data: "{\n \"ReadingSync\": [\n {\n \"ContentId\": \"book-uuid\",\n \"PercentRead\": 55.0,\n \"EntitlementId\": \"entitlement-id\",\n \"RemainingTimeMinutes\": 120,\n \"FirstReadTime\": \"2026-01-25T10:00:00Z\",\n \"LastModified\": \"2026-01-30T20:00:00Z\"\n }\n ],\n \"BookmarkSync\": [\n {\n \"ContentId\": \"book-uuid\",\n \"BookmarkText\": \"Important quote\",\n \"BookmarkType\": \"annotation\",\n \"BookmarkTitle\": \"Chapter 4 - The Truth\"\n },\n {\n \"ContentId\": \"book-uuid\",\n \"BookmarkText\": \"Another quote\",\n \"BookmarkType\": \"annotation\",\n \"BookmarkTitle\": \"Chapter 5\"\n },\n {\n \"ContentId\": \"book-uuid\",\n \"BookmarkText\": \"Note to myself\",\n \"BookmarkType\": \"note\",\n \"BookmarkTitle\": \"Personal note\"\n }\n ]\n}" +docs: "## Sync with Multiple Annotations\n\nSynchronizes reading progress with multiple highlights and notes.\n\n**Method:** POST\n\n**Endpoint:** /api/sync/kobo/markup\n\n**Authentication:** Bearer token\n\n**Headers:**\n- `Authorization`: Bearer {{kobo_device_token}}\n- `x-kobo-device`: Device info (Model: \"Kobo Libra\")\n\n**Request Body:**\n- `ReadingSync` (array): Single book progress\n- `BookmarkSync` (array): Multiple annotations\n - Can include highlights (annotation type)\n - Can include notes (note type)\n - Each has: ContentId, BookmarkText, BookmarkType, BookmarkTitle\n\n**Response:**\n- `progress_synced` (boolean): Progress update status\n- `annotations_synced` (integer): Number of annotations synced\n- `highlights_count` (integer): Highlights synced\n- `notes_count` (integer): Notes synced\n- `conflicts` (array): Any annotation conflicts resolved\n- `timestamp` (string): Sync completion time\n\n**Status Codes:**\n- 200: Successful sync\n- 401: Unauthorized\n- 400: Invalid\ + \ annotation data\n\n**Kobo Annotation Types:**\n- **Highlights:** Selected text passages\n - 5 preset colors available\n - Can have chapter titles\n - Exportable to PDF/Mobile\n- **Notes:** Personal annotations\n - Free-form text\n - Can be attached to highlights\n - Separate from highlights\n- **Bookmarks:** Location markers\n - Chapter positions\n - Quick navigation\n\n**Sync Behavior:**\n- Duplicates detected by content matching\n- Most recent edit wins conflicts\n- Annotations linked to book content\n- Chapter references preserved\n- Order maintained from device\n\n**Kobo Notebook Export:**\n- All annotations appear in Kobo \"Notebook\"\n- Can be exported to PDF\n- Can be exported to Mobile (text)\n- Organized by book\n- Shows highlight context\n\n**Use Cases:**\n- Study and research\n- Book club discussion prep\n- Content review\n- Sharing insights\n- Personal learning archive" diff --git a/bruno/devices/kobo/scenarios/bookmark-sync.yml b/bruno/devices/kobo/scenarios/bookmark-sync.yml index b4142074..4ed0b4b8 100644 --- a/bruno/devices/kobo/scenarios/bookmark-sync.yml +++ b/bruno/devices/kobo/scenarios/bookmark-sync.yml @@ -8,38 +8,8 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"BookmarkSync\": [\n {\n \"BookmarkId\": \"bookmark_2\"\ - ,\n \"ContentId\": \"kobo_xyz789\",\n \"BookmarkText\": \"Important\ - \ note\",\n \"BookmarkType\": \"bookmark\",\n \"DateCreated\"\ - : \"2026-01-31T12:00:00Z\"" + data: "{\n \"BookmarkSync\": [\n {\n \"BookmarkId\": \"bookmark_2\",\n \"ContentId\": \"kobo_xyz789\",\n \"BookmarkText\": \"Important note\",\n \"BookmarkType\": \"bookmark\",\n \"DateCreated\": \"2026-01-31T12:00:00Z\"" headers: - key: Authorization value: Bearer {{device_token - -docs: |- - ## Kobo Bookmark Sync - - Synchronizes bookmarks from a Kobo device to the Bookhoard server. - - **Method:** POST - - **Endpoint:** /api/v1/kobo/bookmark - - **Authentication:** Bearer token (device token) - - **Request Body:** - - `BookmarkSync` (array): Array of bookmark objects - - `BookmarkId` (string): Unique bookmark ID - - `ContentId` (string): Book/content ID - - `BookmarkText` (string): Bookmark text or note - - `BookmarkType` (string): Type (bookmark, highlight, note) - - `DateCreated` (string): ISO 8601 timestamp - - **Response:** - - `Status` (string): Sync status (Success, Partial) - - `BookmarksSynced` (number): Number of bookmarks synced - - **Status Codes:** - - 200: Success - - 401: Unauthorized - - 500: Internal server error +docs: "## Kobo Bookmark Sync\n\n Synchronizes bookmarks from a Kobo device to the Bookhoard server.\n\n **Method:** POST\n\n **Endpoint:** /api/v1/kobo/bookmark\n\n **Authentication:** Bearer token (device token)\n\n **Request Body:**\n - `BookmarkSync` (array): Array of bookmark objects\n - `BookmarkId` (string): Unique bookmark ID\n - `ContentId` (string): Book/content ID\n - `BookmarkText` (string): Bookmark text or note\n - `BookmarkType` (string): Type (bookmark, highlight, note)\n - `DateCreated` (string): ISO 8601 timestamp\n\n **Response:**\n - `Status` (string): Sync status (Success, Partial)\n - `BookmarksSynced` (number): Number of bookmarks synced\n\n **Status Codes:**\n - 200: Success\n - 401: Unauthorized\n - 500: Internal server error" diff --git a/bruno/devices/kobo/scenarios/link-book.yml b/bruno/devices/kobo/scenarios/link-book.yml index 91e37266..ebb10d5b 100644 --- a/bruno/devices/kobo/scenarios/link-book.yml +++ b/bruno/devices/kobo/scenarios/link-book.yml @@ -8,36 +8,8 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"unlinked_book_id\": \"{{unlinked_book_id" + data: "{\n \"unlinked_book_id\": \"{{unlinked_book_id" headers: - key: Authorization value: Bearer {{user_token - -docs: |- - ## Link Unlinked Book - Manual Resolution - - Manually links an unlinked book to a media item in the library. - - **Method:** POST - - **Endpoint:** /api/sync/link-book - - **Authentication:** Bearer token - - **Request Body:** - - `unlinked_book_id` (string): Unlinked book UUID - - `media_item_id` (string): Media item UUID to link to - - `confidence_score` (number): Match confidence (0-1, 1.0 for manual) - - **Response:** - - `status` (string): Link status (linked) - - `unlinked_book_id` (string): Unlinked book UUID - - `media_item_id` (string): Media item UUID - - `message` (string): Success message - - **Status Codes:** - - 200: Success - book linked - - 400: Invalid request - - 401: Unauthorized - - 404: Book or media item not found - - 500: Internal server error +docs: "## Link Unlinked Book - Manual Resolution\n\n Manually links an unlinked book to a media item in the library.\n\n **Method:** POST\n\n **Endpoint:** /api/sync/link-book\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `unlinked_book_id` (string): Unlinked book UUID\n - `media_item_id` (string): Media item UUID to link to\n - `confidence_score` (number): Match confidence (0-1, 1.0 for manual)\n\n **Response:**\n - `status` (string): Link status (linked)\n - `unlinked_book_id` (string): Unlinked book UUID\n - `media_item_id` (string): Media item UUID\n - `message` (string): Success message\n\n **Status Codes:**\n - 200: Success - book linked\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Book or media item not found\n - 500: Internal server error" diff --git a/bruno/devices/kobo/scenarios/sync-bookmark-url-token.yml b/bruno/devices/kobo/scenarios/sync-bookmark-url-token.yml index 7f1906fc..dcf74757 100644 --- a/bruno/devices/kobo/scenarios/sync-bookmark-url-token.yml +++ b/bruno/devices/kobo/scenarios/sync-bookmark-url-token.yml @@ -8,18 +8,8 @@ http: auth: none body: type: json - jsonBody: "{\n \"ContentId\": \"book-uuid\",\n \"BookmarkText\": \"Highlighted\ - \ text\",\n \"BookmarkType\": \"annotation\",\n \"BookmarkTitle\": \"\ - Chapter 3\"" + data: "{\n \"ContentId\": \"book-uuid\",\n \"BookmarkText\": \"Highlighted text\",\n \"BookmarkType\": \"annotation\",\n \"BookmarkTitle\": \"Chapter 3\"" headers: - key: Content-Type value: application/json - -docs: |- - ## Sync Bookmark - URL Path Token - - Synchronizes bookmarks and annotations from Kobo device using token in URL path. - - **Method:** POST - - **Endpoint:** /sync/kobo/{kobo_device_token +docs: "## Sync Bookmark - URL Path Token\n\n Synchronizes bookmarks and annotations from Kobo device using token in URL path.\n\n **Method:** POST\n\n **Endpoint:** /sync/kobo/{kobo_device_token" diff --git a/bruno/devices/kobo/scenarios/sync-markup-url-token.yml b/bruno/devices/kobo/scenarios/sync-markup-url-token.yml index 5f6ded2b..4fbb3ba8 100644 --- a/bruno/devices/kobo/scenarios/sync-markup-url-token.yml +++ b/bruno/devices/kobo/scenarios/sync-markup-url-token.yml @@ -8,19 +8,8 @@ http: auth: none body: type: json - jsonBody: "{\n \"ReadingSync\": [\n {\n \"ContentId\": \"book-uuid\"\ - ,\n \"PercentRead\": 45.6,\n \"EntitlementId\": \"entitlement-id\"\ - ,\n \"RemainingTimeMinutes\": 120,\n \"FirstReadTime\": \"2026-01-25T10:00:00Z\"\ - ,\n \"LastModified\": \"2026-01-30T20:00:00Z\"" + data: "{\n \"ReadingSync\": [\n {\n \"ContentId\": \"book-uuid\",\n \"PercentRead\": 45.6,\n \"EntitlementId\": \"entitlement-id\",\n \"RemainingTimeMinutes\": 120,\n \"FirstReadTime\": \"2026-01-25T10:00:00Z\",\n \"LastModified\": \"2026-01-30T20:00:00Z\"" headers: - key: Content-Type value: application/json - -docs: |- - ## Sync Reading Progress - URL Path Token - - Synchronizes reading progress from Kobo device using token in URL path. - - **Method:** POST - - **Endpoint:** /sync/kobo/{kobo_device_token +docs: "## Sync Reading Progress - URL Path Token\n\n Synchronizes reading progress from Kobo device using token in URL path.\n\n **Method:** POST\n\n **Endpoint:** /sync/kobo/{kobo_device_token" diff --git a/bruno/devices/koreader/scenarios/Checkpoint Sync.yml b/bruno/devices/koreader/scenarios/Checkpoint Sync.yml index 55662a46..87a84899 100644 --- a/bruno/devices/koreader/scenarios/Checkpoint Sync.yml +++ b/bruno/devices/koreader/scenarios/Checkpoint Sync.yml @@ -3,18 +3,8 @@ docs: "## Checkpoint Sync\n\nIncremental sync using checkpoint-based change trac \ Repeat from step 3\n\n**Sync Optimization:**\n- Only books with progress changes\n- Skips unmodified books\n- Delta transfer\n- Compression for large payloads\n- Batch processing\n\n**Failure Handling:**\n- Checkpoint ID preserved on failure\n- Retry with same checkpoint\n- Full sync if checkpoint expired\n- Checkpoint validity: 24 hours\n- Auto-fallback to full sync\n\n**Performance:**\n- Small changes (1-10 books): 100-300ms\n- Medium changes (10-50 books): 300ms-1s\n- Large changes (50-100 books): 1-3s\n- Typical: 5-10x faster than full sync\n\n**When to Use:**\n- Default sync mode for most users\n- Periodic background sync\n- Large library management\n- Mobile/network-constrained environments\n- Battery-powered devices\n\n**Configuration:**\n- Checkpoint expiration: 24 hours\n- Max history: 100 checkpoints\n- Auto-cleanup of old checkpoints\n- Configurable sync interval" http: body: - json: - books: - - chapter: 3 - percentage: 0.45 - uuid: '{{media_item_id}}' - - chapter: 8 - percentage: 0.75 - uuid: '{{media_item_id}}' - checkpoint_id: checkpoint-uuid - since_timestamp: '2026-01-30T19:00:00Z' - sync_mode: checkpoint type: json + data: "{\n \"books\": [\n {\n \"chapter\": 3,\n \"percentage\": 0.45,\n \"uuid\": \"{{media_item_id}}\"\n },\n {\n \"chapter\": 8,\n \"percentage\": 0.75,\n \"uuid\": \"{{media_item_id}}\"\n }\n ],\n \"checkpoint_id\": \"checkpoint-uuid\",\n \"since_timestamp\": \"2026-01-30T19:00:00Z\",\n \"sync_mode\": \"checkpoint\"\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Immediate Sync - Page Turn.yml b/bruno/devices/koreader/scenarios/Immediate Sync - Page Turn.yml index 12a5d653..0bb05085 100644 --- a/bruno/devices/koreader/scenarios/Immediate Sync - Page Turn.yml +++ b/bruno/devices/koreader/scenarios/Immediate Sync - Page Turn.yml @@ -3,14 +3,8 @@ docs: "## Immediate Sync - Page Turn\n\nReal-time progress sync triggered immedi - Research and study\n- Testing sync functionality\n- When power source available\n\n**When NOT to Use:**\n- Battery conservation needed\n- Unstable network\n- Extended reading sessions\n- Background sync preferred" http: body: - json: - books: - - chapter: 3 - percentage: 0.45678 - timestamp: '2026-01-30T20:00:00Z' - uuid: '{{media_item_id}}' - sync_mode: immediate type: json + data: "{\n \"books\": [\n {\n \"chapter\": 3,\n \"percentage\": 0.45678,\n \"timestamp\": \"2026-01-30T20:00:00Z\",\n \"uuid\": \"{{media_item_id}}\"\n }\n ],\n \"sync_mode\": \"immediate\"\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Sync Annotations (Per-Book SHA-256).yml b/bruno/devices/koreader/scenarios/Sync Annotations (Per-Book SHA-256).yml index 7bc649a6..3789d99d 100644 --- a/bruno/devices/koreader/scenarios/Sync Annotations (Per-Book SHA-256).yml +++ b/bruno/devices/koreader/scenarios/Sync Annotations (Per-Book SHA-256).yml @@ -1,8 +1,8 @@ docs: "## KOReader Sync Annotations - Per-Book SHA-256\n\n Synchronizes annotations (highlights) from KOReader with per-annotation SHA-256 hashes for multi-book sync.\n\n **Method:** POST\n\n **Endpoint:** /api/v1/koreader/sync/bookmarks\n\n **Authentication:** Bearer token (KOReader device token)\n\n **Request Body:**\n - `book_uuid` (string): Primary book UUID\n - `highlights` (array): Array of highlight objects\n - `text` (string): Highlighted text\n - `pos0`, `pos1` (string): EPUB CFI positions\n - `color` (string): Highlight color (hex)\n - `page` (number): Page number\n - `book_sha256` (string): SHA-256 hash for this specific book\n\n **Response:**\n - `highlights_synced` (number): Number of highlights synced\n\n **Status Codes:**\n - 200: Success\n - 401: Unauthorized\n - 500: Internal server error\n\n **Note:** Each highlight can include its own book_sha256, allowing annotations from multiple books in a single request." http: body: - jsonBody: "{\n \"book_uuid\": \"{{book_uuid" type: json + data: "{\n \"book_sha256\": \"{{book_sha256}}\",\n \"bookmarks\": [\n {\n \"chapter\": 3,\n \"datetime\": \"2026-01-30T19:55:00Z\",\n \"page\": 45,\n \"pos0\": \"p1\",\n \"text\": \"Per-book annotation\",\n \"type\": \"bookmark\"\n }\n ]\n}" headers: [] method: POST url: '{{base_url}}/api/sync/koreader/bookmarks' diff --git a/bruno/devices/koreader/scenarios/Sync Bookmarks (SHA-256).yml b/bruno/devices/koreader/scenarios/Sync Bookmarks (SHA-256).yml index 4febdebe..a163783a 100644 --- a/bruno/devices/koreader/scenarios/Sync Bookmarks (SHA-256).yml +++ b/bruno/devices/koreader/scenarios/Sync Bookmarks (SHA-256).yml @@ -1,8 +1,8 @@ docs: "## KOReader Sync Bookmarks - SHA-256\n\n Synchronizes bookmarks, notes, and highlights from KOReader using SHA-256 hash for book identification.\n\n **Method:** POST\n\n **Endpoint:** /api/v1/koreader/sync/bookmarks\n\n **Authentication:** Bearer token (KOReader device token)\n\n **Request Body:**\n - `book_sha256` (string): SHA-256 hash of book file\n - `bookmarks` (array): Array of bookmarks\n - `notes` (array): Array of notes\n - `highlights` (array): Array of highlights\n\n **Response:**\n - `sync_status` (string): Sync status\n - `total_synced` (number): Total items synced\n\n **Status Codes:**\n - 200: Success\n - 401: Unauthorized\n - 500: Internal server error" http: body: - jsonBody: "{\n \"book_sha256\": \"{{book_sha256" type: json + data: "{\n \"book_sha256\": \"{{book_sha256}}\",\n \"bookmarks\": [\n {\n \"chapter\": 3,\n \"datetime\": \"2026-01-30T19:55:00Z\",\n \"page\": 45,\n \"pos0\": \"p1\",\n \"text\": \"SHA-256 bookmark\",\n \"type\": \"bookmark\"\n }\n ]\n}" headers: [] method: POST url: '{{base_url}}/api/sync/koreader/bookmarks' diff --git a/bruno/devices/koreader/scenarios/Sync Bookmarks.yml b/bruno/devices/koreader/scenarios/Sync Bookmarks.yml index 71a47ef9..d2ddc5a2 100644 --- a/bruno/devices/koreader/scenarios/Sync Bookmarks.yml +++ b/bruno/devices/koreader/scenarios/Sync Bookmarks.yml @@ -3,20 +3,8 @@ docs: "## Sync KOReader Bookmarks\n\nSynchronizes bookmarks separately from prog \ bookmark\" menu\n- Shown in \"Bookmarks\" panel\n- Quick access via \"Go to bookmark\"\n- Exportable to JSON\n- Can be edited/deleted\n\n**Use Cases:**\n- Quick navigation aids\n- Chapter markers\n- Important passages\n- Reading progress points\n- Study session markers" http: body: - json: - books: - - bookmarks: - - chapter: 3 - datetime: '2026-01-30T19:55:00Z' - notes: Marked this chapter as important - page: 45 - pos0: epubcfi(/6/4/2:15) - pos1: epubcfi(/6/4/2:20) - text: Important passage - type: bookmark - uuid: '{{media_item_id}}' - library_id: optional-library-uuid type: json + data: "{\n \"book_uuid\": \"{{media_item_id}}\",\n \"bookmarks\": [\n {\n \"chapter\": 3,\n \"datetime\": \"2026-01-30T19:55:00Z\",\n \"page\": 45,\n \"pos0\": \"epubcfi(/6/4/2:15)\",\n \"pos1\": \"epubcfi(/6/4/2:20)\",\n \"text\": \"Important passage\",\n \"type\": \"bookmark\"\n }\n ]\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Sync Highlights.yml b/bruno/devices/koreader/scenarios/Sync Highlights.yml index 988cf138..06f7389e 100644 --- a/bruno/devices/koreader/scenarios/Sync Highlights.yml +++ b/bruno/devices/koreader/scenarios/Sync Highlights.yml @@ -4,10 +4,7 @@ docs: "## Sync KOReader Highlights\n\nSynchronizes text highlights separately fr http: body: type: json - jsonBody: "{\n \"book_uuid\": \"786b9c39-e8c3-47ab-a48a-c7f6cd769930\",\n \"highlights\": [\n {\n \"datetime\": \"2026-01-30T19:50:00Z\",\n \"text\": \"bruno sweep highlight\",\n \"chapter\": 1,\n \"page\": 1,\n \"pos0\": \"1\",\n \"pos1\": \"9\",\n \"color\": \"#ffff00\"\n }\n ]\n}" - headers: - - key: Content-Type - value: application/json + data: "{\n \"book_uuid\": \"786b9c39-e8c3-47ab-a48a-c7f6cd769930\",\n \"highlights\": [\n {\n \"datetime\": \"2026-01-30T19:50:00Z\",\n \"text\": \"bruno sweep highlight\",\n \"chapter\": 1,\n \"page\": 1,\n \"pos0\": \"1\",\n \"pos1\": \"9\",\n \"color\": \"#ffff00\"\n }\n ]\n}" method: POST url: '{{base_url}}/api/sync/koreader/bookmarks' auth: diff --git a/bruno/devices/koreader/scenarios/Sync Notes.yml b/bruno/devices/koreader/scenarios/Sync Notes.yml index 7baa8cf5..49fbe1ac 100644 --- a/bruno/devices/koreader/scenarios/Sync Notes.yml +++ b/bruno/devices/koreader/scenarios/Sync Notes.yml @@ -3,8 +3,8 @@ docs: "## Sync KOReader Notes\n\nSynchronizes user notes separately from highlig \ via \"Add note\" option\n- Edited in note editor\n- Shown in \"Notes\" panel\n- Can be organized by chapter\n- Export functionality available\n- Search support\n\n**Advanced Features:**\n- **Markdown**: Bold, italic, lists (some versions)\n- **Tags**: User-defined tags (via plugins)\n- **Links**: Internal/external links\n- **Formatting**: Rich text in newer versions\n\n**Use Cases:**\n- Study notes\n- Personal reflections\n- Academic annotations\n- Research insights\n- Book club discussion prep\n- Knowledge management" http: body: - jsonBody: "{\n \"book_uuid\": \"{{media_item_id}}\",\n \"notes\": [\n {\n \"datetime\": \"2026-01-30T19:52:00Z\",\n \"notes\": \"A personal note about this chapter\",\n \"chapter\": 4,\n \"page\": 57,\n \"pos0\": \"3\"\n }\n ]\n}" type: json + data: "{\n \"book_uuid\": \"{{media_item_id}}\",\n \"notes\": [\n {\n \"datetime\": \"2026-01-30T19:52:00Z\",\n \"notes\": \"A personal note about this chapter\",\n \"chapter\": 4,\n \"page\": 57,\n \"pos0\": \"3\"\n }\n ]\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Sync Progress (SHA-256 Only).yml b/bruno/devices/koreader/scenarios/Sync Progress (SHA-256 Only).yml index d94c1c1c..5048eb3f 100644 --- a/bruno/devices/koreader/scenarios/Sync Progress (SHA-256 Only).yml +++ b/bruno/devices/koreader/scenarios/Sync Progress (SHA-256 Only).yml @@ -1,8 +1,8 @@ docs: "## KOReader Sync Progress - SHA-256 Only\n\n Synchronizes reading progress using only SHA-256 hash for book identification (when UUID is not available).\n\n **Method:** POST\n\n **Endpoint:** /api/v1/koreader/sync/progress\n\n **Authentication:** Bearer token (KOReader device token)\n\n **Request Body:**\n - `sync_mode` (string): Sync mode (immediate, deferred)\n - `books` (array): Array of book progress objects\n - `sha256` (string): SHA-256 hash of book file\n - `file_path` (string): Path to book file\n - `percentage` (number): Progress percentage\n - `page` (number): Current page\n - `total_pages` (number): Total pages\n\n **Response:**\n - `sync_status` (string): Sync status\n - `books_synced` (number): Number of books synced\n\n **Status Codes:**\n - 200: Success\n - 202: Accepted\n - 401: Unauthorized\n - 500: Internal server error\n\n **Note:** Use this when book UUID is not available, falling back to SHA-256 hash for identification." http: body: - jsonBody: "{\n \"sync_mode\": \"immediate\",\n \"books\": [\n {\n \"sha256\": \"{{book_sha256" type: json + data: "{\n \"sync_mode\": \"immediate\",\n \"books\": [\n {\n \"sha256\": \"{{book_sha256}}\",\n \"percentage\": 0.5\n }\n ]\n}" headers: [] method: POST url: '{{base_url}}/api/sync/koreader/progress' diff --git a/bruno/devices/koreader/scenarios/Sync Progress (SHA-256).yml b/bruno/devices/koreader/scenarios/Sync Progress (SHA-256).yml index e67df8ef..bbce1278 100644 --- a/bruno/devices/koreader/scenarios/Sync Progress (SHA-256).yml +++ b/bruno/devices/koreader/scenarios/Sync Progress (SHA-256).yml @@ -2,8 +2,8 @@ docs: "## KOReader Sync Progress - SHA-256\n\n Synchronizes reading progress fr \ (number): Number of books synced\n\n **Status Codes:**\n - 200: Success\n - 202: Accepted - processing\n - 401: Unauthorized\n - 500: Internal server error" http: body: - jsonBody: "{\n \"sync_mode\": \"immediate\",\n \"books\": [\n {\n \"uuid\": \"{{book_uuid" type: json + data: "{\n \"sync_mode\": \"immediate\",\n \"books\": [\n {\n \"sha256\": \"{{book_sha256}}\",\n \"percentage\": 0.5\n }\n ]\n}" headers: [] method: POST url: '{{base_url}}/api/sync/koreader/progress' diff --git a/bruno/devices/koreader/scenarios/Sync Progress - Multiple Books.yml b/bruno/devices/koreader/scenarios/Sync Progress - Multiple Books.yml index 0c317ee1..5a81b6ad 100644 --- a/bruno/devices/koreader/scenarios/Sync Progress - Multiple Books.yml +++ b/bruno/devices/koreader/scenarios/Sync Progress - Multiple Books.yml @@ -2,28 +2,8 @@ docs: "## Sync KOReader Progress - Multiple Books\n\nSynchronizes reading progre \ Batch Behavior:**\n- Triggered on device wake from sleep\n- Occurs during \"Sync now\" operation\n- Runs on WiFi connection\n- Limited to 100 books per request\n- Automatic retry on failed books\n\n**Performance:**\n- Small batch (2-10 books): 200-500ms\n- Medium batch (10-50 books): 500ms-2s\n- Large batch (50-100 books): 2-5s\n- Timeout: 30 seconds\n\n**Use Cases:**\n- Device initialization sync\n- Periodic background sync\n- Post-offline catch-up sync\n- Library-wide progress update\n- Before firmware update" http: body: - json: - books: - - authors: - - Author One - chapter: 1 - epubcfi: epubcfi(/6/4/2:10) - last_read: '2026-01-30T19:00:00Z' - percentage: 0.25 - progress: 0.25 - title: Book One - uuid: '{{media_item_id}}' - - authors: - - Author Two - chapter: 8 - epubcfi: epubcfi(/6/4/2:50) - last_read: '2026-01-30T20:00:00Z' - percentage: 0.75 - progress: 0.75 - title: Book Two - uuid: '{{media_item_id}}' - library_id: optional-library-uuid type: json + data: "{\n \"books\": [\n {\n \"authors\": [\n \"Author One\"\n ],\n \"chapter\": 1,\n \"epubcfi\": \"epubcfi(/6/4/2:10)\",\n \"last_read\": \"2026-01-30T19:00:00Z\",\n \"percentage\": 0.25,\n \"progress\": 0.25,\n \"title\": \"Book One\",\n \"uuid\": \"{{media_item_id}}\"\n },\n {\n \"authors\": [\n \"Author Two\"\n ],\n \"chapter\": 8,\n \"epubcfi\": \"epubcfi(/6/4/2:50)\",\n \"last_read\": \"2026-01-30T20:00:00Z\",\n \"percentage\": 0.75,\n \"progress\": 0.75,\n \"title\": \"Book Two\",\n \"uuid\": \"{{media_item_id}}\"\n }\n ],\n \"library_id\": \"optional-library-uuid\"\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Sync Progress - Single Book.yml b/bruno/devices/koreader/scenarios/Sync Progress - Single Book.yml index 0f58bf95..597cecdd 100644 --- a/bruno/devices/koreader/scenarios/Sync Progress - Single Book.yml +++ b/bruno/devices/koreader/scenarios/Sync Progress - Single Book.yml @@ -3,20 +3,8 @@ docs: "## Sync KOReader Progress - Single Book\n\nSynchronizes reading progress - Real-time page turn sync\n- Progress backup\n- Cross-device continuity\n- Reading time tracking\n- Chapter completion detection" http: body: - json: - books: - - authors: - - Author Name - chapter: 3 - character: 15432 - epubcfi: epubcfi(/6/4/2:15) - last_read: '2026-01-30T20:00:00Z' - percentage: 0.45 - progress: 0.45 - title: Book Title - uuid: '{{media_item_id}}' - library_id: optional-library-uuid type: json + data: "{\n \"books\": [\n {\n \"authors\": [\n \"Author Name\"\n ],\n \"chapter\": 3,\n \"character\": 15432,\n \"epubcfi\": \"epubcfi(/6/4/2:15)\",\n \"last_read\": \"2026-01-30T20:00:00Z\",\n \"percentage\": 0.45,\n \"progress\": 0.45,\n \"title\": \"Book Title\",\n \"uuid\": \"{{media_item_id}}\"\n }\n ],\n \"library_id\": \"optional-library-uuid\"\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Sync Progress - With Bookmarks.yml b/bruno/devices/koreader/scenarios/Sync Progress - With Bookmarks.yml index 4396a27d..6f631245 100644 --- a/bruno/devices/koreader/scenarios/Sync Progress - With Bookmarks.yml +++ b/bruno/devices/koreader/scenarios/Sync Progress - With Bookmarks.yml @@ -3,26 +3,8 @@ docs: "## Sync Progress with Bookmarks\n\nSynchronizes reading progress with boo - Searchable by content\n\n**Use Cases:**\n- Study and research\n- Content review\n- Passage tracking\n- Quick navigation\n- Cross-device bookmark access" http: body: - json: - books: - - authors: - - Author Name - bookmarks: - - chapter: 3 - datetime: '2026-01-30T19:55:00Z' - notes: highlighted text - page: 45 - pos0: epubcfi(/6/4/2:15) - pos1: epubcfi(/6/4/2:20) - text: highlighted text excerpt - type: highlight - last_read: '2026-01-30T20:00:00Z' - percentage: 0.45 - progress: 0.45 - title: Book Title - uuid: '{{media_item_id}}' - library_id: optional-library-uuid type: json + data: "{\n \"books\": [\n {\n \"authors\": [\n \"Author Name\"\n ],\n \"bookmarks\": [\n {\n \"chapter\": 3,\n \"datetime\": \"2026-01-30T19:55:00Z\",\n \"notes\": \"highlighted text\",\n \"page\": 45,\n \"pos0\": \"epubcfi(/6/4/2:15)\",\n \"pos1\": \"epubcfi(/6/4/2:20)\",\n \"text\": \"highlighted text excerpt\",\n \"type\": \"highlight\"\n }\n ],\n \"last_read\": \"2026-01-30T20:00:00Z\",\n \"percentage\": 0.45,\n \"progress\": 0.45,\n \"title\": \"Book Title\",\n \"uuid\": \"{{media_item_id}}\"\n }\n ],\n \"library_id\": \"optional-library-uuid\"\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Sync Progress - With Highlights and Notes.yml b/bruno/devices/koreader/scenarios/Sync Progress - With Highlights and Notes.yml index d03101a4..0d1248e3 100644 --- a/bruno/devices/koreader/scenarios/Sync Progress - With Highlights and Notes.yml +++ b/bruno/devices/koreader/scenarios/Sync Progress - With Highlights and Notes.yml @@ -3,29 +3,8 @@ docs: "## Sync Progress with Highlights and Notes\n\nSynchronizes reading progre \ passages\n- Blue: Key concepts\n- Red: Critical information\n- Orange: Interesting quotes\n- Custom colors available\n\n**Sync Advantages:**\n- Separation allows granular control\n- Highlights sync without notes\n- Notes sync independently\n- Better conflict resolution\n- Efficient for large annotation sets\n\n**Use Cases:**\n- Academic research\n- Study groups\n- Content analysis\n- Personal knowledge management\n- Sharing insights" http: body: - json: - books: - - authors: - - Author Name - highlights: - - chapter: 4 - datetime: '2026-01-30T19:50:00Z' - page_end: 79 - page_start: 78 - pos0: epubcfi(/6/4/2:20) - pos1: epubcfi(/6/4/2:30) - text: Important passage - last_read: '2026-01-30T20:00:00Z' - notes: - - chapter: 4 - datetime: '2026-01-30T19:52:00Z' - text: My note about this chapter - percentage: 0.6 - progress: 0.6 - title: Book Title - uuid: '{{media_item_id}}' - library_id: optional-library-uuid type: json + data: "{\n \"books\": [\n {\n \"authors\": [\n \"Author Name\"\n ],\n \"highlights\": [\n {\n \"chapter\": 4,\n \"datetime\": \"2026-01-30T19:50:00Z\",\n \"page_end\": 79,\n \"page_start\": 78,\n \"pos0\": \"epubcfi(/6/4/2:20)\",\n \"pos1\": \"epubcfi(/6/4/2:30)\",\n \"text\": \"Important passage\"\n }\n ],\n \"last_read\": \"2026-01-30T20:00:00Z\",\n \"notes\": [\n {\n \"chapter\": 4,\n \"datetime\": \"2026-01-30T19:52:00Z\",\n \"text\": \"My note about this chapter\"\n }\n ],\n \"percentage\": 0.6,\n \"progress\": 0.6,\n \"title\": \"Book Title\",\n \"uuid\": \"{{media_item_id}}\"\n }\n ],\n \"library_id\": \"optional-library-uuid\"\n}" headers: - key: Content-Type value: application/json diff --git a/bruno/devices/koreader/scenarios/Sync Progress.yml b/bruno/devices/koreader/scenarios/Sync Progress.yml index 57ad9945..19c8a271 100644 --- a/bruno/devices/koreader/scenarios/Sync Progress.yml +++ b/bruno/devices/koreader/scenarios/Sync Progress.yml @@ -2,8 +2,8 @@ docs: "## KOReader Sync Progress\n\n Synchronizes reading progress from a KORea \ model identifier\n\n **Response:**\n - `sync_status` (string): Sync status (accepted, processing)\n - `books_synced` (number): Number of books synced\n\n **Status Codes:**\n - 202: Accepted - sync queued\n - 401: Unauthorized\n - 500: Internal server error" http: body: - jsonBody: "{\n \"library_id\": null,\n \"books\": [\n {\n \"uuid\": \"{{book_uuid" type: json + data: "{\n \"sync_mode\": \"immediate\",\n \"books\": [\n {\n \"uuid\": \"{{media_item_id}}\",\n \"percentage\": 0.45,\n \"chapter\": 3\n }\n ]\n}" headers: [] method: POST url: '{{base_url}}/api/sync/koreader/progress' diff --git a/bruno/devices/scenarios/Add Books to Kobo Shelf.yml b/bruno/devices/scenarios/Add Books to Kobo Shelf.yml index 3cb6cb40..37c2f819 100644 --- a/bruno/devices/scenarios/Add Books to Kobo Shelf.yml +++ b/bruno/devices/scenarios/Add Books to Kobo Shelf.yml @@ -8,16 +8,8 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"media_item_ids\": [\n \"{{bookUUID1" + data: "{\n \"media_item_ids\": [\n \"{{bookUUID1" headers: - key: Authorization value: Bearer {{userToken - -docs: |- - ## Add Books to Kobo Shelf - - Add one or more books to a Kobo device shelf. Manages which books should be synced to a specific Kobo device. Supports multiple shelves for organization. - - **Method:** POST - - **Endpoint:** /api/devices/{deviceID +docs: "## Add Books to Kobo Shelf\n\n Add one or more books to a Kobo device shelf. Manages which books should be synced to a specific Kobo device. Supports multiple shelves for organization.\n\n **Method:** POST\n\n **Endpoint:** /api/devices/{deviceID" diff --git a/bruno/devices/scenarios/Check Pending Registration.yml b/bruno/devices/scenarios/Check Pending Registration.yml index 5f519a3e..d25f141e 100644 --- a/bruno/devices/scenarios/Check Pending Registration.yml +++ b/bruno/devices/scenarios/Check Pending Registration.yml @@ -2,51 +2,15 @@ info: name: Check Pending Registration type: http seq: 4 - http: method: POST url: '{{base_url}}/api/devices/register/status' auth: none headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - registration_id: registration-uuid-here - -docs: |- - ## Check Registration Status - - Checks the current status of a device registration request. - - **Method:** POST - - **Endpoint:** /api/devices/register/status - - **Authentication:** None - - **Request Body:** - - `registration_id` (string): UUID received from registration request - - **Response:** - - `registration_id` (string): The registration UUID - - `status` (string): Current status - - `pending`: Awaiting user approval - - `approved`: Registration approved, device ready - - `rejected`: Registration rejected by user - - `expired`: Registration expired (not approved in time) - - `device_name` (string): Name of the device - - `device_type` (string): Type of device - - `created_at` (string): Registration timestamp - - `updated_at` (string): Last status update timestamp - - **Status Codes:** - - 200: Status retrieved successfully - - 404: Registration ID not found - - 400: Invalid registration ID format - - **Polling Recommendations:** - - Poll every 5-10 seconds while status is `pending` - - Stop polling when status changes to `approved`, `rejected`, or `expired` - - Use exponential backoff for mobile devices to save battery + data: "{\n \"registration_id\": \"registration-uuid-here\"\n}" +docs: "## Check Registration Status\n\nChecks the current status of a device registration request.\n\n**Method:** POST\n\n**Endpoint:** /api/devices/register/status\n\n**Authentication:** None\n\n**Request Body:**\n- `registration_id` (string): UUID received from registration request\n\n**Response:**\n- `registration_id` (string): The registration UUID\n- `status` (string): Current status\n - `pending`: Awaiting user approval\n - `approved`: Registration approved, device ready\n - `rejected`: Registration rejected by user\n - `expired`: Registration expired (not approved in time)\n- `device_name` (string): Name of the device\n- `device_type` (string): Type of device\n- `created_at` (string): Registration timestamp\n- `updated_at` (string): Last status update timestamp\n\n**Status Codes:**\n- 200: Status retrieved successfully\n- 404: Registration ID not found\n- 400: Invalid registration ID format\n\n**Polling Recommendations:**\n- Poll every 5-10 seconds while status is `pending`\n\ + - Stop polling when status changes to `approved`, `rejected`, or `expired`\n- Use exponential backoff for mobile devices to save battery" diff --git a/bruno/devices/scenarios/Check Registration Status.yml b/bruno/devices/scenarios/Check Registration Status.yml index 2b826d94..4030c259 100644 --- a/bruno/devices/scenarios/Check Registration Status.yml +++ b/bruno/devices/scenarios/Check Registration Status.yml @@ -8,30 +8,5 @@ http: auth: none body: type: json - jsonBody: "{\n \"registration_id\": \"{{registrationId}}" - -docs: |- - ## Check Registration Status - - Checks the current status of a device registration request. - - **Method:** POST - - **Endpoint:** /api/devices/register/status - - **Authentication:** None - - **Request Body:** - - `registration_id` (string, required): Registration request ID - - **Response:** - - `status` (string): Registration status (pending, approved, rejected, expired) - - `device_name` (string): Device name - - `device_type` (string): Device type - - `created_at` (string): Request timestamp - - `expires_at` (string): Expiration timestamp - - **Status Codes:** - - 200: Success - - 404: Registration ID not found - - 410: Registration expired + data: "{\n \"registration_id\": \"{{registrationId}}" +docs: "## Check Registration Status\n\n Checks the current status of a device registration request.\n\n **Method:** POST\n\n **Endpoint:** /api/devices/register/status\n\n **Authentication:** None\n\n **Request Body:**\n - `registration_id` (string, required): Registration request ID\n\n **Response:**\n - `status` (string): Registration status (pending, approved, rejected, expired)\n - `device_name` (string): Device name\n - `device_type` (string): Device type\n - `created_at` (string): Request timestamp\n - `expires_at` (string): Expiration timestamp\n\n **Status Codes:**\n - 200: Success\n - 404: Registration ID not found\n - 410: Registration expired" diff --git a/bruno/devices/scenarios/Create Device File Alias.yml b/bruno/devices/scenarios/Create Device File Alias.yml index 5963f1df..2d40e18c 100644 --- a/bruno/devices/scenarios/Create Device File Alias.yml +++ b/bruno/devices/scenarios/Create Device File Alias.yml @@ -8,48 +8,12 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"file_path\": \"/mnt/sd/books/my-book.kepub.epub\",\n \"\ - media_item_id\": \"{{media_item_id" + data: "{\n \"file_path\": \"/mnt/sd/books/my-book.kepub.epub\",\n \"media_item_id\": \"{{media_item_id" headers: - key: Content-Type value: application/json runtime: scripts: - type: tests - code: "test_create_device_file_alias_success(status, headers, body) {\n if\ - \ (status !== 201 && status !== 200) {\n throw new Error(\"Expected status\ - \ 201 or 200, got \" + status);" - -docs: |- - ## Create Device File Alias - - Creates a new file alias for a device. File aliases map device-specific file paths to media items. - - **Method:** POST - - **Endpoint:** /api/devices/:id/file-aliases - - **Authentication:** Required (Bearer token) - - **Path Parameters:** - - `id` (string, required): Device UUID - - **Request Body:** - - `file_path` (string, required): Device-specific file path - - `media_item_id` (string, required): Media item UUID to link to - - **Response:** Created file alias object - - `id` (string): Alias UUID - - `device_id` (string): Device UUID - - `file_path` (string): Device-specific file path - - `media_item_id` (string): Associated media item UUID - - `created_at` (string): Creation timestamp - - `updated_at` (string): Last update timestamp - - **Status Codes:** - - 201: Created - - 200: Success - - 400: Invalid request body - - 401: Unauthorized - - 404: Device not found - - 500: Internal server error + code: "test_create_device_file_alias_success(status, headers, body) {\n if (status !== 201 && status !== 200) {\n throw new Error(\"Expected status 201 or 200, got \" + status);" +docs: "## Create Device File Alias\n\n Creates a new file alias for a device. File aliases map device-specific file paths to media items.\n\n **Method:** POST\n\n **Endpoint:** /api/devices/:id/file-aliases\n\n **Authentication:** Required (Bearer token)\n\n **Path Parameters:**\n - `id` (string, required): Device UUID\n\n **Request Body:**\n - `file_path` (string, required): Device-specific file path\n - `media_item_id` (string, required): Media item UUID to link to\n\n **Response:** Created file alias object\n - `id` (string): Alias UUID\n - `device_id` (string): Device UUID\n - `file_path` (string): Device-specific file path\n - `media_item_id` (string): Associated media item UUID\n - `created_at` (string): Creation timestamp\n - `updated_at` (string): Last update timestamp\n\n **Status Codes:**\n - 201: Created\n - 200: Success\n - 400: Invalid request body\n - 401: Unauthorized\n - 404: Device not found\n - 500: Internal server error" diff --git a/bruno/devices/scenarios/Disable Device Sync.yml b/bruno/devices/scenarios/Disable Device Sync.yml index ea34ca37..acb05639 100644 --- a/bruno/devices/scenarios/Disable Device Sync.yml +++ b/bruno/devices/scenarios/Disable Device Sync.yml @@ -2,57 +2,77 @@ info: name: Disable Device Sync type: http seq: 8 - http: method: PUT url: '{{base_url}}/api/devices/{{device_id}}' auth: inherit headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - device_name: My Kobo Clara - sync_enabled: false - auto_sync: false - sync_frequency_minutes: 30 + data: "{\n \"device_name\": \"My Kobo Clara\",\n \"sync_enabled\": false,\n \"auto_sync\": false,\n \"sync_frequency_minutes\": 30\n}" +docs: '## Disable Device Sync -docs: |- - ## Disable Device Sync Disables synchronization for a specific device. + **Method:** PUT + **Endpoint:** /api/devices/:device_id + **Authentication:** Required (Bearer token) + **Path Parameters:** + - `device_id` (string): UUID of the device + **Request Body:** + - `device_name` (string, optional): Device name + - `sync_enabled` (boolean): Must be `false` + - `auto_sync` (boolean): Should be `false` + - `sync_frequency_minutes` (integer, optional): Any value (sync disabled) + **Response:** + - `id` (string): Device UUID + - `device_name` (string): Device name + - `sync_enabled` (boolean): `false` + - `auto_sync` (boolean): `false` + - `message` (string): Confirmation message + **Status Codes:** + - 200: Sync disabled successfully + - 401: Unauthorized + - 403: Forbidden + - 404: Device not found + **Use Cases:** + - Temporarily disable sync for troubleshooting + - Stop sync for a lost or stolen device + - Disable sync before selling or giving away device - - Prevent data usage on limited connections + + - Prevent data usage on limited connections' diff --git a/bruno/devices/scenarios/Initiate Device Registration.yml b/bruno/devices/scenarios/Initiate Device Registration.yml index 78678626..7a802734 100644 --- a/bruno/devices/scenarios/Initiate Device Registration.yml +++ b/bruno/devices/scenarios/Initiate Device Registration.yml @@ -8,32 +8,5 @@ http: auth: none body: type: json - jsonBody: "{\n \"device_name\": \"My Kindle Paperwhite\",\n \"device_type\"\ - : \"koreader\",\n \"device_identifier\": \"kindle-pw5-hardware-id-12345\"" - -docs: |- - ## Initiate Device Registration - - Initiates a new device registration request that requires admin approval. - - **Method:** POST - - **Endpoint:** /api/devices/register - - **Authentication:** None (open endpoint) - - **Request Body:** - - `device_name` (string, required): Human-readable device name (max 100 chars) - - `device_type` (string, required): Device type (kobo, koreader, kindle, etc.) - - `device_identifier` (string, required): Unique hardware ID (max 200 chars) - - **Response:** - - `registration_id` (string): Unique registration request ID - - `status` (string): Initial status (pending) - - `expires_at` (string): Expiration timestamp (usually 24 hours) - - `message` (string): Informational message - - **Status Codes:** - - 201: Registration initiated - - 400: Invalid request - - 409: Device already registered + data: "{\n \"device_name\": \"My Kindle Paperwhite\",\n \"device_type\": \"koreader\",\n \"device_identifier\": \"kindle-pw5-hardware-id-12345\"" +docs: "## Initiate Device Registration\n\n Initiates a new device registration request that requires admin approval.\n\n **Method:** POST\n\n **Endpoint:** /api/devices/register\n\n **Authentication:** None (open endpoint)\n\n **Request Body:**\n - `device_name` (string, required): Human-readable device name (max 100 chars)\n - `device_type` (string, required): Device type (kobo, koreader, kindle, etc.)\n - `device_identifier` (string, required): Unique hardware ID (max 200 chars)\n\n **Response:**\n - `registration_id` (string): Unique registration request ID\n - `status` (string): Initial status (pending)\n - `expires_at` (string): Expiration timestamp (usually 24 hours)\n - `message` (string): Informational message\n\n **Status Codes:**\n - 201: Registration initiated\n - 400: Invalid request\n - 409: Device already registered" diff --git a/bruno/devices/scenarios/Register Device.yml b/bruno/devices/scenarios/Register Device.yml index 1a432d45..e4bdcd7d 100644 --- a/bruno/devices/scenarios/Register Device.yml +++ b/bruno/devices/scenarios/Register Device.yml @@ -2,48 +2,62 @@ info: name: Register Device type: http seq: 1 - http: method: POST url: '{{base_url}}/api/devices/register' auth: none headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - device_name: My Kobo Clara - device_type: kobo - device_identifier: N1234567890123 + data: "{\n \"device_name\": \"My Kobo Clara\",\n \"device_type\": \"kobo\",\n \"device_identifier\": \"N1234567890123\"\n}" +docs: '## Register Device -docs: |- - ## Register Device Initiates device registration by sending device information to the server. + **Method:** POST + **Endpoint:** /api/devices/register + **Authentication:** None (public endpoint) + **Request Body:** + - `device_name` (string): Human-readable name for the device + - `device_type` (string): Type of device - `kobo`, `koreader`, or `web` + - `device_identifier` (string): Unique device identifier (serial number or other ID) + **Response:** + - `registration_id` (string): UUID for tracking registration status + - `status` (string): Registration status - `pending`, `approved`, or `rejected` + - `message` (string): Status message + **Status Codes:** + - 201: Registration initiated successfully + - 400: Invalid request data + - 409: Device already registered + **Notes:** + - Device registration requires user approval before activation + - Registration ID should be stored for status checking - - Device identifier must be unique per device + + - Device identifier must be unique per device' diff --git a/bruno/devices/scenarios/Register KOReader Device.yml b/bruno/devices/scenarios/Register KOReader Device.yml index a4b087e4..95cc9f35 100644 --- a/bruno/devices/scenarios/Register KOReader Device.yml +++ b/bruno/devices/scenarios/Register KOReader Device.yml @@ -2,47 +2,60 @@ info: name: Register KOReader Device type: http seq: 2 - http: method: POST url: '{{base_url}}/api/devices/register' auth: none headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - device_name: My Kindle Paperwhite - device_type: koreader - device_identifier: G090GP123456789 + data: "{\n \"device_name\": \"My Kindle Paperwhite\",\n \"device_type\": \"koreader\",\n \"device_identifier\": \"G090GP123456789\"\n}" +docs: '## Register KOReader Device -docs: |- - ## Register KOReader Device Registers a KOReader device (typically Kindle devices running KOReader software). + **Method:** POST + **Endpoint:** /api/devices/register + **Authentication:** None (public endpoint) + **Request Body:** + - `device_name` (string): Human-readable name for the device + - `device_type` (string): Must be `koreader` + - `device_identifier` (string): Kindle serial number or unique device ID + **Response:** + - `registration_id` (string): UUID for tracking registration status + - `status` (string): Registration status - `pending` + - `message` (string): Confirmation message + **Status Codes:** + - 201: Registration initiated + - 400: Invalid device type or data + **KOReader Notes:** + - KOReader runs on Kindle, Kobo, PocketBook, and Android devices + - Device identifier is typically the device serial number - - Supports bookmarks, progress, highlights, and annotations sync + + - Supports bookmarks, progress, highlights, and annotations sync' diff --git a/bruno/devices/scenarios/Register Web Device.yml b/bruno/devices/scenarios/Register Web Device.yml index f2b85c95..a1397ec6 100644 --- a/bruno/devices/scenarios/Register Web Device.yml +++ b/bruno/devices/scenarios/Register Web Device.yml @@ -2,48 +2,62 @@ info: name: Register Web Device type: http seq: 3 - http: method: POST url: '{{base_url}}/api/devices/register' auth: none headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - device_name: Chrome Browser - device_type: web - device_identifier: web-client-abc123 + data: "{\n \"device_name\": \"Chrome Browser\",\n \"device_type\": \"web\",\n \"device_identifier\": \"web-client-abc123\"\n}" +docs: '## Register Web Device -docs: |- - ## Register Web Device Registers a web browser client for reading progress sync. + **Method:** POST + **Endpoint:** /api/devices/register + **Authentication:** None (public endpoint) + **Request Body:** + - `device_name` (string): Browser identification (e.g., "Chrome Browser") + - `device_type` (string): Must be `web` + - `device_identifier` (string): Unique browser/client identifier + **Response:** + - `registration_id` (string): UUID for tracking + - `status` (string): `pending` or `auto-approved` + - `message` (string): Status message + **Status Codes:** + - 201: Registration successful + - 400: Invalid request + **Web Device Notes:** + - Web devices use localStorage for device identification + - May be auto-approved without admin intervention + - Used for browser-based reading progress tracking - - Supports manual progress updates, highlights, and notes + + - Supports manual progress updates, highlights, and notes' diff --git a/bruno/devices/scenarios/Update Device Settings.yml b/bruno/devices/scenarios/Update Device Settings.yml index 6f52b968..c4bed14e 100644 --- a/bruno/devices/scenarios/Update Device Settings.yml +++ b/bruno/devices/scenarios/Update Device Settings.yml @@ -2,60 +2,83 @@ info: name: Update Device Settings type: http seq: 7 - http: method: PUT url: '{{base_url}}/api/devices/{{device_id}}' auth: inherit headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - device_name: Updated Device Name - sync_enabled: true - auto_sync: true - sync_frequency_minutes: 10 + data: "{\n \"device_name\": \"Updated Device Name\",\n \"sync_enabled\": true,\n \"auto_sync\": true,\n \"sync_frequency_minutes\": 10\n}" +docs: '## Update Device Settings -docs: |- - ## Update Device Settings Updates device configuration and sync settings. + **Method:** PUT + **Endpoint:** /api/devices/:device_id + **Authentication:** Required (Bearer token) + **Path Parameters:** + - `device_id` (string): UUID of the device + **Request Body:** + - `device_name` (string, optional): New device name + - `sync_enabled` (boolean, optional): Enable/disable sync + - `auto_sync` (boolean, optional): Enable automatic sync + - `sync_frequency_minutes` (integer, optional): Sync interval (5-1440 minutes) + **Response:** + - `id` (string): Device UUID + - `device_name` (string): Updated device name + - `sync_enabled` (boolean): Updated sync status + - `auto_sync` (boolean): Updated auto-sync setting + - `sync_frequency_minutes` (integer): Updated sync interval + - `updated_at` (string): Update timestamp + **Status Codes:** + - 200: Settings updated successfully + - 400: Invalid sync frequency or data + - 401: Unauthorized + - 403: Forbidden (device belongs to different user) + - 404: Device not found + **Sync Frequency Guidelines:** + - Mobile devices: 10-30 minutes + - E-readers on WiFi: 15-60 minutes + - Web clients: 5-15 minutes + - Minimum: 5 minutes - - Maximum: 1440 minutes (24 hours) + + - Maximum: 1440 minutes (24 hours)' diff --git a/bruno/devices/scenarios/Update Device.yml b/bruno/devices/scenarios/Update Device.yml index 55286a85..49a980b4 100644 --- a/bruno/devices/scenarios/Update Device.yml +++ b/bruno/devices/scenarios/Update Device.yml @@ -8,35 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"device_name\": \"My Updated Kindle\",\n \"sync_enabled\"\ - : true,\n \"auto_sync\": true,\n \"sync_frequency_minutes\": 10" - -docs: |- - ## Update Device - - Updates device settings and synchronization preferences. - - **Method:** PUT - - **Endpoint:** /api/devices/{device_id} - - **Authentication:** Required (Bearer token, admin or device owner) - - **Path Parameters:** - - `device_id` (string): Device ID - - **Request Body:** - - `device_name` (string, optional): New device name (max 100 chars) - - `sync_enabled` (boolean, optional): Enable/disable synchronization - - `auto_sync` (boolean, optional): Enable automatic synchronization - - `sync_frequency_minutes` (integer, optional): Sync frequency in minutes (5-1440) - - **Response:** - - Updated device object - - **Status Codes:** - - 200: Success - - 400: Invalid request - - 401: Unauthorized - - 403: Forbidden (not device owner) - - 404: Device not found + data: "{\n \"device_name\": \"My Updated Kindle\",\n \"sync_enabled\": true,\n \"auto_sync\": true,\n \"sync_frequency_minutes\": 10" +docs: "## Update Device\n\n Updates device settings and synchronization preferences.\n\n **Method:** PUT\n\n **Endpoint:** /api/devices/{device_id}\n\n **Authentication:** Required (Bearer token, admin or device owner)\n\n **Path Parameters:**\n - `device_id` (string): Device ID\n\n **Request Body:**\n - `device_name` (string, optional): New device name (max 100 chars)\n - `sync_enabled` (boolean, optional): Enable/disable synchronization\n - `auto_sync` (boolean, optional): Enable automatic synchronization\n - `sync_frequency_minutes` (integer, optional): Sync frequency in minutes (5-1440)\n\n **Response:**\n - Updated device object\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 403: Forbidden (not device owner)\n - 404: Device not found" diff --git a/bruno/devices/scenarios/Update Sync Frequency.yml b/bruno/devices/scenarios/Update Sync Frequency.yml index 53b88dfc..2ea32e56 100644 --- a/bruno/devices/scenarios/Update Sync Frequency.yml +++ b/bruno/devices/scenarios/Update Sync Frequency.yml @@ -2,65 +2,92 @@ info: name: Update Sync Frequency type: http seq: 9 - http: method: PUT url: '{{base_url}}/api/devices/{{device_id}}' auth: inherit headers: - - key: Content-Type - value: application/json + - key: Content-Type + value: application/json body: type: json - json: - device_name: My Kobo Clara - sync_enabled: true - auto_sync: true - sync_frequency_minutes: 15 + data: "{\n \"device_name\": \"My Kobo Clara\",\n \"sync_enabled\": true,\n \"auto_sync\": true,\n \"sync_frequency_minutes\": 15\n}" +docs: '## Update Sync Frequency -docs: |- - ## Update Sync Frequency Changes how often the device automatically syncs with the server. + **Method:** PUT + **Endpoint:** /api/devices/:device_id + **Authentication:** Required (Bearer token) + **Path Parameters:** + - `device_id` (string): UUID of the device + **Request Body:** + - `device_name` (string, optional): Device name + - `sync_enabled` (boolean): Must be `true` for auto-sync + - `auto_sync` (boolean): Must be `true` + - `sync_frequency_minutes` (integer): New sync interval (5-1440) + **Response:** + - `id` (string): Device UUID + - `sync_enabled` (boolean): `true` + - `auto_sync` (boolean): `true` + - `sync_frequency_minutes` (integer): Updated interval + - `next_sync_at` (string): Estimated next sync time + - `updated_at` (string): Update timestamp + **Status Codes:** + - 200: Frequency updated successfully + - 400: Invalid frequency value + - 401: Unauthorized + - 403: Forbidden + - 404: Device not found + **Recommended Frequencies:** + - **Active reading:** 5-10 minutes + - **Normal usage:** 15-30 minutes + - **Occasional reading:** 30-60 minutes + - **Battery saving:** 60+ minutes + - **WiFi only (3G):** 30-60 minutes + **Notes:** + - More frequent sync = more battery usage + - Sync only occurs when device is online - - Manual sync can be triggered anytime regardless of frequency + + - Manual sync can be triggered anytime regardless of frequency' diff --git a/bruno/environments/Bookhoard.yml b/bruno/environments/Bookhoard.yml index d374e855..8643c49e 100644 --- a/bruno/environments/Bookhoard.yml +++ b/bruno/environments/Bookhoard.yml @@ -40,3 +40,7 @@ variables: name: comic_library_id - secret: true name: manga_library_id + - secret: true + name: book_sha256 + - secret: true + name: book_uuid diff --git a/bruno/saved-filters/Create Saved Filter.yml b/bruno/saved-filters/Create Saved Filter.yml index 8fab964b..1d7612b5 100644 --- a/bruno/saved-filters/Create Saved Filter.yml +++ b/bruno/saved-filters/Create Saved Filter.yml @@ -8,107 +8,7 @@ http: auth: inherit body: type: json - jsonBody: "{\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 - - Creates a new saved filter for the authenticated user. - - **Method:** POST - - **Endpoint:** /api/saved-filters - - **Authentication:** Required (Bearer token) - - **Request Body:** - ```json - { - "name": "My Sci-Fi Books", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction", - "sort": "title ASC", - "author_filter": "", - "series_filter": "" - } - } - ``` - - **Required Fields:** - - `name` (string, required): Filter name - - Must be unique per user + resource type combination - - Maximum 100 characters - - Cannot be empty - - `resource_type` (string, required): Type of resource to filter - - Examples: "media-items", "collections", "devices" - - Must match valid resource types - - `filters` (object, required): Key-value pairs of filter criteria - - Flexible structure - any valid filter fields - - Serialized as JSONB in database - - **Common Filter Fields for media-items:** - - `search`: General search term - - `author_filter`: Filter by author name - - `genre_filter`: Filter by genre - - `series_filter`: Filter by series - - `language_filter`: Filter by language - - `year_min`: Minimum copyright year - - `year_max`: Maximum copyright year - - `has_cover`: Boolean for cover image - - `sort`: Sort order (e.g., "title ASC", "created_at DESC") - - **Response:** - ```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:** - - 201: Created - Filter successfully created - - 400: Bad Request - Invalid request body or missing required fields - - 401: Unauthorized - Invalid or missing authentication token - - 409: Conflict - Filter with this name already exists for this resource type - - **Error Response Examples:** - - Duplicate name error: - ```json - { - "error": "filter with name 'My Sci-Fi Books' already exists for this resource type" - } - ``` - - **Example Usage:** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "My Sci-Fi Books", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction", - "sort": "title ASC" - } - }' \ - "{{base_url}}/api/saved-filters" - ``` - - **Validation Rules:** - - Name must be unique per user + resource type - - Name cannot be empty or whitespace only - - Resource type must be valid - - Filters object must be provided (can be empty object {}) + 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 {})" diff --git a/bruno/saved-filters/Update Saved Filter.yml b/bruno/saved-filters/Update Saved Filter.yml index 6d5cfc2a..ae606fc3 100644 --- a/bruno/saved-filters/Update Saved Filter.yml +++ b/bruno/saved-filters/Update Saved Filter.yml @@ -8,110 +8,7 @@ http: auth: inherit body: type: json - jsonBody: "{\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 - - Updates an existing saved filter's name and/or filter criteria. - - **Method:** PUT - - **Endpoint:** /api/saved-filters/{filter_id} - - **Authentication:** Required (Bearer token) - - **Path Parameters:** - - `filter_id` (string, required): UUID of the filter to update - - Must be a valid UUID - - Must belong to the authenticated user - - **Request Body:** - ```json - { - "name": "Updated Sci-Fi Books", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction Fantasy", - "sort": "author ASC", - "year_min": "2000", - "year_max": "2024" - } - } - ``` - - **Required Fields:** - - `name` (string, required): New filter name - - Must be unique per user + resource type (excluding current filter) - - Maximum 100 characters - - `resource_type` (string, required): Resource type (must match existing) - - Cannot change resource type after creation - - `filters` (object, required): Updated filter criteria - - Replaces existing filters entirely - - **Response:** - ```json - { - "id": "550e8400-e29b-41d4-a716-446655440000", - "name": "Updated Sci-Fi Books", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction Fantasy", - "sort": "author ASC", - "year_min": "2000", - "year_max": "2024" - }, - "created_at": "2024-03-20T12:00:00Z", - "updated_at": "2024-03-20T15:30:00Z" - } - ``` - - **Status Codes:** - - 200: Success - Filter updated successfully - - 400: Bad Request - Invalid request body or filter ID - - 401: Unauthorized - Invalid or missing authentication token - - 404: Not Found - Filter doesn't exist or doesn't belong to user - - 409: Conflict - New name conflicts with existing filter - - **Error Response Examples:** - - Filter not found: - ```json - { - "error": "filter not found or access denied" - } - ``` - - Duplicate name: - ```json - { - "error": "filter with name 'Updated Sci-Fi Books' already exists for this resource type" - } - ``` - - **Example Usage:** - ```bash - curl -X PUT \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Updated Sci-Fi Books", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction Fantasy", - "sort": "author ASC", - "year_min": "2000" - } - }' \ - "{{base_url}}/api/saved-filters/{{filter_id}}" - ``` - - **Important Notes:** - - You can only update filters that belong to you - - The `updated_at` timestamp is automatically updated - - Cannot change the resource_type (it's immutable) - - New name must not conflict with other filters you own for the same resource type - - All filter fields are replaced entirely (partial updates not supported) + 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)" diff --git a/bruno/saved-filters/scenarios/Complete CRUD Workflow.yml b/bruno/saved-filters/scenarios/Complete CRUD Workflow.yml index 4b8c9f9c..5744ddff 100644 --- a/bruno/saved-filters/scenarios/Complete CRUD Workflow.yml +++ b/bruno/saved-filters/scenarios/Complete CRUD Workflow.yml @@ -8,238 +8,11 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"name\": \"Complete Workflow Test\",\n \"resource_type\"\ - : \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\"\ - ,\n \"sort\": \"title ASC\"\n }\n }" - -docs: |- - ## Complete CRUD Workflow - - End-to-end test demonstrating the complete lifecycle of a saved filter: Create โ†’ Read โ†’ Update โ†’ Delete. - - **Workflow Steps:** - - **Step 1: CREATE a new filter** - ```bash - CREATE_RESPONSE=$(curl -s -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Complete Workflow Test", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction", - "sort": "title ASC" - } - }' \ - "{{base_url}}/api/saved-filters") - - echo "Create Response:" - echo $CREATE_RESPONSE | jq '.' - - # Extract filter ID for subsequent requests - FILTER_ID=$(echo $CREATE_RESPONSE | jq -r '.id') - echo "Created filter ID: $FILTER_ID" - - # Expected Status: 201 Created - # Expected Response: - # { - # "id": "550e8400-e29b-41d4-a716-446655440000", - # "name": "Complete Workflow Test", - # "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" - # } - ``` - - **Step 2: READ the created filter (list all)** - ```bash - # List all filters for media-items - LIST_RESPONSE=$(curl -s \ - -H "Authorization: Bearer YOUR_TOKEN" \ - "{{base_url}}/api/saved-filters?resource_type=media-items") - - echo "List Response:" - echo $LIST_RESPONSE | jq '.' - - # Verify our filter is in the list - FILTER_EXISTS=$(echo $LIST_RESPONSE | jq --arg id "$FILTER_ID" \ - '.[] | select(.id == $id) | .id') - - if [ "$FILTER_EXISTS" == "$FILTER_ID" ]; then - echo "โœ“ Filter found in list" - else - echo "โœ— Filter NOT found in list" - fi - - # Expected Status: 200 OK - # Expected Response: Array containing our newly created filter - ``` - - **Step 3: UPDATE the filter** - ```bash - # Update filter name and criteria - UPDATE_RESPONSE=$(curl -s -X PUT \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Complete Workflow Test - Updated", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction Fantasy", - "sort": "author ASC", - "year_min": "2000" - } - }' \ - "{{base_url}}/api/saved-filters/$FILTER_ID") - - echo "Update Response:" - echo $UPDATE_RESPONSE | jq '.' - - # Verify updated_at timestamp changed - UPDATED_AT=$(echo $UPDATE_RESPONSE | jq -r '.updated_at') - echo "Filter updated at: $UPDATED_AT" - - # Expected Status: 200 OK - # Expected Response: - # { - # "id": "550e8400-e29b-41d4-a716-446655440000", - # "name": "Complete Workflow Test - Updated", - # "resource_type": "media-items", - # "filters": { - # "genre_filter": "Science Fiction Fantasy", - # "sort": "author ASC", - # "year_min": "2000" - # }, - # "created_at": "2024-03-20T12:00:00Z", - # "updated_at": "2024-03-20T12:05:00Z" # NEW TIMESTAMP - # } - ``` - - **Step 4: DELETE the filter** - ```bash - # Delete the filter - DELETE_RESPONSE=$(curl -s -X DELETE \ - -H "Authorization: Bearer YOUR_TOKEN" \ - "{{base_url}}/api/saved-filters/$FILTER_ID") - - echo "Delete Response:" - echo $DELETE_RESPONSE - - # Expected Status: 204 No Content - # Expected Response: (empty body) - if [ -z "$DELETE_RESPONSE" ]; then - echo "โœ“ Filter deleted successfully (empty response)" - fi - ``` - - **Step 5: VERIFY deletion** - ```bash - # Try to get the deleted filter - GET_RESPONSE=$(curl -s \ - -H "Authorization: Bearer YOUR_TOKEN" \ - "{{base_url}}/api/saved-filters?resource_type=media-items") - - echo "List After Delete:" - echo $GET_RESPONSE | jq '.' - - # Verify filter is NOT in the list - FILTER_EXISTS=$(echo $GET_RESPONSE | jq --arg id "$FILTER_ID" \ - '.[] | select(.id == $id) | .id') - - if [ -z "$FILTER_EXISTS" ]; then - echo "โœ“ Filter successfully deleted (not found in list)" - else - echo "โœ— Filter still exists (deletion failed)" - fi - - # Expected: Filter no longer appears in list - ``` - - **Shell Script Complete Example:** - ```bash - #!/bin/bash - - set -e # Exit on error - - BASE_URL="{{base_url}}" - TOKEN="YOUR_TOKEN" - - echo "=== Complete CRUD Workflow Test ===" - echo "" - - # 1. CREATE - echo "Step 1: Creating filter..." - FILTER_ID=$(curl -s -X POST \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Workflow Test", - "resource_type": "media-items", - "filters": {"genre_filter": "Science Fiction"} - }' \ - "$BASE_URL/api/saved-filters" | jq -r '.id') - - echo "โœ“ Created filter: $FILTER_ID" - echo "" - - # 2. READ - echo "Step 2: Reading filters..." - curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/saved-filters?resource_type=media-items" | \ - jq --arg id "$FILTER_ID" '.[] | select(.id == $id)' - - echo "โœ“ Filter retrieved" - echo "" - - # 3. UPDATE - echo "Step 3: Updating filter..." - curl -s -X PUT \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Workflow Test - Updated", - "resource_type": "media-items", - "filters": {"genre_filter": "Fantasy"} - }' \ - "$BASE_URL/api/saved-filters/$FILTER_ID" | jq '.' - - echo "โœ“ Filter updated" - echo "" - - # 4. DELETE - echo "Step 4: Deleting filter..." - curl -s -X DELETE \ - -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/saved-filters/$FILTER_ID" -w "\nStatus: %{http_code}\n" - - echo "โœ“ Filter deleted" - echo "" - - # 5. VERIFY - echo "Step 5: Verifying deletion..." - COUNT=$(curl -s -H "Authorization: Bearer $TOKEN" \ - "$BASE_URL/api/saved-filters?resource_type=media-items" | \ - jq --arg id "$FILTER_ID" '[.[] | select(.id == $id)] | length') - - if [ "$COUNT" -eq 0 ]; then - echo "โœ“ Verification successful - filter deleted" - else - echo "โœ— Verification failed - filter still exists" - exit 1 - fi - - echo "" - echo "=== All CRUD operations completed successfully ===" - ``` - - **Success Criteria:** - - โœ… Create returns 201 with generated ID - - โœ… Read returns filter in list - - โœ… Update modifies filter and updates timestamp - - โœ… Delete returns 204 No Content - - โœ… Filter no longer appears in list after deletion + data: "{\n \"name\": \"Complete Workflow Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"sort\": \"title ASC\"\n }\n }" +docs: "## Complete CRUD Workflow\n\nEnd-to-end test demonstrating the complete lifecycle of a saved filter: Create โ†’ Read โ†’ Update โ†’ Delete.\n\n**Workflow Steps:**\n\n**Step 1: CREATE a new filter**\n```bash\nCREATE_RESPONSE=$(curl -s -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Complete Workflow Test\",\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\necho \"Create Response:\"\necho $CREATE_RESPONSE | jq '.'\n\n# Extract filter ID for subsequent requests\nFILTER_ID=$(echo $CREATE_RESPONSE | jq -r '.id')\necho \"Created filter ID: $FILTER_ID\"\n\n# Expected Status: 201 Created\n# Expected Response:\n# {\n# \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n# \"name\": \"Complete Workflow Test\",\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**Step 2: READ the created filter (list all)**\n```bash\n# List all filters for media-items\nLIST_RESPONSE=$(curl -s \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n \"{{base_url}}/api/saved-filters?resource_type=media-items\")\n\necho \"List Response:\"\necho $LIST_RESPONSE | jq '.'\n\n# Verify our filter is in the list\nFILTER_EXISTS=$(echo $LIST_RESPONSE | jq --arg id \"$FILTER_ID\" \\\n '.[] | select(.id == $id) | .id')\n\nif [ \"$FILTER_EXISTS\" == \"$FILTER_ID\" ]; then\n echo \"โœ“ Filter found in list\"\nelse\n echo \"โœ— Filter NOT found in list\"\nfi\n\n# Expected Status: 200 OK\n# Expected Response: Array containing our newly created filter\n```\n\n**Step 3: UPDATE the filter**\n```bash\n# Update filter name and criteria\nUPDATE_RESPONSE=$(curl -s -X PUT \\\n -H \"Authorization: Bearer\ + \ YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Complete Workflow Test - Updated\",\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\necho \"Update Response:\"\necho $UPDATE_RESPONSE | jq '.'\n\n# Verify updated_at timestamp changed\nUPDATED_AT=$(echo $UPDATE_RESPONSE | jq -r '.updated_at')\necho \"Filter updated at: $UPDATED_AT\"\n\n# Expected Status: 200 OK\n# Expected Response:\n# {\n# \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n# \"name\": \"Complete Workflow Test - Updated\",\n# \"resource_type\": \"media-items\",\n# \"filters\": {\n# \"genre_filter\": \"Science Fiction Fantasy\",\n# \"sort\": \"author ASC\",\n# \"year_min\": \"2000\"\n# },\n# \"created_at\": \"2024-03-20T12:00:00Z\",\n# \"updated_at\": \"2024-03-20T12:05:00Z\"\ + \ # NEW TIMESTAMP\n# }\n```\n\n**Step 4: DELETE the filter**\n```bash\n# Delete the filter\nDELETE_RESPONSE=$(curl -s -X DELETE \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n \"{{base_url}}/api/saved-filters/$FILTER_ID\")\n\necho \"Delete Response:\"\necho $DELETE_RESPONSE\n\n# Expected Status: 204 No Content\n# Expected Response: (empty body)\nif [ -z \"$DELETE_RESPONSE\" ]; then\n echo \"โœ“ Filter deleted successfully (empty response)\"\nfi\n```\n\n**Step 5: VERIFY deletion**\n```bash\n# Try to get the deleted filter\nGET_RESPONSE=$(curl -s \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n \"{{base_url}}/api/saved-filters?resource_type=media-items\")\n\necho \"List After Delete:\"\necho $GET_RESPONSE | jq '.'\n\n# Verify filter is NOT in the list\nFILTER_EXISTS=$(echo $GET_RESPONSE | jq --arg id \"$FILTER_ID\" \\\n '.[] | select(.id == $id) | .id')\n\nif [ -z \"$FILTER_EXISTS\" ]; then\n echo \"โœ“ Filter successfully deleted (not found in list)\"\nelse\n echo \"โœ— Filter still\ + \ exists (deletion failed)\"\nfi\n\n# Expected: Filter no longer appears in list\n```\n\n**Shell Script Complete Example:**\n```bash\n#!/bin/bash\n\nset -e # Exit on error\n\nBASE_URL=\"{{base_url}}\"\nTOKEN=\"YOUR_TOKEN\"\n\necho \"=== Complete CRUD Workflow Test ===\"\necho \"\"\n\n# 1. CREATE\necho \"Step 1: Creating filter...\"\nFILTER_ID=$(curl -s -X POST \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Workflow Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"genre_filter\": \"Science Fiction\"}\n }' \\\n \"$BASE_URL/api/saved-filters\" | jq -r '.id')\n\necho \"โœ“ Created filter: $FILTER_ID\"\necho \"\"\n\n# 2. READ\necho \"Step 2: Reading filters...\"\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"$BASE_URL/api/saved-filters?resource_type=media-items\" | \\\n jq --arg id \"$FILTER_ID\" '.[] | select(.id == $id)'\n\necho \"โœ“ Filter retrieved\"\necho \"\"\n\n# 3. UPDATE\necho \"Step 3:\ + \ Updating filter...\"\ncurl -s -X PUT \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Workflow Test - Updated\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"genre_filter\": \"Fantasy\"}\n }' \\\n \"$BASE_URL/api/saved-filters/$FILTER_ID\" | jq '.'\n\necho \"โœ“ Filter updated\"\necho \"\"\n\n# 4. DELETE\necho \"Step 4: Deleting filter...\"\ncurl -s -X DELETE \\\n -H \"Authorization: Bearer $TOKEN\" \\\n \"$BASE_URL/api/saved-filters/$FILTER_ID\" -w \"\\nStatus: %{http_code}\\n\"\n\necho \"โœ“ Filter deleted\"\necho \"\"\n\n# 5. VERIFY\necho \"Step 5: Verifying deletion...\"\nCOUNT=$(curl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"$BASE_URL/api/saved-filters?resource_type=media-items\" | \\\n jq --arg id \"$FILTER_ID\" '[.[] | select(.id == $id)] | length')\n\nif [ \"$COUNT\" -eq 0 ]; then\n echo \"โœ“ Verification successful - filter deleted\"\nelse\n echo \"โœ— Verification failed - filter still exists\"\ + \n exit 1\nfi\n\necho \"\"\necho \"=== All CRUD operations completed successfully ===\"\n```\n\n**Success Criteria:**\n- โœ… Create returns 201 with generated ID\n- โœ… Read returns filter in list\n- โœ… Update modifies filter and updates timestamp\n- โœ… Delete returns 204 No Content\n- โœ… Filter no longer appears in list after deletion" diff --git a/bruno/saved-filters/scenarios/Duplicate Name Validation.yml b/bruno/saved-filters/scenarios/Duplicate Name Validation.yml index 4594f94f..68bf3a1d 100644 --- a/bruno/saved-filters/scenarios/Duplicate Name Validation.yml +++ b/bruno/saved-filters/scenarios/Duplicate Name Validation.yml @@ -8,69 +8,6 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"name\": \"Duplicate Test\",\n \"resource_type\":\ - \ \"media-items\",\n \"filters\": {\n \"search\": \"test\",\n \"genre_filter\"\ - : \"Science Fiction\"\n }\n }" - -docs: |- - ## Duplicate Name Validation - - Tests that the API correctly prevents creating duplicate filter names for the same user + resource type combination. - - **Scenario:** - 1. Create first filter with name "Duplicate Test" - 2. Attempt to create second filter with same name - 3. Expect 409 Conflict error - - **Expected Behavior:** - - First request: 201 Created - - Second request: 409 Conflict with error message - - **Error Response:** - ```json - { - "error": "filter with name 'Duplicate Test' already exists for this resource type" - } - ``` - - **Test Steps:** - ```bash - # First request - should succeed - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Duplicate Test", - "resource_type": "media-items", - "filters": {"search": "test"} - }' \ - "{{base_url}}/api/saved-filters" - - # Response: 201 Created - - # Second request - should fail with 409 - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Duplicate Test", - "resource_type": "media-items", - "filters": {"search": "test"} - }' \ - "{{base_url}}/api/saved-filters" - - # Response: 409 Conflict - # { - # "error": "filter with name 'Duplicate Test' already exists for this resource type" - # } - ``` - - **Important Notes:** - - Duplicate check is per user + resource type - - Same filter name can be used for different resource types - - Different users can have filters with the same name - - Validation happens in service layer before database insertion - - **Related Tests:** - - Update filter with name that conflicts with another filter (should also return 409) - - Create filter with different name for different resource type (should succeed) + data: "{\n \"name\": \"Duplicate Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"search\": \"test\",\n \"genre_filter\": \"Science Fiction\"\n }\n }" +docs: "## Duplicate Name Validation\n\nTests that the API correctly prevents creating duplicate filter names for the same user + resource type combination.\n\n**Scenario:**\n1. Create first filter with name \"Duplicate Test\"\n2. Attempt to create second filter with same name\n3. Expect 409 Conflict error\n\n**Expected Behavior:**\n- First request: 201 Created\n- Second request: 409 Conflict with error message\n\n**Error Response:**\n```json\n{\n \"error\": \"filter with name 'Duplicate Test' already exists for this resource type\"\n}\n```\n\n**Test Steps:**\n```bash\n# First request - should succeed\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Duplicate Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"search\": \"test\"}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Response: 201 Created\n\n# Second request - should fail with 409\ncurl -X POST \\\n -H \"Authorization: Bearer\ + \ YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Duplicate Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"search\": \"test\"}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Response: 409 Conflict\n# {\n# \"error\": \"filter with name 'Duplicate Test' already exists for this resource type\"\n# }\n```\n\n**Important Notes:**\n- Duplicate check is per user + resource type\n- Same filter name can be used for different resource types\n- Different users can have filters with the same name\n- Validation happens in service layer before database insertion\n\n**Related Tests:**\n- Update filter with name that conflicts with another filter (should also return 409)\n- Create filter with different name for different resource type (should succeed)" diff --git a/bruno/saved-filters/scenarios/Filter Validation - Edge Cases.yml b/bruno/saved-filters/scenarios/Filter Validation - Edge Cases.yml index 8c53eb6c..408dfaf8 100644 --- a/bruno/saved-filters/scenarios/Filter Validation - Edge Cases.yml +++ b/bruno/saved-filters/scenarios/Filter Validation - Edge Cases.yml @@ -8,254 +8,10 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"name\": \"\",\n \"resource_type\": \"media-items\"\ - ,\n \"filters\": {}\n }" - -docs: |- - ## Filter Validation - Edge Cases - - Tests various validation edge cases including empty names, invalid resource types, malformed JSON, etc. - - **Test Cases:** - - **Test 1: Empty Filter Name** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "", - "resource_type": "media-items", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request - # { - # "error": "name and resource_type are required" - # } - ``` - - **Test 2: Missing Name Field** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "resource_type": "media-items", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request - # Missing required field validation - ``` - - **Test 3: Missing Resource Type** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Test Filter", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request - # { - # "error": "name and resource_type are required" - # } - ``` - - **Test 4: Invalid Resource Type** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Test Filter", - "resource_type": "invalid-type", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request or success - # Note: Current validation allows any string (extensible design) - # Future: May validate against allowed resource types - ``` - - **Test 5: Empty Filters Object** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Empty Filter", - "resource_type": "media-items", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 201 Created - # Empty filters object is valid (matches no criteria) - ``` - - **Test 6: Very Long Filter Name** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "This is a very long filter name that exceeds one hundred characters and should be rejected by the validation logic in the handler", - "resource_type": "media-items", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request or 201 Created - # Note: Plan specifies max 100 chars, but validation may not be enforced - ``` - - **Test 7: Special Characters in Name** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Filter with ", - "resource_type": "media-items", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 201 Created (input sanitized) - # Note: Names are stored as-is, no HTML escaping needed - ``` - - **Test 8: Unicode Characters in Name** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "๐Ÿš€ Sci-Fi Books ๐Ÿ“š", - "resource_type": "media-items", - "filters": { - "genre_filter": "Science Fiction", - "search": "็ง‘ๅนป" - } - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 201 Created - # Unicode (emoji, CJK characters) should be supported - ``` - - **Test 9: Malformed JSON** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Test", - "resource_type": "media-items", - "filters": { - "missing closing brace": "broken" - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request - # { - # "error": "invalid request body" - # } - ``` - - **Test 10: Invalid Filter ID Format** - ```bash - curl -X PUT \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Updated", - "resource_type": "media-items", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters/not-a-uuid" - - # Expected: 400 Bad Request - # { - # "error": "invalid filter ID" - # } - ``` - - **Test 11: Missing Filters Field** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "Test Filter", - "resource_type": "media-items" - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request - # Missing required field - ``` - - **Test 12: Whitespace-Only Name** - ```bash - curl -X POST \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "name": " ", - "resource_type": "media-items", - "filters": {} - }' \ - "{{base_url}}/api/saved-filters" - - # Expected: 400 Bad Request - # Name validation should reject whitespace-only names - ``` - - **Validation Rules Summary:** - - **Required Fields:** - - `name`: Non-empty string (max 100 chars per plan) - - `resource_type`: Non-empty string - - `filters`: Object (can be empty {}) - - **Optional Fields:** - - None (all 3 fields are required) - - **Field Validation:** - - Name uniqueness checked per user + resource type - - Filter ID must be valid UUID for PUT/DELETE - - Filters must be valid JSON object - - **Common Validation Errors:** - ```json - { - "error": "name and resource_type are required" - } - ``` - - ```json - { - "error": "invalid request body" - } - ``` - - ```json - { - "error": "invalid filter ID" - } - ``` - - ```json - { - "error": "filter with name 'X' already exists for this resource type" - } - ``` + data: "{\n \"name\": \"\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }" +docs: "## Filter Validation - Edge Cases\n\nTests various validation edge cases including empty names, invalid resource types, malformed JSON, etc.\n\n**Test Cases:**\n\n**Test 1: Empty Filter Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"name and resource_type are required\"\n# }\n```\n\n**Test 2: Missing Name Field**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# Missing required field validation\n```\n\n**Test 3: Missing Resource Type**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\"\ + \ \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test Filter\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"name and resource_type are required\"\n# }\n```\n\n**Test 4: Invalid Resource Type**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test Filter\",\n \"resource_type\": \"invalid-type\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request or success\n# Note: Current validation allows any string (extensible design)\n# Future: May validate against allowed resource types\n```\n\n**Test 5: Empty Filters Object**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Empty Filter\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \ + \ \"{{base_url}}/api/saved-filters\"\n\n# Expected: 201 Created\n# Empty filters object is valid (matches no criteria)\n```\n\n**Test 6: Very Long Filter Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"This is a very long filter name that exceeds one hundred characters and should be rejected by the validation logic in the handler\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request or 201 Created\n# Note: Plan specifies max 100 chars, but validation may not be enforced\n```\n\n**Test 7: Special Characters in Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Filter with \",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\ + \n\n# Expected: 201 Created (input sanitized)\n# Note: Names are stored as-is, no HTML escaping needed\n```\n\n**Test 8: Unicode Characters in Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"\U0001F680 Sci-Fi Books \U0001F4DA\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"search\": \"็ง‘ๅนป\"\n }\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 201 Created\n# Unicode (emoji, CJK characters) should be supported\n```\n\n**Test 9: Malformed JSON**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n\"missing closing brace\": \"broken\"\n}' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"invalid request body\"\ + \n# }\n```\n\n**Test 10: Invalid Filter ID Format**\n```bash\ncurl -X PUT \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Updated\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters/not-a-uuid\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"invalid filter ID\"\n# }\n```\n\n**Test 11: Missing Filters Field**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test Filter\",\n \"resource_type\": \"media-items\"\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# Missing required field\n```\n\n**Test 12: Whitespace-Only Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \" \",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n\ + \ }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# Name validation should reject whitespace-only names\n```\n\n**Validation Rules Summary:**\n\n**Required Fields:**\n- `name`: Non-empty string (max 100 chars per plan)\n- `resource_type`: Non-empty string\n- `filters`: Object (can be empty {})\n\n**Optional Fields:**\n- None (all 3 fields are required)\n\n**Field Validation:**\n- Name uniqueness checked per user + resource type\n- Filter ID must be valid UUID for PUT/DELETE\n- Filters must be valid JSON object\n\n**Common Validation Errors:**\n```json\n{\n \"error\": \"name and resource_type are required\"\n}\n```\n\n```json\n{\n \"error\": \"invalid request body\"\n}\n```\n\n```json\n{\n \"error\": \"invalid filter ID\"\n}\n```\n\n```json\n{\n \"error\": \"filter with name 'X' already exists for this resource type\"\n}\n```" diff --git a/bruno/scanner/Start Watch Mode.yml b/bruno/scanner/Start Watch Mode.yml index 3a902b0e..ffa3e937 100644 --- a/bruno/scanner/Start Watch Mode.yml +++ b/bruno/scanner/Start Watch Mode.yml @@ -8,25 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"library_id\": \"{{library_id" - -docs: |- - ## Start Watch Mode - - Starts real-time file system monitoring for a specific library. New media items will be detected and processed almost instantly. - - **Method:** POST - - **Endpoint:** /api/scanner/watch/start - - **Authentication:** Required (Bearer token, Admin only) - - **Request Body:** - - `library_id` (string, required): UUID of the library to watch - - Example: `"550e8400-e29b-41d4-a716-446655440000"` - - **Response:** HTTP 200 (OK) - ```json - { - "message": "watch mode started for library", - "library_id": "550e8400-e29b-41d4-a716-446655440000" + data: "{\n \"library_id\": \"{{library_id" +docs: "## Start Watch Mode\n \n Starts real-time file system monitoring for a specific library. New media items will be detected and processed almost instantly.\n \n **Method:** POST\n \n **Endpoint:** /api/scanner/watch/start\n \n **Authentication:** Required (Bearer token, Admin only)\n \n **Request Body:**\n - `library_id` (string, required): UUID of the library to watch\n - Example: `\"550e8400-e29b-41d4-a716-446655440000\"`\n \n **Response:** HTTP 200 (OK)\n ```json\n {\n \"message\": \"watch mode started for library\",\n \"library_id\": \"550e8400-e29b-41d4-a716-446655440000\"" diff --git a/bruno/scanner/Stop Watch Mode.yml b/bruno/scanner/Stop Watch Mode.yml index 6e9c65ae..59c3d35c 100644 --- a/bruno/scanner/Stop Watch Mode.yml +++ b/bruno/scanner/Stop Watch Mode.yml @@ -8,25 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"library_id\": \"{{library_id" - -docs: |- - ## Stop Watch Mode - - Stops real-time file system monitoring for a specific library. - - **Method:** POST - - **Endpoint:** /api/scanner/watch/stop - - **Authentication:** Required (Bearer token, Admin only) - - **Request Body:** - - `library_id` (string, required): UUID of the library to stop watching - - Example: `"550e8400-e29b-41d4-a716-446655440000"` - - **Response:** HTTP 200 (OK) - ```json - { - "message": "watch mode stopped for library", - "library_id": "550e8400-e29b-41d4-a716-446655440000" + data: "{\n \"library_id\": \"{{library_id" +docs: "## Stop Watch Mode\n \n Stops real-time file system monitoring for a specific library.\n \n **Method:** POST\n \n **Endpoint:** /api/scanner/watch/stop\n \n **Authentication:** Required (Bearer token, Admin only)\n \n **Request Body:**\n - `library_id` (string, required): UUID of the library to stop watching\n - Example: `\"550e8400-e29b-41d4-a716-446655440000\"`\n \n **Response:** HTTP 200 (OK)\n ```json\n {\n \"message\": \"watch mode stopped for library\",\n \"library_id\": \"550e8400-e29b-41d4-a716-446655440000\"" diff --git a/bruno/sidecar/Update System Configuration.yml b/bruno/sidecar/Update System Configuration.yml index 58cede6a..8d9cd5bd 100644 --- a/bruno/sidecar/Update System Configuration.yml +++ b/bruno/sidecar/Update System Configuration.yml @@ -8,35 +8,8 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"base_url\": \"https://bookhoard.example.com\",\n \"opds_base_url\"\ - : \"https://bookhoard.example.com/opds\",\n \"api_base_url\": \"https://bookhoard.example.com/api\"" + data: "{\n \"base_url\": \"https://bookhoard.example.com\",\n \"opds_base_url\": \"https://bookhoard.example.com/opds\",\n \"api_base_url\": \"https://bookhoard.example.com/api\"" headers: - key: Authorization value: Bearer {{admin_token - -docs: |- - ## Update System Configuration - - Updates system-wide configuration settings for the Bookhoard instance. - - **Method:** PUT - - **Endpoint:** /api/system/config - - **Authentication:** Bearer token (admin only) - - **Request Body:** - - `base_url` (string): Base URL for the instance - - `opds_base_url` (string): OPDS endpoint base URL - - `api_base_url` (string): API endpoint base URL - - **Response:** - - `status` (string): Update status - - `config` (object): Updated configuration - - **Status Codes:** - - 200: Success - - 400: Invalid configuration - - 401: Unauthorized - - 403: Forbidden (admin only) - - 500: Internal server error +docs: "## Update System Configuration\n\n Updates system-wide configuration settings for the Bookhoard instance.\n\n **Method:** PUT\n\n **Endpoint:** /api/system/config\n\n **Authentication:** Bearer token (admin only)\n\n **Request Body:**\n - `base_url` (string): Base URL for the instance\n - `opds_base_url` (string): OPDS endpoint base URL\n - `api_base_url` (string): API endpoint base URL\n\n **Response:**\n - `status` (string): Update status\n - `config` (object): Updated configuration\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid configuration\n - 401: Unauthorized\n - 403: Forbidden (admin only)\n - 500: Internal server error" diff --git a/bruno/system/update-scan-settings.yml b/bruno/system/update-scan-settings.yml index 510b8fc5..f54613af 100644 --- a/bruno/system/update-scan-settings.yml +++ b/bruno/system/update-scan-settings.yml @@ -4,22 +4,12 @@ info: seq: 2 http: method: PUT - url: "{{base_url}}/api/libraries/scan-settings" + url: '{{base_url}}/api/libraries/scan-settings' auth: inherit body: type: json - json: - scan_poll_interval_seconds: 30 - auto_scan_enabled: true + data: "{\n \"scan_poll_interval_seconds\": 30,\n \"auto_scan_enabled\": true\n}" headers: - - key: Content-Type - value: application/json - -docs: |- - ## Update System Scan Settings - - Updates system-wide scan settings that apply to all libraries. - - **Authentication**: Admin token required - **Request**: Scan frequency (15-1440 minutes) and enabled status - **Response**: Success message + - key: Content-Type + value: application/json +docs: "## Update System Scan Settings\n\n Updates system-wide scan settings that apply to all libraries.\n\n **Authentication**: Admin token required\n **Request**: Scan frequency (15-1440 minutes) and enabled status\n **Response**: Success message" diff --git a/bruno/user/admin/Reset User Password.yml b/bruno/user/admin/Reset User Password.yml index 0aacd6b8..531fa2f6 100644 --- a/bruno/user/admin/Reset User Password.yml +++ b/bruno/user/admin/Reset User Password.yml @@ -8,29 +8,5 @@ http: auth: inherit body: type: json - jsonBody: |- - { - "new_password": "NewSecurePassword123!", - "confirm_password": "NewSecurePassword123!" - } - -docs: |- - ## Admin Reset User Password - - Admin-only endpoint to reset another user's password. - - **Method:** PUT - - **Endpoint:** /api/auth/password/:id - - **Authentication:** Required (Admin Bearer token) - - **Request Body:** - - `new_password` (string, required): New password - - `confirm_password` (string, required): Must match new_password - - **Status Codes:** - - 200: Success - - 400: Passwords do not match or invalid format - - 403: Not an admin - - 404: User not found + data: "{\n \"new_password\": \"NewSecurePassword123!\",\n \"confirm_password\": \"NewSecurePassword123!\"\n}" +docs: "## Admin Reset User Password\n \n Admin-only endpoint to reset another user's password.\n \n **Method:** PUT\n \n **Endpoint:** /api/auth/password/:id\n \n **Authentication:** Required (Admin Bearer token)\n \n **Request Body:**\n - `new_password` (string, required): New password\n - `confirm_password` (string, required): Must match new_password\n \n **Status Codes:**\n - 200: Success\n - 400: Passwords do not match or invalid format\n - 403: Not an admin\n - 404: User not found" diff --git a/bruno/user/admin/Update User.yml b/bruno/user/admin/Update User.yml index 6e38281a..b380d4a4 100644 --- a/bruno/user/admin/Update User.yml +++ b/bruno/user/admin/Update User.yml @@ -8,35 +8,5 @@ http: auth: inherit body: type: json - jsonBody: |- - { - "username": "newusername", - "email": "newemail@example.com", - "role": "admin" - } - -docs: |- - ## Admin Update User - - Admin-only endpoint to update another user's profile. - - **Method:** PUT - - **Endpoint:** /api/auth/profile/:id - - **Authentication:** Required (Admin Bearer token) - - **Request Body:** - - `username` (string, optional): New username - - `email` (string, optional): New email - - `first_name` (string, optional): First name - - `last_name` (string, optional): Last name - - `theme` (string, optional): Theme preference - - `role` (string, optional): Role ("user" or "admin") - admin only - - **Status Codes:** - - 200: Success - - 400: Invalid role or cannot demote last admin - - 403: Not an admin - - 404: User not found - - 409: Username or email already taken + data: "{\n \"username\": \"newusername\",\n \"email\": \"newemail@example.com\",\n \"role\": \"admin\"\n}" +docs: "## Admin Update User\n \n Admin-only endpoint to update another user's profile.\n \n **Method:** PUT\n \n **Endpoint:** /api/auth/profile/:id\n \n **Authentication:** Required (Admin Bearer token)\n \n **Request Body:**\n - `username` (string, optional): New username\n - `email` (string, optional): New email\n - `first_name` (string, optional): First name\n - `last_name` (string, optional): Last name\n - `theme` (string, optional): Theme preference\n - `role` (string, optional): Role (\"user\" or \"admin\") - admin only\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid role or cannot demote last admin\n - 403: Not an admin\n - 404: User not found\n - 409: Username or email already taken" diff --git a/bruno/user/profile/Update Password.yml b/bruno/user/profile/Update Password.yml index d1c2d886..28d37e4b 100644 --- a/bruno/user/profile/Update Password.yml +++ b/bruno/user/profile/Update Password.yml @@ -8,30 +8,5 @@ http: auth: inherit body: type: json - jsonBody: "{\n \"current_password\": \"password123\",\n \"new_password\"\ - : \"newpassword123\",\n \"confirm_password\": \"newpassword123\"" - -docs: |- - ## Update Password - - Updates the authenticated user's password. - - **Method:** PUT - - **Endpoint:** /api/auth/password - - **Authentication:** Required - - **Request Body:** - - `current_password` (string, required): Current password for verification - - `new_password` (string, required): New password (minimum 6 characters) - - `confirm_password` (string, required): Confirmation of new password - - **Response:** - - `message` (string): Success message - - **Status Codes:** - - 200: Success - - 400: Password validation failed - - 401: Current password incorrect - - 401: Unauthorized + data: "{\n \"current_password\": \"password123\",\n \"new_password\": \"newpassword123\",\n \"confirm_password\": \"newpassword123\"" +docs: "## Update Password\n \n Updates the authenticated user's password.\n \n **Method:** PUT\n \n **Endpoint:** /api/auth/password\n \n **Authentication:** Required\n \n **Request Body:**\n - `current_password` (string, required): Current password for verification\n - `new_password` (string, required): New password (minimum 6 characters)\n - `confirm_password` (string, required): Confirmation of new password\n \n **Response:**\n - `message` (string): Success message\n \n **Status Codes:**\n - 200: Success\n - 400: Password validation failed\n - 401: Current password incorrect\n - 401: Unauthorized" diff --git a/bruno/user/profile/Update Profile.yml b/bruno/user/profile/Update Profile.yml index 4dacb2c9..cfb0925a 100644 --- a/bruno/user/profile/Update Profile.yml +++ b/bruno/user/profile/Update Profile.yml @@ -8,38 +8,5 @@ http: auth: inherit body: type: json - jsonBody: |- - { - "username": "updateduser", - "email": "updated@tests.bookhoard.internal", - "first_name": "Updated", - "last_name": "User", - "theme": "dracula" - } - -docs: |- - ## Update User Profile - - Updates the authenticated user's profile information. - - **Method:** PUT - - **Endpoint:** /api/auth/profile - - **Authentication:** Required (Bearer token) - - **Request Body:** - - `username` (string, optional): New username (must be unique) - - `email` (string, optional): New email address (must be unique) - - `first_name` (string, optional): First name - - `last_name` (string, optional): Last name - - `theme` (string, optional): Theme preference - - **Response:** - - `message` (string): Success message - - **Status Codes:** - - 200: Success - - 400: Invalid request - - 401: Unauthorized - - 409: Username or email already taken + data: "{\n \"username\": \"updateduser\",\n \"email\": \"updated@tests.bookhoard.internal\",\n \"first_name\": \"Updated\",\n \"last_name\": \"User\",\n \"theme\": \"dracula\"\n}" +docs: "## Update User Profile\n \n Updates the authenticated user's profile information.\n \n **Method:** PUT\n \n **Endpoint:** /api/auth/profile\n \n **Authentication:** Required (Bearer token)\n \n **Request Body:**\n - `username` (string, optional): New username (must be unique)\n - `email` (string, optional): New email address (must be unique)\n - `first_name` (string, optional): First name\n - `last_name` (string, optional): Last name\n - `theme` (string, optional): Theme preference\n \n **Response:**\n - `message` (string): Success message\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 409: Username or email already taken" diff --git a/bruno/user/profile/Update Theme.yml b/bruno/user/profile/Update Theme.yml index 34cbd055..4fa5b686 100644 --- a/bruno/user/profile/Update Theme.yml +++ b/bruno/user/profile/Update Theme.yml @@ -8,29 +8,5 @@ http: auth: inherit body: type: json - jsonBody: |- - { - "theme": "dracula" - } - -docs: |- - ## Update Theme - - Fast theme switching for the application UI. - - **Method:** PUT - - **Endpoint:** /api/auth/theme - - **Authentication:** Required (Bearer token) - - **Request Body:** - - `theme` (string, required): Theme name - - **Response:** - - `message` (string): Success message - - **Status Codes:** - - 200: Success - - 400: Invalid theme - - 401: Unauthorized + data: "{\n \"theme\": \"dracula\"\n}" +docs: "## Update Theme\n \n Fast theme switching for the application UI.\n \n **Method:** PUT\n \n **Endpoint:** /api/auth/theme\n \n **Authentication:** Required (Bearer token)\n \n **Request Body:**\n - `theme` (string, required): Theme name\n \n **Response:**\n - `message` (string): Success message\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid theme\n - 401: Unauthorized"