rebranding
This commit is contained in:
95
tasks/kordant-unified-restructure/12-user-family-router.md
Normal file
95
tasks/kordant-unified-restructure/12-user-family-router.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user