This commit is contained in:
2026-06-13 14:01:25 +03:30
parent 5f82f62763
commit fdda306e85
8 changed files with 367 additions and 90 deletions

View File

@@ -6,6 +6,7 @@ import userRouter from "../../modules/user/routes/user.router";
import reportRouter from "../../modules/report/routes/report.router";
import authMiddleware from "../middleware/auth.middleware";
import RoleRouter from "../../modules/roles/role.routes";
import PermissionRouter from "../../modules/permission/permission.routes";
const mainRouter = router.Router();
@@ -15,5 +16,6 @@ mainRouter.use("/ticket", authMiddleware, ticketRouter);
mainRouter.use("/user", authMiddleware, userRouter);
mainRouter.use("/report", authMiddleware, reportRouter);
mainRouter.use("/role", authMiddleware, RoleRouter);
mainRouter.use("/permission", authMiddleware, PermissionRouter);
export default mainRouter;

View File

@@ -1,92 +1,92 @@
import {
Model,
DataTypes,
Optional,
Sequelize,
BelongsToManySetAssociationsMixin,
BelongsToManyAddAssociationsMixin,
BelongsToManyRemoveAssociationsMixin,
BelongsToManyGetAssociationsMixin,
HasManyGetAssociationsMixin
} from "sequelize";
import User from "./User";
import Permission from "./Permission";
import {
Model,
DataTypes,
Optional,
Sequelize,
BelongsToManySetAssociationsMixin,
BelongsToManyAddAssociationsMixin,
BelongsToManyRemoveAssociationsMixin,
BelongsToManyGetAssociationsMixin,
HasManyGetAssociationsMixin
} from "sequelize";
import User from "./User";
import Permission from "./Permission";
interface RoleAttributes {
id: string;
name: string;
description?: string;
createdAt?: Date;
updatedAt?: Date;
}
interface RoleCreationAttributes
extends Optional<RoleAttributes, "id" | "description"> {}
class Role
extends Model<RoleAttributes, RoleCreationAttributes>
implements RoleAttributes
{
public id!: string;
public name!: string;
public description?: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
// Users relation
public getUsers!: HasManyGetAssociationsMixin<User>;
// Permissions relation
public getPermissions!: BelongsToManyGetAssociationsMixin<Permission>;
public setPermissions!: BelongsToManySetAssociationsMixin<Permission, string>;
public addPermissions!: BelongsToManyAddAssociationsMixin<Permission, string>;
public removePermissions!: BelongsToManyRemoveAssociationsMixin<Permission, string>;
static initModel(sequelize: Sequelize): typeof Role {
Role.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
description: {
type: DataTypes.STRING,
},
},
{
sequelize,
modelName: "Role",
tableName: "roles",
timestamps: true,
}
);
return Role;
interface RoleAttributes {
id: string;
name: string;
description?: string;
createdAt?: Date;
updatedAt?: Date;
}
static associate(models: any) {
Role.hasMany(models.User, {
foreignKey: "roleId",
as: "users",
});
interface RoleCreationAttributes
extends Optional<RoleAttributes, "id" | "description"> {}
Role.belongsToMany(models.Permission, {
through: models.RolePermission,
foreignKey: "roleId",
otherKey: "permissionId",
as: "permissions",
});
class Role
extends Model<RoleAttributes, RoleCreationAttributes>
implements RoleAttributes
{
public id!: string;
public name!: string;
public description?: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
// Users relation
public getUsers!: HasManyGetAssociationsMixin<User>;
// Permissions relation
public getPermissions!: BelongsToManyGetAssociationsMixin<Permission>;
public setPermissions!: BelongsToManySetAssociationsMixin<Permission, string>;
public addPermissions!: BelongsToManyAddAssociationsMixin<Permission, string>;
public removePermissions!: BelongsToManyRemoveAssociationsMixin<Permission, string>;
static initModel(sequelize: Sequelize): typeof Role {
Role.init(
{
id: {
type: DataTypes.UUID,
primaryKey: true,
defaultValue: DataTypes.UUIDV4,
},
name: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
description: {
type: DataTypes.STRING,
},
},
{
sequelize,
modelName: "Role",
tableName: "roles",
timestamps: true,
}
);
return Role;
}
static associate(models: any) {
Role.hasMany(models.User, {
foreignKey: "roleId",
as: "users",
});
Role.belongsToMany(models.Permission, {
through: models.RolePermission,
foreignKey: "roleId",
otherKey: "permissionId",
as: "permissions",
});
}
}
}
export default Role;
export default Role;

