- Add hooks (useAuth, useSubscription, useNotifications) for real API data - Add auth service (login/signup) with password hashing and session support - Replace stub auth with real tRPC calls in login/signup/onboarding pages - Replace mock dashboard data with real API data from hooks - Create service pages: DarkWatch, VoicePrint, SpamShield, HomeTitle, RemoveBrokers, Settings - Update Navbar, TopBar, Sidebar with real user data and correct routes - Add passwordHash field to users schema for credential auth - Fix tests to work with real hooks (mock tRPC/hooks)
115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
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;
|
|
}),
|
|
});
|