New frontmatter'd docs with handler-compatible examples: - devices/get_device.md (DeviceInfo shape) - devices/regenerate_device_token.md (token + sync_urls) - devices/get_device_mappings.md (wrapper + DeviceMappingSummary) - devices/update_device_mapping.md (UpdateDeviceMappingRequest) - devices/download_sidecar_config.md (sidecar config JSON file) - devices/link_book.md (LinkBookRequest; one UNVERIFIED marker on required-tag) Schema registry: 6 new entries; DeviceMappingSummary hoisted to package scope. Gate passes (the UNVERIFIED marker is logged, not a failure). Excludes /api/devices-page (htmx page route, per ground rules).
123 lines
4.1 KiB
Go
123 lines
4.1 KiB
Go
package docs
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"bookhoard/internal/handlers"
|
|
"bookhoard/internal/services"
|
|
)
|
|
|
|
// Schema pins the machine-checkable contract for one route. The drift gate
|
|
// (drift_test.go) validates every frontmatter'd doc against the entry for its
|
|
// "METHOD path" key.
|
|
//
|
|
// MAINTENANCE CONTRACT: every doc with frontmatter MUST have a registry
|
|
// entry, and every new/changed route that ships a frontmatter'd doc updates
|
|
// its entry in the same commit. The gate fails loudly on a missing entry —
|
|
// that is the discipline, not an inconvenience.
|
|
//
|
|
// Request: the struct the handler Binds into (nil for bodyless routes).
|
|
// - example-request is decoded with unknown fields DISALLOWED (typo guard)
|
|
// - fields whose validate tag contains "required" must appear in the
|
|
// example (staleness guard)
|
|
//
|
|
// Response: what the handler passes to c.JSON.
|
|
// - struct / slice of struct: marshaled and its TOP-LEVEL key set compared
|
|
// against example-response in both directions
|
|
// - json.RawMessage: hand-built map responses pin their key set as a
|
|
// sample; promote to a struct when the endpoint is next touched
|
|
// - zero value (Schema{}): no JSON body expected (e.g. 204) — the doc must
|
|
// not carry an example-response block
|
|
type Schema struct {
|
|
Request any
|
|
Response any
|
|
}
|
|
|
|
// Schemas is keyed by "METHOD path" in Echo syntax. Sample values inside
|
|
// RawMessage pins use empty strings / zero values — only the KEY SET is
|
|
// checked, never the values.
|
|
var Schemas = map[string]Schema{
|
|
// --- collections (pilot area) ---
|
|
"POST /api/collections": {
|
|
Request: handlers.CreateCollectionRequest{},
|
|
Response: json.RawMessage(`{
|
|
"id": "", "user_id": "", "name": "", "description": "",
|
|
"color": "", "icon": "", "collection_type": "",
|
|
"auto_assign_rules": null, "view_settings": {},
|
|
"book_count": 0, "created_at": ""
|
|
}`),
|
|
},
|
|
"GET /api/collections": {
|
|
Response: struct {
|
|
Collections []handlers.CollectionSummary `json:"collections"`
|
|
Total int `json:"total"`
|
|
}{},
|
|
},
|
|
"GET /api/collections/:id": {
|
|
Response: json.RawMessage(`{
|
|
"id": "", "user_id": "", "name": "", "description": "",
|
|
"color": "", "icon": "", "auto_assign_rules": null,
|
|
"view_settings": {}, "books": [], "book_count": 0,
|
|
"is_smart": false, "up_next": [], "created_at": ""
|
|
}`),
|
|
},
|
|
"PUT /api/collections/:id": {
|
|
Request: handlers.UpdateCollectionRequest{},
|
|
Response: json.RawMessage(`{
|
|
"id": "", "user_id": "", "name": "", "description": "",
|
|
"color": "", "icon": "", "auto_assign_rules": null,
|
|
"view_settings": {}, "created_at": ""
|
|
}`),
|
|
},
|
|
"DELETE /api/collections/:id": {},
|
|
// --- devices (batch 1) ---
|
|
"GET /api/devices/:id": {
|
|
Response: handlers.DeviceInfo{},
|
|
},
|
|
"PUT /api/devices/:id/regenerate-token": {
|
|
Response: json.RawMessage(`{
|
|
"message": "", "auth_token": "",
|
|
"device": {
|
|
"id": "", "device_name": "", "device_type": "",
|
|
"last_sync": null, "last_seen": null,
|
|
"sync_enabled": false, "auto_sync": false,
|
|
"sync_frequency_minutes": 0, "created_at": "",
|
|
"device_metadata": null, "auth_token": ""
|
|
},
|
|
"sync_urls": {}
|
|
}`),
|
|
},
|
|
"GET /api/devices/:id/collections": {
|
|
Response: struct {
|
|
DeviceID string `json:"device_id"`
|
|
Mappings []handlers.DeviceMappingSummary `json:"mappings"`
|
|
Total int `json:"total"`
|
|
}{},
|
|
},
|
|
"PUT /api/devices/:id/collections/:collectionId": {
|
|
Request: handlers.UpdateDeviceMappingRequest{},
|
|
Response: json.RawMessage(`{
|
|
"id": "", "collection_id": "", "device_id": "",
|
|
"device_shelf_name": "", "sync_direction": "", "created_at": ""
|
|
}`),
|
|
},
|
|
"GET /api/devices/:id/sidecar/download": {
|
|
Response: json.RawMessage(`{
|
|
"version": "", "bookhoard": {}, "books": {},
|
|
"collections": [], "opds_enabled": false,
|
|
"sidecar_enabled": false, "last_updated": ""
|
|
}`),
|
|
},
|
|
"POST /api/devices/:deviceId/sync/link-book": {
|
|
Request: services.LinkBookRequest{},
|
|
Response: json.RawMessage(`{
|
|
"status": "",
|
|
"device_file_alias": {
|
|
"id": "", "media_item_id": "", "device_id": "",
|
|
"file_path": "", "file_sha256": "",
|
|
"confidence_score": 0.0
|
|
}
|
|
}`),
|
|
},
|
|
}
|