docs(analytics,jobs): Work Order 3 batch 4 — device usage, popular books, job create/status

DeviceUsageResponse / PopularBooksResponse / services.JobResult pinned by
type; jobs create uses an inline anonymous request struct (RawMessage pin).
Two UNVERIFIED markers recorded (analytics date-range params, facet value
element shape).
This commit is contained in:
John O'Keefe committed 2026-10-24 15:08:18 -04:00
1 parent 4a6fb033cb
commit 7471ca99e8
5 files changed
+169

No files matched your search

@@ -0,0 +1,40 @@
---
method: GET
path: /api/analytics/device-usage
auth: jwt
area: analytics
---
<!-- source: internal/handlers/analytics.go DeviceUsageResponse / GetDeviceUsage -->
# Get Device Usage
Per-device sync activity for the signed-in user.
## Example response
```json example-response
{
"devices": [
{
"device_id": "550e8400-e29b-41d4-a716-446655440000",
"device_name": "Kobo Clara",
"device_type": "kobo",
"sync_count": 42,
"last_sync": "2026-10-24T13:57:01Z",
"total_time_seconds": 3600,
"total_time_minutes": 60
}
]
}
```
## Query parameters
UNVERIFIED: accepts the standard analytics date-range params; confirm exact
names before relying on them.
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 500 | `{"error": "..."}` | unexpected server error |
@@ -0,0 +1,39 @@
---
method: GET
path: /api/analytics/popular-books
auth: jwt
area: analytics
---
<!-- source: internal/handlers/analytics.go PopularBooksResponse / GetPopularBooks -->
# Get Popular Books
Most-read books for the signed-in user.
## Example response
```json example-response
{
"books": [
{
"media_item_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "1984",
"author": "George Orwell",
"read_count": 3,
"avg_completion": 0.95,
"last_read": "2026-10-24T13:57:01Z"
}
]
}
```
## Query parameters
UNVERIFIED: accepts the standard analytics date-range params; confirm exact
names before relying on them.
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 500 | `{"error": "..."}` | unexpected server error |
+40
View File
@@ -0,0 +1,40 @@
---
method: POST
path: /api/jobs
auth: jwt
area: jobs
---
<!-- source: internal/handlers/jobs.go CreateJob -->
# Create Job
Enqueues a background job (scan, import, convert — the set accepted by the
worker). Success is **202 Accepted** with the job id; poll its status via
`GET /api/jobs/:jobId`.
## Example request
```json example-request
{
"type": "import",
"params": { "library_id": "990e8400-e29b-41d4-a716-446655440000" }
}
```
## Example response
```json example-response
{
"message": "Job created",
"job_id": "ee0e8400-e29b-41d4-a716-446655440000",
"type": "import",
"status": "pending"
}
```
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 400 | `{"error": "Invalid job type"}` / others | unknown type or params |
| 500 | `{"error": "..."}` | enqueue failure |
+34
View File
@@ -0,0 +1,34 @@
---
method: GET
path: /api/jobs/:jobId
auth: jwt
area: jobs
---
<!-- source: internal/handlers/jobs.go:76 GetJobStatus; services/worker.go JobResult -->
# Get Job Status
Returns the in-memory status/result of a background job.
## Example response
```json example-response
{
"job_id": "ee0e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"error": "",
"result": null,
"progress": 100,
"files_scanned": 120,
"new_items": 3,
"errors": 0
}
```
`library_id` and `job_type` appear only on scan jobs (`omitempty`).
## Errors
| Status | Body | When |
| ------ | ---- | ---- |
| 404 | `{"error": "Job not found"}` | unknown id, or the in-memory result was evicted by a restart |
+16
View File
@@ -238,4 +238,20 @@ var Schemas = map[string]Schema{
"GET /api/media-items/filter-count": { "GET /api/media-items/filter-count": {
Response: json.RawMessage(`{"count": 0}`), Response: json.RawMessage(`{"count": 0}`),
}, },
// --- analytics + jobs (batch 4) ---
"GET /api/analytics/device-usage": {
Response: handlers.DeviceUsageResponse{},
},
"GET /api/analytics/popular-books": {
Response: handlers.PopularBooksResponse{},
},
"POST /api/jobs": {
// Inline anonymous request struct {type, params} — pinned by sample.
Request: json.RawMessage(`{"type": "", "params": {}}`),
Response: json.RawMessage(`{"message": "", "job_id": "", "type": "", "status": ""}`),
},
"GET /api/jobs/:jobId": {
Response: services.JobResult{},
},
} }