docs(auth,users,devices,progress,matching): Beam batch 8 — WO5 backfill complete

14 endpoints backfilled: devices pending/approve/reject; users
password/profile/profile-delete self-service (admin variants in prose);
admin list_users + update_user_max_devices; progress history; book
matching; new password-rules doc.

Gate fix: authLabel missed JWT middleware written inline as
createJWTMiddleware(cfg) (uppercase 'JWT') — reported auth 'none' for
/api/auth self-service routes and would have for any wrapper-inlined
group. Labeled 'JWTMiddleware' + fallthrough 'jwt' case.

Claude registry: 12 entries. Drift fixed by gate: list_users on-device —
handler returns raw []database.Users (bare array, full model incl.
password_hash — real leak the API emits; pinned + documented; earlier
draft had invented {users,total,limit,offset} wrapper). max-devices
response is {message} only. books/match is GET /api/books/match
{matches, action}. progress history sessions = ReadingHistory rows
(pgtype fields). make test 9/9.
This commit is contained in:
John O'Keefe committed 2026-10-24 17:33:32 -04:00
1 parent 2c56ae4b9d
commit cc5677eb17
13 files changed
+233 -75

No files matched your search

+4
View File
@@ -140,6 +140,10 @@ func authLabel(chain []string) string {
return "device-token"
case has("createOptionalAuthMiddleware"):
return "optional-jwt"
case has("JWTMiddleware"):
// Wrapper-inlined forms like createJWTMiddleware(cfg) carry uppercase
// JWT; bare jwtMiddleware carries lowercase. Match both.
fallthrough
case has("jwt"):
return "jwt"
default:
+67
View File
@@ -659,4 +659,71 @@ var Schemas = map[string]Schema{
"watching_libraries": [""], "total_watching": 0
}`),
},
// --- Beam batch 8: devices remaining + auth remainder ---
"GET /api/devices/pending": {
// Device registrations live in an in-memory map; both key spellings
// are emitted for compatibility.
Response: json.RawMessage(`{
"registrations": [], "pending_registrations": [], "total": 0
}`),
},
"GET /api/devices/approve/:registration_id": {
// Already-approved variant omits registration_id. Credentials ride
// the registration map polled elsewhere — NOT this JSON body.
Response: json.RawMessage(`{
"message": "", "device_name": "", "device_type": "",
"registration_id": "", "approved": false
}`),
},
"POST /api/devices/reject/:registration_id": {
Response: json.RawMessage(`{"message": ""}`),
},
"POST /api/auth/users": {
Request: handlers.RegisterRequest{},
Response: json.RawMessage(`{
"id": "", "email": "", "username": "", "role": "",
"timezone": "", "created_at": null
}`),
},
"GET /api/auth/users": {
Response: []database.Users{},
},
"PUT /api/auth/users/:id/max-devices": {
Request: handlers.UpdateUserMaxDevicesRequest{},
Response: json.RawMessage(`{"message": ""}`),
},
"PUT /api/auth/password": {
// Self-change (jwt). Admin variant PUT /auth/password/:id documented
// in prose; handler is shared (admin mode skips current_password).
Request: json.RawMessage(`{
"current_password": "", "new_password": "", "confirm_password": ""
}`),
Response: json.RawMessage(`{"message": ""}`),
},
"PUT /api/auth/profile": {
// Shared with admin variant (PUT /auth/profile/:id).
Request: handlers.AdminUpdateUserRequest{},
Response: json.RawMessage(`{"message": ""}`),
},
"DELETE /api/auth/profile": {
// Self-delete; admin variant DELETE /auth/profile/:id (reassignment
// of admin-owned assets to another admin before cascade).
Response: json.RawMessage(`{"message": ""}`),
},
"GET /api/progress/:id/history": {
// id is MEDIA ITEM id; sessions are ReadingHistory rows.
Response: json.RawMessage(`{
"media_item_id": "", "sessions": [], "count": 0
}`),
},
"GET /api/books/match": {
Response: services.BookQueryResponse{},
},
"GET /api/auth/password-rules": {
Response: json.RawMessage(`{
"min_length": 8, "require_upper": true, "require_lower": true,
"require_number": true, "require_special": true
}`),
},
}