docs(libraries): Work Order 3 batch 2 — 10 missing endpoints + array-response gate support

New frontmatter'd docs with handler-compatible shapes:
- get_library_types.md (public; []LibraryTypes)
- list_libraries.md (admin; {data: ListLibrariesRow})
- get_library_folders.md (admin; []LibraryFolders)
- list/get stats/resolve/delete processing issues (admin; family-lens 403s
  documented)
- scan_library.md (202 + 409 coalescing with active_job_id)
- get/update scan-settings (ScanSettingsResponse)

Gate improvements forced by this batch:
- Array responses: registered slice -> doc example must be a JSON array; key
  set compared against the element type
- RawMessage pins may now be arrays
- update scan-settings uses a RawMessage pin: handler always sets Message but
  the struct's omitempty would reject correct docs via zero-value marshal

Registry: sqlc models pinned directly (LibraryTypes, LibraryFolders,
ListLibrariesRow — json tags verified in the generated code).
This commit is contained in:
John O'Keefe committed 2026-10-24 14:57:45 -04:00
1 parent c4b62af213
commit 85cdaf7196
12 files changed
+423 -11

No files matched your search

@@ -0,0 +1,29 @@
---
method: DELETE
path: /api/libraries/:id/issues/:issueId
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/processing_issues.go:183 ProcessingIssuesHandler.DeleteProcessingIssue -->
# Delete Processing Issue
Permanently deletes a processing issue record (distinct from resolving it).
## Example response
```json example-response
{
"success": true,
"message": "Issue deleted"
}
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "Invalid issue ID"}` | malformed id |
| 403 | — | issue's library not in requester's visible set |
| 404 | — | no such issue |
| 500 | `{"error": "Failed to delete issue"}` | unexpected server error |
@@ -0,0 +1,32 @@
---
method: GET
path: /api/libraries/:id/folders
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:216 LibraryHandler.GetLibraryFolders -->
# Get Library Folders
Lists the filesystem folders configured for a library.
## Example response
```json example-response
[
{
"id": "aa0e8400-e29b-41d4-a716-446655440000",
"library_id": "990e8400-e29b-41d4-a716-446655440000",
"folder_path": "/books/manga",
"created_at": "2026-01-01T00:00:00Z"
}
]
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "invalid library id"}` | malformed id |
| 401 / 403 | — | missing JWT / not an admin |
| 500 | `{"error": "..."}` | unexpected server error |
@@ -0,0 +1,33 @@
---
method: GET
path: /api/libraries/types
auth: none
area: libraries
---
<!-- source: internal/handlers/library.go:51 LibraryHandler.GetLibraryTypes -->
# Get Library Types
Public endpoint: lists the library types the server supports (ebooks, comics,
audiobooks, …) with their allowed file extensions. Used by the create-library
form before login is required.
## Example response
```json example-response
[
{
"id": "880e8400-e29b-41d4-a716-446655440000",
"name": "ebooks",
"description": "Electronic books",
"allowed_extensions": [".epub", ".pdf", ".mobi"],
"created_at": "2026-01-01T00:00:00Z"
}
]
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 500 | `{"error": "..."}` | unexpected server error |
@@ -0,0 +1,29 @@
---
method: GET
path: /api/libraries/:id/issues/stats
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/processing_issues.go:128 ProcessingIssuesHandler.GetProcessingIssueStats -->
# Get Processing Issue Stats
Severity counts for a library's processing issues.
## Example response
```json example-response
{
"error_count": 2,
"warning_count": 5,
"info_count": 1
}
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 403 | — | library not in requester's visible set (family lens) |
| 404 | — | no such library |
| 500 | `{"error": "Failed to fetch stats"}` | unexpected server error |
@@ -0,0 +1,30 @@
---
method: GET
path: /api/libraries/scan-settings
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/system_settings.go:253 SystemSettingsHandler.GetScanSettings -->
# Get Scan Settings
Returns the global scan poll interval and auto-scan flag (system settings,
admin-scoped). Defaults to 1800s / enabled when unset.
## Example response
```json example-response
{
"scan_poll_interval_seconds": 1800,
"auto_scan_enabled": true
}
```
`message` appears only in the update response (`omitempty`).
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 401 / 403 | — | missing JWT / not an admin |
| 500 | `{"error": "..."}` | unexpected server error |
@@ -0,0 +1,43 @@
---
method: GET
path: /api/libraries
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/library.go:102 LibraryHandler.ListLibraries -->
# List Libraries (Admin)
Lists **all** libraries regardless of per-user visibility. (Non-admin users
see their subset via `GET /api/libraries/visibility`.)
## Example response
```json example-response
{
"data": [
{
"id": "990e8400-e29b-41d4-a716-446655440000",
"name": "Manga",
"description": null,
"library_type_id": "880e8400-e29b-41d4-a716-446655440000",
"created_by_admin_id": "92413c3a-e1b8-4fff-b3f9-95c74478d19a",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z",
"type_name": "manga",
"type_description": null
}
]
}
```
Nullable fields (`description`, `type_description`) are JSON `null` when
unset. `type_name`/`type_description` come from the joined library type.
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 401 | — | missing/invalid JWT |
| 403 | — | not an admin |
| 500 | `{"error": "..."}` | unexpected server error |
@@ -0,0 +1,40 @@
---
method: GET
path: /api/libraries/:id/issues/list
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/processing_issues.go:83 ProcessingIssuesHandler.ListProcessingIssues -->
# List Processing Issues
Lists scan/processing issues for a library (import failures, metadata
problems). Row-less import failures have no media item — their
`media_item_id` is `""` and `title` falls back to the file path.
## Example response
```json example-response
[
{
"id": "bb0e8400-e29b-41d4-a716-446655440000",
"media_item_id": "cc0e8400-e29b-41d4-a716-446655440000",
"title": "1984",
"file_path": "/books/1984.epub",
"format_group": "text",
"library_type_name": "ebooks",
"issue_type": "import_failed",
"issue_description": "unsupported format",
"severity": "error",
"created_at": "2026-10-24T13:57:01Z"
}
]
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 403 | — | library not in requester's visible set (family lens) |
| 404 | — | no such library |
| 500 | `{"error": "Failed to fetch issues"}` | unexpected server error |
@@ -0,0 +1,30 @@
---
method: POST
path: /api/libraries/:id/issues/:issueId/:mediaItemId/resolve
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/processing_issues.go:150 ProcessingIssuesHandler.ResolveProcessingIssue -->
# Resolve Processing Issue
Marks one processing issue as resolved. The issue's library must be in the
requester's visible set.
## Example response
```json example-response
{
"success": true,
"message": "Issue marked as resolved"
}
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "Invalid issue ID"}` / `{"error": "Invalid media item ID"}` | malformed path params |
| 403 | — | issue's library not in requester's visible set |
| 404 | — | no such issue |
| 500 | `{"error": "Failed to resolve issue"}` | unexpected server error |
@@ -0,0 +1,35 @@
---
method: POST
path: /api/libraries/:id/scan
auth: jwt+admin
area: libraries
---
<!-- source: internal/router/library.go:43 (wraps handlers/scanner.go:34 Handler.ScanLibrary) -->
# Scan Library
Enqueues a full scan of one library's configured folders. The library id comes
from the path — no request body is needed (the generic `POST /api/scanner/scan`
variant is the one that takes `folder_paths`).
Scans are **coalesced**: if a scan for this library is already pending or
running you get **409** with the active job id instead of a second scan.
## Example response (202 Accepted)
```json example-response
{
"message": "scan job enqueued",
"job_id": "dd0e8400-e29b-41d4-a716-446655440000",
"status": "pending"
}
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "invalid library id"}` / `{"error": "library has no folders configured"}` | malformed id / empty library |
| 404 | `{"error": "library not found or has no folders"}` | unknown library |
| 409 | `{"error": "a scan for this library is already pending or running", "active_job_id": "..."}` | scan coalescing |
| 500 | `{"error": "failed to enqueue scan job: ..."}` | queue failure |
@@ -0,0 +1,47 @@
---
method: PUT
path: /api/libraries/scan-settings
auth: jwt+admin
area: libraries
---
<!-- source: internal/handlers/system_settings.go:210 SystemSettingsHandler.UpdateScanSettings -->
# Update Scan Settings
Sets the global scan poll interval and auto-scan flag, then reloads the
settings registry so running code picks the new values up.
## Example request
```json example-request
{
"scan_poll_interval_seconds": 3600,
"auto_scan_enabled": true
}
```
## Example response
```json example-response
{
"scan_poll_interval_seconds": 3600,
"auto_scan_enabled": true,
"message": "scan settings updated successfully"
}
```
## Request fields
| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| scan_poll_interval_seconds | number | yes | 1–3600 |
| auto_scan_enabled | boolean | no | defaults false when omitted |
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "..."}` | validation failure |
| 401 / 403 | — | missing JWT / not an admin |
| 404 | `{"error": "system setting not found"}` | settings rows missing from DB |
| 500 | `{"error": "..."}` | unexpected server error |