import { TRPCError } from "@trpc/server"; import { count, eq, sum } from "drizzle-orm"; import { z } from "zod"; import { users } from "@lifetracker/db/schema"; import { changeRoleSchema, resetPasswordSchema, zAdminCreateUserSchema, } from "@lifetracker/shared/types/admin"; import { hashPassword } from "../auth"; import { adminProcedure, router } from "../index"; import { createUser } from "./users"; export const adminAppRouter = router({ stats: adminProcedure .output( z.object({ numUsers: z.number(), numEntries: z.number(), }), ) .query(async ({ ctx }) => { const [ [{ value: numUsers }], ] = await Promise.all([ ctx.db.select({ value: count() }).from(users)]); return { numUsers, numEntries: 420.69, }; }), userStats: adminProcedure .output( z.record( z.string(), z.object({ numDays: z.number(), numEntries: z.number(), }), ), ) .query(async ({ ctx }) => { const [userIds] = await Promise.all([ ctx.db.select({ id: users.id }).from(users) ]); const results: Record< string, { numDays: number; numEntries: number } > = {}; for (const user of userIds) { results[user.id] = { numDays: 0, numEntries: 0, }; } return results; }), createUser: adminProcedure .input(zAdminCreateUserSchema) .output( z.object({ id: z.string(), name: z.string(), email: z.string(), role: z.enum(["user", "admin"]).nullable(), }), ) .mutation(async ({ input, ctx }) => { return createUser(input, ctx, input.role); }), changeRole: adminProcedure .input(changeRoleSchema) .mutation(async ({ input, ctx }) => { if (ctx.user.id == input.userId) { throw new TRPCError({ code: "BAD_REQUEST", message: "Cannot change own role", }); } const result = await ctx.db .update(users) .set({ role: input.role }) .where(eq(users.id, input.userId)); if (!result.changes) { throw new TRPCError({ code: "NOT_FOUND", message: "User not found", }); } }), resetPassword: adminProcedure .input(resetPasswordSchema) .mutation(async ({ input, ctx }) => { if (ctx.user.id == input.userId) { throw new TRPCError({ code: "BAD_REQUEST", message: "Cannot reset own password", }); } const hashedPassword = await hashPassword(input.newPassword); const result = await ctx.db .update(users) .set({ password: hashedPassword }) .where(eq(users.id, input.userId)); if (result.changes == 0) { throw new TRPCError({ code: "NOT_FOUND", message: "User not found", }); } }), });