Files
familysync/.planning/phases/20-admin-member-editor-form-declutter/20-01-SUMMARY.md
T

4.7 KiB

phase, plan, subsystem, status, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem status tags dependency_graph tech_stack key_files decisions metrics
20-admin-member-editor-form-declutter 01 api/admin complete
tdd
backend
admin
member-profile
last-admin-guard
requires provides affects
PATCH /api/admin/members/:id (member-profile update: displayName and/or isAdmin)
isAdmin field on GET /api/admin/members response
apps/api/src/routes/admin.ts
apps/api/tests/routes/admin.test.ts
added patterns
Last-admin guard via COUNT(*) query before demoting the only admin (D-03)
Partial update via whichever fields are present in updateMemberSchema
noEchoHook + parsePositiveIntParam reuse for new PATCH route
created modified
apps/api/src/routes/admin.ts
apps/api/tests/routes/admin.test.ts
Use PATCH verb for the member-profile update route (idiomatic REST for partial update)
D-03 guard uses COUNT(*) on users.isAdmin — adapted from auth/user.ts:151-156 pattern
noEchoHook applied to PATCH route for consistency even though body has no sensitive data
Test D: switch currentDevUserId to adminId2 for GET verification after self-demotion (adminId1 is no longer admin post-PATCH)
duration completed tasks_completed files_changed
4m 2026-06-18 3 2

Phase 20 Plan 01: Member-profile update route + isAdmin read Summary

PATCH /api/admin/members/:id with displayName/isAdmin partial update, D-03 last-admin guard (409), and isAdmin added to GET /members — implemented test-first.

Tasks Completed

Task Name Commit Files
1 RED — failing tests for member-profile route + isAdmin read a0a82ac apps/api/tests/routes/admin.test.ts
2 GREEN — implement PATCH /members/:id + isAdmin in GET /members bc48632 apps/api/src/routes/admin.ts, apps/api/tests/routes/admin.test.ts
3 REFACTOR — tidy + pass CI gates (no changes needed)

What Was Built

  • PATCH /api/admin/members/:id route in apps/api/src/routes/admin.ts:
    • Accepts { displayName?: string; isAdmin?: boolean } via updateMemberSchema
    • Protected by router-wide requireAdmin (no second guard — D-02)
    • parsePositiveIntParam rejects malformed ids → 400
    • Existence check → 404 for unknown member ids
    • D-03 last-admin guard: when demoting the only admin → 409 { error: 'Cannot remove the last admin' }
    • Self-demotion with a second admin present → 200
    • Partial set() from whichever fields are present; try/catch 503 fallback
    • noEchoHook applied per T-20-04 consistency posture
  • isAdmin field added to GET /api/admin/members select and mapped response object

Test Coverage (8 scenarios, all passing)

Test Scenario Status
A displayName update → 200; GET reflects change GREEN
B isAdmin promote → 200; GET shows isAdmin true GREEN
C Last-admin demotion → 409; member stays admin GREEN
D Self-demotion with second admin → 200; one admin remains GREEN
E Non-admin PATCH → 403 (requireAdmin boundary) GREEN
F Wrong-type body → 400 Invalid request; malformed :id → 400 GREEN
G Non-existent member id → 404 GREEN
H GET /members includes boolean isAdmin per member GREEN

Full suite: 44 tests passed, 0 failed.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Test D GET called with demoted user

  • Found during: Task 2 (GREEN run)
  • Issue: Test D called GET /members while currentDevUserId was still adminId1, who had just been demoted — resulting in 403 instead of 200 for the verification GET
  • Fix: Switched currentDevUserId = adminId2 before the GET call so the verification uses the remaining admin's session
  • Files modified: apps/api/tests/routes/admin.test.ts
  • Commit: bc48632

CI Gates

All gates pass for modified files:

  • tsc --noEmit: pass
  • eslint src/routes/admin.ts: pass
  • prettier --check: pass (both files)

TDD Gate Compliance

  • RED gate commit: a0a82ac (test(20-01): ...) — 7 tests failing for right reasons
  • GREEN gate commit: bc48632 (feat(20-01): ...) — all 44 tests passing
  • REFACTOR: no code changes needed — code was already clean from GREEN

Known Stubs

None.

Threat Flags

None — no new network surfaces beyond the planned PATCH route. All T-20-xx mitigations applied as specified.

Self-Check: PASSED

Check Result
apps/api/src/routes/admin.ts FOUND
apps/api/tests/routes/admin.test.ts FOUND
20-01-SUMMARY.md FOUND
Commit a0a82ac (RED) FOUND
Commit bc48632 (GREEN) FOUND
PATCH route registered FOUND (line 230)
isAdmin in GET /members select FOUND (line 109)