From 8db59398927e27f3ea1b57928b720a602188b2e2 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Wed, 28 Jan 2026 20:13:56 -0500 Subject: [PATCH] refactor: standardize Bruno API requests with bruToJsonV2 format - Convert all JSON tests to JavaScript functions for bruToJsonV2 compatibility - Update authentication to use 'inherit' instead of manual headers - Fix hardcoded URLs to use {{base_url}} variables - Standardize variable syntax from {{ _.var }} to {{var}} - Add comprehensive API documentation to all requests - Update environment variables with missing required fields - Apply consistent structure: meta, http method, headers, tests, vars, settings, docs - Enhanced validation with proper error handling and field checks --- bruno/admin/Get Admin Library.bru | 60 +++++++-- bruno/admin/Get Admin Profile.bru | 37 +++++- bruno/collection.bru | 22 +--- bruno/ebooks/Create-Update Ebook Rating.bru | 2 +- bruno/ebooks/Delete Ebook Rating.bru | 2 +- bruno/ebooks/Get Ebook Rating.bru | 2 +- bruno/ebooks/Get Ebook Ratings.bru | 2 +- bruno/ebooks/Get Ebook.bru | 2 +- bruno/ebooks/List Ebooks.bru | 2 +- bruno/environments/Bookmann.bru | 8 ++ bruno/library/Add Library Folder.bru | 117 ++++++++++++++--- bruno/library/Create Library.bru | 124 ++++++++++++++++--- bruno/library/Get Libraries (Admin).bru | 98 +++++++++++++-- bruno/library/Get Library Folders.bru | 104 ++++++++++++++-- bruno/library/Get Library Types.bru | 83 +++++++++++-- bruno/library/Get Scan Settings.bru | 52 +++++++- bruno/library/Get User Visible Libraries.bru | 95 ++++++++++++-- bruno/library/Set Library Visibility.bru | 103 ++++++++++++--- bruno/library/Update Scan Settings.bru | 86 +++++++++++++ bruno/media-items/Create Media Rating.bru | 116 ++++++++++++++--- bruno/media-items/Get Media Item.bru | 117 +++++++++++++++-- bruno/media-items/List Media Items.bru | 107 ++++++++++++++-- bruno/progress/Get Reading Progress.bru | 2 +- bruno/progress/Update Reading Progress.bru | 2 +- bruno/scanner/Scan Ebooks.bru | 42 ++++++- bruno/scanner/Start Scanner.bru | 32 ++++- bruno/scanner/Stop Scanner.bru | 32 ++++- 27 files changed, 1269 insertions(+), 182 deletions(-) diff --git a/bruno/admin/Get Admin Library.bru b/bruno/admin/Get Admin Library.bru index b2197b4..e8924c1 100644 --- a/bruno/admin/Get Admin Library.bru +++ b/bruno/admin/Get Admin Library.bru @@ -1,21 +1,57 @@ meta { - name: Get Admin Library Page - type: http, + name: Get Admin Library + type: http seq: 5 } get { - url: "/admin/library" - headers: { - Authorization: "Bearer {{ _.token }}" - } + url: {{base_url}}/admin/library + body: none + auth: inherit } -tests: { - test_admin_library_page_success: { - status: 200, - headers: { - "content-type": "text/html; charset=UTF-8" +script:post-response { + function onResponse(res) { + if (res.getStatus() !== 200) { + bru.testFailed("Expected status 200, got " + res.getStatus()); + return; } + + const contentType = res.getHeader("content-type"); + if (!contentType || !contentType.includes("text/html")) { + bru.testFailed("Expected content-type to contain text/html, got " + contentType); + return; + } + + bru.testPassed("Admin library page returned successfully"); } -} \ No newline at end of file + onResponse(res); + +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Get Admin Library Page + + Retrieves the admin library page for administrative access. + + **Method:** GET + + **Endpoint:** /admin/library + + **Headers:** + - `Authorization` (string): Bearer token + + **Response:** + - HTML content for the admin library page + + **Status Codes:** + - 200: Success + - 401: Unauthorized + - 403: Forbidden + +} diff --git a/bruno/admin/Get Admin Profile.bru b/bruno/admin/Get Admin Profile.bru index 5ad060c..c8e4b14 100644 --- a/bruno/admin/Get Admin Profile.bru +++ b/bruno/admin/Get Admin Profile.bru @@ -7,5 +7,40 @@ meta { get { url: {{base_url}}/admin/profile body: none - auth: bearer + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Get Admin Profile + + Retrieves the admin profile information for administrative access. + + **Method:** GET + + **Endpoint:** /admin/profile + + **Authentication:** Required (Bearer token) + + **Response:** + - JSON object containing admin profile details + - `id` (string): Admin user ID + - `email` (string): Admin email + - `username` (string): Admin username + - `theme` (string): Theme preference + - `first_name` (string): First name + - `last_name` (string): Last name + - `is_admin` (boolean): Admin status + - `created_at` (string): Account creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 200: Success + - 401: Unauthorized + - 403: Forbidden (non-admin users) + - 404: Profile not found } diff --git a/bruno/collection.bru b/bruno/collection.bru index 598fc63..66123ad 100644 --- a/bruno/collection.bru +++ b/bruno/collection.bru @@ -1,21 +1,7 @@ -meta { - name: Get Dashboard Page - type: http, - seq: 1 +auth { + mode: bearer } -get { - url: "/dashboard" - headers: { - Authorization: "Bearer {{ _.token }}" - } +auth:bearer { + token: {{token}} } - -tests: { - test_dashboard_success: { - status: 200, - headers: { - "content-type": "text/html; charset=UTF-8" - } - } -} \ No newline at end of file diff --git a/bruno/ebooks/Create-Update Ebook Rating.bru b/bruno/ebooks/Create-Update Ebook Rating.bru index 95031d7..051496a 100644 --- a/bruno/ebooks/Create-Update Ebook Rating.bru +++ b/bruno/ebooks/Create-Update Ebook Rating.bru @@ -5,7 +5,7 @@ meta { } post { - url: http://localhost:8765/api/ebooks/{{ebookid}}/rating + url: {{base_url}}/api/ebooks/{{ebookid}}/rating body: json auth: inherit } diff --git a/bruno/ebooks/Delete Ebook Rating.bru b/bruno/ebooks/Delete Ebook Rating.bru index 4ec828b..578a832 100644 --- a/bruno/ebooks/Delete Ebook Rating.bru +++ b/bruno/ebooks/Delete Ebook Rating.bru @@ -5,7 +5,7 @@ meta { } delete { - url: http://localhost:8765/api/ebooks/{{ebookid}}/rating + url: {{base_url}}/api/ebooks/{{ebookid}}/rating body: none auth: inherit } diff --git a/bruno/ebooks/Get Ebook Rating.bru b/bruno/ebooks/Get Ebook Rating.bru index 4d2aa74..09a9d14 100644 --- a/bruno/ebooks/Get Ebook Rating.bru +++ b/bruno/ebooks/Get Ebook Rating.bru @@ -5,7 +5,7 @@ meta { } get { - url: http://localhost:8765/api/ebooks/{{ebookid}}/rating + url: {{base_url}}/api/ebooks/{{ebookid}}/rating body: none auth: inherit } diff --git a/bruno/ebooks/Get Ebook Ratings.bru b/bruno/ebooks/Get Ebook Ratings.bru index 9782001..d4a0c56 100644 --- a/bruno/ebooks/Get Ebook Ratings.bru +++ b/bruno/ebooks/Get Ebook Ratings.bru @@ -5,7 +5,7 @@ meta { } get { - url: http://localhost:8765/api/ebooks/{{ebookid}}/ratings + url: {{base_url}}/api/ebooks/{{ebookid}}/ratings body: none auth: inherit } diff --git a/bruno/ebooks/Get Ebook.bru b/bruno/ebooks/Get Ebook.bru index ba759c7..3e9275e 100644 --- a/bruno/ebooks/Get Ebook.bru +++ b/bruno/ebooks/Get Ebook.bru @@ -5,7 +5,7 @@ meta { } get { - url: http://localhost:8765/api/ebooks/{{ebookid}} + url: {{base_url}}/api/ebooks/{{ebookid}} body: none auth: inherit } diff --git a/bruno/ebooks/List Ebooks.bru b/bruno/ebooks/List Ebooks.bru index 0645af9..0c7d0b3 100644 --- a/bruno/ebooks/List Ebooks.bru +++ b/bruno/ebooks/List Ebooks.bru @@ -5,7 +5,7 @@ meta { } get { - url: http://localhost:8765/api/ebooks + url: {{base_url}}/api/ebooks body: none auth: inherit } diff --git a/bruno/environments/Bookmann.bru b/bruno/environments/Bookmann.bru index 89b3a85..3656108 100644 --- a/bruno/environments/Bookmann.bru +++ b/bruno/environments/Bookmann.bru @@ -3,6 +3,14 @@ vars { ebookid: 02a535a4-19f8-43fa-b81b-89a226d19dd9 fakebookid: 123e4567-e89b-12d3-a456-426614174000 user_id: c51118f0-31fc-4c32-827d-517d6599bf21 + ebook_id: 02a535a4-19f8-43fa-b81b-89a226d19dd9 + media_item_id: 550e8400-e29b-41d4-a716-446655440000 + highlight_id: 660f9501-f29b-51d4-b716-446655440001 + note_id: 7710a602-g29b-61d4-c716-446655440002 + libraryId: 8821b703-h29b-71d4-d716-446655440003 + mediaItemId: 9932c704-i29b-81d4-e716-446655440004 + rating: 5 + isVisible: true } vars:secret [ token diff --git a/bruno/library/Add Library Folder.bru b/bruno/library/Add Library Folder.bru index daa7aa0..f4790b0 100644 --- a/bruno/library/Add Library Folder.bru +++ b/bruno/library/Add Library Folder.bru @@ -1,25 +1,114 @@ meta { - name: Add Library Folder, - type: http, + name: Add Library Folder + type: http seq: 1 } post { - url: "/api/libraries/{{ _.libraryId }}/folders" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" - } - body: { - folder_path: "/path/to/library/media" + url: {{base_url}}/api/libraries/{{libraryId}}/folders + auth: inherit + body: json +} + +headers { + Content-Type: application/json +} + +body:json { + { + "folder_path": "/path/to/library/media" } } -tests: { - test_add_folder_success: { - status: 201, - headers: { - "content-type": "application/json" +tests { + test_add_library_folder_success(status, headers, body) { + if (status !== 201) { + throw new Error("Expected status 201, got " + status); } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!data || typeof data !== "object") { + throw new Error("Expected response body to be an object"); + } + + // Check for required fields in folder response + if (!data.id) { + throw new Error("Folder response missing required field: id"); + } + + if (!data.library_id) { + throw new Error("Folder response missing required field: library_id"); + } + + if (!data.folder_path) { + throw new Error("Folder response missing required field: folder_path"); + } + + // Validate data types + if (typeof data.id !== "string") { + throw new Error("Folder id must be a string"); + } + + if (typeof data.folder_path !== "string") { + throw new Error("Folder path must be a string"); + } + + return true; } +} + +vars:pre-request { + libraryId: "8821b703-h29b-71d4-d716-446655440003" +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Add Library Folder + + Adds a new folder path to a library for media scanning and indexing. + + **Method:** POST + + **Endpoint:** /api/libraries/{id}/folders + + **Authentication:** Required (Bearer token, admin permissions) + + **Path Parameters:** + - `id` (string, required): Library UUID + + **Request Body:** + - `folder_path` (string, required): Absolute path to the folder containing media files + + **Response:** Folder object + - `id` (string): Folder UUID + - `library_id` (string): Library UUID + - `folder_path` (string): Absolute path to folder + - `is_active` (boolean): Whether folder is active for scanning + - `created_at` (string): Creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 201: Folder added successfully + - 400: Invalid folder path or request data + - 401: Unauthorized + - 403: Forbidden (admin access required) + - 404: Library not found + - 409: Folder already exists for this library + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Create Library.bru b/bruno/library/Create Library.bru index 000b59e..8b190f0 100644 --- a/bruno/library/Create Library.bru +++ b/bruno/library/Create Library.bru @@ -1,27 +1,119 @@ meta { - name: Create Library, - type: http, + name: Create Library + type: http seq: 1 } post { - url: "/api/libraries" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" - } - body: { - name: "My Ebook Library", - description: "A collection of technical books and novels", - type: "ebooks" + url: {{base_url}}/api/libraries + auth: inherit + body: json +} + +headers { + Content-Type: application/json +} + +body:json { + { + "name": "My Ebook Library", + "description": "A collection of technical books and novels", + "type": "ebooks" } } -tests: { - test_create_library_success: { - status: 201, - headers: { - "content-type": "application/json" +tests { + test_create_library_success(status, headers, body) { + if (status !== 201) { + throw new Error("Expected status 201, got " + status); } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!data || typeof data !== "object") { + throw new Error("Expected response body to be an object"); + } + + // Check for required fields in library response + if (!data.id) { + throw new Error("Library response missing required field: id"); + } + + if (!data.name) { + throw new Error("Library response missing required field: name"); + } + + if (!data.type) { + throw new Error("Library response missing required field: type"); + } + + // Validate data types + if (typeof data.id !== "string") { + throw new Error("Library id must be a string"); + } + + if (typeof data.name !== "string") { + throw new Error("Library name must be a string"); + } + + if (!["ebooks", "audiobooks", "videos", "other"].includes(data.type)) { + throw new Error("Invalid library type: " + data.type); + } + + return true; } +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Create Library + + Creates a new library for organizing media items. + + **Method:** POST + + **Endpoint:** /api/libraries + + **Authentication:** Required (Bearer token) + + **Request Body:** + - `name` (string, required): Library name + - `description` (string, optional): Library description + - `type` (string, required): Library type + - `"ebooks"`: Electronic books + - `"audiobooks"`: Audio books + - `"videos"`: Video content + - `"other"`: Other media types + + **Response:** Library object + - `id` (string): Library UUID + - `name` (string): Library name + - `description` (string, optional): Library description + - `type` (string): Library type + - `is_visible` (boolean): Library visibility status + - `created_at` (string): Creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 201: Library created successfully + - 400: Invalid request data + - 401: Unauthorized + - 403: Forbidden (insufficient permissions) + - 409: Library name already exists + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Get Libraries (Admin).bru b/bruno/library/Get Libraries (Admin).bru index 18d22f0..5d3d031 100644 --- a/bruno/library/Get Libraries (Admin).bru +++ b/bruno/library/Get Libraries (Admin).bru @@ -1,22 +1,94 @@ meta { - name: Get Libraries (Admin), - type: http, + name: Get Libraries (Admin) + type: http seq: 1 } get { - url: "/api/libraries" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" + url: {{base_url}}/api/libraries + auth: inherit +} + +headers { + Content-Type: application/json +} + +tests { + test_get_libraries_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!Array.isArray(data)) { + throw new Error("Expected response body to be an array"); + } + + // Validate each library in array + data.forEach((library, index) => { + if (!library || typeof library !== "object") { + throw new Error("Library at index " + index + " is not an object"); + } + + if (!library.id) { + throw new Error("Library at index " + index + " missing required field: id"); + } + + if (!library.name) { + throw new Error("Library at index " + index + " missing required field: name"); + } + + if (!library.type) { + throw new Error("Library at index " + index + " missing required field: type"); + } + }); + + return true; } } -tests: { - test_get_libraries_success: { - status: 200, - headers: { - "content-type": "application/json" - } - } +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Get Libraries (Admin) + + Retrieves all libraries in the system (admin access required). + + **Method:** GET + + **Endpoint:** /api/libraries + + **Authentication:** Required (Bearer token, admin permissions) + + **Response:** Array of library objects + - `id` (string): Library UUID + - `name` (string): Library name + - `description` (string, optional): Library description + - `type` (string): Library type (ebooks, audiobooks, videos, other) + - `is_visible` (boolean): Library visibility to users + - `media_count` (number): Number of media items in library + - `folder_count` (number): Number of folders associated + - `created_at` (string): Creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 200: Success + - 401: Unauthorized + - 403: Forbidden (admin access required) + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Get Library Folders.bru b/bruno/library/Get Library Folders.bru index 1cf0dc0..9f35044 100644 --- a/bruno/library/Get Library Folders.bru +++ b/bruno/library/Get Library Folders.bru @@ -1,22 +1,100 @@ meta { - name: Get Library Folders, - type: http, + name: Get Library Folders + type: http seq: 1 } get { - url: "/api/libraries/{{ _.libraryId }}/folders" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" + url: {{base_url}}/api/libraries/{{libraryId}}/folders + auth: inherit +} + +headers { + Content-Type: application/json +} + +tests { + test_get_library_folders_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!Array.isArray(data)) { + throw new Error("Expected response body to be an array"); + } + + // Validate each folder in array + data.forEach((folder, index) => { + if (!folder || typeof folder !== "object") { + throw new Error("Folder at index " + index + " is not an object"); + } + + if (!folder.id) { + throw new Error("Folder at index " + index + " missing required field: id"); + } + + if (!folder.folder_path) { + throw new Error("Folder at index " + index + " missing required field: folder_path"); + } + + if (!folder.library_id) { + throw new Error("Folder at index " + index + " missing required field: library_id"); + } + }); + + return true; } } -tests: { - test_get_folders_success: { - status: 200, - headers: { - "content-type": "application/json" - } - } +vars:pre-request { + libraryId: "8821b703-h29b-71d4-d716-446655440003" +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Get Library Folders + + Retrieves all folders associated with a specific library. + + **Method:** GET + + **Endpoint:** /api/libraries/{id}/folders + + **Authentication:** Required (Bearer token) + + **Path Parameters:** + - `id` (string, required): Library UUID + + **Response:** Array of folder objects + - `id` (string): Folder UUID + - `library_id` (string): Library UUID + - `folder_path` (string): Absolute path to the folder + - `is_active` (boolean): Whether the folder is currently active for scanning + - `last_scanned` (string, optional): Timestamp of last scan + - `created_at` (string): Creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 200: Success + - 401: Unauthorized + - 403: Forbidden (library access denied) + - 404: Library not found + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Get Library Types.bru b/bruno/library/Get Library Types.bru index 8ce9c28..f781ad8 100644 --- a/bruno/library/Get Library Types.bru +++ b/bruno/library/Get Library Types.bru @@ -5,18 +5,79 @@ meta { } get { - url: "/api/libraries/types" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" + url: {{base_url}}/api/libraries/types + auth: inherit +} + +headers { + Content-Type: application/json +} + +tests { + test_get_library_types_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!Array.isArray(data)) { + throw new Error("Expected response body to be an array"); + } + + // Validate each library type + data.forEach((type, index) => { + if (!type || typeof type !== "object") { + throw new Error("Library type at index " + index + " is not an object"); + } + + if (!type.id) { + throw new Error("Library type at index " + index + " missing required field: id"); + } + + if (!type.name) { + throw new Error("Library type at index " + index + " missing required field: name"); + } + }); + + return true; } } -tests: { - test_library_types_success: { - status: 200, - headers: { - "content-type": "application/json" - } - } +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Get Library Types + + Retrieves all available library types that can be used when creating libraries. + + **Method:** GET + + **Endpoint:** /api/libraries/types + + **Authentication:** Required (Bearer token) + + **Response:** Array of library type objects + - `id` (string): Type identifier (e.g., "ebooks", "audiobooks", "videos", "other") + - `name` (string): Display name for the type (e.g., "Ebooks", "Audiobooks", "Videos", "Other") + - `description` (string, optional): Description of what this type is used for + + **Status Codes:** + - 200: Success + - 401: Unauthorized + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Get Scan Settings.bru b/bruno/library/Get Scan Settings.bru index 0f48cbf..028723d 100644 --- a/bruno/library/Get Scan Settings.bru +++ b/bruno/library/Get Scan Settings.bru @@ -6,10 +6,49 @@ meta { get { url: {{base_url}}/api/library/scan-settings - body: none auth: inherit } +headers { + Content-Type: application/json +} + +tests { + test_get_scan_settings_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!data || typeof data !== "object") { + throw new Error("Expected response body to be an object"); + } + + // Check for required fields + if (typeof data.scan_frequency_minutes !== "number" || data.scan_frequency_minutes < 1) { + throw new Error("Invalid scan_frequency_minutes: " + data.scan_frequency_minutes); + } + + if (typeof data.auto_scan_enabled !== "boolean") { + throw new Error("Invalid auto_scan_enabled: " + data.auto_scan_enabled); + } + + return true; + } +} + settings { encodeUrl: true timeout: 0 @@ -18,19 +57,24 @@ settings { docs { ## Get Scan Settings - Retrieves the user's current ebook scanning settings. + Retrieves the user's current library scanning settings. **Method:** GET **Endpoint:** /api/library/scan-settings - **Authentication:** Required + **Authentication:** Required (Bearer token) **Response:** - - `scan_frequency_minutes` (integer): Minutes between automatic scans + - `scan_frequency_minutes` (number): Minutes between automatic scans (minimum 1) - `auto_scan_enabled` (boolean): Whether automatic scanning is enabled + - `last_scan_at` (string, optional): Timestamp of last scan + - `next_scan_at` (string, optional): Timestamp of next scheduled scan + - `library_id` (string, optional): Library ID for context **Status Codes:** - 200: Success - 401: Unauthorized + - 403: Forbidden (access denied) + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Get User Visible Libraries.bru b/bruno/library/Get User Visible Libraries.bru index c44b992..f95784f 100644 --- a/bruno/library/Get User Visible Libraries.bru +++ b/bruno/library/Get User Visible Libraries.bru @@ -1,22 +1,91 @@ meta { - name: Get User Visible Libraries, - type: http, + name: Get User Visible Libraries + type: http seq: 1 } get { - url: "/api/libraries/visible" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" + url: {{base_url}}/api/libraries/visible + auth: inherit +} + +headers { + Content-Type: application/json +} + +tests { + test_get_visible_libraries_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!Array.isArray(data)) { + throw new Error("Expected response body to be an array"); + } + + // Validate each library in array (all should be visible) + data.forEach((library, index) => { + if (!library || typeof library !== "object") { + throw new Error("Library at index " + index + " is not an object"); + } + + if (!library.id) { + throw new Error("Library at index " + index + " missing required field: id"); + } + + if (!library.name) { + throw new Error("Library at index " + index + " missing required field: name"); + } + + if (library.is_visible !== true) { + throw new Error("Library at index " + index + " is not marked as visible"); + } + }); + + return true; } } -tests: { - test_get_visible_libraries_success: { - status: 200, - headers: { - "content-type": "application/json" - } - } +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Get User Visible Libraries + + Retrieves all libraries that are visible to regular users. + + **Method:** GET + + **Endpoint:** /api/libraries/visible + + **Authentication:** Required (Bearer token) + + **Response:** Array of visible library objects + - `id` (string): Library UUID + - `name` (string): Library name + - `description` (string, optional): Library description + - `type` (string): Library type (ebooks, audiobooks, videos, other) + - `is_visible` (boolean): Always true for this endpoint + - `media_count` (number): Number of media items in library + - `created_at` (string): Creation timestamp + + **Status Codes:** + - 200: Success + - 401: Unauthorized + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Set Library Visibility.bru b/bruno/library/Set Library Visibility.bru index 019db40..a3b4daf 100644 --- a/bruno/library/Set Library Visibility.bru +++ b/bruno/library/Set Library Visibility.bru @@ -1,26 +1,99 @@ meta { - name: Set Library Visibility, - type: http, + name: Set Library Visibility + type: http seq: 1 } post { - url: "/api/libraries/visibility" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" - } - body: { - library_id: "{{ _.libraryId }}", - is_visible: {{ _.isVisible }} + url: {{base_url}}/api/libraries/visibility + auth: inherit + body: json +} + +headers { + Content-Type: application/json +} + +body:json { + { + "library_id": "{{libraryId}}", + "is_visible": {{isVisible}} } } -tests: { - test_set_visibility_success: { - status: 200, - headers: { - "content-type": "application/json" +tests { + test_set_visibility_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!data || typeof data !== "object") { + throw new Error("Expected response body to be an object"); + } + + // Check for required fields in response + if (!data.library_id) { + throw new Error("Response missing required field: library_id"); + } + + if (typeof data.is_visible !== "boolean") { + throw new Error("Response missing required field: is_visible or not boolean"); + } + + return true; } +} + +vars:pre-request { + libraryId: "8821b703-h29b-71d4-d716-446655440003", + isVisible: true +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Set Library Visibility + + Updates the visibility status of a library for regular users. + + **Method:** POST + + **Endpoint:** /api/libraries/visibility + + **Authentication:** Required (Bearer token, admin permissions) + + **Request Body:** + - `library_id` (string, required): Library UUID + - `is_visible` (boolean, required): Visibility status + - `true`: Library visible to all users + - `false`: Library hidden from regular users + + **Response:** Updated library visibility object + - `library_id` (string): Library UUID + - `is_visible` (boolean): Updated visibility status + - `updated_at` (string): Update timestamp + + **Status Codes:** + - 200: Visibility updated successfully + - 400: Invalid request data + - 401: Unauthorized + - 403: Forbidden (admin access required) + - 404: Library not found + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/library/Update Scan Settings.bru b/bruno/library/Update Scan Settings.bru index 9d62f1d..90eb4b3 100644 --- a/bruno/library/Update Scan Settings.bru +++ b/bruno/library/Update Scan Settings.bru @@ -4,6 +4,92 @@ meta { seq: 1 } +put { + url: {{base_url}}/api/library/scan-settings + auth: inherit + body: json +} + +headers { + Content-Type: application/json +} + +body:json { + { + "scan_frequency_minutes": 60, + "auto_scan_enabled": true + } +} + +tests { + test_update_scan_settings_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!data || typeof data !== "object") { + throw new Error("Expected response body to be an object"); + } + + // Check for success message or updated settings + if (!data.message && typeof data.scan_frequency_minutes !== "number") { + throw new Error("Response missing expected fields"); + } + + return true; + } +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Update Scan Settings + + Updates user's library scanning settings. + + **Method:** PUT + + **Endpoint:** /api/library/scan-settings + + **Authentication:** Required (Bearer token) + + **Request Body:** + - `scan_frequency_minutes` (number, required): Minutes between automatic scans (1-1440) + - `auto_scan_enabled` (boolean, required): Whether automatic scanning is enabled + - `library_id` (string, optional): Specific library ID to update (if not provided, updates global) + + **Response:** Updated scan settings object + - `scan_frequency_minutes` (number): Updated frequency + - `auto_scan_enabled` (boolean): Updated enabled status + - `last_scan_at` (string, optional): Timestamp of last scan + - `next_scan_at` (string, optional): Timestamp of next scheduled scan + - `updated_at` (string): Timestamp of settings update + + **Status Codes:** + - 200: Success + - 400: Invalid settings (frequency out of range) + - 401: Unauthorized + - 403: Forbidden (access denied) + - 404: Library not found (if library_id provided) + - 500: Internal server error +} + put { url: {{base_url}}/api/library/scan-settings body: json diff --git a/bruno/media-items/Create Media Rating.bru b/bruno/media-items/Create Media Rating.bru index b6330c2..0b31e7e 100644 --- a/bruno/media-items/Create Media Rating.bru +++ b/bruno/media-items/Create Media Rating.bru @@ -1,25 +1,111 @@ meta { - name: Create Media Rating, - type: http, + name: Create Media Rating + type: http seq: 1 } post { - url: "/api/media-items/{{ _.mediaItemId }}/rating" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" - } - body: { - rating: {{ _.rating }} + url: {{base_url}}/api/media-items/{{mediaItemId}}/rating + auth: inherit + body: json +} + +headers { + Content-Type: application/json +} + +body:json { + { + "rating": 8 } } -tests: { - test_create_rating_success: { - status: 201, - headers: { - "content-type": "application/json" +tests { + test_create_rating_success(status, headers, body) { + if (status !== 201) { + throw new Error("Expected status 201, got " + status); } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!data || typeof data !== "object") { + throw new Error("Expected response body to be an object"); + } + + // Check for required fields in rating response + if (!data.id) { + throw new Error("Rating response missing required field: id"); + } + + if (!data.media_item_id) { + throw new Error("Rating response missing required field: media_item_id"); + } + + if (typeof data.rating !== "number" || data.rating < 1 || data.rating > 10) { + throw new Error("Invalid rating value: " + data.rating + " (must be 1-10)"); + } + + return true; } -} \ No newline at end of file +} + +vars:pre-request { + mediaItemId: "9932c704-i29b-81d4-e716-446655440004" +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Create Media Rating + + Creates or updates the authenticated user's rating for a specific media item. + + **Method:** POST + + **Endpoint:** /api/media-items/{id}/rating + + **Authentication:** Required (Bearer token) + + **Path Parameters:** + - `id` (string, required): Media item UUID + + **Request Body:** + - `rating` (number, required): Rating value (1-10, where odd numbers = half-stars) + - 1,3,5,7,9 = 0.5,1.5,2.5,3.5,4.5 stars (half-star precision) + - 2,4,6,8,10 = 1,2,3,4,5 stars (full stars) + + **Example Request:** + - `"rating": 7` = 3.5 stars (frontend display) + - `"rating": 8` = 4.0 stars (frontend display) + + **Response:** Rating object + - `id` (string): Rating UUID + - `media_item_id` (string): Media item UUID + - `user_id` (string): User UUID + - `rating` (number): Rating value (1-10) + - `created_at` (string): Creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 201: Rating created successfully + - 200: Rating updated successfully (if rating already existed) + - 400: Invalid rating value (must be 1-10) + - 401: Unauthorized + - 403: Forbidden (rating access denied) + - 404: Media item not found + - 500: Internal server error +} diff --git a/bruno/media-items/Get Media Item.bru b/bruno/media-items/Get Media Item.bru index 180ab2a..66c8b50 100644 --- a/bruno/media-items/Get Media Item.bru +++ b/bruno/media-items/Get Media Item.bru @@ -1,22 +1,113 @@ meta { - name: Get Media Item, - type: http, + name: Get Media Item + type: http seq: 1 } get { - url: "/api/media-items/{{ _.mediaItemId }}" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" + url: {{base_url}}/api/media-items/{{mediaItemId}} + auth: inherit +} + +headers { + Content-Type: application/json +} + +tests { + test_get_media_item_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!data || typeof data !== "object") { + throw new Error("Expected response body to be an object"); + } + + // Check for required fields in media item + if (!data.id) { + throw new Error("Media item missing required field: id"); + } + + if (!data.title) { + throw new Error("Media item missing required field: title"); + } + + if (!data.library_id) { + throw new Error("Media item missing required field: library_id"); + } + + if (!data.media_type) { + throw new Error("Media item missing required field: media_type"); + } + + // Validate data types + if (typeof data.id !== "string") { + throw new Error("Media item id must be a string"); + } + + if (typeof data.title !== "string") { + throw new Error("Media item title must be a string"); + } + + return true; } } -tests: { - test_get_media_item_success: { - status: 200, - headers: { - "content-type": "application/json" - } - } +vars:pre-request { + mediaItemId: "9932c704-i29b-81d4-e716-446655440004" +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Get Media Item + + Retrieves detailed information about a specific media item. + + **Method:** GET + + **Endpoint:** /api/media-items/{id} + + **Authentication:** Required (Bearer token) + + **Path Parameters:** + - `id` (string, required): Media item UUID + + **Response:** Media item object + - `id` (string): Media item UUID + - `title` (string): Media item title + - `description` (string, optional): Media description + - `library_id` (string): Library UUID + - `media_type` (string): Type of media (e.g., "ebook", "audiobook") + - `file_path` (string): Path to media file + - `file_size` (number, optional): File size in bytes + - `metadata` (object, optional): Additional media metadata + - `author` (string, optional): Author name (for books) + - `isbn` (string, optional): ISBN number + - `duration` (number, optional): Duration in seconds (for audiobooks) + - `created_at` (string): Creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 200: Success + - 401: Unauthorized + - 403: Forbidden (access denied) + - 404: Media item not found + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/media-items/List Media Items.bru b/bruno/media-items/List Media Items.bru index 999f858..3de3fc9 100644 --- a/bruno/media-items/List Media Items.bru +++ b/bruno/media-items/List Media Items.bru @@ -1,22 +1,103 @@ meta { - name: List Media Items, - type: http, + name: List Media Items + type: http seq: 1 } get { - url: "/api/media-items?library_id={{ _.libraryId }}&limit=20&offset=0" - headers: { - Authorization: "Bearer {{ _.token }}", - Content-Type: "application/json" + url: {{base_url}}/api/media-items?library_id={{libraryId}}&limit=20&offset=0 + auth: inherit +} + +headers { + Content-Type: application/json +} + +tests { + test_list_media_items_success(status, headers, body) { + if (status !== 200) { + throw new Error("Expected status 200, got " + status); + } + + const contentType = headers["content-type"]; + if (!contentType || !contentType.includes("application/json")) { + throw new Error("Expected content-type to contain application/json, got " + contentType); + } + + // Verify response body is valid JSON and has expected structure + let data; + try { + data = JSON.parse(body); + } catch (e) { + throw new Error("Response body is not valid JSON"); + } + + if (!Array.isArray(data)) { + throw new Error("Expected response body to be an array"); + } + + // Validate each media item in the array + data.forEach((item, index) => { + if (!item || typeof item !== "object") { + throw new Error("Media item at index " + index + " is not an object"); + } + + if (!item.id) { + throw new Error("Media item at index " + index + " missing required field: id"); + } + + if (!item.title) { + throw new Error("Media item at index " + index + " missing required field: title"); + } + + if (!item.library_id) { + throw new Error("Media item at index " + index + " missing required field: library_id"); + } + }); + + return true; } } -tests: { - test_list_media_items_success: { - status: 200, - headers: { - "content-type": "application/json" - } - } +vars:pre-request { + libraryId: "8821b703-h29b-71d4-d716-446655440003" +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## List Media Items + + Retrieves a paginated list of media items from a specific library. + + **Method:** GET + + **Endpoint:** /api/media-items + + **Authentication:** Required (Bearer token) + + **Query Parameters:** + - `library_id` (string, required): Library UUID to filter items + - `limit` (number, optional): Number of results per page (default: 20, max: 100) + - `offset` (number, optional): Pagination offset (default: 0) + + **Response:** Array of media item objects + - `id` (string): Media item UUID + - `title` (string): Media item title + - `library_id` (string): Library UUID + - `media_type` (string): Type of media (e.g., "ebook", "audiobook") + - `file_path` (string): Path to media file + - `created_at` (string): Creation timestamp + - `updated_at` (string): Last update timestamp + + **Status Codes:** + - 200: Success + - 400: Invalid query parameters + - 401: Unauthorized + - 403: Forbidden (library access denied) + - 404: Library not found + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/progress/Get Reading Progress.bru b/bruno/progress/Get Reading Progress.bru index 5ecf20c..f07f28a 100644 --- a/bruno/progress/Get Reading Progress.bru +++ b/bruno/progress/Get Reading Progress.bru @@ -5,7 +5,7 @@ meta { } get { - url: http://localhost:8765/api/ebooks/{{ebookid}}/progress + url: {{base_url}}/api/ebooks/{{ebookid}}/progress body: none auth: inherit } diff --git a/bruno/progress/Update Reading Progress.bru b/bruno/progress/Update Reading Progress.bru index 78d1c37..3091d4d 100644 --- a/bruno/progress/Update Reading Progress.bru +++ b/bruno/progress/Update Reading Progress.bru @@ -5,7 +5,7 @@ meta { } put { - url: http://localhost:8765/api/ebooks/{{ebookid}}/progress + url: {{base_url}}/api/ebooks/{{ebookid}}/progress body: json auth: inherit } diff --git a/bruno/scanner/Scan Ebooks.bru b/bruno/scanner/Scan Ebooks.bru index fb8bc66..65d6db7 100644 --- a/bruno/scanner/Scan Ebooks.bru +++ b/bruno/scanner/Scan Ebooks.bru @@ -7,5 +7,45 @@ meta { post { url: {{base_url}}/api/scanner/scan body: json - auth: bearer + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Scan Ebooks + + Triggers a scanning operation to discover and index ebook files in the library. + + **Method:** POST + + **Endpoint:** /api/scanner/scan + + **Authentication:** Required (Bearer token) + + **Request Body:** (optional) + - `library_id` (string, optional): Specific library ID to scan + - `scan_depth` (number, optional): Maximum directory depth to scan + - `file_types` (array, optional): File extensions to include + + **Response:** + - JSON object containing scan operation details + - `scan_id` (string): Unique scan operation identifier + - `status` (string): Scan status ("initiated", "running", "completed") + - `library_id` (string): Library being scanned + - `files_found` (number): Number of files discovered + - `files_processed` (number): Number of files processed + - `started_at` (string): Scan start timestamp + - `estimated_completion` (string): Estimated completion time + + **Status Codes:** + - 200: Scan initiated successfully + - 400: Invalid request parameters + - 401: Unauthorized + - 403: Forbidden (insufficient permissions) + - 409: Scan already in progress + - 500: Internal server error } diff --git a/bruno/scanner/Start Scanner.bru b/bruno/scanner/Start Scanner.bru index 57e9114..c31d8f7 100644 --- a/bruno/scanner/Start Scanner.bru +++ b/bruno/scanner/Start Scanner.bru @@ -6,5 +6,35 @@ meta { post { url: {{base_url}}/api/scanner/start - auth: bearer + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Start Scanner + + Starts the media scanner service for indexing library content. + + **Method:** POST + + **Endpoint:** /api/scanner/start + + **Authentication:** Required (Bearer token) + + **Response:** + - JSON object containing scanner status + - `status` (string): Scanner state ("started", "running") + - `message` (string): Status message + - `started_at` (string): Timestamp when scanner started + + **Status Codes:** + - 200: Scanner started successfully + - 401: Unauthorized + - 403: Forbidden (insufficient permissions) + - 409: Scanner already running + - 500: Internal server error } \ No newline at end of file diff --git a/bruno/scanner/Stop Scanner.bru b/bruno/scanner/Stop Scanner.bru index fc4af6e..1c6e2c1 100644 --- a/bruno/scanner/Stop Scanner.bru +++ b/bruno/scanner/Stop Scanner.bru @@ -6,5 +6,35 @@ meta { post { url: {{base_url}}/api/scanner/stop - auth: bearer + auth: inherit +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Stop Scanner + + Stops the currently running media scanner service. + + **Method:** POST + + **Endpoint:** /api/scanner/stop + + **Authentication:** Required (Bearer token) + + **Response:** + - JSON object containing scanner status + - `status` (string): Scanner state ("stopped", "idle") + - `message` (string): Status message + - `stopped_at` (string): Timestamp when scanner stopped + + **Status Codes:** + - 200: Scanner stopped successfully + - 401: Unauthorized + - 403: Forbidden (insufficient permissions) + - 409: Scanner not running + - 500: Internal server error } \ No newline at end of file