Files
Kordant/tasks/shieldai-unified-restructure/12-user-family-router.md
2026-05-25 12:23:23 -04:00

5.3 KiB

12. Backend Router — User & Family Group Management

meta: id: shieldai-unified-restructure-12 feature: shieldai-unified-restructure priority: P1 depends_on: [shieldai-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.meprotectedProcedure returning current user profile
    • user.updateprotectedProcedure updating user name, email, image
    • user.deleteprotectedProcedure deleting account and all related data
    • user.listFamilyMembersprotectedProcedure returning family group members
    • user.inviteFamilyMemberprotectedProcedure sending invite by email
    • user.removeFamilyMemberprotectedProcedure removing member from group
    • user.updateFamilyMemberRoleprotectedProcedure 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.