Merge branch 'development' of https://git.sena.my/corrad-software/EDMS into development

This commit is contained in:
Aiman Fakhrullah Mantasan 2025-05-31 17:59:26 +08:00
commit d0a4736a2a
25 changed files with 22539 additions and 1874 deletions

17639
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@
"devDependencies": {
"@nuxtjs/tailwindcss": "^6.8.0",
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
"@types/jsonwebtoken": "^9.0.9",
"@vite-pwa/nuxt": "^0.1.0",
"eslint": "^8.39.0",
"eslint-plugin-vue": "^9.16.1",

View File

@ -41,7 +41,11 @@ model department {
org_id Int
cabinets cabinets[]
organization organization @relation(fields: [org_id], references: [org_id], onDelete: Cascade, onUpdate: NoAction, map: "department_organization_FK")
<<<<<<< HEAD
user user[]
=======
users sys_user[]
>>>>>>> d4880c491e3491be4f09fbfbc0e0a9f8b5cfb1b8
@@index([org_id], map: "department_organization_FK")
}
@ -60,23 +64,6 @@ model cabinets {
@@index([dp_id], map: "cabinets_department_FK")
}
model sys_user {
su_id Int @id @default(autoincrement())
su_username String @unique(map: "sys_user_unique") @db.VarChar(100)
su_name String @db.VarChar(255)
su_nric Int @unique(map: "sys_user_unique_1")
su_dob DateTime @db.Date
su_email String? @db.VarChar(255)
su_password String @db.VarChar(255)
dp_id Int
su_active Int? @default(1)
su_lock Int? @default(0)
su_org_id Int
department department @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "sys_user_department_FK")
@@index([dp_id], map: "sys_user_department_FK")
}
model role {
roleID Int @id @default(autoincrement())
roleName String? @db.VarChar(255)
@ -114,39 +101,8 @@ model userrole {
@@index([userRoleUserID], map: "FK_userrole_user")
}
model site_settings {
settingID Int @id @default(autoincrement())
siteName String? @default("corradAF") @db.VarChar(255)
siteNameFontSize Int? @default(18)
siteDescription String? @db.Text
siteLogo String? @db.VarChar(500)
siteLoadingLogo String? @db.VarChar(500)
siteFavicon String? @db.VarChar(500)
siteLoginLogo String? @db.VarChar(500)
showSiteNameInHeader Boolean? @default(true)
customCSS String? @db.LongText
themeMode String? @default("biasa") @db.VarChar(100)
customThemeFile String? @db.VarChar(500)
currentFont String? @db.VarChar(100)
fontSource String? @db.VarChar(100)
seoTitle String? @db.VarChar(255)
seoDescription String? @db.Text
seoKeywords String? @db.Text
seoAuthor String? @db.VarChar(255)
seoOgImage String? @db.VarChar(500)
seoTwitterCard String? @default("summary_large_image") @db.VarChar(100)
seoCanonicalUrl String? @db.VarChar(500)
seoRobots String? @default("index, follow") @db.VarChar(100)
seoGoogleAnalytics String? @db.VarChar(255)
seoGoogleTagManager String? @db.VarChar(255)
seoFacebookPixel String? @db.VarChar(255)
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
}
model dms_settings {
settingID Int @id @default(autoincrement())
// User & Access Management
userRoles String? @db.Text
rbacEnabled Boolean? @default(true)
userGroups String? @db.Text
@ -159,8 +115,6 @@ model dms_settings {
mfaRequired Boolean? @default(false)
ldapIntegration Boolean? @default(false)
sessionTimeout Int? @default(8)
// Document & Folder Settings
folderMaxDepth Int? @default(5)
folderDefaultStructure String? @db.Text
folderTemplates String? @db.Text
@ -173,16 +127,12 @@ model dms_settings {
versionControlEnabled Boolean? @default(true)
versionControlMaxVersions Int? @default(10)
versionControlAutoVersioning Boolean? @default(true)
// Metadata & Tagging
metadataCustomFields String? @db.LongText
taggingPredefinedTags String? @db.Text
taggingUserGeneratedTags Boolean? @default(true)
taggingTagSuggestions Boolean? @default(true)
classificationAutoEnabled Boolean? @default(true)
classificationRules String? @db.Text
// Workflow & Automation
workflowApprovalEnabled Boolean? @default(true)
workflowDefaultFlow String? @default("department-head-approval") @db.VarChar(255)
workflowCustomFlows String? @db.Text
@ -192,8 +142,6 @@ model dms_settings {
notificationDeadlineReminders Boolean? @default(true)
automationTriggers String? @db.Text
automationActions String? @db.Text
// Upload & Storage Settings
uploadAllowedFileTypes String? @db.Text
uploadBlockedFileTypes String? @db.Text
uploadFileSizeLimit Int? @default(100)
@ -204,8 +152,6 @@ model dms_settings {
storagePath String? @default("/var/uploads/edms") @db.VarChar(500)
storageBackupEnabled Boolean? @default(true)
storageCompressionEnabled Boolean? @default(false)
// System Settings
systemTimezone String? @default("Asia/Kuala_Lumpur") @db.VarChar(100)
systemBackupSchedule String? @default("daily") @db.VarChar(100)
systemLogLevel String? @default("info") @db.VarChar(100)
@ -213,7 +159,36 @@ model dms_settings {
systemAutoUpdates Boolean? @default(false)
systemMonitoring Boolean? @default(true)
systemPerformanceMetrics Boolean? @default(true)
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
}
model site_settings {
settingID Int @id @default(autoincrement())
siteName String? @default("corradAF") @db.VarChar(255)
siteNameFontSize Int? @default(18)
siteDescription String? @db.Text
siteLogo String? @db.VarChar(500)
siteLoadingLogo String? @db.VarChar(500)
siteFavicon String? @db.VarChar(500)
siteLoginLogo String? @db.VarChar(500)
showSiteNameInHeader Boolean? @default(true)
customCSS String? @db.LongText
themeMode String? @default("biasa") @db.VarChar(100)
customThemeFile String? @db.VarChar(500)
currentFont String? @db.VarChar(100)
fontSource String? @db.VarChar(100)
seoTitle String? @db.VarChar(255)
seoDescription String? @db.Text
seoKeywords String? @db.Text
seoAuthor String? @db.VarChar(255)
seoOgImage String? @db.VarChar(500)
seoTwitterCard String? @default("summary_large_image") @db.VarChar(100)
seoCanonicalUrl String? @db.VarChar(500)
seoRobots String? @default("index, follow") @db.VarChar(100)
seoGoogleAnalytics String? @db.VarChar(255)
seoGoogleTagManager String? @db.VarChar(255)
seoFacebookPixel String? @db.VarChar(255)
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
}

126
server/api/auth/login.js Normal file
View File

@ -0,0 +1,126 @@
import prisma from "../../utils/prisma";
import sha256 from "crypto-js/sha256";
export default defineEventHandler(async (event) => {
try {
// Get request body
const body = await readBody(event);
// Validate required fields
if (!body.username || !body.password) {
return {
statusCode: 400,
message: "Username and password are required"
};
}
// Find user by username
const user = await prisma.user.findFirst({
where: {
userUsername: body.username
},
include: {
department: {
select: {
dp_id: true,
dp_name: true,
organization: {
select: {
org_id: true,
org_name: true
}
}
}
},
userrole: {
select: {
role: {
select: {
roleID: true,
roleName: true
}
}
}
}
}
});
if (!user) {
return {
statusCode: 404,
message: "User not found"
};
}
// Check if user is active
if (user.userStatus !== "ACTIVE") {
return {
statusCode: 403,
message: "User account is not active"
};
}
// Verify password
const hashedPassword = sha256(body.password).toString();
if (user.userPassword !== hashedPassword) {
return {
statusCode: 401,
message: "Invalid password"
};
}
// Create audit log for successful login
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'POST',
auditAction: 'USER_LOGIN',
auditDetails: JSON.stringify({
userID: user.userID,
username: user.userUsername
}),
auditUserID: user.userID,
auditUsername: user.userUsername
}
});
// Extract roles for response
const roles = user.userrole.map(ur => ur.role.roleName);
// Prepare user data for response (remove sensitive information)
const userData = {
userID: user.userID,
username: user.userUsername,
fullName: user.userFullName,
email: user.userEmail,
phone: user.userPhone,
department: user.department,
roles: roles
};
return {
statusCode: 200,
message: "Login successful",
data: userData
};
} catch (error) {
console.error("Login error:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,102 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get department ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid department ID"
};
}
// Check if department exists and get related entities
const existingDept = await prisma.department.findUnique({
where: {
dp_id: id
},
include: {
cabinets: {
select: {
cb_id: true
}
},
users: {
select: {
su_id: true
}
}
}
});
if (!existingDept) {
return {
statusCode: 404,
message: "Department not found"
};
}
// Check if department has related cabinets
if (existingDept.cabinets.length > 0) {
return {
statusCode: 409,
message: "Cannot delete department with existing cabinets. Remove all cabinets first.",
cabinetCount: existingDept.cabinets.length
};
}
// Check if department has related users
if (existingDept.users.length > 0) {
return {
statusCode: 409,
message: "Cannot delete department with existing users. Reassign or remove all users first.",
userCount: existingDept.users.length
};
}
// Delete department
const department = await prisma.department.delete({
where: {
dp_id: id
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'DELETE',
auditAction: 'DELETE_DEPARTMENT',
auditDetails: JSON.stringify(existingDept),
auditUserID: null,
auditUsername: null
}
});
return {
statusCode: 200,
message: "Department deleted successfully"
};
} catch (error) {
console.error("Error deleting department:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,69 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get department ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid department ID"
};
}
// Get department with related data
const department = await prisma.department.findUnique({
where: {
dp_id: id
},
include: {
organization: {
select: {
org_id: true,
org_name: true,
org_country: true,
org_state: true,
org_active: true
}
},
cabinets: {
select: {
cb_id: true,
cb_name: true,
cb_private: true
}
},
user: {
select: {
userID: true,
userUsername: true,
userFullName: true,
userEmail: true,
userStatus: true
}
}
}
});
if (!department) {
return {
statusCode: 404,
message: "Department not found"
};
}
return {
statusCode: 200,
data: department
};
} catch (error) {
console.error("Error fetching department:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

View File

@ -0,0 +1,129 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get department ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid department ID"
};
}
// Get request body
const body = await readBody(event);
console.log("PUT Department body:", JSON.stringify(body));
// Validate request body
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Invalid request body, expected JSON object",
received: body
};
}
// Check if department exists
const existingDept = await prisma.department.findUnique({
where: {
dp_id: id
}
});
if (!existingDept) {
return {
statusCode: 404,
message: "Department not found"
};
}
// Validate required fields
if (!body.dp_name) {
return {
statusCode: 400,
message: "Department name is required",
receivedBody: body
};
}
// If org_id is provided, check if organization exists
if (body.org_id !== undefined) {
const orgId = parseInt(body.org_id);
if (isNaN(orgId)) {
return {
statusCode: 400,
message: "Invalid organization ID format"
};
}
const organization = await prisma.organization.findUnique({
where: {
org_id: orgId
}
});
if (!organization) {
return {
statusCode: 404,
message: "Organization not found",
org_id: body.org_id
};
}
}
// Update department
const department = await prisma.department.update({
where: {
dp_id: id
},
data: {
dp_name: body.dp_name,
org_id: body.org_id !== undefined ? parseInt(body.org_id) : existingDept.org_id
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'PUT',
auditAction: 'UPDATE_DEPARTMENT',
auditDetails: JSON.stringify({
before: existingDept,
after: department
}),
auditUserID: null,
auditUsername: null
}
});
return {
statusCode: 200,
message: "Department updated successfully",
data: department
};
} catch (error) {
console.error("Error updating department:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,80 @@
import prisma from "../../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get department ID from route
const departmentId = parseInt(event.context.params.id);
if (isNaN(departmentId)) {
return {
statusCode: 400,
message: "Invalid department ID"
};
}
// Check if department exists
const department = await prisma.department.findUnique({
where: {
dp_id: departmentId
}
});
if (!department) {
return {
statusCode: 404,
message: "Department not found"
};
}
// Get users for this department
const users = await prisma.user.findMany({
where: {
dp_id: 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
}
}
}
}
}
});
return {
statusCode: 200,
data: users,
meta: {
department: {
dp_id: department.dp_id,
dp_name: department.dp_name
},
total: users.length
}
};
} catch (error) {
console.error("Error fetching users by department:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

View File

@ -0,0 +1,88 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get query parameters
const query = getQuery(event);
const page = parseInt(query.page) || 1;
const limit = parseInt(query.limit) || 10;
const search = query.search || '';
const orgId = query.org_id ? parseInt(query.org_id) : null;
// Calculate pagination
const skip = (page - 1) * limit;
// Define filters
const filters = {
where: {}
};
// Filter by organization if provided
if (orgId) {
filters.where.org_id = orgId;
}
// Add search filter if provided
if (search) {
filters.where = {
...filters.where,
dp_name: {
contains: search
}
};
}
// Get departments with pagination
const [departments, total] = await Promise.all([
prisma.department.findMany({
...filters,
skip,
take: limit,
orderBy: {
dp_name: 'asc'
},
include: {
organization: {
select: {
org_id: true,
org_name: true
}
},
_count: {
select: {
cabinets: true,
user: true
}
}
}
}),
prisma.department.count(filters)
]);
// Calculate pagination metadata
const totalPages = Math.ceil(total / limit);
const hasNextPage = page < totalPages;
const hasPrevPage = page > 1;
return {
statusCode: 200,
data: departments,
meta: {
page,
limit,
total,
totalPages,
hasNextPage,
hasPrevPage
}
};
} catch (error) {
console.error("Error fetching departments:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

View File

@ -0,0 +1,96 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get request body
const body = await readBody(event);
console.log("POST Department body:", JSON.stringify(body));
// Validate request body
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Invalid request body, expected JSON object",
received: body
};
}
// Validate required fields
if (!body.dp_name) {
return {
statusCode: 400,
message: "Department name is required",
receivedBody: body
};
}
if (!body.org_id) {
return {
statusCode: 400,
message: "Organization ID (org_id) is required",
receivedBody: body
};
}
// Check if organization exists
const organization = await prisma.organization.findUnique({
where: {
org_id: parseInt(body.org_id)
}
});
if (!organization) {
return {
statusCode: 404,
message: "Organization not found",
org_id: body.org_id
};
}
// Create department
const department = await prisma.department.create({
data: {
dp_name: body.dp_name,
org_id: parseInt(body.org_id)
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'POST',
auditAction: 'CREATE_DEPARTMENT',
auditDetails: JSON.stringify(department),
auditUserID: null,
auditUsername: null
}
});
return {
statusCode: 201,
message: "Department created successfully",
data: department
};
} catch (error) {
console.error("Error creating department:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,87 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get organization ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid organization ID"
};
}
// Check if organization exists
const existingOrg = await prisma.organization.findUnique({
where: {
org_id: id
},
include: {
departments: {
select: {
dp_id: true
}
}
}
});
if (!existingOrg) {
return {
statusCode: 404,
message: "Organization not found"
};
}
// Check if organization has departments
if (existingOrg.departments.length > 0) {
return {
statusCode: 409,
message: "Cannot delete organization with existing departments. Remove all departments first."
};
}
// Delete organization
const organization = await prisma.organization.delete({
where: {
org_id: id
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'DELETE',
auditAction: 'DELETE_ORGANIZATION',
auditDetails: JSON.stringify(existingOrg),
auditUserID: null,
auditUsername: null
}
});
return {
statusCode: 200,
message: "Organization deleted successfully"
};
} catch (error) {
console.error("Error deleting organization:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,56 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get organization ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid organization ID"
};
}
// Get organization with its departments
const organization = await prisma.organization.findUnique({
where: {
org_id: id
},
include: {
departments: {
select: {
dp_id: true,
dp_name: true,
_count: {
select: {
users: true,
cabinets: true
}
}
}
}
}
});
if (!organization) {
return {
statusCode: 404,
message: "Organization not found"
};
}
return {
statusCode: 200,
data: organization
};
} catch (error) {
console.error("Error fetching organization:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

View File

@ -0,0 +1,126 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get organization ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid organization ID"
};
}
// Get request body
const body = await readBody(event);
console.log("Request body:", JSON.stringify(body));
// Validate required fields
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Invalid request body, expected JSON object",
received: body
};
}
if (!body.org_name) {
return {
statusCode: 400,
message: "Organization name is required",
receivedBody: body
};
}
// Check if organization exists
const existingOrg = await prisma.organization.findUnique({
where: {
org_id: id
}
});
if (!existingOrg) {
return {
statusCode: 404,
message: "Organization not found"
};
}
// Check if the name is already taken by another organization
if (body.org_name !== existingOrg.org_name) {
const nameExists = await prisma.organization.findFirst({
where: {
org_name: body.org_name,
org_id: {
not: id
}
}
});
if (nameExists) {
return {
statusCode: 409,
message: "Organization with this name already exists"
};
}
}
// Update organization
const organization = await prisma.organization.update({
where: {
org_id: id
},
data: {
org_name: body.org_name,
org_address1: body.org_address1 !== undefined ? body.org_address1 : existingOrg.org_address1,
org_address2: body.org_address2 !== undefined ? body.org_address2 : existingOrg.org_address2,
org_postcode: body.org_postcode !== undefined ? parseInt(body.org_postcode) : existingOrg.org_postcode,
org_state: body.org_state !== undefined ? body.org_state : existingOrg.org_state,
org_country: body.org_country !== undefined ? body.org_country : existingOrg.org_country,
org_active: body.org_active !== undefined ? parseInt(body.org_active) : existingOrg.org_active
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'PUT',
auditAction: 'UPDATE_ORGANIZATION',
auditDetails: JSON.stringify({
before: existingOrg,
after: organization
}),
auditUserID: null,
auditUsername: null
}
});
return {
statusCode: 200,
message: "Organization updated successfully",
data: organization
};
} catch (error) {
console.error("Error updating organization:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,67 @@
import prisma from "../../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get organization ID from route
const orgId = parseInt(event.context.params.id);
if (isNaN(orgId)) {
return {
statusCode: 400,
message: "Invalid organization ID"
};
}
// Check if organization exists
const organization = await prisma.organization.findUnique({
where: {
org_id: orgId
}
});
if (!organization) {
return {
statusCode: 404,
message: "Organization not found"
};
}
// Get departments for this organization
const departments = await prisma.department.findMany({
where: {
org_id: orgId
},
orderBy: {
dp_name: 'asc'
},
include: {
_count: {
select: {
cabinets: true,
users: true
}
}
}
});
return {
statusCode: 200,
data: departments,
meta: {
organization: {
org_id: organization.org_id,
org_name: organization.org_name
},
total: departments.length
}
};
} catch (error) {
console.error("Error fetching departments by organization:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

View File

@ -0,0 +1,77 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get query parameters
const query = getQuery(event);
const page = parseInt(query.page) || 1;
const limit = parseInt(query.limit) || 10;
const search = query.search || '';
// Calculate pagination
const skip = (page - 1) * limit;
// Define filters
const filters = {
where: {}
};
// Add search filter if provided
if (search) {
filters.where = {
OR: [
{ org_name: { contains: search } },
{ org_address1: { contains: search } },
{ org_state: { contains: search } },
{ org_country: { contains: search } }
]
};
}
// Get organizations with pagination
const [organizations, total] = await Promise.all([
prisma.organization.findMany({
...filters,
skip,
take: limit,
orderBy: {
org_name: 'asc'
},
include: {
_count: {
select: {
departments: true
}
}
}
}),
prisma.organization.count(filters)
]);
// Calculate pagination metadata
const totalPages = Math.ceil(total / limit);
const hasNextPage = page < totalPages;
const hasPrevPage = page > 1;
return {
statusCode: 200,
data: organizations,
meta: {
page,
limit,
total,
totalPages,
hasNextPage,
hasPrevPage
}
};
} catch (error) {
console.error("Error fetching organizations:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

View File

@ -0,0 +1,92 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get request body
const body = await readBody(event);
console.log("POST Request body:", JSON.stringify(body));
// Validate request body
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Invalid request body, expected JSON object",
received: body
};
}
// Validate required fields
if (!body.org_name) {
return {
statusCode: 400,
message: "Organization name is required",
receivedBody: body
};
}
// Check if organization already exists
const existingOrg = await prisma.organization.findUnique({
where: {
org_name: body.org_name
}
});
if (existingOrg) {
return {
statusCode: 409,
message: "Organization with this name already exists"
};
}
// Create organization
const organization = await prisma.organization.create({
data: {
org_name: body.org_name,
org_address1: body.org_address1 || null,
org_address2: body.org_address2 || null,
org_postcode: body.org_postcode ? parseInt(body.org_postcode) : null,
org_state: body.org_state || null,
org_country: body.org_country || null,
org_active: body.org_active !== undefined ? parseInt(body.org_active) : 1
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'POST',
auditAction: 'CREATE_ORGANIZATION',
auditDetails: JSON.stringify(organization),
auditUserID: null,
auditUsername: null
}
});
return {
statusCode: 201,
message: "Organization created successfully",
data: organization
};
} catch (error) {
console.error("Error creating organization:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,96 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get user ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid user ID"
};
}
// Check if user exists
const existingUser = await prisma.user.findUnique({
where: {
userID: id
},
include: {
userrole: {
select: {
userRoleID: true
}
}
}
});
if (!existingUser) {
return {
statusCode: 404,
message: "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: id
}
});
}
// Create a sanitized copy of user data for audit log
const sanitizedUser = {
...existingUser,
userPassword: "[REDACTED]",
userSecretKey: "[REDACTED]"
};
// Delete user
await prisma.user.delete({
where: {
userID: id
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'DELETE',
auditAction: 'DELETE_USER',
auditDetails: JSON.stringify(sanitizedUser),
auditUserID: null,
auditUsername: null
}
});
return {
statusCode: 200,
message: "User deleted successfully"
};
} catch (error) {
console.error("Error deleting user:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,77 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get user ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid user ID"
};
}
// Get user with related data
const user = await prisma.user.findUnique({
where: {
userID: 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 {
statusCode: 404,
message: "User not found"
};
}
return {
statusCode: 200,
data: user
};
} catch (error) {
console.error("Error fetching user:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

162
server/api/user/[id].put.js Normal file
View File

@ -0,0 +1,162 @@
import prisma from "../../utils/prisma";
import sha256 from "crypto-js/sha256";
export default defineEventHandler(async (event) => {
try {
// Get user ID from route
const id = parseInt(event.context.params.id);
if (isNaN(id)) {
return {
statusCode: 400,
message: "Invalid user ID"
};
}
// Get request body
const body = await readBody(event);
console.log("PUT User body:", JSON.stringify({
...body,
userPassword: body.userPassword ? '[REDACTED]' : undefined
}));
// Validate request body
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Invalid request body, expected JSON object",
received: body
};
}
// Check if user exists
const existingUser = await prisma.user.findUnique({
where: {
userID: id
}
});
if (!existingUser) {
return {
statusCode: 404,
message: "User not found"
};
}
// Check if department exists if department ID is provided
if (body.dp_id) {
const department = await prisma.department.findUnique({
where: {
dp_id: parseInt(body.dp_id)
}
});
if (!department) {
return {
statusCode: 404,
message: "Department not found",
dp_id: body.dp_id
};
}
}
// Check if username is taken by another user
if (body.userUsername && body.userUsername !== existingUser.userUsername) {
const usernameExists = await prisma.user.findFirst({
where: {
userUsername: body.userUsername,
userID: {
not: id
}
}
});
if (usernameExists) {
return {
statusCode: 409,
message: "Username is already taken by another user"
};
}
}
// Prepare update data
const updateData = {};
// Only update fields that are provided
if (body.userUsername !== undefined) updateData.userUsername = body.userUsername;
if (body.userFullName !== undefined) updateData.userFullName = body.userFullName;
if (body.userEmail !== undefined) updateData.userEmail = body.userEmail;
if (body.userPhone !== undefined) updateData.userPhone = body.userPhone;
if (body.userStatus !== undefined) updateData.userStatus = body.userStatus ? body.userStatus.toUpperCase() : null;
if (body.dp_id !== undefined) updateData.dp_id = body.dp_id ? parseInt(body.dp_id) : null;
// Hash password if provided
if (body.userPassword) {
updateData.userPassword = sha256(body.userPassword).toString();
}
// Update modified date
updateData.userModifiedDate = new Date();
// Update user
const user = await prisma.user.update({
where: {
userID: id
},
data: updateData
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'PUT',
auditAction: 'UPDATE_USER',
auditDetails: JSON.stringify({
before: {
...existingUser,
userPassword: "[REDACTED]"
},
after: {
...user,
userPassword: "[REDACTED]"
}
}),
auditUserID: null,
auditUsername: null
}
});
// Remove password from response
const userResponse = {
...user,
userPassword: undefined
};
return {
statusCode: 200,
message: "User updated successfully",
data: userResponse
};
} catch (error) {
console.error("Error updating user:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}

View File

@ -0,0 +1,116 @@
import prisma from "../../utils/prisma";
export default defineEventHandler(async (event) => {
try {
// Get query parameters
const query = getQuery(event);
const page = parseInt(query.page) || 1;
const limit = parseInt(query.limit) || 10;
const search = query.search || '';
const departmentId = query.dp_id ? parseInt(query.dp_id) : null;
const status = query.status || null;
// Calculate pagination
const skip = (page - 1) * limit;
// Define filters
const filters = {
where: {}
};
// Filter by department if provided
if (departmentId) {
filters.where.dp_id = 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);
const hasNextPage = page < totalPages;
const hasPrevPage = page > 1;
return {
statusCode: 200,
data: users,
meta: {
page,
limit,
total,
totalPages,
hasNextPage,
hasPrevPage
}
};
} catch (error) {
console.error("Error fetching users:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});

View File

@ -0,0 +1,153 @@
import prisma from "../../utils/prisma";
import sha256 from "crypto-js/sha256";
export default defineEventHandler(async (event) => {
try {
// Get request body
const body = await readBody(event);
console.log("POST User body:", JSON.stringify(body));
// Validate request body
if (!body || typeof body !== 'object') {
return {
statusCode: 400,
message: "Invalid request body, expected JSON object",
received: body
};
}
// Validate required fields
if (!body.userUsername) {
return {
statusCode: 400,
message: "Username is required",
receivedBody: body
};
}
if (!body.userPassword) {
return {
statusCode: 400,
message: "Password is required",
receivedBody: body
};
}
if (!body.userFullName) {
return {
statusCode: 400,
message: "Full name is required",
receivedBody: body
};
}
// Check if department exists if department ID is provided
if (body.dp_id) {
const department = await prisma.department.findUnique({
where: {
dp_id: parseInt(body.dp_id)
}
});
if (!department) {
return {
statusCode: 404,
message: "Department not found",
dp_id: body.dp_id
};
}
}
// Check if username already exists
const existingUser = await prisma.user.findFirst({
where: {
userUsername: body.userUsername
}
});
if (existingUser) {
return {
statusCode: 409,
message: "Username already exists"
};
}
// Create random secret key
const secretKey = generateRandomKey(32);
// Encrypt password with SHA256
const hashedPassword = sha256(body.userPassword).toString();
// Create user
const user = await prisma.user.create({
data: {
userSecretKey: secretKey,
userUsername: body.userUsername,
userPassword: hashedPassword,
userFullName: body.userFullName,
userEmail: body.userEmail || null,
userPhone: body.userPhone || null,
userStatus: body.userStatus ? body.userStatus.toUpperCase() : "ACTIVE",
dp_id: body.dp_id ? parseInt(body.dp_id) : null,
userCreatedDate: new Date(),
userModifiedDate: new Date()
}
});
// Create audit log
await prisma.audit.create({
data: {
auditIP: getRequestIP(event),
auditURL: getRequestURL(event),
auditURLMethod: 'POST',
auditAction: 'CREATE_USER',
auditDetails: JSON.stringify({
...user,
userPassword: "[REDACTED]" // Redact password in audit log
}),
auditUserID: null,
auditUsername: null
}
});
// Remove password from response
const userResponse = {
...user,
userPassword: undefined
};
return {
statusCode: 201,
message: "User created successfully",
data: userResponse
};
} catch (error) {
console.error("Error creating user:", error);
return {
statusCode: 500,
message: "Internal server error",
error: error.message
};
}
});
// Helper functions
function getRequestIP(event) {
return event.node.req.headers['x-forwarded-for'] ||
event.node.req.connection.remoteAddress;
}
function getRequestURL(event) {
return event.node.req.url;
}
function 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;
}

View File

@ -0,0 +1,301 @@
import prisma from './prisma';
/**
* Collection of utility functions for working with departments
*/
export const DepartmentUtils = {
/**
* Create a new department
*
* @param {Object} data Department data
* @param {string} data.dp_name Department name
* @param {number} data.org_id Organization ID
* @returns {Promise<Object>} Created department
*/
async create(data) {
if (!data.dp_name) {
throw new Error('Department name is required');
}
if (!data.org_id) {
throw new Error('Organization ID is required');
}
// Check if organization exists
const organization = await prisma.organization.findUnique({
where: {
org_id: parseInt(data.org_id)
}
});
if (!organization) {
throw new Error(`Organization with ID ${data.org_id} not found`);
}
return prisma.department.create({
data: {
dp_name: data.dp_name,
org_id: parseInt(data.org_id)
}
});
},
/**
* Get all departments 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.orgId Filter by organization ID
* @returns {Promise<Object>} Departments and pagination metadata
*/
async getAll({ page = 1, limit = 10, search = '', orgId = null }) {
const skip = (page - 1) * limit;
// Define filters
const filters = {
where: {}
};
// Filter by organization if provided
if (orgId) {
filters.where.org_id = parseInt(orgId);
}
// Add search filter if provided
if (search) {
filters.where = {
...filters.where,
dp_name: {
contains: search
}
};
}
// Get departments with pagination
const [departments, total] = await Promise.all([
prisma.department.findMany({
...filters,
skip,
take: limit,
orderBy: {
dp_name: 'asc'
},
include: {
organization: {
select: {
org_id: true,
org_name: true
}
},
_count: {
select: {
cabinets: true,
user: true
}
}
}
}),
prisma.department.count(filters)
]);
// Calculate pagination metadata
const totalPages = Math.ceil(total / limit);
return {
departments,
pagination: {
page,
limit,
total,
totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
}
};
},
/**
* Get department by ID
*
* @param {number} id Department ID
* @returns {Promise<Object|null>} Department or null if not found
*/
async getById(id) {
if (isNaN(parseInt(id))) {
throw new Error('Invalid department ID');
}
return prisma.department.findUnique({
where: {
dp_id: parseInt(id)
},
include: {
organization: {
select: {
org_id: true,
org_name: true,
org_country: true,
org_state: true,
org_active: true
}
},
cabinets: {
select: {
cb_id: true
}
},
user: {
select: {
userID: true,
userUsername: true,
userFullName: true,
userEmail: true,
userStatus: true
}
}
}
});
},
/**
* Update a department
*
* @param {number} id Department ID
* @param {Object} data Department data
* @returns {Promise<Object>} Updated department
*/
async update(id, data) {
if (isNaN(parseInt(id))) {
throw new Error('Invalid department ID');
}
// Check if department exists
const existingDept = await prisma.department.findUnique({
where: {
dp_id: parseInt(id)
}
});
if (!existingDept) {
throw new Error('Department not found');
}
// Validate required fields
if (!data.dp_name) {
throw new Error('Department name is required');
}
// If org_id is provided, check if organization exists
if (data.org_id !== undefined) {
const orgId = parseInt(data.org_id);
if (isNaN(orgId)) {
throw new Error('Invalid organization ID format');
}
const organization = await prisma.organization.findUnique({
where: {
org_id: orgId
}
});
if (!organization) {
throw new Error(`Organization with ID ${data.org_id} not found`);
}
}
return prisma.department.update({
where: {
dp_id: parseInt(id)
},
data: {
dp_name: data.dp_name,
org_id: data.org_id !== undefined ? parseInt(data.org_id) : existingDept.org_id
}
});
},
/**
* Delete a department
*
* @param {number} id Department ID
* @returns {Promise<Object>} Deleted department
*/
async delete(id) {
if (isNaN(parseInt(id))) {
throw new Error('Invalid department ID');
}
// Check if department exists and get related entities
const existingDept = await prisma.department.findUnique({
where: {
dp_id: parseInt(id)
},
include: {
cabinets: {
select: {
cb_id: true
}
},
user: {
select: {
userID: true
}
}
}
});
if (!existingDept) {
throw new Error('Department not found');
}
// Check if department has related cabinets
if (existingDept.cabinets.length > 0) {
throw new Error(`Cannot delete department with existing cabinets. Remove all cabinets first (${existingDept.cabinets.length} found).`);
}
// Check if department has related users
if (existingDept.user.length > 0) {
throw new Error(`Cannot delete department with existing users. Reassign or remove all users first (${existingDept.user.length} found).`);
}
return prisma.department.delete({
where: {
dp_id: parseInt(id)
}
});
},
/**
* Get departments by organization ID
*
* @param {number} orgId Organization ID
* @returns {Promise<Array>} List of departments
*/
async getByOrganization(orgId) {
if (isNaN(parseInt(orgId))) {
throw new Error('Invalid organization ID');
}
return prisma.department.findMany({
where: {
org_id: parseInt(orgId)
},
orderBy: {
dp_name: 'asc'
},
include: {
_count: {
select: {
cabinets: true,
user: true
}
}
}
});
}
};
export default DepartmentUtils;

View File

@ -0,0 +1,240 @@
import prisma from './prisma';
/**
* Collection of utility functions for working with organizations
*/
export const OrganizationUtils = {
/**
* Create a new organization
*
* @param {Object} data Organization data
* @returns {Promise<Object>} Created organization
*/
async create(data) {
if (!data.org_name) {
throw new Error('Organization name is required');
}
// Check if organization already exists
const existingOrg = await prisma.organization.findUnique({
where: {
org_name: data.org_name
}
});
if (existingOrg) {
throw new Error('Organization with this name already exists');
}
return prisma.organization.create({
data: {
org_name: data.org_name,
org_address1: data.org_address1 || null,
org_address2: data.org_address2 || null,
org_postcode: data.org_postcode ? parseInt(data.org_postcode) : null,
org_state: data.org_state || null,
org_country: data.org_country || null,
org_active: data.org_active !== undefined ? parseInt(data.org_active) : 1
}
});
},
/**
* Get all organizations with pagination
*
* @param {Object} options Query options
* @param {number} options.page Page number
* @param {number} options.limit Items per page
* @param {string} options.search Search term
* @returns {Promise<Object>} Organizations and pagination metadata
*/
async getAll({ page = 1, limit = 10, search = '' }) {
const skip = (page - 1) * limit;
// Define filters
const filters = {
where: {}
};
// Add search filter if provided
if (search) {
filters.where = {
OR: [
{ org_name: { contains: search } },
{ org_address1: { contains: search } },
{ org_state: { contains: search } },
{ org_country: { contains: search } }
]
};
}
// Get organizations with pagination
const [organizations, total] = await Promise.all([
prisma.organization.findMany({
...filters,
skip,
take: limit,
orderBy: {
org_name: 'asc'
},
include: {
_count: {
select: {
departments: true
}
}
}
}),
prisma.organization.count(filters)
]);
// Calculate pagination metadata
const totalPages = Math.ceil(total / limit);
return {
organizations,
pagination: {
page,
limit,
total,
totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
}
};
},
/**
* Get organization by ID
*
* @param {number} id Organization ID
* @returns {Promise<Object|null>} Organization or null if not found
*/
async getById(id) {
if (isNaN(parseInt(id))) {
throw new Error('Invalid organization ID');
}
return prisma.organization.findUnique({
where: {
org_id: parseInt(id)
},
include: {
departments: {
select: {
dp_id: true,
dp_name: true,
_count: {
select: {
users: true,
cabinets: true
}
}
}
}
}
});
},
/**
* Update an organization
*
* @param {number} id Organization ID
* @param {Object} data Organization data
* @returns {Promise<Object>} Updated organization
*/
async update(id, data) {
if (isNaN(parseInt(id))) {
throw new Error('Invalid organization ID');
}
if (!data.org_name) {
throw new Error('Organization name is required');
}
// Check if organization exists
const existingOrg = await prisma.organization.findUnique({
where: {
org_id: parseInt(id)
}
});
if (!existingOrg) {
throw new Error('Organization not found');
}
// Check if name is already taken by another organization
if (data.org_name !== existingOrg.org_name) {
const nameExists = await prisma.organization.findFirst({
where: {
org_name: data.org_name,
org_id: {
not: parseInt(id)
}
}
});
if (nameExists) {
throw new Error('Organization with this name already exists');
}
}
return prisma.organization.update({
where: {
org_id: parseInt(id)
},
data: {
org_name: data.org_name,
org_address1: data.org_address1 || null,
org_address2: data.org_address2 || null,
org_postcode: data.org_postcode ? parseInt(data.org_postcode) : null,
org_state: data.org_state || null,
org_country: data.org_country || null,
org_active: data.org_active !== undefined ? parseInt(data.org_active) : existingOrg.org_active
}
});
},
/**
* Delete an organization
*
* @param {number} id Organization ID
* @returns {Promise<Object>} Deleted organization
*/
async delete(id) {
if (isNaN(parseInt(id))) {
throw new Error('Invalid organization ID');
}
// Check if organization exists
const existingOrg = await prisma.organization.findUnique({
where: {
org_id: parseInt(id)
},
include: {
departments: {
select: {
dp_id: true
}
}
}
});
if (!existingOrg) {
throw new Error('Organization not found');
}
// Check if organization has departments
if (existingOrg.departments.length > 0) {
throw new Error('Cannot delete organization with existing departments. Remove all departments first.');
}
return prisma.organization.delete({
where: {
org_id: parseInt(id)
}
});
}
};
export default OrganizationUtils;

490
server/utils/userUtils.js Normal file
View File

@ -0,0 +1,490 @@
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<Object>} 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<Object>} 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<Object|null>} 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<Object>} 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<Object>} 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<Array>} 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<Object|null>} 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;

3850
yarn.lock

File diff suppressed because it is too large Load Diff