freshyo/verifier/admin-apis/apis/staff-user.ts
2026-03-22 20:20:18 +05:30

242 lines
No EOL
6.4 KiB
TypeScript

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,
})),
};
}),
});