generated from corrad-software/corrad-af-2024
Merge branch 'development' of https://git.sena.my/corrad-software/EDMS into development
This commit is contained in:
commit
d0a4736a2a
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": {
|
||||
"@nuxtjs/tailwindcss": "^6.8.0",
|
||||
"@pinia-plugin-persistedstate/nuxt": "^1.1.1",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@vite-pwa/nuxt": "^0.1.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-plugin-vue": "^9.16.1",
|
||||
|
@ -41,7 +41,11 @@ model department {
|
||||
org_id Int
|
||||
cabinets cabinets[]
|
||||
organization organization @relation(fields: [org_id], references: [org_id], onDelete: Cascade, onUpdate: NoAction, map: "department_organization_FK")
|
||||
<<<<<<< HEAD
|
||||
user user[]
|
||||
=======
|
||||
users sys_user[]
|
||||
>>>>>>> d4880c491e3491be4f09fbfbc0e0a9f8b5cfb1b8
|
||||
|
||||
@@index([org_id], map: "department_organization_FK")
|
||||
}
|
||||
@ -60,23 +64,6 @@ model cabinets {
|
||||
@@index([dp_id], map: "cabinets_department_FK")
|
||||
}
|
||||
|
||||
model sys_user {
|
||||
su_id Int @id @default(autoincrement())
|
||||
su_username String @unique(map: "sys_user_unique") @db.VarChar(100)
|
||||
su_name String @db.VarChar(255)
|
||||
su_nric Int @unique(map: "sys_user_unique_1")
|
||||
su_dob DateTime @db.Date
|
||||
su_email String? @db.VarChar(255)
|
||||
su_password String @db.VarChar(255)
|
||||
dp_id Int
|
||||
su_active Int? @default(1)
|
||||
su_lock Int? @default(0)
|
||||
su_org_id Int
|
||||
department department @relation(fields: [dp_id], references: [dp_id], onDelete: NoAction, onUpdate: NoAction, map: "sys_user_department_FK")
|
||||
|
||||
@@index([dp_id], map: "sys_user_department_FK")
|
||||
}
|
||||
|
||||
model role {
|
||||
roleID Int @id @default(autoincrement())
|
||||
roleName String? @db.VarChar(255)
|
||||
@ -114,39 +101,8 @@ model userrole {
|
||||
@@index([userRoleUserID], map: "FK_userrole_user")
|
||||
}
|
||||
|
||||
model site_settings {
|
||||
settingID Int @id @default(autoincrement())
|
||||
siteName String? @default("corradAF") @db.VarChar(255)
|
||||
siteNameFontSize Int? @default(18)
|
||||
siteDescription String? @db.Text
|
||||
siteLogo String? @db.VarChar(500)
|
||||
siteLoadingLogo String? @db.VarChar(500)
|
||||
siteFavicon String? @db.VarChar(500)
|
||||
siteLoginLogo String? @db.VarChar(500)
|
||||
showSiteNameInHeader Boolean? @default(true)
|
||||
customCSS String? @db.LongText
|
||||
themeMode String? @default("biasa") @db.VarChar(100)
|
||||
customThemeFile String? @db.VarChar(500)
|
||||
currentFont String? @db.VarChar(100)
|
||||
fontSource String? @db.VarChar(100)
|
||||
seoTitle String? @db.VarChar(255)
|
||||
seoDescription String? @db.Text
|
||||
seoKeywords String? @db.Text
|
||||
seoAuthor String? @db.VarChar(255)
|
||||
seoOgImage String? @db.VarChar(500)
|
||||
seoTwitterCard String? @default("summary_large_image") @db.VarChar(100)
|
||||
seoCanonicalUrl String? @db.VarChar(500)
|
||||
seoRobots String? @default("index, follow") @db.VarChar(100)
|
||||
seoGoogleAnalytics String? @db.VarChar(255)
|
||||
seoGoogleTagManager String? @db.VarChar(255)
|
||||
seoFacebookPixel String? @db.VarChar(255)
|
||||
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
|
||||
settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
|
||||
}
|
||||
|
||||
model dms_settings {
|
||||
settingID Int @id @default(autoincrement())
|
||||
// User & Access Management
|
||||
userRoles String? @db.Text
|
||||
rbacEnabled Boolean? @default(true)
|
||||
userGroups String? @db.Text
|
||||
@ -159,8 +115,6 @@ model dms_settings {
|
||||
mfaRequired Boolean? @default(false)
|
||||
ldapIntegration Boolean? @default(false)
|
||||
sessionTimeout Int? @default(8)
|
||||
|
||||
// Document & Folder Settings
|
||||
folderMaxDepth Int? @default(5)
|
||||
folderDefaultStructure String? @db.Text
|
||||
folderTemplates String? @db.Text
|
||||
@ -173,16 +127,12 @@ model dms_settings {
|
||||
versionControlEnabled Boolean? @default(true)
|
||||
versionControlMaxVersions Int? @default(10)
|
||||
versionControlAutoVersioning Boolean? @default(true)
|
||||
|
||||
// Metadata & Tagging
|
||||
metadataCustomFields String? @db.LongText
|
||||
taggingPredefinedTags String? @db.Text
|
||||
taggingUserGeneratedTags Boolean? @default(true)
|
||||
taggingTagSuggestions Boolean? @default(true)
|
||||
classificationAutoEnabled Boolean? @default(true)
|
||||
classificationRules String? @db.Text
|
||||
|
||||
// Workflow & Automation
|
||||
workflowApprovalEnabled Boolean? @default(true)
|
||||
workflowDefaultFlow String? @default("department-head-approval") @db.VarChar(255)
|
||||
workflowCustomFlows String? @db.Text
|
||||
@ -192,8 +142,6 @@ model dms_settings {
|
||||
notificationDeadlineReminders Boolean? @default(true)
|
||||
automationTriggers String? @db.Text
|
||||
automationActions String? @db.Text
|
||||
|
||||
// Upload & Storage Settings
|
||||
uploadAllowedFileTypes String? @db.Text
|
||||
uploadBlockedFileTypes String? @db.Text
|
||||
uploadFileSizeLimit Int? @default(100)
|
||||
@ -204,8 +152,6 @@ model dms_settings {
|
||||
storagePath String? @default("/var/uploads/edms") @db.VarChar(500)
|
||||
storageBackupEnabled Boolean? @default(true)
|
||||
storageCompressionEnabled Boolean? @default(false)
|
||||
|
||||
// System Settings
|
||||
systemTimezone String? @default("Asia/Kuala_Lumpur") @db.VarChar(100)
|
||||
systemBackupSchedule String? @default("daily") @db.VarChar(100)
|
||||
systemLogLevel String? @default("info") @db.VarChar(100)
|
||||
@ -213,7 +159,36 @@ model dms_settings {
|
||||
systemAutoUpdates Boolean? @default(false)
|
||||
systemMonitoring Boolean? @default(true)
|
||||
systemPerformanceMetrics Boolean? @default(true)
|
||||
|
||||
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
|
||||
settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
|
||||
}
|
||||
|
||||
model site_settings {
|
||||
settingID Int @id @default(autoincrement())
|
||||
siteName String? @default("corradAF") @db.VarChar(255)
|
||||
siteNameFontSize Int? @default(18)
|
||||
siteDescription String? @db.Text
|
||||
siteLogo String? @db.VarChar(500)
|
||||
siteLoadingLogo String? @db.VarChar(500)
|
||||
siteFavicon String? @db.VarChar(500)
|
||||
siteLoginLogo String? @db.VarChar(500)
|
||||
showSiteNameInHeader Boolean? @default(true)
|
||||
customCSS String? @db.LongText
|
||||
themeMode String? @default("biasa") @db.VarChar(100)
|
||||
customThemeFile String? @db.VarChar(500)
|
||||
currentFont String? @db.VarChar(100)
|
||||
fontSource String? @db.VarChar(100)
|
||||
seoTitle String? @db.VarChar(255)
|
||||
seoDescription String? @db.Text
|
||||
seoKeywords String? @db.Text
|
||||
seoAuthor String? @db.VarChar(255)
|
||||
seoOgImage String? @db.VarChar(500)
|
||||
seoTwitterCard String? @default("summary_large_image") @db.VarChar(100)
|
||||
seoCanonicalUrl String? @db.VarChar(500)
|
||||
seoRobots String? @default("index, follow") @db.VarChar(100)
|
||||
seoGoogleAnalytics String? @db.VarChar(255)
|
||||
seoGoogleTagManager String? @db.VarChar(255)
|
||||
seoFacebookPixel String? @db.VarChar(255)
|
||||
settingCreatedDate DateTime? @default(now()) @db.DateTime(0)
|
||||
settingModifiedDate DateTime? @default(now()) @db.DateTime(0)
|
||||
}
|
||||
|
126
server/api/auth/login.js
Normal file
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