From fdda306e85fe825bff1f1c409aa20b28abf6e3d2 Mon Sep 17 00:00:00 2001 From: webserver-lab Date: Sat, 13 Jun 2026 14:01:25 +0330 Subject: [PATCH] update --- src/core/router/main.router.ts | 2 + src/models/Role.ts | 170 +++++++++--------- .../permission/permission.controller.ts | 75 ++++++++ src/modules/permission/permission.routes.ts | 19 ++ src/modules/permission/permission.service.ts | 81 +++++++++ src/modules/roles/role.controller.ts | 16 +- src/modules/roles/role.routes.ts | 3 +- src/modules/roles/role.service.ts | 91 +++++++++- 8 files changed, 367 insertions(+), 90 deletions(-) create mode 100644 src/modules/permission/permission.controller.ts create mode 100644 src/modules/permission/permission.routes.ts create mode 100644 src/modules/permission/permission.service.ts diff --git a/src/core/router/main.router.ts b/src/core/router/main.router.ts index 709eb5e..9270b49 100644 --- a/src/core/router/main.router.ts +++ b/src/core/router/main.router.ts @@ -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; diff --git a/src/models/Role.ts b/src/models/Role.ts index 5ae4829..606892d 100644 --- a/src/models/Role.ts +++ b/src/models/Role.ts @@ -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 {} - -class Role - extends Model - implements RoleAttributes -{ - public id!: string; - public name!: string; - public description?: string; - - public readonly createdAt!: Date; - public readonly updatedAt!: Date; - - // Users relation - public getUsers!: HasManyGetAssociationsMixin; - - // Permissions relation - public getPermissions!: BelongsToManyGetAssociationsMixin; - public setPermissions!: BelongsToManySetAssociationsMixin; - public addPermissions!: BelongsToManyAddAssociationsMixin; - public removePermissions!: BelongsToManyRemoveAssociationsMixin; - - 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 {} - Role.belongsToMany(models.Permission, { - through: models.RolePermission, - foreignKey: "roleId", - otherKey: "permissionId", - as: "permissions", - }); + class Role + extends Model + implements RoleAttributes + { + public id!: string; + public name!: string; + public description?: string; + + public readonly createdAt!: Date; + public readonly updatedAt!: Date; + + // Users relation + public getUsers!: HasManyGetAssociationsMixin; + + // Permissions relation + public getPermissions!: BelongsToManyGetAssociationsMixin; + public setPermissions!: BelongsToManySetAssociationsMixin; + public addPermissions!: BelongsToManyAddAssociationsMixin; + public removePermissions!: BelongsToManyRemoveAssociationsMixin; + + 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; diff --git a/src/modules/permission/permission.controller.ts b/src/modules/permission/permission.controller.ts new file mode 100644 index 0000000..c80a4c9 --- /dev/null +++ b/src/modules/permission/permission.controller.ts @@ -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; diff --git a/src/modules/permission/permission.routes.ts b/src/modules/permission/permission.routes.ts new file mode 100644 index 0000000..22f777c --- /dev/null +++ b/src/modules/permission/permission.routes.ts @@ -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; diff --git a/src/modules/permission/permission.service.ts b/src/modules/permission/permission.service.ts new file mode 100644 index 0000000..d14a7df --- /dev/null +++ b/src/modules/permission/permission.service.ts @@ -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; diff --git a/src/modules/roles/role.controller.ts b/src/modules/roles/role.controller.ts index 106641c..df0ea2b 100644 --- a/src/modules/roles/role.controller.ts +++ b/src/modules/roles/role.controller.ts @@ -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); diff --git a/src/modules/roles/role.routes.ts b/src/modules/roles/role.routes.ts index 0fd6121..48ab79e 100644 --- a/src/modules/roles/role.routes.ts +++ b/src/modules/roles/role.routes.ts @@ -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); diff --git a/src/modules/roles/role.service.ts b/src/modules/roles/role.service.ts index f7c1a78..c4cbfb4 100644 --- a/src/modules/roles/role.service.ts +++ b/src/modules/roles/role.service.ts @@ -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: [