import { router, publicProcedure, protectedProcedure } from '@/src/trpc/trpc-index' import { z } from 'zod'; import { db } from '@/src/db/db_index' import { staffUsers, staffRoles, users, userDetails, orders } from '@/src/db/schema' import { eq, or, ilike, and, lt, desc } from 'drizzle-orm'; import bcrypt from 'bcryptjs'; import { ApiError } from '@/src/lib/api-error' import { signToken } from '@/src/lib/jwt-utils' export const staffUserRouter = router({ login: publicProcedure .input(z.object({ name: z.string(), password: z.string(), })) .mutation(async ({ input }) => { const { name, password } = input; if (!name || !password) { throw new ApiError('Name and password are required', 400); } const staff = await db.query.staffUsers.findFirst({ where: eq(staffUsers.name, name), }); if (!staff) { throw new ApiError('Invalid credentials', 401); } const isPasswordValid = await bcrypt.compare(password, staff.password); if (!isPasswordValid) { throw new ApiError('Invalid credentials', 401); } const token = await signToken( { staffId: staff.id, name: staff.name }, '30d' ); return { message: 'Login successful', token, staff: { id: staff.id, name: staff.name }, }; }), getStaff: protectedProcedure .query(async ({ ctx }) => { const staff = await db.query.staffUsers.findMany({ columns: { id: true, name: true, }, with: { role: { with: { rolePermissions: { with: { permission: true, }, }, }, }, }, }); // Transform the data to include role and permissions in a cleaner format const transformedStaff = staff.map((user) => ({ id: user.id, name: user.name, role: user.role ? { id: user.role.id, name: user.role.roleName, } : null, permissions: user.role?.rolePermissions.map((rp) => ({ id: rp.permission.id, name: rp.permission.permissionName, })) || [], })); return { staff: transformedStaff, }; }), getUsers: protectedProcedure .input(z.object({ cursor: z.number().optional(), limit: z.number().default(20), search: z.string().optional(), })) .query(async ({ input }) => { const { cursor, limit, search } = input; let whereCondition = undefined; if (search) { whereCondition = or( ilike(users.name, `%${search}%`), ilike(users.email, `%${search}%`), ilike(users.mobile, `%${search}%`) ); } if (cursor) { const cursorCondition = lt(users.id, cursor); whereCondition = whereCondition ? and(whereCondition, cursorCondition) : cursorCondition; } const allUsers = await db.query.users.findMany({ where: whereCondition, with: { userDetails: true, }, orderBy: desc(users.id), limit: limit + 1, // fetch one extra to check if there's more }); const hasMore = allUsers.length > limit; const usersToReturn = hasMore ? allUsers.slice(0, limit) : allUsers; const formattedUsers = usersToReturn.map(user => ({ id: user.id, name: user.name, email: user.email, mobile: user.mobile, image: user.userDetails?.profileImage || null, })); return { users: formattedUsers, nextCursor: hasMore ? usersToReturn[usersToReturn.length - 1].id : undefined, }; }), getUserDetails: protectedProcedure .input(z.object({ userId: z.number() })) .query(async ({ input }) => { const { userId } = input; const user = await db.query.users.findFirst({ where: eq(users.id, userId), with: { userDetails: true, orders: { orderBy: desc(orders.createdAt), limit: 1, }, }, }); if (!user) { throw new ApiError("User not found", 404); } const lastOrder = user.orders[0]; return { id: user.id, name: user.name, email: user.email, mobile: user.mobile, addedOn: user.createdAt, lastOrdered: lastOrder?.createdAt || null, isSuspended: user.userDetails?.isSuspended || false, }; }), updateUserSuspension: protectedProcedure .input(z.object({ userId: z.number(), isSuspended: z.boolean() })) .mutation(async ({ input }) => { const { userId, isSuspended } = input; await db .insert(userDetails) .values({ userId, isSuspended }) .onConflictDoUpdate({ target: userDetails.userId, set: { isSuspended }, }); return { success: true }; }), createStaffUser: protectedProcedure .input(z.object({ name: z.string().min(1, 'Name is required'), password: z.string().min(6, 'Password must be at least 6 characters'), roleId: z.number().int().positive('Role is required'), })) .mutation(async ({ input, ctx }) => { const { name, password, roleId } = input; // Check if staff user already exists const existingUser = await db.query.staffUsers.findFirst({ where: eq(staffUsers.name, name), }); if (existingUser) { throw new ApiError('Staff user with this name already exists', 409); } // Check if role exists const role = await db.query.staffRoles.findFirst({ where: eq(staffRoles.id, roleId), }); if (!role) { throw new ApiError('Invalid role selected', 400); } // Hash password const hashedPassword = await bcrypt.hash(password, 12); // Create staff user const [newUser] = await db.insert(staffUsers).values({ name: name.trim(), password: hashedPassword, staffRoleId: roleId, }).returning(); return { success: true, user: { id: newUser.id, name: newUser.name } }; }), getRoles: protectedProcedure .query(async ({ ctx }) => { const roles = await db.query.staffRoles.findMany({ columns: { id: true, roleName: true, }, }); return { roles: roles.map(role => ({ id: role.id, name: role.roleName, })), }; }), });