docs(saved-filters,dashboard,system,series): Beam WO3 batch 5 — 15 endpoints + dedupe

Beam drafted 15 frontmatter'd docs (saved-filters CRUD, dashboard
sections/preferences/restore, system config/settings, series list + books);
Claude verified shapes against handlers and pinned the registry:
- saved-filters: SavedFilterResponse pinned by type (RFC3339 timestamps here,
  unlike most of the API); create/update share CreateSavedFilterRequest
  (resource_type oneof media-items|collections|devices)
- dashboard: preferences GET pin {hidden_collections, collection_order,
  items_per_section}; PUT returns full UserDashboardPreferences row; sections
  wrapper {sections}
- system: GET+PUT /api/system/config are admin-defined key/value maps — new
  AnyJSONObject sentinel (gate shape-checks without key comparison; request
  side too). UpdateSystemConfiguration returns {status, message} — verified,
  not guessed. GET /api/system/settings returns []SettingEntry.
- series: {series[{name, library_id, book_count, total_in_series,
  cover_paths, last_entry_at}], total, limit, offset}; books by
  ?name=&library_id= with family-lens 403

Dedupe: deleted dashboard.md, system/config.md, system/settings.md — each
covered the same endpoints as Beam's per-endpoint files (content merged by
the gate-verified shapes; old prose recoverable from git).

Beam clarifications confirmed: scanner routes are WO5 (docs exist);
progress/books-match are WO5.
This commit is contained in:
John O'Keefe committed 2026-10-24 15:29:24 -04:00
1 parent 7471ca99e8
commit fa00d6d3cb
20 files changed
+714 -416

No files matched your search

