This commit is contained in:
2026-06-07 14:21:02 +03:30
parent db32d35fe7
commit 7d8aeea90f
13 changed files with 486 additions and 13 deletions

View File

@@ -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",
});
};
}

View File

@@ -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<UserPermissionAttributes, "id"> {}
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;

View File

@@ -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,

View File

@@ -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);

View File

@@ -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;

View File

@@ -91,6 +91,7 @@ class ReportControllerClass extends Controller {
data: { system, operators, departments },
});
} catch (error) {
console.log(error)
next(error);
}
}

View File

@@ -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: [
{

View File

@@ -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"] },

View File

@@ -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) {

View File

@@ -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}`);
}

View File

@@ -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");
}

View File

@@ -22,8 +22,8 @@ export async function seedUsers() {
},
{
fullname: "خورشیدکلاه",
nationalCode: "0010000004",
mobile: "09120000004",
nationalCode: "2130560415",
mobile: "09385745269",
roleId: "48cb63ff-398f-4a9a-8cec-43540c8260b3",
},
];

View File

@@ -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:"}${