View File

@@ -0,0 +1,75 @@
import { NextFunction, Request, Response } from "express";
import { Controller } from "../../core/controller/main.controller";
import PermissionService from "./permission.service";
import { ServerResponse } from "../../core/types";
class PermissionControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = PermissionService;
}
// ایجاد دسترسی جدید
create = async (req:any, res: ServerResponse, next: NextFunction) => {
try {
const result = await this.#service.create(req.body);
return res.status(201).json({status:201,data:result});
} catch (error) {
next(error);
}
};
// دریافت لیست دسترسی‌ها (با فیلتر و صفحه‌بندی)
getAll = async (req:any, res: ServerResponse, next: NextFunction) => {
try {
const result = await this.#service.getAll(req.query);
return res.status(200).json({status:200,data:result});
} catch (error) {
next(error);
}
};
// دریافت یک دسترسی خاص
getById = async (req:any, res: ServerResponse, next: NextFunction) => {
try {
const { id } = req.params;
const result = await this.#service.getById(id);
return res.status(200).json({status:200,data:result});
} catch (error) {
next(error);
}
};
// ویرایش دسترسی
update = async (req:any, res: ServerResponse, next: NextFunction) => {
try {
const { id } = req.params;
const result = await this.#service.update(id, req.body);
return res.status(200).json({status:200,data:result});
} catch (error) {
next(error);
}
};
getList = async(req:any,res:ServerResponse,next:NextFunction) => {
try {
const result = await this.#service.getList();
return res.status(200).json({status:200,data:result});
} catch (error) {
next(error);
}
}
// حذف دسترسی
delete = async (req:any, res: ServerResponse, next: NextFunction) => {
try {
const { id } = req.params;
const result = await this.#service.delete(id);
return res.status(200).json({status:200,data:result});
} catch (error) {
next(error);
}
};
}
const PermissionController = new PermissionControllerClass();
export default PermissionController;

View File

@@ -0,0 +1,19 @@
import { Router } from "express";
import PermissionController from "./permission.controller";
const PermissionRouter = Router();
PermissionRouter.post("/create", PermissionController.create);
PermissionRouter.get("/all", PermissionController.getAll);
PermissionRouter.get("/list", PermissionController.getList);
PermissionRouter.get("/get/:id", PermissionController.getById);
PermissionRouter.put("/update/:id", PermissionController.update);
PermissionRouter.delete("/remove/:id", PermissionController.delete);
export default PermissionRouter;

View File

@@ -0,0 +1,81 @@
import { Op } from "sequelize";
import createHttpError from "http-errors";
import Permission from "../../models/Permission";
class PermissionServiceClass {
async create(data: { name: string }) {
const existing = await Permission.findOne({ where: { name: data.name } });
if (existing) {
throw createHttpError.BadRequest("این دسترسی قبلاً ثبت شده است.");
}
return await Permission.create(data);
}
async getAll(query: any) {
const { page = 1, limit = 10, search, startDate, endDate } = query;
const offset = (page - 1) * limit;
const whereClause: any = {};
if (search) {
whereClause.name = { [Op.iLike]: `%${search}%` };
}
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 Permission.findAndCountAll({
where: whereClause,
order: [["createdAt", "DESC"]],
limit: parseInt(limit),
offset: parseInt(offset.toString()),
distinct: true,
});
return {
data: rows,
totalItems: count,
totalPages: Math.ceil(count / limit),
currentPage: parseInt(page),
};
}
async getList() {
const data = await Permission.findAll({});
return data;
}
async getById(id: string) {
const permission = await Permission.findByPk(id);
if (!permission) {
throw createHttpError.NotFound("دسترسی مورد نظر یافت نشد.");
}
return permission;
}
async update(id: string, data: { name: string }) {
const permission = await this.getById(id);
if (data.name && data.name !== permission.name) {
const existing = await Permission.findOne({ where: { name: data.name } });
if (existing) throw createHttpError.BadRequest("نام دسترسی تکراری است.");
}
return await permission.update(data);
}
async delete(id: string) {
const permission = await this.getById(id);
await permission.destroy();
return { message: "دسترسی با موفقیت حذف شد." };
}
}
const PermissionService = new PermissionServiceClass();
export default PermissionService;

