import prisma from './prisma'; import sha256 from 'crypto-js/sha256'; /** * Collection of utility functions for working with users */ export const UserUtils = { /** * Create a new user * * @param {Object} data User data * @param {string} data.userUsername Username * @param {string} data.userPassword Password (will be hashed) * @param {string} data.userFullName Full name * @param {string} data.userEmail Email (optional) * @param {string} data.userPhone Phone (optional) * @param {string} data.userStatus Status (optional, defaults to "active") * @param {number} data.dp_id Department ID (optional) * @returns {Promise} Created user (without password) */ async create(data) { // Validate required fields if (!data.userUsername) { throw new Error('Username is required'); } if (!data.userPassword) { throw new Error('Password is required'); } if (!data.userFullName) { throw new Error('Full name is required'); } // Check if username already exists const existingUser = await prisma.user.findFirst({ where: { userUsername: data.userUsername } }); if (existingUser) { throw new Error('Username already exists'); } // Check if department exists if provided if (data.dp_id) { const department = await prisma.department.findUnique({ where: { dp_id: parseInt(data.dp_id) } }); if (!department) { throw new Error(`Department with ID ${data.dp_id} not found`); } } // Create random secret key const secretKey = this.generateRandomKey(32); // Encrypt password with SHA256 const hashedPassword = sha256(data.userPassword).toString(); // Create user const user = await prisma.user.create({ data: { userSecretKey: secretKey, userUsername: data.userUsername, userPassword: hashedPassword, userFullName: data.userFullName, userEmail: data.userEmail || null, userPhone: data.userPhone || null, userStatus: data.userStatus ? data.userStatus.toUpperCase() : "ACTIVE", dp_id: data.dp_id ? parseInt(data.dp_id) : null, userCreatedDate: new Date(), userModifiedDate: new Date() } }); // Return user without password const { userPassword, ...userWithoutPassword } = user; return userWithoutPassword; }, /** * Get all users with pagination and filtering * * @param {Object} options Query options * @param {number} options.page Page number * @param {number} options.limit Items per page * @param {string} options.search Search term * @param {number} options.departmentId Filter by department ID * @param {string} options.status Filter by status * @returns {Promise} Users and pagination metadata */ async getAll({ page = 1, limit = 10, search = '', departmentId = null, status = null }) { const skip = (page - 1) * limit; // Define filters const filters = { where: {} }; // Filter by department if provided if (departmentId) { filters.where.dp_id = parseInt(departmentId); } // Filter by status if provided if (status) { filters.where.userStatus = status; } // Add search filter if provided if (search) { filters.where = { ...filters.where, OR: [ { userUsername: { contains: search } }, { userFullName: { contains: search } }, { userEmail: { contains: search } } ] }; } // Get users with pagination const [users, total] = await Promise.all([ prisma.user.findMany({ ...filters, skip, take: limit, orderBy: { userFullName: 'asc' }, select: { userID: true, userUsername: true, userFullName: true, userEmail: true, userPhone: true, userStatus: true, dp_id: true, userCreatedDate: true, userModifiedDate: true, department: { select: { dp_id: true, dp_name: true, organization: { select: { org_id: true, org_name: true } } } }, userrole: { select: { userRoleID: true, role: { select: { roleID: true, roleName: true } } } } } }), prisma.user.count(filters) ]); // Calculate pagination metadata const totalPages = Math.ceil(total / limit); return { users, pagination: { page, limit, total, totalPages, hasNextPage: page < totalPages, hasPrevPage: page > 1 } }; }, /** * Get user by ID * * @param {number} id User ID * @returns {Promise} User or null if not found */ async getById(id) { if (isNaN(parseInt(id))) { throw new Error('Invalid user ID'); } const user = await prisma.user.findUnique({ where: { userID: parseInt(id) }, select: { userID: true, userUsername: true, userFullName: true, userEmail: true, userPhone: true, userStatus: true, dp_id: true, userCreatedDate: true, userModifiedDate: true, department: { select: { dp_id: true, dp_name: true, organization: { select: { org_id: true, org_name: true } } } }, userrole: { select: { userRoleID: true, role: { select: { roleID: true, roleName: true, roleDescription: true } } } } } }); if (!user) { return null; } return user; }, /** * Update a user * * @param {number} id User ID * @param {Object} data User data to update * @returns {Promise} Updated user (without password) */ async update(id, data) { if (isNaN(parseInt(id))) { throw new Error('Invalid user ID'); } // Check if user exists const existingUser = await prisma.user.findUnique({ where: { userID: parseInt(id) } }); if (!existingUser) { throw new Error('User not found'); } // Check if department exists if provided if (data.dp_id !== undefined) { if (data.dp_id !== null) { const department = await prisma.department.findUnique({ where: { dp_id: parseInt(data.dp_id) } }); if (!department) { throw new Error(`Department with ID ${data.dp_id} not found`); } } } // Check if username is taken by another user if (data.userUsername && data.userUsername !== existingUser.userUsername) { const usernameExists = await prisma.user.findFirst({ where: { userUsername: data.userUsername, userID: { not: parseInt(id) } } }); if (usernameExists) { throw new Error('Username is already taken by another user'); } } // Prepare update data const updateData = {}; // Only update fields that are provided if (data.userUsername !== undefined) updateData.userUsername = data.userUsername; if (data.userFullName !== undefined) updateData.userFullName = data.userFullName; if (data.userEmail !== undefined) updateData.userEmail = data.userEmail; if (data.userPhone !== undefined) updateData.userPhone = data.userPhone; if (data.userStatus !== undefined) updateData.userStatus = data.userStatus ? data.userStatus.toUpperCase() : null; if (data.dp_id !== undefined) updateData.dp_id = data.dp_id ? parseInt(data.dp_id) : null; // Hash password if provided if (data.userPassword) { updateData.userPassword = sha256(data.userPassword).toString(); } // Update modified date updateData.userModifiedDate = new Date(); // Update user const user = await prisma.user.update({ where: { userID: parseInt(id) }, data: updateData }); // Return user without password const { userPassword, ...userWithoutPassword } = user; return userWithoutPassword; }, /** * Delete a user * * @param {number} id User ID * @returns {Promise} Deleted user (without password) */ async delete(id) { if (isNaN(parseInt(id))) { throw new Error('Invalid user ID'); } // Check if user exists const existingUser = await prisma.user.findUnique({ where: { userID: parseInt(id) }, include: { userrole: { select: { userRoleID: true } } } }); if (!existingUser) { throw new Error('User not found'); } // Check if user has any roles assigned if (existingUser.userrole && existingUser.userrole.length > 0) { // Delete all associated user roles first await prisma.userrole.deleteMany({ where: { userRoleUserID: parseInt(id) } }); } // Delete user const user = await prisma.user.delete({ where: { userID: parseInt(id) } }); // Return user without password const { userPassword, ...userWithoutPassword } = user; return userWithoutPassword; }, /** * Get users by department ID * * @param {number} departmentId Department ID * @returns {Promise} List of users */ async getByDepartment(departmentId) { if (isNaN(parseInt(departmentId))) { throw new Error('Invalid department ID'); } // Check if department exists const department = await prisma.department.findUnique({ where: { dp_id: parseInt(departmentId) } }); if (!department) { throw new Error('Department not found'); } // Get users for this department return prisma.user.findMany({ where: { dp_id: parseInt(departmentId) }, orderBy: { userFullName: 'asc' }, select: { userID: true, userUsername: true, userFullName: true, userEmail: true, userPhone: true, userStatus: true, userCreatedDate: true, userModifiedDate: true, userrole: { select: { userRoleID: true, role: { select: { roleID: true, roleName: true } } } } } }); }, /** * Verify user credentials * * @param {string} username Username * @param {string} password Raw password * @returns {Promise} User data if credentials are valid, null otherwise */ async verifyCredentials(username, password) { if (!username || !password) { return null; } // Get user by username const user = await prisma.user.findFirst({ where: { userUsername: username } }); if (!user) { return null; } // Check password const hashedPassword = sha256(password).toString(); if (user.userPassword !== hashedPassword) { return null; } // Return user without password const { userPassword, ...userWithoutPassword } = user; return userWithoutPassword; }, /** * Generate a random key * * @param {number} length Length of the key * @returns {string} Random key */ generateRandomKey(length) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } }; export default UserUtils;