-77
View File
@@ -1,77 +0,0 @@
# System Config API
## Overview
Raw key/value system configuration storage (backed by the `system_config` table). Unlike the typed [System Settings API](settings.md), this endpoint reads and writes arbitrary config keys as plain strings — including keys without registry metadata, such as `base_url`.
**Base URL**: `/api/system`
**Authentication**: Admin JWT token required
**Content-Type**: `application/json`
---
## Endpoints
### Get System Configuration
Retrieve all system configuration entries as a flat key/value map.
**Endpoint**: `GET /api/system/config`
**Authentication**: Admin role required
**Response**: **200 OK**
```json
{
"base_url": "http://192.168.1.100:8765",
"default_timezone": "America/New_York"
}
```
**Example**:
```bash
curl -X GET https://bookhoard.example.com/api/system/config \
-H "Authorization: Bearer <admin_token>"
```
---
### Update System Configuration
Update one or more config values.
**Endpoint**: `PUT /api/system/config`
**Authentication**: Admin role required
**Request Body**: a flat map of keys to string values. Only the supplied keys are updated.
```json
{
"base_url": "https://bookhoard.example.com"
}
```
**Validation**: values for known keys are validated where applicable — for example, `default_timezone` must be a valid IANA timezone (`time.LoadLocation`); invalid values return `400` without persisting.
**Response**: **200 OK** on success; `400` (invalid value/format), `401`, `403`, `500` on failure.
**Example**:
```bash
curl -X PUT https://bookhoard.example.com/api/system/config \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{"base_url": "https://bookhoard.example.com"}'
```
> **Note:** settings that appear in the typed settings registry (e.g. `default_timezone`) are better managed through [`PUT /api/system/settings`](settings.md), which also returns metadata and reload hints. Writes through either endpoint refresh the shared registry cache.
---
## Related Endpoints
- [System Settings API](settings.md) — typed, validated tunable settings with metadata
- `GET /api/devices/:id/sidecar` — device setup config derived from system config (see [Devices API](../devices/))
+30
View File
@@ -0,0 +1,30 @@
---
method: GET
path: /api/system/config
auth: jwt+admin
area: system
---
<!-- source: internal/handlers/sidecar.go:374 SidecarHandler.GetSystemConfiguration -->
# Get System Configuration
Returns all system-wide configuration key/value pairs (backed by the
`system_config` table).
## Example response
```json example-response
{
"base_url": "https://books.example.com",
"opds_base_url": "https://books.example.com/opds",
"api_base_url": "https://books.example.com/api"
}
```
UNVERIFIED: exact config key set is arbitrary (admin-defined); example shows common keys only.
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 500 | `{"error": "failed to fetch system config"}` | unexpected server error |
+34
View File
@@ -0,0 +1,34 @@
---
method: GET
path: /api/system/settings
auth: jwt+admin
area: system
---
<!-- source: internal/handlers/system_settings.go:44 SystemSettingsHandler.GetSettings -->
# Get System Settings
Returns metadata and current values for every known tunable system setting.
## Example response
```json example-response
[
{
"key": "scan_poll_interval_seconds",
"value": "60",
"type": "int",
"requires_restart": false,
"category": "scanner",
"group": "Scanning",
"description": "How often to scan all libraries (seconds)",
"is_default": true
}
]
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 503 | `{"error": "settings registry not initialized"}` | registry not wired |
-196
View File
@@ -1,196 +0,0 @@
# System Settings API
## Overview
The System Settings API is the canonical way to read and write Bookhoard's tunable system settings (scanning, security, rate limits, sync, performance, and defaults). Every setting carries full metadata — type, range, category, description, and whether a restart is required — so the admin UI (and API clients) can render and validate settings generically.
**Base URL**: `/api/system`
**Authentication**: Admin JWT token required
**Content-Type**: `application/json`
---
## Endpoints
### List All Settings
Retrieve every known tunable setting with its current value and metadata.
**Endpoint**: `GET /api/system/settings`
**Authentication**: Admin role required
**Response**: **200 OK**
```json
[
{
"key": "scan_poll_interval_seconds",
"value": "60",
"type": "int",
"min": "1",
"max": "3600",
"requires_restart": false,
"category": "scanner",
"group": "Scanning",
"description": "How often to scan all libraries (seconds)",
"is_default": true
}
]
```
**Entry fields**:
| Field | Type | Description |
| ------------------ | ------- | -------------------------------------------------------- |
| `key` | string | Setting identifier (stable API name) |
| `value` | string | Current value (validated/clamped by the registry) |
| `type` | string | `int`, `bool`, or `string` |
| `min` / `max` | string | Range bounds for `int` settings (omitted otherwise) |
| `requires_restart` | boolean | Change takes effect only after a server restart |
| `category` | string | Coarse area: `scanner`, `security`, `api`, `sync`, `performance`, `general` |
| `group` | string | Sub-section shown in the admin UI |
| `description` | string | Human-readable description |
| `is_default` | boolean | True when the current value equals the compiled default |
**Example**:
```bash
curl -X GET https://bookhoard.example.com/api/system/settings \
-H "Authorization: Bearer <admin_token>"
```
---
### Update a Setting
Validate, persist, and reload a single setting.
**Endpoint**: `PUT /api/system/settings`
**Authentication**: Admin role required
**Request Body**:
```json
{
"key": "scan_poll_interval_seconds",
"value": "30"
}
```
| Field | Type | Required | Description |
| ------- | ------ | -------- | ------------------------------- |
| `key` | string | Yes | Setting key (from the list) |
| `value` | string | Yes | New value, as a string |
**Response**: **200 OK**
```json
{
"key": "scan_poll_interval_seconds",
"value": "30",
"type": "int",
"min": "1",
"max": "3600",
"requires_restart": false,
"category": "scanner",
"group": "Scanning",
"description": "How often to scan all libraries (seconds)",
"is_default": false,
"reload_required": false,
"message": ""
}
```
- `reload_required: true` means the change takes effect only after a restart (e.g. rate limits, worker pool, lockout settings).
- Validation is type-aware: `int` values are checked against `min`/`max`, `bool` values must parse, `default_timezone` must be a valid IANA timezone via `time.LoadLocation`, and strings must be non-empty.
**Errors**: `400` (unknown key, invalid value, out of range), `401`, `403`, `503` (settings registry not initialized).
**Example**:
```bash
curl -X PUT https://bookhoard.example.com/api/system/settings \
-H "Authorization: Bearer <admin_token>" \
-H "Content-Type: application/json" \
-d '{"key": "scan_poll_interval_seconds", "value": "30"}'
```
---
## Setting Catalog
Current tunable settings by category:
**Scanner** (`scanner`)
| Key | Default | Range | Restart | Description |
| ------------------------------ | ------- | -------- | ------- | ----------------------------------------- |
| `scan_poll_interval_seconds` | `60` | 1-3600 | No | How often to scan all libraries (seconds) |
| `auto_scan_enabled` | `true` | - | No | Whether auto-scanning is enabled |
**General** (`general`)
| Key | Default | Restart | Description |
| ----------------- | ------- | ------- | ------------------------- |
| `default_timezone`| `UTC` | No | System default timezone |
**Security** (`security`)
| Key | Default | Range | Restart | Description |
| ---------------------------- | --------- | ------------ | ------- | ---------------------------------------------- |
| `session_duration_seconds` | `604800` | 300-31536000 | No | How long a login session stays valid |
| `password_min_length` | `8` | 1-128 | No | Minimum password length |
| `password_require_upper` | `true` | - | No | Require at least one uppercase letter |
| `password_require_lower` | `true` | - | No | Require at least one lowercase letter |
| `password_require_number` | `true` | - | No | Require at least one number |
| `password_require_special` | `true` | - | No | Require at least one special character |
| `auth_rate_limit_per_min` | `10` | 1-10000 | **Yes** | Global auth API rate limit (req/min) |
| `login_max_attempts` | `5` | 1-100 | **Yes** | Failed login attempts before lockout |
| `login_lockout_minutes` | `15` | 1-10080 | **Yes** | Lockout duration after failed logins |
**API** (`api`)
| Key | Default | Range | Restart | Description |
| ------------------------------- | ------- | --------- | ------- | ------------------------------------ |
| `opds_default_page_size` | `50` | 1-500 | No | Default OPDS page size |
| `opds_max_page_size` | `200` | 1-1000 | No | Maximum OPDS page size |
| `device_rate_sync_per_min` | `60` | 1-10000 | No | Device sync requests per minute |
| `device_rate_progress_per_min` | `120` | 1-10000 | No | Device progress requests per minute |
| `device_rate_metadata_per_min` | `30` | 1-10000 | No | Device metadata requests per minute |
**Sync** (`sync`)
| Key | Default | Range | Restart | Description |
| ------------------------------- | ------- | -------- | ------- | -------------------------------------------------- |
| `annotation_tombstone_ttl_days` | `30` | 1-3650 | No | How long deleted annotations are kept before purge |
| `sync_queue_interval_seconds` | `5` | 1-3600 | **Yes** | How often the sync queue flushes |
| `sync_queue_batch_size` | `50` | 1-10000 | **Yes** | Max items processed per sync queue flush |
**Performance** (`performance`)
| Key | Default | Range | Restart | Description |
| ------------------------ | ------- | --------- | ------- | ------------------------------------------- |
| `conversion_cache_ttl_hours` | `24` | 1-720 | No | How long converted (KEPUB) files are cached |
| `worker_pool_size` | `3` | 1-100 | **Yes** | Number of background worker goroutines |
| `worker_queue_cap` | `100` | 1-10000 | **Yes** | Background worker job queue capacity |
---
## Legacy Scan Settings Routes
The older JSON routes still work for backward compatibility and now refresh the settings registry cache on write, but they are **superseded** by `GET/PUT /api/system/settings`:
- `GET /api/libraries/scan-settings` — returns only `scan_poll_interval_seconds` and `auto_scan_enabled`
- `PUT /api/libraries/scan-settings` — accepts `{ "scan_poll_interval_seconds": int, "auto_scan_enabled": bool }`
Both fields are backed by the same registry entries documented above.
---
## Related Endpoints
- `GET/PUT /api/system/config` — raw key/value system configuration (see [System Config API](config.md))
- `POST /api/scanner/scan` — trigger a manual scan (see [Scanner API](../scanner/))
- `GET /api/admin/hash-conflicts` — duplicates found during hashing (see [Hash Conflicts API](../admin/hash-conflicts.md))
@@ -0,0 +1,46 @@
---
method: PUT
path: /api/system/config
auth: jwt+admin
area: system
---
<!-- source: internal/handlers/sidecar.go:396 SidecarHandler.UpdateSystemConfiguration -->
# Update System Configuration
Updates one or more system-wide configuration keys. Admin only. When
`base_url` is provided, derived keys (`opds_base_url`, `api_base_url`) are
updated automatically. `default_timezone` is validated as a timezone.
## Example request
```json example-request
{
"base_url": "https://books.example.com",
"default_timezone": "UTC"
}
```
## Example response
```json example-response
{
"status": "success",
"message": "System configuration updated"
}
```
## Fields
| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| (any key) | string | No | Arbitrary config key/value pairs |
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "invalid request format"}` / `{"error": "invalid timezone"}` | malformed body or bad timezone |
| 401 | `{"error": "unauthorized"}` | no/invalid JWT |
| 403 | `{"error": "admin access required"}` | non-admin |
| 500 | `{"error": "failed to update config key: ..."}` / `{"error": "failed to update default timezone"}` / `{"error": "failed to update derived config key: ..."}` | unexpected server error |
@@ -0,0 +1,50 @@
---
method: PUT
path: /api/system/settings
auth: jwt+admin
area: system
---
<!-- source: internal/handlers/system_settings.go:65 SystemSettingsHandler.UpdateSetting -->
# Update System Setting
Validates, persists, and reloads a single tunable system setting. Admin only.
## Example request
```json example-request
{
"key": "scan_poll_interval_seconds",
"value": "60"
}
```
## Example response
```json example-response
{
"key": "scan_poll_interval_seconds",
"value": "60",
"type": "int",
"requires_restart": false,
"category": "scanner",
"group": "Scanning",
"description": "How often to scan all libraries (seconds)",
"is_default": true,
"reload_required": false
}
```
## Fields
| Field | Type | Required | Notes |
| ----- | ---- | -------- | ----- |
| key | string | Yes | Known setting key |
| value | string | Yes | Value (validated against type/bounds) |
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "invalid request"}` / `{"error": "key is required"}` / `{"error": "unknown setting key: ..."}` / `{"error": "value must be ..."}` | malformed body or invalid value |
| 503 | `{"error": "settings registry not initialized"}` | registry not wired |