View File

@@ -23,9 +23,9 @@ class RoleControllerClass extends Controller {
}
}
async findAll(_req: any, res: ServerResponse, next: NextFunction) {
async findAll(req: any, res: ServerResponse, next: NextFunction) {
try {
const roles = await this.#serivce.findAll();
const roles = await this.#serivce.findAll(req);
return res.status(200).json({
status: 200,
@@ -36,6 +36,18 @@ class RoleControllerClass extends Controller {
}
}
async findList(req: any, res: ServerResponse, next: NextFunction) {
try {
const roles = await this.#serivce.findList();
return res.status(200).json({
status: 200,
data: roles,
});
} catch (error) {
next(error);
}
}
findById = async (req: any, res: ServerResponse, next: NextFunction) => {
try {
const role = await this.#serivce.findById(req.params.id);

View File

@@ -3,7 +3,8 @@ import RoleController from "./role.controller";
const RoleRouter = Router();
RoleRouter.post("/create", RoleController.create);
RoleRouter.get("/list", RoleController.findAll);
RoleRouter.get("/list", RoleController.findList);
RoleRouter.get("/all", RoleController.findAll);
RoleRouter.get("/:id", RoleController.findById);
RoleRouter.put("/update/:id", RoleController.update);
RoleRouter.delete("/remove/:id", RoleController.delete);

View File

@@ -1,9 +1,12 @@
import createHttpError from "http-errors";
import Permission from "../../models/Permission";
import Role from "../../models/Role";
import User from "../../models/User";
import { GlobalMessages } from "../../core/messages";
import { Op } from "sequelize";
class RoleServiceClass {
async create(data: { name: string; description?: string }) {
async create(data: { name: string; description?: string,permissions:string[] }) {
const existingRole = await Role.findOne({
where: { name: data.name },
});
@@ -12,15 +15,28 @@ class RoleServiceClass {
throw new Error("Role already exists");
}
// 1⃣ ساخت Role
const role = await Role.create({
name: data.name,
description: data.description,
});
// 2⃣ اگر permission ارسال شده بود
if (data.permissions && data.permissions.length) {
const permissions = await Permission.findAll({
where: {
id: data.permissions,
},
});
// 3⃣ اتصال permissions به role
await role.setPermissions(permissions);
}
return role;
}
async findAll() {
async findList() {
return await Role.findAll({
include: [
{
@@ -37,6 +53,77 @@ class RoleServiceClass {
});
}
async findAll(req: any) {
try {
const {
page = 1,
limit = 10,
name, // فیلتر نام نقش (اختیاری)
startDate,
endDate,
} = req.query;
// ۱. محاسبه offset برای صفحه‌بندی
const offset = (page - 1) * limit;
// ۲. ساخت آبجکت فیلترها به صورت داینامیک
const whereClause: any = {};
if (name) {
// استفاده از Op.iLike برای جستجوی متنی (Case-Insensitive) در دیتابیس‌های PostgreSQL
// اگر از MySQL استفاده می‌کنید، از Op.like استفاده کنید
whereClause.name = { [Op.iLike]: `%${name}%` };
}
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; // تاریخ پایان (کوچکتر مساوی)
}
}
// ۳. استفاده از findAndCountAll برای دریافت هم‌زمان دیتا و تعداد کل رکوردها
const { count, rows } = await Role.findAndCountAll({
where: whereClause,
distinct: true, // اضافه کردن این خط مشکل شمارش تکراری را حل می‌کند
col: "id", // به صورت اختیاری مشخص کنید روی کدام ستون distinct انجام شود
include: [
{
model: Permission,
as: "permissions",
through: { attributes: [] },
},
{
model: User,
as: "users",
},
],
order: [["createdAt", "DESC"]],
limit: parseInt(limit),
offset: parseInt(offset.toString()),
});
// ۴. خروجی استاندارد صفحه‌بندی شده
return {
data: rows,
totalItems: count,
totalPages: Math.ceil(count / limit),
currentPage: parseInt(page),
};
} catch (error) {
// مدیریت خطا مشابه متد Ticket
throw new createHttpError.InternalServerError(
GlobalMessages.errors.server,
);
}
}
async findById(id: string) {
const role = await Role.findByPk(id, {
include: [