# 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.