From ff08da133009b07f6b2decc9a7fbb8c6db5f9d1a Mon Sep 17 00:00:00 2001 From: realrafi Date: Wed, 8 Jul 2026 10:27:01 +0330 Subject: [PATCH] refactor: changed project functions logic of permissions --- ...772-TMaddingSomeNewPermissionsToTheEnum.ts | 20 ++++++++ src/common/enums/message.enum.ts | 5 +- .../controllers/project.controller.ts | 46 +++++------------ .../task-manager/providers/project.service.ts | 50 ++++++++----------- .../providers/workspace.service.ts | 2 +- src/modules/users/enums/permission.enum.ts | 4 +- 6 files changed, 61 insertions(+), 66 deletions(-) create mode 100644 database/migrations/1783428145772-TMaddingSomeNewPermissionsToTheEnum.ts diff --git a/database/migrations/1783428145772-TMaddingSomeNewPermissionsToTheEnum.ts b/database/migrations/1783428145772-TMaddingSomeNewPermissionsToTheEnum.ts new file mode 100644 index 0000000..f0ee3a6 --- /dev/null +++ b/database/migrations/1783428145772-TMaddingSomeNewPermissionsToTheEnum.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class TMaddingSomeNewPermissionsToTheEnum1783428145772 implements MigrationInterface { + name = 'TMaddingSomeNewPermissionsToTheEnum1783428145772' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TYPE "public"."permission_name_enum" RENAME TO "permission_name_enum_old"`); + await queryRunner.query(`CREATE TYPE "public"."permission_name_enum" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'view_all_workspaces', 'project', 'view_all_projects', 'task', 'task_phase', 'remark')`); + await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum" USING "name"::"text"::"public"."permission_name_enum"`); + await queryRunner.query(`DROP TYPE "public"."permission_name_enum_old"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TYPE "public"."permission_name_enum_old" AS ENUM('services', 'customers', 'agents', 'developers', 'invoices', 'transactions', 'discounts', 'admins', 'tickets', 'criticisms', 'contacts_us', 'advertisements', 'announcements', 'blogs', 'learnings', 'logs', 'settings', 'bank_accounts', 'payments', 'manage_sso_clients', 'support_plan', 'dmenu', 'dkala', 'dpage', 'dmail', 'reseller', 'workspace', 'project', 'task_phase')`); + await queryRunner.query(`ALTER TABLE "permission" ALTER COLUMN "name" TYPE "public"."permission_name_enum_old" USING "name"::"text"::"public"."permission_name_enum_old"`); + await queryRunner.query(`DROP TYPE "public"."permission_name_enum"`); + await queryRunner.query(`ALTER TYPE "public"."permission_name_enum_old" RENAME TO "permission_name_enum"`); + } + +} diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index 3cd2337..a8cd15e 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -922,14 +922,15 @@ export const enum WorkSpaceMessage { USERS_DONT_BELONG_TO_THIS_WORKSPACE = "کاربران تعیین شده برای این فضای کاری نیستند!", WORKSPACE_SAME_NAME_EXISTS = "فضای کاری با این اسم از قبل وجود دارد!", WORKSPACE_ID_PARAM_NOT_EMPTY = "پارامتر شناسه فضای کار نباید خالی باشد!", - WORKSPACE_ID_UUID = "شناسه فضای کار باید uuid باشد" + WORKSPACE_ID_UUID = "شناسه فضای کار باید uuid باشد", + FORBIDDEN = "شما اجازه دسترسی به این فضای کار را ندارید و یا عضو آن نیستید!" } export const enum ProjectMessage { PROJECT_EXISTS = "پروژه مورد نظر شما از قبل وجود دارد!", PROJECT_NOT_FOUND = "پروژه مورد نظر شما وجود ندارد!", PROJECT_CONTAINS_TASK_PHASES = "پروژه شامل فاز تسک است. لطفا ابتدا فاز تسک های این پروژه را حذف کنید.", - NOT_MEMBER_OF_PROJECT = "شما عضو این پروژه نیستید!" + NOT_MEMBER_OF_PROJECT = "شما عضو این پروژه نیستید!", } export const enum TaskPhaseMessage { diff --git a/src/modules/task-manager/controllers/project.controller.ts b/src/modules/task-manager/controllers/project.controller.ts index 3da243d..d63207b 100644 --- a/src/modules/task-manager/controllers/project.controller.ts +++ b/src/modules/task-manager/controllers/project.controller.ts @@ -11,6 +11,7 @@ import { PermissionEnum } from "../../users/enums/permission.enum"; import { UserDec } from "../../../common/decorators/user.decorator"; import { AuthGuards } from "../../../common/decorators/auth-guard.decorator"; import { ParamDto } from "../../../common/DTO/param.dto"; +import { ITokenPayload } from "../../auth/interfaces/IToken-payload"; @AuthGuards() @AdminRoute() @@ -19,24 +20,6 @@ import { ParamDto } from "../../../common/DTO/param.dto"; export class ProjectController { constructor(private readonly projectService: ProjectService) {} - @Get() - @PermissionsDec(PermissionEnum.PROJECT) - @ApiOperation({ summary: "Get all projects, optionally filtered by workspace" }) - @ApiResponse({ status: 200, description: "Paginated list of projects" }) - findAll(@Query() pagination: PaginationDto): Promise<{ projects: TMProject[]; count: number; paginate: true }> { - return this.projectService.findAll(pagination); - } - - @Get(":id") - @PermissionsDec(PermissionEnum.PROJECT) - @ApiOperation({ summary: "Get a project by ID" }) - @ApiParam({ name: "id", description: "Project ID" }) - @ApiResponse({ status: 200, description: "Project found", type: TMProject }) - @ApiResponse({ status: 404, description: "Project not found" }) - findOne(@Param("id") id: string): Promise { - return this.projectService.findOneOrFail(id); - } - @Post() @PermissionsDec(PermissionEnum.PROJECT) @ApiOperation({ summary: "Create a new project" }) @@ -69,26 +52,23 @@ export class ProjectController { } @Get("user/workspace/:id") - @ApiOperation({ summary: "Get user projects!" }) + @ApiOperation({ summary: "Get user projects in a specific workspace!" }) @ApiResponse({ status: 200, description: "Got user projects successfully!" }) - @ApiResponse({status: 400, description: "Bad Request!"}) - findUserProjects( - @UserDec("id") userId: string, - @Param() paramDto : ParamDto, + @ApiResponse({ status: 400, description: "Bad Request!" }) + findUserProjectsByWorkspace( + @UserDec() user: ITokenPayload, + @Param() paramDto: ParamDto, @Query() pagination: PaginationDto, ): Promise<{ projects: TMProject[]; count: number; paginate: true }> { - return this.projectService.findUserProjects(userId, paramDto.id, pagination); + return this.projectService.findUserProjectsByWorkspace(user.id, user.permissions, paramDto.id, pagination); } @Get("project/:id") - @ApiOperation({summary: "Get project detail"}) - @ApiResponse({status: 200, description: "Got Project Detail Successfully!"}) - @ApiResponse({status: 400, description: "Bad request!"}) - @ApiResponse({status: 404, description: "The project was not found!"}) - getProjectDetail(@Param() paramDto: ParamDto, - @UserDec('id') userId: string -): Promise { - return this.projectService.getProjectDetail(paramDto.id, userId); + @ApiOperation({ summary: "Get project detail" }) + @ApiResponse({ status: 200, description: "Got Project Detail Successfully!" }) + @ApiResponse({ status: 400, description: "Bad request!" }) + @ApiResponse({ status: 404, description: "The project was not found!" }) + getProjectDetail(@Param() paramDto: ParamDto, @UserDec() user: ITokenPayload): Promise { + return this.projectService.getProjectDetail(paramDto.id, user.id, user.permissions); } - } diff --git a/src/modules/task-manager/providers/project.service.ts b/src/modules/task-manager/providers/project.service.ts index df8054e..384d6f6 100644 --- a/src/modules/task-manager/providers/project.service.ts +++ b/src/modules/task-manager/providers/project.service.ts @@ -9,6 +9,7 @@ import { PaginationDto } from "../../../common/DTO/pagination.dto"; import { User } from "../../users/entities/user.entity"; import { ProjectMessage, WorkSpaceMessage } from "../../../common/enums/message.enum"; import { WorkspaceService } from "./workspace.service"; +import { PermissionEnum } from "../../users/enums/permission.enum"; @Injectable() export class ProjectService { @@ -19,29 +20,6 @@ export class ProjectService { private readonly userRepository: Repository, ) {} - async findAll(pagination: PaginationDto): Promise<{ projects: TMProject[]; count: number; paginate: true }> { - const page = pagination.page ?? 1; - const limit = pagination.limit ?? 10; - const skip = (page - 1) * limit; - - const [projects, count] = await this.projectRepository.findAndCount({ - skip: skip, - take: limit, - relations: { - users: true, - }, - select: { - users: { - id: true, - firstName: true, - lastName: true, - }, - }, - }); - - return { projects, count, paginate: true }; - } - async findOneOrFail(id: string): Promise { const project = await this.projectRepository.findOne({ where: { id } }); if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND); @@ -124,21 +102,36 @@ export class ProjectService { return project; } - async findUserProjects( + async findUserProjectsByWorkspace( userId: string, + permissions: PermissionEnum[], wsId: string, pagination: PaginationDto, ): Promise<{ projects: TMProject[]; count: number; paginate: true }> { + const workspace = await this.workspaceService.findOneOrFail(wsId); + const page = pagination.page ?? 1; const limit = pagination.limit ?? 10; const skip = (page - 1) * limit; - const [projects, count] = await this.projectRepository.findUserProjects(userId, wsId, skip, limit); + const isPermissionIncluded = permissions.includes(PermissionEnum.VIEW_ALL_PROJECTS); + const isUserMemeberOfWorkspace = workspace.users.some((user) => user.id === userId); + + var res: [TMProject[], number]; + if (isUserMemeberOfWorkspace) { + res = await this.projectRepository.findUserProjects(userId, wsId, skip, limit); + } else if (isPermissionIncluded) { + res = await this.projectRepository.findAndCount({ where: { workspaceId: wsId } }); + } else { + throw new ForbiddenException(WorkSpaceMessage.FORBIDDEN); + } + + const [projects, count] = res; return { projects, count, paginate: true }; } - async getProjectDetail(projectId: string, userId: string): Promise { + async getProjectDetail(projectId: string, userId: string, permissions: PermissionEnum[]): Promise { const project = await this.projectRepository.findOne({ where: { id: projectId }, relations: { @@ -147,8 +140,9 @@ export class ProjectService { }); if (!project) throw new NotFoundException(ProjectMessage.PROJECT_NOT_FOUND); - const isUserMember = project.users.some(user => user.id === userId); - if(!isUserMember) throw new ForbiddenException(ProjectMessage.NOT_MEMBER_OF_PROJECT); + const isUserMember = project.users.some((user) => user.id === userId); + if (!isUserMember && !permissions.includes(PermissionEnum.VIEW_ALL_PROJECTS)) + throw new ForbiddenException(ProjectMessage.NOT_MEMBER_OF_PROJECT); const projectDetail = await this.projectRepository.getProjectDetail(projectId); diff --git a/src/modules/task-manager/providers/workspace.service.ts b/src/modules/task-manager/providers/workspace.service.ts index d983d02..931ecf7 100644 --- a/src/modules/task-manager/providers/workspace.service.ts +++ b/src/modules/task-manager/providers/workspace.service.ts @@ -70,7 +70,7 @@ export class WorkspaceService { } async findByUser(userId: string, permissions: PermissionEnum[]): Promise { - if(permissions.includes(PermissionEnum.WORKSPACE)) { + if(permissions.includes(PermissionEnum.VIEW_ALL_WORKSPACES)) { return await this.workspaceRepository.find(); } return await this.workspaceRepository.findByUser(userId); diff --git a/src/modules/users/enums/permission.enum.ts b/src/modules/users/enums/permission.enum.ts index 82043ae..7abceb7 100755 --- a/src/modules/users/enums/permission.enum.ts +++ b/src/modules/users/enums/permission.enum.ts @@ -25,9 +25,9 @@ export enum PermissionEnum { DPAGE = "dpage", DMAIL = 'dmail', RESELLER="reseller", - WORKSPACE="workspace", + WORKSPACE="workspace", // create, update, remove VIEW_ALL_WORKSPACES="view_all_workspaces", - PROJECT="project", + PROJECT="project", // create, update, remove VIEW_ALL_PROJECTS="view_all_projects", TASK="task", TASK_PHASE="task_phase",