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
|
||||
{
|
||||
"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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
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 |
|
||||
|
||||
@@ -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 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"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 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"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 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"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 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"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+57
-4
@@ -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
|
||||
}`),
|
||||
},
|
||||
}
|
||||
Reference in new issue
Block a user