diff --git a/docs/developer/api/collections/update_rule.md b/docs/developer/api/collections/update_rule.md index a1d4bc17..2f31f937 100644 --- a/docs/developer/api/collections/update_rule.md +++ b/docs/developer/api/collections/update_rule.md @@ -26,13 +26,7 @@ the rules endpoint. ```json example-response { "collection_id": "550e8400-e29b-41d4-a716-446655440000", - "is_smart": true, - "enabled": null, - "field": null, - "id": null, - "operator": null, - "priority": null, - "value": null + "is_smart": true } ``` diff --git a/docs/developer/api/scanner/cancel_scan.md b/docs/developer/api/scanner/cancel_scan.md new file mode 100644 index 00000000..59f8b2b7 --- /dev/null +++ b/docs/developer/api/scanner/cancel_scan.md @@ -0,0 +1,36 @@ +--- +method: POST +path: /api/scanner/cancel/:jobId +auth: jwt+admin +area: scanner +--- + + +# Cancel Scan + +Requests cancellation of a pending or running scan job. Cancellation is +cooperative — the file currently being processed finishes, the walk stops +at its next context check, and the archive pass is skipped. + +## Example response + +```json example-response +{ + "message": "scan cancellation requested", + "job_id": "job-uuid", + "status": "cancelled" +} +``` + +## Path parameters + +| Param | Type | Required | Notes | +| ----- | ---- | -------- | ----- | +| jobId | string | Yes | Scan job id | + +## Errors + +| Status | Body | When | +| ------ | ---- | ---- | +| 404 | `{"error": "job not found"}` | no such job | +| 409 | `{"error": "..."}` | job cannot be cancelled | \ No newline at end of file diff --git a/docs/developer/api/scanner/get_active_scans.md b/docs/developer/api/scanner/get_active_scans.md new file mode 100644 index 00000000..1ea9b136 --- /dev/null +++ b/docs/developer/api/scanner/get_active_scans.md @@ -0,0 +1,28 @@ +--- +method: GET +path: /api/scanner/active +auth: jwt+admin +area: scanner +--- + + +# Get Active Scans + +Returns every pending/running scan job, so the admin page can re-attach to +in-flight scans after a refresh. + +## Example response + +```json example-response +{ + "scanning": true, + "jobs": [] +} +``` + +## Errors + +| Status | Body | When | +| ------ | ---- | ---- | +| 401 | `{"error": "unauthorized"}` | no/invalid JWT | +| 403 | `{"error": "admin access required"}` | non-admin | \ No newline at end of file diff --git a/docs/developer/api/scanner/get_scan_status.md b/docs/developer/api/scanner/get_scan_status.md index 2f680b55..a99a5eab 100644 --- a/docs/developer/api/scanner/get_scan_status.md +++ b/docs/developer/api/scanner/get_scan_status.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/scanner/status/:jobId +auth: jwt+admin +area: scanner +--- + + # Get Scan Status Get the status of a specific scan job. @@ -26,13 +34,10 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { "job_id": "uuid", - "library_id": "uuid", "status": "in_progress", - "started_at": "2026-02-08T10:00:00Z", - "updated_at": "2026-02-08T10:05:00Z", "progress": { "total_files": 1523, "scanned_files": 850, @@ -46,7 +51,11 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... "file_path": "/path/to/file.epub", "error": "Invalid EPUB format" } - ] + ], + "error": null, + "files_scanned": null, + "new_items": null, + "result": null } ``` diff --git a/docs/developer/api/scanner/get_scan_summary.md b/docs/developer/api/scanner/get_scan_summary.md new file mode 100644 index 00000000..4bde64e3 --- /dev/null +++ b/docs/developer/api/scanner/get_scan_summary.md @@ -0,0 +1,27 @@ +--- +method: GET +path: /api/scanner/active-summary +auth: jwt +area: scanner +--- + + +# Get Scan Summary + +Whether a scan is running and its average progress (0-1). Powers the +sitewide sidebar indicator. + +## Example response + +```json example-response +{ + "scanning": true, + "progress": 0.5 +} +``` + +## Errors + +| Status | Body | When | +| ------ | ---- | ---- | +| 401 | `{"error": "unauthorized"}` | no/invalid JWT | \ No newline at end of file diff --git a/docs/developer/api/scanner/get_watch_mode_status.md b/docs/developer/api/scanner/get_watch_mode_status.md index a79e350d..ed6b72e7 100644 --- a/docs/developer/api/scanner/get_watch_mode_status.md +++ b/docs/developer/api/scanner/get_watch_mode_status.md @@ -1,3 +1,11 @@ +--- +method: GET +path: /api/scanner/watch/status +auth: jwt+admin +area: scanner +--- + + # Get Watch Mode Status Get the watch mode status for a library. @@ -26,18 +34,10 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ## Response (200 OK) -```json +```json example-response { - "library_id": "uuid", - "status": "watching", - "started_at": "2026-02-08T10:00:00Z", - "watched_folders": ["/path/to/library/folder1", "/path/to/library/folder2"], - "stats": { - "files_detected": 15, - "files_processed": 12, - "files_failed": 1, - "last_activity": "2026-02-08T11:30:00Z" - } + "total_watching": null, + "watching_libraries": null } ``` diff --git a/docs/developer/api/scanner/scan_library.md b/docs/developer/api/scanner/scan_library.md index c69eed4c..3d68698b 100644 --- a/docs/developer/api/scanner/scan_library.md +++ b/docs/developer/api/scanner/scan_library.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/scanner/scan +auth: jwt+admin +area: scanner +--- + + # Scan Library Initiate a one-time scan of a library for ebooks, manga, or comics. @@ -26,25 +34,26 @@ The scanner automatically detects and processes files based on the library type: ### Example Request -```json +```json example-request { "library_id": "550e8400-e29b-41d4-a716-446655440000", - "recursive": true, "force": false } ``` ## Response (202 Accepted) -```json +```json example-response { - "message": "Scan initiated successfully", - "job_id": "uuid", - "library_id": "uuid", + "message": "scan job enqueued", + "job_id": "b3d3a0c2-8f4e-4c1d-9a2b-6e5f1a7c9d01", "status": "pending" } ``` +A scan for the same library already pending or running returns **409** with +`{"error": "a scan for this library is already pending or running", "active_job_id": "..."}`. + ## Error Responses | Code | Description | diff --git a/docs/developer/api/scanner/start_scanner.md b/docs/developer/api/scanner/start_scanner.md index 26dce100..ef6676e9 100644 --- a/docs/developer/api/scanner/start_scanner.md +++ b/docs/developer/api/scanner/start_scanner.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/scanner/start +auth: jwt+admin +area: scanner +--- + + # Start Scanner Start the automated background scanner for a library. Supports ebook, manga, and comic libraries. @@ -21,21 +29,17 @@ Start the automated background scanner for a library. Supports ebook, manga, and ### Example Request -```json +```json example-request { - "library_id": "550e8400-e29b-41d4-a716-446655440000", - "interval_seconds": 3600 + "folder_paths": ["/data/books"] } ``` ## Response (200 OK) -```json +```json example-response { - "message": "Scanner started successfully", - "library_id": "uuid", - "interval_seconds": 3600, - "status": "running" + "message": "Scanner started successfully" } ``` diff --git a/docs/developer/api/scanner/start_watch_mode.md b/docs/developer/api/scanner/start_watch_mode.md index 1e10cd46..b553e630 100644 --- a/docs/developer/api/scanner/start_watch_mode.md +++ b/docs/developer/api/scanner/start_watch_mode.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/scanner/watch/start +auth: jwt+admin +area: scanner +--- + + # Start Watch Mode Start watch mode for a library to automatically detect and process new/modified files. Supports ebook, manga, and comic libraries with real-time file system monitoring. @@ -24,7 +32,7 @@ Watch mode automatically detects and processes: ### Example Request -```json +```json example-request { "library_id": "550e8400-e29b-41d4-a716-446655440000" } @@ -32,11 +40,10 @@ Watch mode automatically detects and processes: ## Response (200 OK) -```json +```json example-response { "message": "Watch mode started successfully", - "library_id": "uuid", - "status": "watching" + "library_id": "uuid" } ``` diff --git a/docs/developer/api/scanner/stop_scanner.md b/docs/developer/api/scanner/stop_scanner.md index f2429986..2db1c79b 100644 --- a/docs/developer/api/scanner/stop_scanner.md +++ b/docs/developer/api/scanner/stop_scanner.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/scanner/stop +auth: jwt+admin +area: scanner +--- + + # Stop Scanner Stop the automated background scanner for a library. @@ -14,19 +22,11 @@ Stop the automated background scanner for a library. ### Example Request -```json -{ - "library_id": "550e8400-e29b-41d4-a716-446655440000" -} -``` - ## Response (200 OK) -```json +```json example-response { - "message": "Scanner stopped successfully", - "library_id": "uuid", - "status": "stopped" + "message": "Scanner stopped successfully" } ``` diff --git a/docs/developer/api/scanner/stop_watch_mode.md b/docs/developer/api/scanner/stop_watch_mode.md index 8d502950..9a16bed9 100644 --- a/docs/developer/api/scanner/stop_watch_mode.md +++ b/docs/developer/api/scanner/stop_watch_mode.md @@ -1,3 +1,11 @@ +--- +method: POST +path: /api/scanner/watch/stop +auth: jwt+admin +area: scanner +--- + + # Stop Watch Mode Stop watch mode for a library. @@ -14,7 +22,7 @@ Stop watch mode for a library. ### Example Request -```json +```json example-request { "library_id": "550e8400-e29b-41d4-a716-446655440000" } @@ -22,11 +30,10 @@ Stop watch mode for a library. ## Response (200 OK) -```json +```json example-response { "message": "Watch mode stopped successfully", - "library_id": "uuid", - "status": "stopped" + "library_id": "uuid" } ``` diff --git a/internal/docs/drift_test.go b/internal/docs/drift_test.go index 54763e8e..93a3b789 100644 --- a/internal/docs/drift_test.go +++ b/internal/docs/drift_test.go @@ -70,8 +70,11 @@ func extractRoutesFromRouterSource(t *testing.T) map[string]extractedRoute { // names the call site actually passes (router.go: protected=/api jwt, // admin=/api jwt+admin). if base == "scanner.go" { - groups["protected"] = group{prefix: "/api", middleware: []string{"jwt"}} - groups["admin"] = group{prefix: "/api", middleware: []string{"jwt", "admin"}} + // Middleware values must carry the marker substrings authLabel + // matches on (AdminMiddleware, createJWTMiddleware) — the seeded + // "jwt"/"admin" tokens were invisible to it. + groups["protected"] = group{prefix: "/api", middleware: []string{"jwtMiddleware"}} + groups["admin"] = group{prefix: "/api", middleware: []string{"jwtMiddleware", "AdminMiddleware"}} } raw, err := os.ReadFile(file) if err != nil { diff --git a/internal/docs/schema.go b/internal/docs/schema.go index 2fbf74d6..8081367a 100644 --- a/internal/docs/schema.go +++ b/internal/docs/schema.go @@ -599,11 +599,64 @@ var Schemas = map[string]Schema{ Response: json.RawMessage(`{"status": "reordered"}`), }, "PUT /api/collections/:id/rules/:ruleId": { - // Inline anonymous request struct — pinned by sample. - Request: json.RawMessage(`{"rule": {}}`), + // Handler is SetRuleEnabled: toggle only, request {enabled required}, + // response {collection_id, is_smart}. + Request: json.RawMessage(`{"enabled": false}`), Response: json.RawMessage(`{ - "id": "", "collection_id": "", "field": "", "operator": "", - "value": "", "priority": 0, "enabled": false, "is_smart": false + "collection_id": "", "is_smart": false + }`), + }, + + // --- Beam batch 7: scanner --- + "POST /api/scanner/scan": { + Request: handlers.ScanLibraryRequest{}, + // 202 Accepted; 409 coalescing variant adds {active_job_id}. + Response: json.RawMessage(`{ + "message": "", "job_id": "", "status": "" + }`), + }, + "POST /api/scanner/start": { + // Starts the long-lived watch scanner over folder_paths. + Request: handlers.ScanLibraryRequest{}, + Response: json.RawMessage(`{"message": ""}`), + }, + "POST /api/scanner/stop": { + Response: json.RawMessage(`{"message": ""}`), + }, + "GET /api/scanner/status/:jobId": { + // result is interface{} (job payload). 404 {error} when unknown job. + Response: json.RawMessage(`{ + "job_id": "", "status": "", "error": "", "result": null, + "progress": 0, "files_scanned": 0, "new_items": 0, "errors": 0 + }`), + }, + "GET /api/scanner/active": { + // jobs are []JobResult (job_id/status/progress/files_scanned/...). + Response: json.RawMessage(`{ + "scanning": false, "jobs": [] + }`), + }, + "GET /api/scanner/active-summary": { + Response: json.RawMessage(`{ + "scanning": false, "progress": 0 + }`), + }, + "POST /api/scanner/cancel/:jobId": { + Response: json.RawMessage(`{ + "message": "", "job_id": "", "status": "" + }`), + }, + "POST /api/scanner/watch/start": { + Request: json.RawMessage(`{"library_id": ""}`), + Response: json.RawMessage(`{"message": "", "library_id": ""}`), + }, + "POST /api/scanner/watch/stop": { + Request: json.RawMessage(`{"library_id": ""}`), + Response: json.RawMessage(`{"message": "", "library_id": ""}`), + }, + "GET /api/scanner/watch/status": { + Response: json.RawMessage(`{ + "watching_libraries": [""], "total_watching": 0 }`), }, }