5.3 KiB
5.3 KiB
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/andpackages/shared-auth/into a unifieduserrouter.
deliverables:
web/src/server/api/routers/user.ts— User router with procedures:user.me—protectedProcedurereturning current user profileuser.update—protectedProcedureupdating user name, email, imageuser.delete—protectedProceduredeleting account and all related datauser.listFamilyMembers—protectedProcedurereturning family group membersuser.inviteFamilyMember—protectedProceduresending invite by emailuser.removeFamilyMember—protectedProcedureremoving member from groupuser.updateFamilyMemberRole—protectedProcedurechanging member role
web/src/server/services/user.service.ts— Business logic layer:getUserById(id)— fetch user with relationsupdateUser(id, data)— update and return userdeleteUser(id)— cascade delete all user datagetFamilyGroup(userId)— get family group with membersinviteMember(groupId, email)— create invite record, send emailremoveMember(groupId, userId)— remove membershipupdateMemberRole(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:
- Create
web/src/server/api/routers/user.ts. - 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'])
- Implement
user.me:protectedProcedure.query(({ ctx }) => ctx.db.select().from(users).where(eq(users.id, ctx.user.id)))- Include relations: accounts, sessions, familyGroups, subscriptions
- Implement
user.update:- Validate input with Zod
- Update user record, return updated user
- Implement
user.delete:- Verify user exists
- Delete all related records (cascade where defined, manual where not)
- Return success
- Implement family procedures:
listFamilyMembers: query family group where user is owner or member, return members with rolesinviteFamilyMember: createFamilyGroupMemberrecord with pending status, trigger email notification (task 14)removeFamilyMember: verify caller is owner/admin, delete membership recordupdateFamilyMemberRole: verify caller is owner, update role enum
- Create service layer
web/src/server/services/user.service.ts:- Extract reusable DB queries
- Handle error cases (user not found, unauthorized, duplicate email)
- Create
web/src/server/services/family.service.ts. - Wire router into
web/src/server/api/root.ts. - Write unit tests for service functions.
steps:
- Unit:
getUserByIdreturns user with correct relations - Unit:
updateUserapplies changes and returns updated record - Unit:
inviteMembercreates record and rejects duplicate invites - Unit:
removeMemberrejects non-admin callers - Unit:
updateMemberRolerejects non-owner callers - Integration: tRPC
user.mereturns authenticated user - Integration: tRPC
user.inviteFamilyMembercreates member record
acceptance_criteria:
user.mereturns the authenticated user's profile with all relationsuser.updatemodifies allowed fields and rejects invalid datauser.deleteremoves 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.mereturns seeded user data from task 10 - Attempt to remove a family member as a regular member → expect TRPCError FORBIDDEN
- Run
cd web && pnpm testfor 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.deleteoperation 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
Invitationtable with expiration if not already in schema. If not, add it as part of this task.