generated from corrad-software/corrad-af-2024
push bunch of backend and new scheme.prisma
This commit is contained in:
parent
1c0afe2b8a
commit
660988352f
17639
package-lock.json
generated
Normal file
17639
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.8.0",
|
"@nuxtjs/tailwindcss": "^6.8.0",
|
||||||
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
|
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
|
||||||
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@vite-pwa/nuxt": "^0.1.0",
|
"@vite-pwa/nuxt": "^0.1.0",
|
||||||
"eslint": "^8.39.0",
|
"eslint": "^8.39.0",
|
||||||
"eslint-plugin-vue": "^9.16.1",
|
"eslint-plugin-vue": "^9.16.1",
|
||||||
|
@ -12,52 +12,81 @@ model audit {
|
|||||||
auditIP String? @db.VarChar(255)
|
auditIP String? @db.VarChar(255)
|
||||||
auditURL String? @db.VarChar(255)
|
auditURL String? @db.VarChar(255)
|
||||||
auditURLMethod String? @db.VarChar(255)
|
auditURLMethod String? @db.VarChar(255)
|
||||||
auditURLPayload String? @db.VarChar(255)
|
auditURLPayload String? @db.Text
|
||||||
auditCreatedDate DateTime? @db.DateTime(0)
|
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 {
|
model organization {
|
||||||
lookupID Int @id @default(autoincrement())
|
org_id Int @id @default(autoincrement())
|
||||||
lookupOrder Int?
|
org_name String @unique(map: "organization_unique") @db.VarChar(255)
|
||||||
lookupTitle String? @db.VarChar(255)
|
org_address1 String? @db.VarChar(255)
|
||||||
lookupRefCode String? @db.VarChar(255)
|
org_address2 String? @db.VarChar(255)
|
||||||
lookupValue String? @db.VarChar(255)
|
org_postcode Int?
|
||||||
lookupType String? @db.VarChar(255)
|
org_state String? @db.VarChar(100)
|
||||||
lookupStatus String? @db.VarChar(255)
|
org_country String? @db.VarChar(100)
|
||||||
lookupCreatedDate DateTime? @db.DateTime(0)
|
org_active Int?
|
||||||
lookupModifiedDate DateTime? @db.DateTime(0)
|
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 {
|
model role {
|
||||||
roleID Int @id @default(autoincrement())
|
roleID Int @id @default(autoincrement())
|
||||||
roleName String? @db.VarChar(255)
|
roleName String? @db.VarChar(255)
|
||||||
roleDescription String? @db.VarChar(255)
|
roleDescription String? @db.VarChar(255)
|
||||||
roleStatus String? @db.VarChar(255)
|
roleStatus String? @db.VarChar(255)
|
||||||
roleCreatedDate DateTime? @db.DateTime(0)
|
roleCreatedDate DateTime? @db.DateTime(0)
|
||||||
roleModifiedDate DateTime? @db.DateTime(0)
|
roleModifiedDate DateTime? @db.DateTime(0)
|
||||||
userrole userrole[]
|
userrole userrole[]
|
||||||
permissions AccessPermission[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model user {
|
model user {
|
||||||
userID Int @id @default(autoincrement())
|
userID Int @id @default(autoincrement())
|
||||||
userSecretKey String? @db.VarChar(255)
|
userSecretKey String? @db.VarChar(255)
|
||||||
userUsername String? @db.VarChar(255)
|
userUsername String? @db.VarChar(255)
|
||||||
userPassword String? @db.VarChar(255)
|
userPassword String? @db.VarChar(255)
|
||||||
userFullName String? @db.VarChar(255)
|
userFullName String? @db.VarChar(255)
|
||||||
userEmail String? @db.VarChar(255)
|
userEmail String? @db.VarChar(255)
|
||||||
userPhone String? @db.VarChar(255)
|
userPhone String? @db.VarChar(255)
|
||||||
userStatus String? @db.VarChar(255)
|
userStatus String? @db.VarChar(255)
|
||||||
userCreatedDate DateTime? @db.DateTime(0)
|
dp_id Int?
|
||||||
userModifiedDate DateTime? @db.DateTime(0)
|
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[]
|
userrole userrole[]
|
||||||
accessRequests AccessRequest[]
|
|
||||||
permissions AccessPermission[]
|
@@index([dp_id], map: "user_department_FK")
|
||||||
documents Document[] @relation("DocumentCreator")
|
|
||||||
cabinets Cabinet[] @relation("CabinetCreator")
|
|
||||||
drawers Drawer[] @relation("DrawerCreator")
|
|
||||||
folders Folder[] @relation("FolderCreator")
|
|
||||||
subfolders Subfolder[] @relation("SubfolderCreator")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model userrole {
|
model userrole {
|
||||||
@ -72,199 +101,94 @@ model userrole {
|
|||||||
@@index([userRoleUserID], map: "FK_userrole_user")
|
@@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 {
|
model site_settings {
|
||||||
settingID Int @id @default(autoincrement())
|
settingID Int @id @default(autoincrement())
|
||||||
siteName String? @db.VarChar(255)
|
siteName String? @default("corradAF") @db.VarChar(255)
|
||||||
siteNameFontSize Int? @default(18)
|
siteNameFontSize Int? @default(18)
|
||||||
siteDescription String? @db.Text
|
siteDescription String? @db.Text
|
||||||
siteLogo String? @db.VarChar(500)
|
siteLogo String? @db.VarChar(500)
|
||||||
siteLoadingLogo String? @db.VarChar(500)
|
siteLoadingLogo String? @db.VarChar(500)
|
||||||
siteFavicon String? @db.VarChar(500)
|
siteFavicon String? @db.VarChar(500)
|
||||||
|
siteLoginLogo String? @db.VarChar(500)
|
||||||
showSiteNameInHeader Boolean? @default(true)
|
showSiteNameInHeader Boolean? @default(true)
|
||||||
primaryColor String? @db.VarChar(50)
|
customCSS String? @db.LongText
|
||||||
secondaryColor String? @db.VarChar(50)
|
themeMode String? @default("biasa") @db.VarChar(100)
|
||||||
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)
|
|
||||||
customThemeFile String? @db.VarChar(500)
|
customThemeFile String? @db.VarChar(500)
|
||||||
currentFont String? @db.VarChar(255)
|
currentFont String? @db.VarChar(100)
|
||||||
fontSource String? @db.VarChar(500)
|
fontSource String? @db.VarChar(100)
|
||||||
seoTitle String? @db.VarChar(255)
|
seoTitle String? @db.VarChar(255)
|
||||||
seoDescription String? @db.Text
|
seoDescription String? @db.Text
|
||||||
seoKeywords String? @db.Text
|
seoKeywords String? @db.Text
|
||||||
seoAuthor String? @db.VarChar(255)
|
seoAuthor String? @db.VarChar(255)
|
||||||
seoOgImage String? @db.VarChar(500)
|
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)
|
seoCanonicalUrl String? @db.VarChar(500)
|
||||||
seoRobots String? @default("index, follow") @db.VarChar(100)
|
seoRobots String? @default("index, follow") @db.VarChar(100)
|
||||||
seoGoogleAnalytics String? @db.VarChar(255)
|
seoGoogleAnalytics String? @db.VarChar(255)
|
||||||
seoGoogleTagManager String? @db.VarChar(255)
|
seoGoogleTagManager String? @db.VarChar(255)
|
||||||
seoFacebookPixel String? @db.VarChar(255)
|
seoFacebookPixel String? @db.VarChar(255)
|
||||||
settingCreatedDate DateTime? @db.DateTime(0)
|
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
|
||||||
settingModifiedDate DateTime? @db.DateTime(0)
|
settingModifiedDate DateTime? @default(now()) @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])
|
|
||||||
}
|
}
|
||||||
|
126
server/api/auth/login.js
Normal file
126
server/api/auth/login.js
Normal 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;
|
||||||
|
}
|
102
server/api/department/[id].delete.js
Normal file
102
server/api/department/[id].delete.js
Normal 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;
|
||||||
|
}
|
69
server/api/department/[id].get.js
Normal file
69
server/api/department/[id].get.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
129
server/api/department/[id].put.js
Normal file
129
server/api/department/[id].put.js
Normal 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;
|
||||||
|
}
|
80
server/api/department/[id]/users.get.js
Normal file
80
server/api/department/[id]/users.get.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
88
server/api/department/index.get.js
Normal file
88
server/api/department/index.get.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
96
server/api/department/index.post.js
Normal file
96
server/api/department/index.post.js
Normal 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;
|
||||||
|
}
|
87
server/api/organization/[id].delete.js
Normal file
87
server/api/organization/[id].delete.js
Normal 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;
|
||||||
|
}
|
56
server/api/organization/[id].get.js
Normal file
56
server/api/organization/[id].get.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
126
server/api/organization/[id].put.js
Normal file
126
server/api/organization/[id].put.js
Normal 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;
|
||||||
|
}
|
67
server/api/organization/[id]/departments.get.js
Normal file
67
server/api/organization/[id]/departments.get.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
77
server/api/organization/index.get.js
Normal file
77
server/api/organization/index.get.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
92
server/api/organization/index.post.js
Normal file
92
server/api/organization/index.post.js
Normal 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;
|
||||||
|
}
|
96
server/api/user/[id].delete.js
Normal file
96
server/api/user/[id].delete.js
Normal 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;
|
||||||
|
}
|
77
server/api/user/[id].get.js
Normal file
77
server/api/user/[id].get.js
Normal 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
162
server/api/user/[id].put.js
Normal 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;
|
||||||
|
}
|
116
server/api/user/index.get.js
Normal file
116
server/api/user/index.get.js
Normal 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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
153
server/api/user/index.post.js
Normal file
153
server/api/user/index.post.js
Normal 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;
|
||||||
|
}
|
301
server/utils/departmentUtils.js
Normal file
301
server/utils/departmentUtils.js
Normal 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;
|
240
server/utils/organizationUtils.js
Normal file
240
server/utils/organizationUtils.js
Normal 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
490
server/utils/userUtils.js
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user