docs(scanner): Beam batch 7 — all 10 scanner endpoints backfilled
Scanner surface frontmatter'd (scan/start/stop/cancel/status/active/
active-summary + watch start/stop/status) with provenance comments;
active-summary on a non-admin group.
Claude registry: 10 entries. Fixed handler drift Beam missed: scan_library
request has no "recursive"; scanner/start binds folder_paths (not
invented interval_seconds); scanner/stop binds no body; response is
{message, job_id, status} with 409 coalescing variant
{error, active_job_id}.
Gate fix in drift_test seed: registerScannerRoutes group seeds used bare
"jwt"/"admin" tokens invisible to authLabel; and silently passed before
only because the seeded set was unchecked. Seeds now 'jwtMiddleware'/
'AdminMiddleware' markers. Also: cleanup of a stale-merge-script residue
in collections/update_rule.md example (extra null keys). make test 9/9.
This commit is contained in:
1 parent
25ef0d12ba
commit
2c56ae4b9d
13 files changed
+238
-61
No files matched your search
@@ -26,13 +26,7 @@ the rules endpoint.
|
|||||||
```json example-response
|
```json example-response
|
||||||
{
|
{
|
||||||
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
"is_smart": true,
|
"is_smart": true
|
||||||
"enabled": null,
|
|
||||||
"field": null,
|
|
||||||
"id": null,
|
|
||||||
"operator": null,
|
|
||||||
"priority": null,
|
|
||||||
"value": null
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
method: POST
|
||||||
|
path: /api/scanner/cancel/:jobId
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:236 Handler.CancelScan -->
|
||||||
|
|
||||||
|
# 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 |
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
method: GET
|
||||||
|
path: /api/scanner/active
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:203 Handler.GetActiveScans -->
|
||||||
|
|
||||||
|
# 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 |
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
method: GET
|
||||||
|
path: /api/scanner/status/:jobId
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:179 Handler.GetScanStatus -->
|
||||||
|
|
||||||
# Get Scan Status
|
# Get Scan Status
|
||||||
|
|
||||||
Get the status of a specific scan job.
|
Get the status of a specific scan job.
|
||||||
@@ -26,13 +34,10 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|||||||
|
|
||||||
## Response (200 OK)
|
## Response (200 OK)
|
||||||
|
|
||||||
```json
|
```json example-response
|
||||||
{
|
{
|
||||||
"job_id": "uuid",
|
"job_id": "uuid",
|
||||||
"library_id": "uuid",
|
|
||||||
"status": "in_progress",
|
"status": "in_progress",
|
||||||
"started_at": "2026-02-08T10:00:00Z",
|
|
||||||
"updated_at": "2026-02-08T10:05:00Z",
|
|
||||||
"progress": {
|
"progress": {
|
||||||
"total_files": 1523,
|
"total_files": 1523,
|
||||||
"scanned_files": 850,
|
"scanned_files": 850,
|
||||||
@@ -46,7 +51,11 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|||||||
"file_path": "/path/to/file.epub",
|
"file_path": "/path/to/file.epub",
|
||||||
"error": "Invalid EPUB format"
|
"error": "Invalid EPUB format"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"error": null,
|
||||||
|
"files_scanned": null,
|
||||||
|
"new_items": null,
|
||||||
|
"result": null
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
method: GET
|
||||||
|
path: /api/scanner/active-summary
|
||||||
|
auth: jwt
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:216 Handler.GetScanSummary -->
|
||||||
|
|
||||||
|
# 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 |
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
method: GET
|
||||||
|
path: /api/scanner/watch/status
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:376 Handler.GetWatchModeStatus -->
|
||||||
|
|
||||||
# Get Watch Mode Status
|
# Get Watch Mode Status
|
||||||
|
|
||||||
Get the watch mode status for a library.
|
Get the watch mode status for a library.
|
||||||
@@ -26,18 +34,10 @@ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|||||||
|
|
||||||
## Response (200 OK)
|
## Response (200 OK)
|
||||||
|
|
||||||
```json
|
```json example-response
|
||||||
{
|
{
|
||||||
"library_id": "uuid",
|
"total_watching": null,
|
||||||
"status": "watching",
|
"watching_libraries": null
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
method: POST
|
||||||
|
path: /api/scanner/scan
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:34 Handler.ScanLibrary -->
|
||||||
|
|
||||||
# Scan Library
|
# Scan Library
|
||||||
|
|
||||||
Initiate a one-time scan of a library for ebooks, manga, or comics.
|
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
|
### Example Request
|
||||||
|
|
||||||
```json
|
```json example-request
|
||||||
{
|
{
|
||||||
"library_id": "550e8400-e29b-41d4-a716-446655440000",
|
"library_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
"recursive": true,
|
|
||||||
"force": false
|
"force": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Response (202 Accepted)
|
## Response (202 Accepted)
|
||||||
|
|
||||||
```json
|
```json example-response
|
||||||
{
|
{
|
||||||
"message": "Scan initiated successfully",
|
"message": "scan job enqueued",
|
||||||
"job_id": "uuid",
|
"job_id": "b3d3a0c2-8f4e-4c1d-9a2b-6e5f1a7c9d01",
|
||||||
"library_id": "uuid",
|
|
||||||
"status": "pending"
|
"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
|
## Error Responses
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
method: POST
|
||||||
|
path: /api/scanner/start
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:134 Handler.StartScanner -->
|
||||||
|
|
||||||
# Start Scanner
|
# Start Scanner
|
||||||
|
|
||||||
Start the automated background scanner for a library. Supports ebook, manga, and comic libraries.
|
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
|
### Example Request
|
||||||
|
|
||||||
```json
|
```json example-request
|
||||||
{
|
{
|
||||||
"library_id": "550e8400-e29b-41d4-a716-446655440000",
|
"folder_paths": ["/data/books"]
|
||||||
"interval_seconds": 3600
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Response (200 OK)
|
## Response (200 OK)
|
||||||
|
|
||||||
```json
|
```json example-response
|
||||||
{
|
{
|
||||||
"message": "Scanner started successfully",
|
"message": "Scanner started successfully"
|
||||||
"library_id": "uuid",
|
|
||||||
"interval_seconds": 3600,
|
|
||||||
"status": "running"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
method: POST
|
||||||
|
path: /api/scanner/watch/start
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:314 Handler.StartWatchMode -->
|
||||||
|
|
||||||
# Start Watch Mode
|
# 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.
|
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
|
### Example Request
|
||||||
|
|
||||||
```json
|
```json example-request
|
||||||
{
|
{
|
||||||
"library_id": "550e8400-e29b-41d4-a716-446655440000"
|
"library_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
}
|
}
|
||||||
@@ -32,11 +40,10 @@ Watch mode automatically detects and processes:
|
|||||||
|
|
||||||
## Response (200 OK)
|
## Response (200 OK)
|
||||||
|
|
||||||
```json
|
```json example-response
|
||||||
{
|
{
|
||||||
"message": "Watch mode started successfully",
|
"message": "Watch mode started successfully",
|
||||||
"library_id": "uuid",
|
"library_id": "uuid"
|
||||||
"status": "watching"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
method: POST
|
||||||
|
path: /api/scanner/stop
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:168 Handler.StopScanner -->
|
||||||
|
|
||||||
# Stop Scanner
|
# Stop Scanner
|
||||||
|
|
||||||
Stop the automated background scanner for a library.
|
Stop the automated background scanner for a library.
|
||||||
@@ -14,19 +22,11 @@ Stop the automated background scanner for a library.
|
|||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"library_id": "550e8400-e29b-41d4-a716-446655440000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Response (200 OK)
|
## Response (200 OK)
|
||||||
|
|
||||||
```json
|
```json example-response
|
||||||
{
|
{
|
||||||
"message": "Scanner stopped successfully",
|
"message": "Scanner stopped successfully"
|
||||||
"library_id": "uuid",
|
|
||||||
"status": "stopped"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
---
|
||||||
|
method: POST
|
||||||
|
path: /api/scanner/watch/stop
|
||||||
|
auth: jwt+admin
|
||||||
|
area: scanner
|
||||||
|
---
|
||||||
|
<!-- source: internal/handlers/scanner.go:348 Handler.StopWatchMode -->
|
||||||
|
|
||||||
# Stop Watch Mode
|
# Stop Watch Mode
|
||||||
|
|
||||||
Stop watch mode for a library.
|
Stop watch mode for a library.
|
||||||
@@ -14,7 +22,7 @@ Stop watch mode for a library.
|
|||||||
|
|
||||||
### Example Request
|
### Example Request
|
||||||
|
|
||||||
```json
|
```json example-request
|
||||||
{
|
{
|
||||||
"library_id": "550e8400-e29b-41d4-a716-446655440000"
|
"library_id": "550e8400-e29b-41d4-a716-446655440000"
|
||||||
}
|
}
|
||||||
@@ -22,11 +30,10 @@ Stop watch mode for a library.
|
|||||||
|
|
||||||
## Response (200 OK)
|
## Response (200 OK)
|
||||||
|
|
||||||
```json
|
```json example-response
|
||||||
{
|
{
|
||||||
"message": "Watch mode stopped successfully",
|
"message": "Watch mode stopped successfully",
|
||||||
"library_id": "uuid",
|
"library_id": "uuid"
|
||||||
"status": "stopped"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -70,8 +70,11 @@ func extractRoutesFromRouterSource(t *testing.T) map[string]extractedRoute {
|
|||||||
// names the call site actually passes (router.go: protected=/api jwt,
|
// names the call site actually passes (router.go: protected=/api jwt,
|
||||||
// admin=/api jwt+admin).
|
// admin=/api jwt+admin).
|
||||||
if base == "scanner.go" {
|
if base == "scanner.go" {
|
||||||
groups["protected"] = group{prefix: "/api", middleware: []string{"jwt"}}
|
// Middleware values must carry the marker substrings authLabel
|
||||||
groups["admin"] = group{prefix: "/api", middleware: []string{"jwt", "admin"}}
|
// 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)
|
raw, err := os.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+57
-4
@@ -599,11 +599,64 @@ var Schemas = map[string]Schema{
|
|||||||
Response: json.RawMessage(`{"status": "reordered"}`),
|
Response: json.RawMessage(`{"status": "reordered"}`),
|
||||||
},
|
},
|
||||||
"PUT /api/collections/:id/rules/:ruleId": {
|
"PUT /api/collections/:id/rules/:ruleId": {
|
||||||
// Inline anonymous request struct — pinned by sample.
|
// Handler is SetRuleEnabled: toggle only, request {enabled required},
|
||||||
Request: json.RawMessage(`{"rule": {}}`),
|
// response {collection_id, is_smart}.
|
||||||
|
Request: json.RawMessage(`{"enabled": false}`),
|
||||||
Response: json.RawMessage(`{
|
Response: json.RawMessage(`{
|
||||||
"id": "", "collection_id": "", "field": "", "operator": "",
|
"collection_id": "", "is_smart": false
|
||||||
"value": "", "priority": 0, "enabled": false, "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
|
||||||
}`),
|
}`),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user