diff --git a/src/core/router/main.router.ts b/src/core/router/main.router.ts index 662df49..709eb5e 100644 --- a/src/core/router/main.router.ts +++ b/src/core/router/main.router.ts @@ -5,6 +5,7 @@ import ticketRouter from "../../modules/ticket/routes/ticket.routes"; 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"; const mainRouter = router.Router(); @@ -13,5 +14,6 @@ mainRouter.use("/department", authMiddleware, departmentRouter); mainRouter.use("/ticket", authMiddleware, ticketRouter); mainRouter.use("/user", authMiddleware, userRouter); mainRouter.use("/report", authMiddleware, reportRouter); +mainRouter.use("/role", authMiddleware, RoleRouter); export default mainRouter; diff --git a/src/models/Role.ts b/src/models/Role.ts index 8ba03d9..5ae4829 100644 --- a/src/models/Role.ts +++ b/src/models/Role.ts @@ -2,8 +2,16 @@ import { Model, DataTypes, Optional, - Sequelize + Sequelize, + BelongsToManySetAssociationsMixin, + BelongsToManyAddAssociationsMixin, + BelongsToManyRemoveAssociationsMixin, + BelongsToManyGetAssociationsMixin, + HasManyGetAssociationsMixin } from "sequelize"; +import User from "./User"; +import Permission from "./Permission"; + interface RoleAttributes { id: string; @@ -27,6 +35,15 @@ class Role 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( { @@ -58,21 +75,17 @@ class Role } static associate(models: any) { - - // Role -> Users Role.hasMany(models.User, { foreignKey: "roleId", as: "users", }); - // Role <-> Permission Role.belongsToMany(models.Permission, { through: models.RolePermission, foreignKey: "roleId", otherKey: "permissionId", as: "permissions", }); - } } diff --git a/src/modules/roles/role.controller.ts b/src/modules/roles/role.controller.ts new file mode 100644 index 0000000..106641c --- /dev/null +++ b/src/modules/roles/role.controller.ts @@ -0,0 +1,151 @@ +import { NextFunction } from "express"; +import { Controller } from "../../core/controller/main.controller"; +import RoleService from "./role.service"; +import { ServerResponse } from "../../core/types"; + +class RoleControllerClass extends Controller { + #serivce; + constructor() { + super(); + this.#serivce = RoleService; + } + async create(req: any, res: ServerResponse, next: NextFunction) { + try { + const role = await this.#serivce.create(req.body); + + return res.status(201).json({ + status: 200, + message: "Role created successfully", + data: role, + }); + } catch (error) { + next(error); + } + } + + async findAll(_req: any, res: ServerResponse, next: NextFunction) { + try { + const roles = await this.#serivce.findAll(); + + 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); + + return res.status(200).json({ + status: 200, + data: role, + }); + } catch (error) { + next(error); + } + }; + + update = async (req: any, res: ServerResponse, next: NextFunction) => { + try { + const updatedRole = await this.#serivce.update(req.params.id, req.body); + + return res.status(200).json({ + status: 200, + message: "Role updated statusfully", + data: updatedRole, + }); + } catch (error) { + next(error); + } + }; + + delete = async (req: any, res: ServerResponse, next: NextFunction) => { + try { + const result = await this.#serivce.delete(req.params.id); + + return res.status(200).json({ + status: 200, + data: { ...result }, + }); + } catch (error) { + next(error); + } + }; + + assignPermissions = async ( + req: any, + res: ServerResponse, + next: NextFunction, + ) => { + try { + const { permissionIds } = req.body; + + const role = await this.#serivce.assignPermissions( + req.params.id, + permissionIds, + ); + + return res.status(200).json({ + status: 200, + message: "Permissions assigned statusfully", + data: role, + }); + } catch (error) { + next(error); + } + }; + + addPermissions = async ( + req: any, + res: ServerResponse, + next: NextFunction, + ) => { + try { + const { permissionIds } = req.body; + + const role = await this.#serivce.addPermissions( + req.params.id, + permissionIds, + ); + + return res.status(200).json({ + status: 200, + message: "Permissions added statusfully", + data: role, + }); + } catch (error) { + next(error); + } + }; + + removePermissions = async ( + req: any, + res: ServerResponse, + next: NextFunction, + ) => { + try { + const { permissionIds } = req.body; + + const role = await this.#serivce.removePermissions( + req.params.id, + permissionIds, + ); + + return res.status(200).json({ + status: 200, + message: "Permissions removed statusfully", + data: role, + }); + } catch (error) { + next(error); + } + }; +} + +const RoleController = new RoleControllerClass(); + +export default RoleController; diff --git a/src/modules/roles/role.routes.ts b/src/modules/roles/role.routes.ts new file mode 100644 index 0000000..0fd6121 --- /dev/null +++ b/src/modules/roles/role.routes.ts @@ -0,0 +1,15 @@ +import { Router } from "express"; +import RoleController from "./role.controller"; + +const RoleRouter = Router(); +RoleRouter.post("/create", RoleController.create); +RoleRouter.get("/list", RoleController.findAll); +RoleRouter.get("/:id", RoleController.findById); +RoleRouter.put("/update/:id", RoleController.update); +RoleRouter.delete("/remove/:id", RoleController.delete); + +RoleRouter.post("/:id/permissions/assign", RoleController.assignPermissions); +RoleRouter.post("/:id/permissions/add", RoleController.addPermissions); +RoleRouter.post("/:id/permissions/remove", RoleController.removePermissions); + +export default RoleRouter; diff --git a/src/modules/roles/role.service.ts b/src/modules/roles/role.service.ts new file mode 100644 index 0000000..f7c1a78 --- /dev/null +++ b/src/modules/roles/role.service.ts @@ -0,0 +1,168 @@ +import Permission from "../../models/Permission"; +import Role from "../../models/Role"; +import User from "../../models/User"; + +class RoleServiceClass { + async create(data: { name: string; description?: string }) { + const existingRole = await Role.findOne({ + where: { name: data.name }, + }); + + if (existingRole) { + throw new Error("Role already exists"); + } + + const role = await Role.create({ + name: data.name, + description: data.description, + }); + + return role; + } + + async findAll() { + return await Role.findAll({ + include: [ + { + model: Permission, + as: "permissions", + through: { attributes: [] }, + }, + { + model: User, + as: "users", + }, + ], + order: [["createdAt", "DESC"]], + }); + } + + async findById(id: string) { + const role = await Role.findByPk(id, { + include: [ + { + model: Permission, + as: "permissions", + through: { attributes: [] }, + }, + { + model: User, + as: "users", + }, + ], + }); + + if (!role) { + throw new Error("Role not found"); + } + + return role; + } + + async update(id: string, data: { name?: string; description?: string }) { + const role = await Role.findByPk(id); + + if (!role) { + throw new Error("Role not found"); + } + + if (data.name) { + const existingRole = await Role.findOne({ + where: { name: data.name }, + }); + + if (existingRole && existingRole.id !== id) { + throw new Error("Another role with this name already exists"); + } + } + + await role.update({ + name: data.name ?? role.name, + description: data.description ?? role.description, + }); + + return role; + } + + async delete(id: string) { + const role = await Role.findByPk(id); + + if (!role) { + throw new Error("Role not found"); + } + + await role.destroy(); + + return { message: "Role deleted successfully" }; + } + + async assignPermissions(roleId: string, permissionIds: string[]) { + const role = await Role.findByPk(roleId); + + if (!role) { + throw new Error("Role not found"); + } + + await role.setPermissions(permissionIds); + + const updatedRole = await Role.findByPk(roleId, { + include: [ + { + model: Permission, + as: "permissions", + through: { attributes: [] }, + }, + ], + }); + + return updatedRole; + } + + async addPermissions(roleId: string, permissionIds: string[]) { + const role = await Role.findByPk(roleId); + + if (!role) { + throw new Error("Role not found"); + } + + await role.addPermissions(permissionIds); + + const updatedRole = await Role.findByPk(roleId, { + include: [ + { + model: Permission, + as: "permissions", + through: { attributes: [] }, + }, + ], + }); + + return updatedRole; + } + + async removePermissions(roleId: string, permissionIds: string[]) { + const role = await Role.findByPk(roleId); + + if (!role) { + throw new Error("Role not found"); + } + + await role.removePermissions(permissionIds); + + const updatedRole = await Role.findByPk(roleId, { + include: [ + { + model: Permission, + as: "permissions", + through: { attributes: [] }, + }, + ], + }); + + return updatedRole; + } +} + +const RoleService = new RoleServiceClass(); + +export default RoleService;