Files
Kordant/tasks/kordant-unified-restructure/12-user-family-router.md
2026-05-25 22:49:37 -04:00

96 lines
5.3 KiB
Markdown

# 12. Backend Router — User & Family Group Management
meta:
id: kordant-unified-restructure-12
feature: kordant-unified-restructure
priority: P1
depends_on: [kordant-unified-restructure-11]
tags: [backend, trpc, users, family, api]
objective:
- Build the tRPC router for user management and family group functionality. Port all user-related logic from the legacy `packages/api/src/routes/` and `packages/shared-auth/` into a unified `user` router.
deliverables:
- `web/src/server/api/routers/user.ts` — User router with procedures:
- `user.me``protectedProcedure` returning current user profile
- `user.update``protectedProcedure` updating user name, email, image
- `user.delete``protectedProcedure` deleting account and all related data
- `user.listFamilyMembers``protectedProcedure` returning family group members
- `user.inviteFamilyMember``protectedProcedure` sending invite by email
- `user.removeFamilyMember``protectedProcedure` removing member from group
- `user.updateFamilyMemberRole``protectedProcedure` changing member role
- `web/src/server/services/user.service.ts` — Business logic layer:
- `getUserById(id)` — fetch user with relations
- `updateUser(id, data)` — update and return user
- `deleteUser(id)` — cascade delete all user data
- `getFamilyGroup(userId)` — get family group with members
- `inviteMember(groupId, email)` — create invite record, send email
- `removeMember(groupId, userId)` — remove membership
- `updateMemberRole(groupId, userId, role)` — update role enum
- `web/src/server/services/family.service.ts` — Family group logic:
- `createFamilyGroup(ownerId, name)`
- `getFamilyGroupWithMembers(groupId)`
- `transferOwnership(groupId, newOwnerId)`
- Zod schemas for all inputs in `web/src/server/api/schemas/user.ts`
steps:
1. Create `web/src/server/api/routers/user.ts`.
2. Define Zod schemas:
- `updateUserSchema`: `name: z.string().min(1).optional()`, `email: z.string().email().optional()`
- `inviteMemberSchema`: `email: z.string().email()`, `role: z.enum(['admin', 'member']).default('member')`
- `removeMemberSchema`: `userId: z.string().uuid()`
- `updateRoleSchema`: `userId: z.string().uuid()`, `role: z.enum(['owner', 'admin', 'member'])`
3. Implement `user.me`:
- `protectedProcedure.query(({ ctx }) => ctx.db.select().from(users).where(eq(users.id, ctx.user.id)))`
- Include relations: accounts, sessions, familyGroups, subscriptions
4. Implement `user.update`:
- Validate input with Zod
- Update user record, return updated user
5. Implement `user.delete`:
- Verify user exists
- Delete all related records (cascade where defined, manual where not)
- Return success
6. Implement family procedures:
- `listFamilyMembers`: query family group where user is owner or member, return members with roles
- `inviteFamilyMember`: create `FamilyGroupMember` record with pending status, trigger email notification (task 14)
- `removeFamilyMember`: verify caller is owner/admin, delete membership record
- `updateFamilyMemberRole`: verify caller is owner, update role enum
7. Create service layer `web/src/server/services/user.service.ts`:
- Extract reusable DB queries
- Handle error cases (user not found, unauthorized, duplicate email)
8. Create `web/src/server/services/family.service.ts`.
9. Wire router into `web/src/server/api/root.ts`.
10. Write unit tests for service functions.
steps:
- Unit: `getUserById` returns user with correct relations
- Unit: `updateUser` applies changes and returns updated record
- Unit: `inviteMember` creates record and rejects duplicate invites
- Unit: `removeMember` rejects non-admin callers
- Unit: `updateMemberRole` rejects non-owner callers
- Integration: tRPC `user.me` returns authenticated user
- Integration: tRPC `user.inviteFamilyMember` creates member record
acceptance_criteria:
- [ ] `user.me` returns the authenticated user's profile with all relations
- [ ] `user.update` modifies allowed fields and rejects invalid data
- [ ] `user.delete` removes the user and all related data
- [ ] Family member listing returns correct members with roles
- [ ] Family invites create pending members and validate permissions
- [ ] Family member removal and role updates enforce authorization rules
- [ ] All procedures use Zod input validation
- [ ] Service layer is pure and testable (no request/response logic)
validation:
- Create a temporary test page or use tRPC playground to call each procedure
- Verify `user.me` returns seeded user data from task 10
- Attempt to remove a family member as a regular member → expect TRPCError FORBIDDEN
- Run `cd web && pnpm test` for unit tests
notes:
- Reference legacy code: `packages/api/src/routes/` for route patterns, `packages/shared-auth/src/` for auth logic.
- Family group ownership transfer is a sensitive operation — ensure only the current owner can initiate it.
- When a user is deleted, consider soft-delete (set `deletedAt`) instead of hard delete to preserve audit trails. Update schema if needed.
- The `user.delete` operation must handle cascade deletes carefully to avoid foreign key constraint errors. Either rely on Drizzle's cascade definitions or delete in correct order.
- For invites, consider adding an `Invitation` table with expiration if not already in schema. If not, add it as part of this task.