push bunch of backend and new scheme.prisma

This commit is contained in:
MuhdAthir 2025-05-31 16:58:30 +08:00
parent 1c0afe2b8a
commit 660988352f
25 changed files with 22641 additions and 2027 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

@ -12,20 +12,52 @@ model audit {
auditIP String? @db.VarChar(255)
auditURL String? @db.VarChar(255)
auditURLMethod String? @db.VarChar(255)
auditURLPayload String? @db.VarChar(255)
auditCreatedDate DateTime? @db.DateTime(0)
auditURLPayload String? @db.Text
auditCreatedDate DateTime? @default(now()) @db.DateTime(0)
auditAction String? @db.VarChar(255)
auditDetails String? @db.Text
auditUserID Int?
auditUsername String? @db.VarChar(255)
user user? @relation(fields: [auditUserID], references: [userID])
@@index([auditUserID], map: "FK_audit_user")
}
model lookup {
lookupID Int @id @default(autoincrement())
lookupOrder Int?
lookupTitle String? @db.VarChar(255)
lookupRefCode String? @db.VarChar(255)
lookupValue String? @db.VarChar(255)
lookupType String? @db.VarChar(255)
lookupStatus String? @db.VarChar(255)
lookupCreatedDate DateTime? @db.DateTime(0)
lookupModifiedDate DateTime? @db.DateTime(0)
model organization {
org_id Int @id @default(autoincrement())
org_name String @unique(map: "organization_unique") @db.VarChar(255)
org_address1 String? @db.VarChar(255)
org_address2 String? @db.VarChar(255)
org_postcode Int?
org_state String? @db.VarChar(100)
org_country String? @db.VarChar(100)
org_active Int?
departments department[]
}
model department {
dp_id Int @id @default(autoincrement())
dp_name String @db.VarChar(255)
org_id Int
cabinets cabinets[]
organization organization @relation(fields: [org_id], references: [org_id], onDelete: Cascade, onUpdate: NoAction, map: "department_organization_FK")
user user[]
@@index([org_id], map: "department_organization_FK")
}
model cabinets {
cb_id Int @id @default(autoincrement())
cb_name String @unique(map: "cabinet_master_unique") @db.VarChar(255)
cb_parent_id Int?
cb_private Int? @default(0)
cb_owner Int?
dp_id Int?
created_at DateTime? @db.DateTime(0)
modified_at DateTime? @db.DateTime(0)
department department? @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "cabinets_department_FK")
@@index([dp_id], map: "cabinets_department_FK")
}
model role {
@ -36,7 +68,6 @@ model role {
roleCreatedDate DateTime? @db.DateTime(0)
roleModifiedDate DateTime? @db.DateTime(0)
userrole userrole[]
permissions AccessPermission[]
}
model user {
@ -48,16 +79,14 @@ model user {
userEmail String? @db.VarChar(255)
userPhone String? @db.VarChar(255)
userStatus String? @db.VarChar(255)
dp_id Int?
userCreatedDate DateTime? @db.DateTime(0)
userModifiedDate DateTime? @db.DateTime(0)
audit audit[]
department department? @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "user_department_FK")
userrole userrole[]
accessRequests AccessRequest[]
permissions AccessPermission[]
documents Document[] @relation("DocumentCreator")
cabinets Cabinet[] @relation("CabinetCreator")
drawers Drawer[] @relation("DrawerCreator")
folders Folder[] @relation("FolderCreator")
subfolders Subfolder[] @relation("SubfolderCreator")
@@index([dp_id], map: "user_department_FK")
}
model userrole {
@ -72,199 +101,94 @@ model userrole {
@@index([userRoleUserID], map: "FK_userrole_user")
}
model dms_settings {
settingID Int @id @default(autoincrement())
userRoles String? @db.Text
rbacEnabled Boolean? @default(true)
userGroups String? @db.Text
permissionView Boolean? @default(true)
permissionEdit Boolean? @default(true)
permissionDelete Boolean? @default(false)
permissionDownload Boolean? @default(true)
permissionShare Boolean? @default(true)
ssoEnabled Boolean? @default(false)
mfaRequired Boolean? @default(false)
ldapIntegration Boolean? @default(false)
sessionTimeout Int? @default(8)
folderMaxDepth Int? @default(5)
folderDefaultStructure String? @db.Text
folderTemplates String? @db.Text
namingAutoGenerate Boolean? @default(true)
namingMandatoryFields String? @db.Text
namingPattern String? @default("{department}_{title}_{date}") @db.VarChar(255)
retentionEnabled Boolean? @default(true)
retentionDefaultDays Int? @default(2555)
retentionArchiveBeforeDelete Boolean? @default(true)
versionControlEnabled Boolean? @default(true)
versionControlMaxVersions Int? @default(10)
versionControlAutoVersioning Boolean? @default(true)
metadataCustomFields String? @db.LongText
taggingPredefinedTags String? @db.Text
taggingUserGeneratedTags Boolean? @default(true)
taggingTagSuggestions Boolean? @default(true)
classificationAutoEnabled Boolean? @default(true)
classificationRules String? @db.Text
workflowApprovalEnabled Boolean? @default(true)
workflowDefaultFlow String? @default("department-head-approval") @db.VarChar(255)
workflowCustomFlows String? @db.Text
notificationEmail Boolean? @default(true)
notificationInApp Boolean? @default(true)
notificationUploadAlerts Boolean? @default(true)
notificationDeadlineReminders Boolean? @default(true)
automationTriggers String? @db.Text
automationActions String? @db.Text
uploadAllowedFileTypes String? @db.Text
uploadBlockedFileTypes String? @db.Text
uploadFileSizeLimit Int? @default(100)
uploadQuotaPerUser Int? @default(5000)
uploadQuotaPerGroup Int? @default(50000)
uploadQuotaPerProject Int? @default(100000)
storageType String? @default("local") @db.VarChar(100)
storagePath String? @default("/var/uploads/edms") @db.VarChar(500)
storageBackupEnabled Boolean? @default(true)
storageCompressionEnabled Boolean? @default(false)
systemTimezone String? @default("Asia/Kuala_Lumpur") @db.VarChar(100)
systemBackupSchedule String? @default("daily") @db.VarChar(100)
systemLogLevel String? @default("info") @db.VarChar(100)
systemMaintenanceMode Boolean? @default(false)
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? @db.VarChar(255)
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)
primaryColor String? @db.VarChar(50)
secondaryColor String? @db.VarChar(50)
successColor String? @db.VarChar(50)
infoColor String? @db.VarChar(50)
warningColor String? @db.VarChar(50)
dangerColor String? @db.VarChar(50)
customCSS String? @db.Text
themeMode String? @db.VarChar(50)
customCSS String? @db.LongText
themeMode String? @default("biasa") @db.VarChar(100)
customThemeFile String? @db.VarChar(500)
currentFont String? @db.VarChar(255)
fontSource 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(50)
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? @db.DateTime(0)
settingModifiedDate DateTime? @db.DateTime(0)
siteLoginLogo String? @db.VarChar(500)
}
// DMS Models
model Cabinet {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.Text
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
createdBy Int
status String @default("active") @db.VarChar(50)
user user @relation("CabinetCreator", fields: [createdBy], references: [userID])
drawers Drawer[]
permissions AccessPermission[]
@@index([createdBy])
}
model Drawer {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.Text
cabinetId Int
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
createdBy Int
status String @default("active") @db.VarChar(50)
cabinet Cabinet @relation(fields: [cabinetId], references: [id], onDelete: Cascade)
user user @relation("DrawerCreator", fields: [createdBy], references: [userID])
folders Folder[]
permissions AccessPermission[]
@@index([cabinetId])
@@index([createdBy])
}
model Folder {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.Text
drawerId Int
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
createdBy Int
status String @default("active") @db.VarChar(50)
drawer Drawer @relation(fields: [drawerId], references: [id], onDelete: Cascade)
user user @relation("FolderCreator", fields: [createdBy], references: [userID])
subfolders Subfolder[]
documents Document[] @relation("FolderDocuments")
permissions AccessPermission[]
@@index([drawerId])
@@index([createdBy])
}
model Subfolder {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.Text
folderId Int
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
createdBy Int
status String @default("active") @db.VarChar(50)
folder Folder @relation(fields: [folderId], references: [id], onDelete: Cascade)
user user @relation("SubfolderCreator", fields: [createdBy], references: [userID])
documents Document[] @relation("SubfolderDocuments")
permissions AccessPermission[]
@@index([folderId])
@@index([createdBy])
}
model Document {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
description String? @db.Text
fileSize Int @default(0)
fileType String @db.VarChar(100)
fileExtension String @db.VarChar(20)
filePath String @db.VarChar(500)
version Int @default(1)
isTemplate Boolean @default(false)
isPublic Boolean @default(false)
folderId Int?
subfolderId Int?
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
createdBy Int
status String @default("active") @db.VarChar(50)
folder Folder? @relation("FolderDocuments", fields: [folderId], references: [id], onDelete: SetNull)
subfolder Subfolder? @relation("SubfolderDocuments", fields: [subfolderId], references: [id], onDelete: SetNull)
user user @relation("DocumentCreator", fields: [createdBy], references: [userID])
accessRequests AccessRequest[]
permissions AccessPermission[]
versions DocumentVersion[]
@@index([folderId])
@@index([subfolderId])
@@index([createdBy])
}
model DocumentVersion {
id Int @id @default(autoincrement())
documentId Int
version Int
filePath String @db.VarChar(500)
fileSize Int @default(0)
createdAt DateTime @default(now()) @db.DateTime(0)
createdBy Int
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
@@index([documentId])
}
model AccessRequest {
id Int @id @default(autoincrement())
documentId Int
userId Int
requestedLevel String @db.VarChar(50) // view, download, print, edit, full
justification String? @db.Text
status String @default("pending") @db.VarChar(50) // pending, approved, rejected
responseNote String? @db.Text
requestedAt DateTime @default(now()) @db.DateTime(0)
respondedAt DateTime? @db.DateTime(0)
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
user user @relation(fields: [userId], references: [userID])
@@index([documentId])
@@index([userId])
}
model AccessPermission {
id Int @id @default(autoincrement())
userId Int?
roleId Int?
documentId Int?
cabinetId Int?
drawerId Int?
folderId Int?
subfolderId Int?
permissionLevel String @db.VarChar(50) // view, download, print, edit, full
createdAt DateTime @default(now()) @db.DateTime(0)
updatedAt DateTime @updatedAt @db.DateTime(0)
expiresAt DateTime? @db.DateTime(0)
user user? @relation(fields: [userId], references: [userID], onDelete: SetNull)
role role? @relation(fields: [roleId], references: [roleID], onDelete: SetNull)
document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
cabinet Cabinet? @relation(fields: [cabinetId], references: [id], onDelete: Cascade)
drawer Drawer? @relation(fields: [drawerId], references: [id], onDelete: Cascade)
folder Folder? @relation(fields: [folderId], references: [id], onDelete: Cascade)
subfolder Subfolder? @relation(fields: [subfolderId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([roleId])
@@index([documentId])
@@index([cabinetId])
@@index([drawerId])
@@index([folderId])
@@index([subfolderId])
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