refactor: changed project functions logic of permissions

This commit is contained in:
2026-07-08 10:27:01 +03:30
parent 29d647556c
commit ff08da1330
6 changed files with 61 additions and 66 deletions
@@ -0,0 +1,20 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class TMaddingSomeNewPermissionsToTheEnum1783428145772 implements MigrationInterface {
name = 'TMaddingSomeNewPermissionsToTheEnum1783428145772'
public async up(queryRunner: QueryRunner): Promise<void> {
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<void> {
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"`);
}
}
+3 -2
View File
@@ -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 {
@@ -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<TMProject> {
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<TMProject | null> {
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<TMProject | null> {
return this.projectService.getProjectDetail(paramDto.id, user.id, user.permissions);
}
}
@@ -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<User>,
) {}
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<TMProject> {
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<TMProject | null> {
async getProjectDetail(projectId: string, userId: string, permissions: PermissionEnum[]): Promise<TMProject | null> {
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);
@@ -70,7 +70,7 @@ export class WorkspaceService {
}
async findByUser(userId: string, permissions: PermissionEnum[]): Promise<TMWorkspace[]> {
if(permissions.includes(PermissionEnum.WORKSPACE)) {
if(permissions.includes(PermissionEnum.VIEW_ALL_WORKSPACES)) {
return await this.workspaceRepository.find();
}
return await this.workspaceRepository.findByUser(userId);
+2 -2
View File
@@ -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",