import { wrap } from "@typeschema/valibot"; import { object, string, minLength, email as emailVal } from "valibot"; import { TRPCError } from "@trpc/server"; import { createTRPCRouter, publicProcedure, protectedProcedure } from "../utils"; import { UpdateUserSchema, InviteMemberSchema, RemoveMemberSchema, UpdateRoleSchema, } from "../schemas/user"; import { getUserById, updateUser, deleteUser, createUserWithPassword, authenticateUser } from "~/server/services/user.service"; import { getFamilyGroup, inviteMember, removeMember, updateMemberRole, } from "~/server/services/family.service"; const LoginSchema = object({ email: string([emailVal()]), password: string([minLength(1)]), }); const SignupSchema = object({ name: string([minLength(1)]), email: string([emailVal()]), password: string([minLength(8)]), }); export const userRouter = createTRPCRouter({ login: publicProcedure .input(wrap(LoginSchema)) .mutation(async ({ input }) => { return authenticateUser(input.email, input.password); }), signup: publicProcedure .input(wrap(SignupSchema)) .mutation(async ({ input }) => { const user = await createUserWithPassword(input.name, input.email, input.password); const { createSession } = await import("~/server/auth/session"); const session = await createSession(user.id); return { user, sessionToken: session.sessionToken }; }), me: protectedProcedure.query(async ({ ctx }) => { const user = await getUserById(ctx.user.id); return user; }), update: protectedProcedure .input(wrap(UpdateUserSchema)) .mutation(async ({ ctx, input }) => { const updated = await updateUser(ctx.user.id, input); return updated; }), delete: protectedProcedure.mutation(async ({ ctx }) => { await deleteUser(ctx.user.id); return { success: true }; }), listFamilyMembers: protectedProcedure.query(async ({ ctx }) => { const group = await getFamilyGroup(ctx.user.id); return group.members; }), inviteFamilyMember: protectedProcedure .input(wrap(InviteMemberSchema)) .mutation(async ({ ctx, input }) => { const group = await getFamilyGroup(ctx.user.id); const callerMember = group.members.find( (m) => m.userId === ctx.user.id, ); if (!callerMember || (callerMember.role !== "owner" && callerMember.role !== "admin")) { throw new TRPCError({ code: "FORBIDDEN", message: "Only owner or admin can invite members", }); } const invitation = await inviteMember( group.id, input.email, ctx.user.id, input.role, ); return invitation; }), removeFamilyMember: protectedProcedure .input(wrap(RemoveMemberSchema)) .mutation(async ({ ctx, input }) => { const group = await getFamilyGroup(ctx.user.id); await removeMember(group.id, input.userId, ctx.user.id); return { success: true }; }), updateFamilyMemberRole: protectedProcedure .input(wrap(UpdateRoleSchema)) .mutation(async ({ ctx, input }) => { const group = await getFamilyGroup(ctx.user.id); const updated = await updateMemberRole( group.id, input.userId, input.role, ctx.user.id, ); return updated; }), });