From 7d8aeea90fe05be51a5278b74c278d4f867a6396 Mon Sep 17 00:00:00 2001 From: webserver-lab Date: Sun, 7 Jun 2026 14:21:02 +0330 Subject: [PATCH] update --- src/core/middleware/check-permission.ts | 46 ++++++ src/models/UserPermission.ts | 86 +++++++++++ .../controller/department.controller.ts | 17 ++- .../department/routes/department.routes.ts | 1 + .../department/services/department.service.ts | 57 ++++++- .../report/controller/report.controller.ts | 1 + src/modules/report/service/report.service.ts | 13 +- src/modules/ticket/services/ticket.service.ts | 3 +- src/seeders/department.seed.ts | 2 + src/seeders/permission.seed.ts | 139 ++++++++++++++++++ src/seeders/rolePermission.ts | 122 +++++++++++++++ src/seeders/user.seed.ts | 4 +- src/server.ts | 8 +- 13 files changed, 486 insertions(+), 13 deletions(-) create mode 100644 src/core/middleware/check-permission.ts create mode 100644 src/models/UserPermission.ts create mode 100644 src/seeders/permission.seed.ts create mode 100644 src/seeders/rolePermission.ts diff --git a/src/core/middleware/check-permission.ts b/src/core/middleware/check-permission.ts new file mode 100644 index 0000000..7e3c5f7 --- /dev/null +++ b/src/core/middleware/check-permission.ts @@ -0,0 +1,46 @@ +import Permission from "../../models/Permission"; +import RolePermission from "../../models/RolePermission"; +import UserPermission from "../../models/UserPermission"; + + +export function checkPermission(permissionName: string) { + return async (req: any, res: any, next: any) => { + const user = req.user; + + const permission = await Permission.findOne({ + where: { name: permissionName }, + }); + + if (!permission) { + return res.status(403).json({ message: "Permission not found" }); + } + + // ✅ 1- چک سطح کاربر + const userPermission = await UserPermission.findOne({ + where: { + userId: user.id, + permissionId: permission.id, + }, + }); + + if (userPermission) { + return next(); + } + + // ✅ 2- چک سطح رول + const rolePermission = await RolePermission.findOne({ + where: { + roleId: user.roleId, + permissionId: permission.id, + }, + }); + + if (rolePermission) { + return next(); + } + + return res.status(403).json({ + message: "Access Denied", + }); + }; +} diff --git a/src/models/UserPermission.ts b/src/models/UserPermission.ts new file mode 100644 index 0000000..5b4b547 --- /dev/null +++ b/src/models/UserPermission.ts @@ -0,0 +1,86 @@ +import { + Model, + DataTypes, + Optional, + Sequelize +} from "sequelize"; + +interface UserPermissionAttributes { + id: string; + userId: string; + permissionId: string; + createdAt?: Date; + updatedAt?: Date; +} + +interface UserPermissionCreationAttributes + extends Optional {} + +class UserPermission + extends Model< + UserPermissionAttributes, + UserPermissionCreationAttributes + > + implements UserPermissionAttributes +{ + public id!: string; + public userId!: string; + public permissionId!: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + static initModel(sequelize: Sequelize): typeof UserPermission { + UserPermission.init( + { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: DataTypes.UUIDV4, + }, + + userId: { + type: DataTypes.UUID, + allowNull: false, + }, + + permissionId: { + type: DataTypes.UUID, + allowNull: false, + }, + }, + { + sequelize, + modelName: "UserPermission", + tableName: "user_permissions", + timestamps: true, + + // ✅ جلوگیری از تکراری شدن + indexes: [ + { + unique: true, + fields: ["userId", "permissionId"], + }, + ], + } + ); + + return UserPermission; + } + + static associate(models: any) { + UserPermission.belongsTo(models.User, { + foreignKey: "userId", + as: "user", + onDelete: "CASCADE", + }); + + UserPermission.belongsTo(models.Permission, { + foreignKey: "permissionId", + as: "permission", + onDelete: "CASCADE", + }); + } +} + +export default UserPermission; diff --git a/src/modules/department/controller/department.controller.ts b/src/modules/department/controller/department.controller.ts index ea5edc9..a6eb94a 100644 --- a/src/modules/department/controller/department.controller.ts +++ b/src/modules/department/controller/department.controller.ts @@ -15,7 +15,22 @@ class DepartmentControllerClass extends Controller { try { - const data = await this.#service.getAll(); + const data = await this.#service.getAll(req); + return res.status(200).json({ + status: 200, + data, + message: "Ok", + }); + } catch (error) { + console.log(error) + next(error); + } + } + async getAllList(req: any, res: ServerResponse, next: NextFunction) { + + + try { + const data = await this.#service.getAllList(); return res.status(200).json({ status: 200, data, diff --git a/src/modules/department/routes/department.routes.ts b/src/modules/department/routes/department.routes.ts index 7697a54..9926c49 100644 --- a/src/modules/department/routes/department.routes.ts +++ b/src/modules/department/routes/department.routes.ts @@ -4,6 +4,7 @@ import DepartmentController from "../controller/department.controller"; const departmentRouter = router.Router(); departmentRouter.get("/all", DepartmentController.getAll); +departmentRouter.get("/list", DepartmentController.getAllList); departmentRouter.get("/get/:id", DepartmentController.getById); departmentRouter.post("/create", DepartmentController.create); departmentRouter.delete("/remove/:id", DepartmentController.delete); diff --git a/src/modules/department/services/department.service.ts b/src/modules/department/services/department.service.ts index 71d10e3..84b929b 100644 --- a/src/modules/department/services/department.service.ts +++ b/src/modules/department/services/department.service.ts @@ -1,5 +1,5 @@ import createHttpError from "http-errors"; -import { UniqueConstraintError } from "sequelize"; +import { Op, UniqueConstraintError } from "sequelize"; import { DepartmentMessages } from "../messages"; import { GlobalMessages } from "../../../core/messages"; import { Controller } from "../../../core/controller/main.controller"; @@ -11,9 +11,62 @@ interface CreateDepartmentDataBody { } class DepartmentServiceClass extends Controller { - async getAll() { + async getAll(req: any) { try { + const { + page = 1, + limit = 10, + displayName, + startDate, + endDate, + } = req.query; + const offset = (page - 1) * limit; + + const whereClause: any = {}; + + if (displayName) { + whereClause.displayName = { + [Op.like]: `%${displayName}%`, + }; + } + + if (startDate || endDate) { + whereClause.createdAt = {}; + + if (startDate) { + whereClause.createdAt[Op.gte] = new Date(startDate); + } + + if (endDate) { + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); + whereClause.createdAt[Op.lte] = end; + } + } + + const { count, rows } = await Department.findAndCountAll({ + where: whereClause, + order: [["createdAt", "DESC"]], + limit: parseInt(limit), + offset: parseInt(offset.toString()), + raw: true, + }); + + return { + data: rows, + totalItems: count, + totalPages: Math.ceil(count / limit), + currentPage: parseInt(page), + }; + } catch (error) { + throw new createHttpError.InternalServerError( + GlobalMessages.errors.server, + ); + } + } + async getAllList() { + try { const data = await Department.findAll({ raw: true }); return data; diff --git a/src/modules/report/controller/report.controller.ts b/src/modules/report/controller/report.controller.ts index 12edc2e..370a0fd 100644 --- a/src/modules/report/controller/report.controller.ts +++ b/src/modules/report/controller/report.controller.ts @@ -91,6 +91,7 @@ class ReportControllerClass extends Controller { data: { system, operators, departments }, }); } catch (error) { + console.log(error) next(error); } } diff --git a/src/modules/report/service/report.service.ts b/src/modules/report/service/report.service.ts index 9e8b2a6..6d67604 100644 --- a/src/modules/report/service/report.service.ts +++ b/src/modules/report/service/report.service.ts @@ -68,8 +68,7 @@ class ReportServiceClass extends Controller { group: ["departmentId", "department.id"], }); - - console.log(data) + console.log(data); return data; } @@ -297,7 +296,15 @@ class ReportServiceClass extends Controller { attributes: [ "assignedTo", [fn("COUNT", col("Ticket.id")), "total"], - [fn("SUM", col("status = 'resolved'")), "resolved"], + [ + fn( + "SUM", + literal( + `CASE WHEN "Ticket"."status" = 'resolved' THEN 1 ELSE 0 END`, + ), + ), + "resolved", + ], ], include: [ { diff --git a/src/modules/ticket/services/ticket.service.ts b/src/modules/ticket/services/ticket.service.ts index 6fa82c2..154848d 100644 --- a/src/modules/ticket/services/ticket.service.ts +++ b/src/modules/ticket/services/ticket.service.ts @@ -107,7 +107,6 @@ class TicketServiceClass extends Controller { report.loadFile(reportPath); // ۲. فچ کردن داده‌ها از دیتابیس (مشابه قبل) - const offset = (page - 1) * limit; const whereClause: any = {}; if (departmentId) whereClause.departmentId = departmentId; if (priority) whereClause.priority = priority; @@ -124,7 +123,7 @@ class TicketServiceClass extends Controller { } } - const { count, rows } = await Ticket.findAndCountAll({ + const rows = await Ticket.findAll({ where: whereClause, include: [ { model: Department, as: "department", attributes: ["displayName"] }, diff --git a/src/seeders/department.seed.ts b/src/seeders/department.seed.ts index 47ba244..479f3f3 100644 --- a/src/seeders/department.seed.ts +++ b/src/seeders/department.seed.ts @@ -60,6 +60,8 @@ export async function seedDepartments() { { slug: "human_resources", displayName: "مدیریت منابع انسانی" }, { slug: "treatment_management", displayName: "مدیریت درمان" }, { slug: "finance_management", displayName: "مدیریت مالی" }, + { slug: "tose-omran", displayName: "توسعه عمران" }, + { slug: "hounam", displayName: "هونام" }, ]; for (const dep of departments) { diff --git a/src/seeders/permission.seed.ts b/src/seeders/permission.seed.ts new file mode 100644 index 0000000..747db12 --- /dev/null +++ b/src/seeders/permission.seed.ts @@ -0,0 +1,139 @@ +import Permission from "../models/Permission"; + +export async function seedPermissions() { + const permissions = [ + "filter.view", + /* ================================ + ✅ TICKETS + ================================= */ + "ticket.create", + "ticket.view", + "ticket.view_all", + "ticket.update", + "ticket.delete", + "ticket.assign", + "ticket.change_status", + "ticket.close", + "ticket.reopen", + "ticket.comment", + "ticket.internal_note", + "ticket.upload_file", + "ticket.merge", + "ticket.export", + + /* ================================ + ✅ USERS + ================================= */ + "user.view", + "user.view_all", + "user.create", + "user.update", + "user.delete", + "user.activate", + "user.deactivate", + "user.assign_role", + + /* ================================ + ✅ ROLES + ================================= */ + "role.view", + "role.create", + "role.update", + "role.delete", + + /* ================================ + ✅ PERMISSIONS + ================================= */ + "permission.view", + "permission.assign_to_role", + "permission.assign_to_user", + + /* ================================ + ✅ DEPARTMENTS + ================================= */ + "department.view", + "department.create", + "department.update", + "department.delete", + + /* ================================ + ✅ REPORTS - SYSTEM LEVEL + ================================= */ + "report.system.view", + "report.system.export", + + "report.department.view", + "report.department.export", + + "report.operator.view", + "report.operator.export", + + "report.sla.view", + "report.sla.export", + + "report.kpi.view", + "report.kpi.export", + + /* ================================ + ✅ DASHBOARD + ================================= */ + "dashboard.view", + "dashboard.view_advanced", + + /* ================================ + ✅ SLA MANAGEMENT + ================================= */ + "sla.view", + "sla.create", + "sla.update", + "sla.delete", + + /* ================================ + ✅ REQUEST TYPES + ================================= */ + "request_type.view", + "request_type.create", + "request_type.update", + "request_type.delete", + + /* ================================ + ✅ TAGS + ================================= */ + "tag.view", + "tag.create", + "tag.update", + "tag.delete", + + /* ================================ + ✅ SETTINGS + ================================= */ + "settings.view", + "settings.update", + + /* ================================ + ✅ LOGS + ================================= */ + "log.view", + "audit.view", + + /* ================================ + ✅ OPERATOR / PERFORMANCE + ================================= */ + "operator.view", + "operator.performance.view", + "operator.performance.export", + ]; + + let createdCount = 0; + + for (const name of permissions) { + const [perm, created] = await Permission.findOrCreate({ + where: { name }, + defaults: { name }, + }); + + if (created) createdCount++; + } + + console.log(`✅ Permissions Seeded. New created: ${createdCount}`); +} diff --git a/src/seeders/rolePermission.ts b/src/seeders/rolePermission.ts new file mode 100644 index 0000000..0e9c9b1 --- /dev/null +++ b/src/seeders/rolePermission.ts @@ -0,0 +1,122 @@ +import Role from "../models/Role"; +import Permission from "../models/Permission"; +import RolePermission from "../models/RolePermission"; + +export async function seedRolePermissions() { + const roles = await Role.findAll(); + const permissions = await Permission.findAll(); + + const roleMap: any = {}; + roles.forEach((r: any) => (roleMap[r.name] = r)); + + const permissionMap: any = {}; + permissions.forEach((p: any) => (permissionMap[p.name] = p)); + + const assignPermissions = async ( + roleName: string, + permissionNames: string[], + ) => { + const role = roleMap[roleName]; + if (!role) return; + + for (const permName of permissionNames) { + const permission = permissionMap[permName]; + if (!permission) continue; + + await RolePermission.findOrCreate({ + where: { + roleId: role.id, + permissionId: permission.id, + }, + defaults: { + roleId: role.id, + permissionId: permission.id, + }, + }); + } + }; + + /* ========================================= + ✅ MANAGER (دسترسی بالا) + ========================================== */ + await assignPermissions("manager", [ + // tickets + "ticket.view_all", + "ticket.assign", + "ticket.change_status", + "ticket.export", + + // users + "user.view_all", + "user.create", + "user.update", + + // reports + "report.system.view", + "report.department.view", + "report.operator.view", + "report.sla.view", + "report.kpi.view", + + "report.system.export", + "report.department.export", + + // dashboard + "dashboard.view_advanced", + + // operator + "operator.performance.view", + ]); + + /* ========================================= + ✅ SUPERVISOR + ========================================== */ + await assignPermissions("supervisor", [ + "ticket.view_all", + "ticket.assign", + "ticket.change_status", + "ticket.comment", + + "report.department.view", + "report.operator.view", + + "dashboard.view", + ]); + + /* ========================================= + ✅ SENIOR EXPERT + ========================================== */ + await assignPermissions("senior_expert", [ + "ticket.view", + "ticket.change_status", + "ticket.comment", + "ticket.internal_note", + + "dashboard.view", + ]); + + /* ========================================= + ✅ EXPERT + ========================================== */ + await assignPermissions("expert", [ + "ticket.view", + "ticket.change_status", + "ticket.comment", + ]); + + /* ========================================= + ✅ TECHNICIAN + ========================================== */ + await assignPermissions("technician", ["ticket.view", "ticket.comment"]); + + /* ========================================= + ✅ NORMAL USER + ========================================== */ + await assignPermissions("user", [ + "ticket.create", + "ticket.view", + "ticket.comment", + ]); + + console.log("✅ RolePermissions seeded successfully"); +} diff --git a/src/seeders/user.seed.ts b/src/seeders/user.seed.ts index ac58fb4..979188f 100644 --- a/src/seeders/user.seed.ts +++ b/src/seeders/user.seed.ts @@ -22,8 +22,8 @@ export async function seedUsers() { }, { fullname: "خورشیدکلاه", - nationalCode: "0010000004", - mobile: "09120000004", + nationalCode: "2130560415", + mobile: "09385745269", roleId: "48cb63ff-398f-4a9a-8cec-43540c8260b3", }, ]; diff --git a/src/server.ts b/src/server.ts index e209b60..1ed5c0a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,6 +10,8 @@ import dotenv from "dotenv"; import { seedDepartments } from "./seeders/department.seed"; import { seedUsers } from "./seeders/user.seed"; import { seedRoles } from "./seeders/role.seed"; +import { seedPermissions } from "./seeders/permission.seed"; +import { seedRolePermissions } from "./seeders/rolePermission"; const express = require("express") as typeof import("express"); dotenv.config(); @@ -28,7 +30,7 @@ export default class ServerApplication { this.#APP.use(secureApp); this.#APP.use(express.json()); this.#APP.use(express.urlencoded({ extended: true })); - + this.#APP.set("json spaces", 2); this.#APP.use( "/media/images", @@ -43,9 +45,9 @@ export default class ServerApplication { await initDB(); // await seedDepartments(); // await seedRoles(); - + // await seedPermissions(); // await seedUsers(); - + // await seedRolePermissions(); this.#APP.listen(this.#PORT, () => { console.log( `Server Running on PORT ${this.#PORT} url : ${"http://localhost:"}${