242 lines
No EOL
6.4 KiB
TypeScript
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,
|
|
})),
|
|
};
|
|
}),
|
|
}); |