update
This commit is contained in:
46
src/core/middleware/check-permission.ts
Normal file
46
src/core/middleware/check-permission.ts
Normal 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",
|
||||
});
|
||||
};
|
||||
}
|
||||
86
src/models/UserPermission.ts
Normal file
86
src/models/UserPermission.ts
Normal 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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -91,6 +91,7 @@ class ReportControllerClass extends Controller {
|
||||
data: { system, operators, departments },
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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"] },
|
||||
|
||||
@@ -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) {
|
||||
|
||||
139
src/seeders/permission.seed.ts
Normal file
139
src/seeders/permission.seed.ts
Normal 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}`);
|
||||
}
|
||||
122
src/seeders/rolePermission.ts
Normal file
122
src/seeders/rolePermission.ts
Normal 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");
|
||||
}
|
||||
@@ -22,8 +22,8 @@ export async function seedUsers() {
|
||||
},
|
||||
{
|
||||
fullname: "خورشیدکلاه",
|
||||
nationalCode: "0010000004",
|
||||
mobile: "09120000004",
|
||||
nationalCode: "2130560415",
|
||||
mobile: "09385745269",
|
||||
roleId: "48cb63ff-398f-4a9a-8cec-43540c8260b3",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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:"}${
|
||||
|
||||
Reference in New Issue
Block a user