Compare commits

12 Commits

20 changed files with 168 additions and 395 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"`);
}
}
+11 -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 {
@@ -945,4 +946,12 @@ export const enum TaskMessage {
export const enum remarkMessage {
REMARK_NOT_FOUND = "برچسب مورد نظر پیدا نشد!"
}
export const enum checkListItemMessage {
CHECKLISTITEM_NOT_FOUND = "آیتم چک لیست پیدا نشد!"
}
export const enum attachmentMessage {
ATTACHMENT_NOT_FOUND = "ضمیمه پیدا نشد!"
}
@@ -1,33 +1,19 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
import { Controller, Post, Patch, Delete, Param, Body } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import { AttachmentService } from '../providers/attachment.service';
import { CreateAttachmentDto } from '../dto/attachment/create-attachment.dto';
import { UpdateAttachmentDto } from '../dto/attachment/update-attachment.dto';
import { TMAttachment } from '../entities/attachment.entity';
import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
import { AdminRoute } from '../../../common/decorators/admin.decorator';
@AuthGuards()
@AdminRoute()
@ApiTags('Task Manager - Attachments')
@Controller('task-manager/attachments')
export class AttachmentController {
constructor(private readonly attachmentService: AttachmentService) {}
@Get()
@ApiOperation({ summary: 'Get all attachments, optionally filtered by task' })
@ApiQuery({ name: 'taskId', required: false, description: 'Filter attachments by task ID' })
@ApiResponse({ status: 200, description: 'List of attachments', type: [TMAttachment] })
findAll(@Query('taskId') taskId?: string): Promise<TMAttachment[]> {
if (taskId) return this.attachmentService.findByTask(taskId);
return this.attachmentService.findAll();
}
@Get(':id')
@ApiOperation({ summary: 'Get an attachment by ID' })
@ApiParam({ name: 'id', description: 'Attachment ID' })
@ApiResponse({ status: 200, description: 'Attachment found', type: TMAttachment })
@ApiResponse({ status: 404, description: 'Attachment not found' })
findOne(@Param('id') id: string): Promise<TMAttachment> {
return this.attachmentService.findOne(id);
}
@Post()
@ApiOperation({ summary: 'Create a new attachment' })
@ApiResponse({ status: 201, description: 'Attachment created', type: TMAttachment })
@@ -1,36 +1,20 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
import { Controller, Post, Patch, Delete, Param, Body } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
import { CheckListItemService } from "../providers/check-list-item.service";
import { CreateCheckListItemDto } from "../dto/check-list-item/create-check-list-item.dto";
import { UpdateCheckListItemDto } from "../dto/check-list-item/update-check-list-item.dto";
import { CursorPaginationDto } from "../../../common/DTO/cursor-pagination.dto";
import { TMCheckListItem } from "../entities/check-list-item.entity";
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
import { AdminRoute } from "../../../common/decorators/admin.decorator";
import { ParamDto } from "../../../common/DTO/param.dto";
@AuthGuards()
@AdminRoute()
@ApiTags("Task Manager - Check List Items")
@Controller("task-manager/check-list-items")
export class CheckListItemController {
constructor(private readonly checkListItemService: CheckListItemService) {}
@Get()
@ApiOperation({ summary: "Get all check list items, optionally filtered by task" })
@ApiQuery({ name: "taskId", required: false, description: "Filter check list items by task ID" })
@ApiQuery({ name: "cursor", required: false, description: "Cursor for pagination (createdAt ISO string)" })
@ApiQuery({ name: "limit", required: false, description: "Number of records to return (max 50)" })
@ApiResponse({ status: 200, description: "Cursor-paginated list of check list items" })
findAll(@Query() pagination: CursorPaginationDto, @Query("taskId") taskId?: string) {
if (taskId) return this.checkListItemService.findByTask(taskId, pagination);
return this.checkListItemService.findAll(pagination);
}
@Get(":id")
@ApiOperation({ summary: "Get a check list item by ID" })
@ApiParam({ name: "id", description: "CheckListItem ID" })
@ApiResponse({ status: 200, description: "CheckListItem found", type: TMCheckListItem })
@ApiResponse({ status: 404, description: "CheckListItem not found" })
findOne(@Param("id") id: string): Promise<TMCheckListItem> {
return this.checkListItemService.findOne(id);
}
@Post()
@ApiOperation({ summary: "Create a new check list item" })
@ApiResponse({ status: 201, description: "CheckListItem created", type: TMCheckListItem })
@@ -56,4 +40,13 @@ export class CheckListItemController {
remove(@Param("id") id: string): Promise<TMCheckListItem> {
return this.checkListItemService.remove(id);
}
@Patch("change-status/:id")
@ApiOperation({summary: "change status of a check list item."})
@ApiParam({name: "id", description: "CheckListItem ID"})
@ApiResponse({status: 200, description: "successfully changed the status!"})
@ApiResponse({ status: 404, description: "CheckListItem not found" })
changeStatus(@Param() paramDto: ParamDto) {
return this.checkListItemService.toggleStatus(paramDto.id);
}
}
@@ -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);
}
}
@@ -1,40 +1,19 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
import { Controller, Post, Patch, Delete, Param, Body } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
import { RemarkService } from "../providers/remark.service";
import { CreateRemarkDto } from "../dto/remark/create-remark.dto";
import { UpdateRemarkDto } from "../dto/remark/update-remark.dto";
import { TMRemark } from "../entities/remark.entity";
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
import { AdminRoute } from "../../../common/decorators/admin.decorator";
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
import { PermissionEnum } from "../../users/enums/permission.enum";
@AuthGuards()
@AdminRoute()
@PermissionsDec(PermissionEnum.REMARK)
@ApiTags("Task Manager - Remarks")
@Controller("task-manager/remarks")
export class RemarkController {
constructor(private readonly remarkService: RemarkService) {}
@Get()
@ApiOperation({ summary: "Get all remarks, optionally filtered by task" })
@ApiQuery({ name: "taskId", required: false, description: "Filter remarks by task ID" })
@ApiResponse({ status: 200, description: "List of remarks", type: [TMRemark] })
findAll(@Query("taskId") taskId?: string): Promise<TMRemark[]> {
if (taskId) return this.remarkService.findByTask(taskId);
return this.remarkService.findAll();
}
@Get(":id")
@ApiOperation({ summary: "Get a remark by ID" })
@ApiParam({ name: "id", description: "Remark ID" })
@ApiResponse({ status: 200, description: "Remark found", type: TMRemark })
@ApiResponse({ status: 404, description: "Remark not found" })
findOne(@Param("id") id: string): Promise<TMRemark> {
return this.remarkService.findOneOrFail(id);
}
@Post()
@ApiOperation({ summary: "Create a new remark" })
@ApiResponse({ status: 201, description: "Remark created", type: TMRemark })
@@ -1,5 +1,5 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger";
import { Controller, Post, Patch, Delete, Param, Body } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
import { TaskPhaseService } from "../providers/task-phase.service";
import { CreateTaskPhaseDto } from "../dto/task-phase/create-task-phase.dto";
import { UpdateTaskPhaseDto } from "../dto/task-phase/update-task-phase.dto";
@@ -17,24 +17,6 @@ import { PermissionEnum } from "../../users/enums/permission.enum";
export class TaskPhaseController {
constructor(private readonly taskPhaseService: TaskPhaseService) {}
@Get()
@ApiOperation({ summary: "Get all task phases, optionally filtered by project" })
@ApiQuery({ name: "projectId", required: false, description: "Filter task phases by project ID" })
@ApiResponse({ status: 200, description: "List of task phases", type: [TMTaskPhase] })
findAll(@Query("projectId") projectId?: string): Promise<TMTaskPhase[]> {
if (projectId) return this.taskPhaseService.findByProject(projectId);
return this.taskPhaseService.findAll();
}
@Get(":id")
@ApiOperation({ summary: "Get a task phase by ID" })
@ApiParam({ name: "id", description: "TaskPhase ID" })
@ApiResponse({ status: 200, description: "TaskPhase found", type: TMTaskPhase })
@ApiResponse({ status: 404, description: "TaskPhase not found" })
findOne(@Param("id") id: string): Promise<TMTaskPhase> {
return this.taskPhaseService.findOneOrFail(id);
}
@Post()
@ApiOperation({ summary: "Create a new task phase" })
@ApiResponse({ status: 201, description: "TaskPhase created", type: TMTaskPhase })
@@ -1,5 +1,5 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
import { Controller, Get, Post, Patch, Delete, Param, Body } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
import { TaskService } from '../providers/task.service';
import { CreateTaskDto } from '../dto/task/create-task.dto';
import { UpdateTaskDto } from '../dto/task/update-task.dto';
@@ -11,6 +11,7 @@ import { UserDec } from '../../../common/decorators/user.decorator';
import { ParamDto } from '../../../common/DTO/param.dto';
import { PermissionsDec } from '../../../common/decorators/permission.decorator';
import { PermissionEnum } from '../../users/enums/permission.enum';
import { ITokenPayload } from '../../auth/interfaces/IToken-payload';
@AuthGuards()
@AdminRoute()
@@ -19,26 +20,6 @@ import { PermissionEnum } from '../../users/enums/permission.enum';
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Get()
@PermissionsDec(PermissionEnum.TASK)
@ApiOperation({ summary: 'Get all tasks, optionally filtered by phase' })
@ApiQuery({ name: 'taskPhaseId', required: false, description: 'Filter tasks by task phase ID' })
@ApiResponse({ status: 200, description: 'List of tasks', type: [TMTask] })
findAll(@Query('taskPhaseId') taskPhaseId?: string): Promise<TMTask[]> {
if (taskPhaseId) return this.taskService.findByPhase(taskPhaseId);
return this.taskService.findAll();
}
@Get(':id')
@PermissionsDec(PermissionEnum.TASK)
@ApiOperation({ summary: 'Get a task by ID' })
@ApiParam({ name: 'id', description: 'Task ID' })
@ApiResponse({ status: 200, description: 'Task found', type: TMTask })
@ApiResponse({ status: 404, description: 'Task not found' })
findOne(@Param('id') id: string): Promise<TMTask> {
return this.taskService.findOneOrFail(id);
}
@Post()
@PermissionsDec(PermissionEnum.TASK)
@ApiOperation({ summary: 'Create a new task' })
@@ -85,9 +66,9 @@ export class TaskController {
@ApiResponse({ status: 200, description: 'Task detail received!' })
@ApiResponse({ status: 404, description: 'Task not found' })
getTaskDetail(
@UserDec('id') userId: string,
@UserDec() user: ITokenPayload,
@Param() paramDto: ParamDto
): Promise<TMTask | null> {
return this.taskService.getTaskDetail(userId, paramDto.id);
return this.taskService.getTaskDetail(user.id, user.permissions, paramDto.id);
}
}
@@ -4,12 +4,12 @@ import { WorkspaceService } from '../providers/workspace.service';
import { CreateWorkspaceDto } from '../dto/workspace/create-workspace.dto';
import { UpdateWorkspaceDto } from '../dto/workspace/update-workspace.dto';
import { TMWorkspace } from '../entities/workspace.entity';
// import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
import { AdminRoute } from '../../../common/decorators/admin.decorator';
import { PermissionsDec } from '../../../common/decorators/permission.decorator';
import { PermissionEnum } from '../../users/enums/permission.enum';
import { UserDec } from '../../../common/decorators/user.decorator';
import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
import { ITokenPayload } from '../../auth/interfaces/IToken-payload';
@AuthGuards()
@AdminRoute()
@@ -18,24 +18,6 @@ import { AuthGuards } from '../../../common/decorators/auth-guard.decorator';
export class WorkspaceController {
constructor(private readonly workspaceService: WorkspaceService) {}
@Get()
@PermissionsDec(PermissionEnum.WORKSPACE)
@ApiOperation({ summary: 'Get all workspaces' })
@ApiResponse({ status: 200, description: 'List of workspaces', type: [TMWorkspace] })
findAll(): Promise<TMWorkspace[]> {
return this.workspaceService.findAll();
}
@Get(':id')
@PermissionsDec(PermissionEnum.WORKSPACE)
@ApiOperation({ summary: 'Get a workspace by ID' })
@ApiParam({ name: 'id', description: 'Workspace ID' })
@ApiResponse({ status: 200, description: 'Workspace found', type: TMWorkspace })
@ApiResponse({ status: 404, description: 'Workspace not found' })
findOne(@Param('id') id: string): Promise<TMWorkspace> {
return this.workspaceService.findOneOrFail(id);
}
@Post()
@PermissionsDec(PermissionEnum.WORKSPACE)
@ApiOperation({ summary: 'Create a new workspace' })
@@ -68,7 +50,7 @@ export class WorkspaceController {
@ApiOperation({summary: 'Get User WorkSpaces'})
@ApiResponse({ status: 200, description: 'Got Workspaces successfully!' })
@ApiResponse({ status: 404, description: 'Bad Request!' })
findByUser(@UserDec('id') userId: string) {
return this.workspaceService.findByUser(userId);
findByUser(@UserDec() user: ITokenPayload) {
return this.workspaceService.findByUser(user.id, user.permissions);
}
}
@@ -1,45 +1,36 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { AttachmentRepository } from "../repositories/attachment.repository";
import { TaskRepository } from "../repositories/task.repository";
import { TMAttachment } from "../entities/attachment.entity";
import { CreateAttachmentDto } from "../dto/attachment/create-attachment.dto";
import { UpdateAttachmentDto } from "../dto/attachment/update-attachment.dto";
import { attachmentMessage } from "../../../common/enums/message.enum";
import { TaskService } from "./task.service";
@Injectable()
export class AttachmentService {
constructor(
private readonly attachmentRepository: AttachmentRepository,
private readonly taskRepository: TaskRepository,
private readonly taskService: TaskService,
) {}
findAll(): Promise<TMAttachment[]> {
return this.attachmentRepository.findAll();
}
findByTask(taskId: string): Promise<TMAttachment[]> {
return this.attachmentRepository.findByTask(taskId);
}
async findOne(id: string): Promise<TMAttachment> {
const attachment = await this.attachmentRepository.findOneById(id);
if (!attachment) throw new NotFoundException(`Attachment #${id} not found`);
async findOneOrFail(id: string): Promise<TMAttachment> {
const attachment = await this.attachmentRepository.findOne({ where: { id }, relations: { task: true } });
if (!attachment) throw new NotFoundException(attachmentMessage.ATTACHMENT_NOT_FOUND);
return attachment;
}
async create(dto: CreateAttachmentDto): Promise<TMAttachment> {
const task = await this.taskRepository.findOneById(dto.taskId);
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
await this.taskService.findOneOrFail(dto.taskId);
const attachment = this.attachmentRepository.create(dto);
return this.attachmentRepository.save(attachment);
}
async update(id: string, dto: UpdateAttachmentDto): Promise<TMAttachment> {
const attachment = await this.findOne(id);
const attachment = await this.findOneOrFail(id);
if (dto.taskId) {
const task = await this.taskRepository.findOneById(dto.taskId);
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
await this.taskService.findOneOrFail(dto.taskId);
}
Object.assign(attachment, dto);
@@ -47,7 +38,7 @@ export class AttachmentService {
}
async remove(id: string): Promise<void> {
await this.findOne(id);
await this.findOneOrFail(id);
await this.attachmentRepository.delete(id);
}
}
@@ -1,61 +1,36 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { CheckListItemRepository } from '../repositories/check-list-item.repository';
import { TaskRepository } from '../repositories/task.repository';
import { TMCheckListItem } from '../entities/check-list-item.entity';
import { CreateCheckListItemDto } from '../dto/check-list-item/create-check-list-item.dto';
import { UpdateCheckListItemDto } from '../dto/check-list-item/update-check-list-item.dto';
import { CursorPaginationDto } from '../../../common/DTO/cursor-pagination.dto';
import { CursorPaginatedResult } from '../../../common/interfaces/cursor-paginated-result.interface';
import { buildCursorPageFormat } from '../../../common/helpers/cursor-paginated.helper';
import { checkListItemMessage } from '../../../common/enums/message.enum';
import { TaskService } from './task.service';
@Injectable()
export class CheckListItemService {
constructor(
private readonly checkListItemRepository: CheckListItemRepository,
private readonly taskRepository: TaskRepository,
private readonly taskService: TaskService,
) {}
async findAll(pagination: CursorPaginationDto): Promise<CursorPaginatedResult<TMCheckListItem>> {
const limit = pagination.limit ?? 10;
const cursorDate = pagination.cursor ? new Date(pagination.cursor) : undefined;
const [data] = await this.checkListItemRepository.findAll(limit, cursorDate);
return buildCursorPageFormat(data, limit, cursorDate);
}
async findByTask(
taskId: string,
pagination: CursorPaginationDto,
): Promise<CursorPaginatedResult<TMCheckListItem>> {
const limit = pagination.limit ?? 10;
const cursorDate = pagination.cursor ? new Date(pagination.cursor) : undefined;
const [data] = await this.checkListItemRepository.findByTask(taskId, limit, cursorDate);
return buildCursorPageFormat(data, limit, cursorDate);
}
async findOne(id: string): Promise<TMCheckListItem> {
async findOneOrFail(id: string): Promise<TMCheckListItem> {
const checkListItem = await this.checkListItemRepository.findOneById(id);
if (!checkListItem) throw new NotFoundException(`CheckListItem #${id} not found`);
if (!checkListItem) throw new NotFoundException(checkListItemMessage.CHECKLISTITEM_NOT_FOUND);
return checkListItem;
}
async create(dto: CreateCheckListItemDto): Promise<TMCheckListItem> {
const task = await this.taskRepository.findOneById(dto.taskId);
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
await this.taskService.findOneOrFail(dto.taskId);
const checkListItem = this.checkListItemRepository.create(dto);
return this.checkListItemRepository.save(checkListItem);
}
async update(id: string, dto: UpdateCheckListItemDto): Promise<TMCheckListItem> {
const checkListItem = await this.findOne(id);
const checkListItem = await this.findOneOrFail(id);
if (dto.taskId) {
const task = await this.taskRepository.findOneById(dto.taskId);
if (!task) throw new NotFoundException(`Task #${dto.taskId} not found`);
await this.taskService.findOneOrFail(dto.taskId);
}
Object.assign(checkListItem, dto);
@@ -63,8 +38,17 @@ export class CheckListItemService {
}
async remove(id: string): Promise<TMCheckListItem> {
const checkListItem = await this.findOne(id);
const checkListItem = await this.findOneOrFail(id);
await this.checkListItemRepository.delete(id);
return checkListItem;
}
async toggleStatus(id: string) : Promise<TMCheckListItem> {
const checkListItem = await this.findOneOrFail(id);
const isDone = checkListItem.isDone;
checkListItem.isDone = !isDone;
await this.checkListItemRepository.save(checkListItem);
return checkListItem;
}
}
@@ -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);
@@ -13,19 +13,6 @@ export class RemarkService {
private readonly taskService: TaskService,
) {}
findAll(): Promise<TMRemark[]> {
return this.remarkRepository.find({ relations: { task: true } });
}
findByTask(taskId: string): Promise<TMRemark[]> {
return this.remarkRepository.find({
where: { taskId },
relations: {
task: true,
},
});
}
async findOneOrFail(id: string): Promise<TMRemark> {
const remark = await this.remarkRepository.findOne({ where: { id } });
if (!remark) throw new NotFoundException(remarkMessage.REMARK_NOT_FOUND);
@@ -13,16 +13,6 @@ export class TaskPhaseService {
private readonly projectService: ProjectService
) {}
findAll(): Promise<TMTaskPhase[]> {
return this.taskPhaseRepository.find();
}
findByProject(projectId: string): Promise<TMTaskPhase[]> {
return this.taskPhaseRepository.find({
where: {projectId}
});
}
async findOneOrFail(id: string): Promise<TMTaskPhase> {
const taskPhase = await this.taskPhaseRepository.findOne({where: {id}});
if (!taskPhase) throw new NotFoundException(TaskPhaseMessage.TASK_PHASE_NOT_FOUND);
@@ -10,6 +10,7 @@ import { User } from "../../users/entities/user.entity";
import { TaskMessage, TaskPhaseMessage } from "../../../common/enums/message.enum";
import { TaskPhaseRepository } from "../repositories/task-phase.repository";
import { TMTaskPhase } from "../entities/task-phase.entity";
import { PermissionEnum } from "../../users/enums/permission.enum";
@Injectable()
export class TaskService {
@@ -20,22 +21,12 @@ export class TaskService {
private readonly userRepository: Repository<User>,
) {}
async findAll(): Promise<TMTask[]> {
return this.taskRepository.find({
relations: ["taskPhase", "remarks", "attachments", "checkListItems", "users"],
});
}
async findByPhase(taskPhaseId: string): Promise<TMTask[]> {
return this.taskRepository.find({ where: { taskPhaseId } });
}
async findOneOrFail(id: string): Promise<TMTask> {
const task = await this.taskRepository.findOne({
where: { id },
relations: {
taskPhase: true,
users: true
users: true,
},
});
if (!task) throw new NotFoundException(TaskMessage.TASK_NOT_FOUND);
@@ -125,11 +116,12 @@ export class TaskService {
await this.taskRepository.delete(id);
}
async getTaskDetail(userId: string, taskId: string): Promise<TMTask | null> {
async getTaskDetail(userId: string, permissions: PermissionEnum[], taskId: string): Promise<TMTask | null> {
const task = await this.findOneOrFail(taskId);
const isTaskMember = task.users.some((user) => user.id === userId);
if(!isTaskMember) throw new ForbiddenException(TaskMessage.USER_NOT_TASK_MEMBER);
const hasPermission = permissions.includes(PermissionEnum.TASK);
if (!isTaskMember && !hasPermission) throw new ForbiddenException(TaskMessage.USER_NOT_TASK_MEMBER);
const taskDetail = await this.taskRepository.getTaskDetail(taskId);
return taskDetail;
@@ -7,6 +7,7 @@ import { CreateWorkspaceDto } from "../dto/workspace/create-workspace.dto";
import { UpdateWorkspaceDto } from "../dto/workspace/update-workspace.dto";
import { User } from "../../users/entities/user.entity";
import { WorkSpaceMessage } from "../../../common/enums/message.enum";
import { PermissionEnum } from "../../users/enums/permission.enum";
@Injectable()
export class WorkspaceService {
@@ -68,7 +69,10 @@ export class WorkspaceService {
return workspace;
}
async findByUser(userId: string): Promise<TMWorkspace[]> {
async findByUser(userId: string, permissions: PermissionEnum[]): Promise<TMWorkspace[]> {
if(permissions.includes(PermissionEnum.VIEW_ALL_WORKSPACES)) {
return await this.workspaceRepository.find();
}
return await this.workspaceRepository.findByUser(userId);
}
}
@@ -4,44 +4,10 @@ import { Repository } from 'typeorm';
import { TMAttachment } from '../entities/attachment.entity';
@Injectable()
export class AttachmentRepository {
export class AttachmentRepository extends Repository<TMAttachment>{
constructor(
@InjectRepository(TMAttachment)
private readonly repository: Repository<TMAttachment>,
) {}
findAll() {
return this.repository.find({
relations: ['task'],
});
}
findByTask(taskId: string) {
return this.repository.find({
where: { taskId },
});
}
findOneById(id: string) {
return this.repository.findOne({
where: { id },
relations: ['task'],
});
}
create(data: Partial<TMAttachment>) {
return this.repository.create(data);
}
save(attachment: TMAttachment) {
return this.repository.save(attachment);
}
update(id: string, data: Partial<TMAttachment>) {
return this.repository.update(id, data);
}
delete(id: string) {
return this.repository.delete(id);
@InjectRepository(TMAttachment) attachmentRepository: Repository<TMAttachment>,
) {
super(attachmentRepository.target, attachmentRepository.manager, attachmentRepository.queryRunner);
}
}
@@ -1,62 +1,13 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, MoreThan } from 'typeorm';
import { Repository } from 'typeorm';
import { TMCheckListItem } from '../entities/check-list-item.entity';
@Injectable()
export class CheckListItemRepository {
export class CheckListItemRepository extends Repository<TMCheckListItem>{
constructor(
@InjectRepository(TMCheckListItem)
private readonly repository: Repository<TMCheckListItem>,
) {}
findAll(limit: number, cursor?: Date) {
return this.repository.findAndCount({
where: cursor ? { createdAt: MoreThan(cursor) } : {},
order: { createdAt: 'ASC' },
take: limit + 1,
relations: ['task'],
});
}
findByTask(taskId: string, limit: number, cursor?: Date) {
return this.repository.findAndCount({
where: cursor
? { taskId, createdAt: MoreThan(cursor) }
: { taskId },
order: { createdAt: 'ASC' },
take: limit + 1,
relations: ['task'],
});
}
findOneById(id: string) {
return this.repository.findOne({
where: { id },
relations: ['task'],
});
}
findOneByCreatedAt(createdAt: Date) {
return this.repository.findOne({
where: { createdAt },
order: { createdAt: 'ASC' },
});
}
create(data: Partial<TMCheckListItem>) {
return this.repository.create(data);
}
save(checkListItem: TMCheckListItem) {
return this.repository.save(checkListItem);
}
update(id: string, data: Partial<TMCheckListItem>) {
return this.repository.update(id, data);
}
delete(id: string) {
return this.repository.delete(id);
@InjectRepository(TMCheckListItem) checkListItemRepository: Repository<TMCheckListItem>,
) {
super(checkListItemRepository.target, checkListItemRepository.manager, checkListItemRepository.queryRunner);
}
}
@@ -22,21 +22,22 @@ export class TaskRepository extends Repository<TMTask> {
qb.where("completedCheckListItems.isDone = :isDone", { isDone: true }),
)
.select(["remark.id",
"remark.title",
"remark.color",
"checkListItem.id",
"checkListItem.title",
"checkListItem.isDone",
"attachment.id",
"attachment.title",
"attachment.type",
"attachment.file",
"user.id",
"user.firstName",
"user.lastName"
])
.select([
"remark.id",
"remark.title",
"remark.color",
"checkListItem.id",
"checkListItem.title",
"checkListItem.isDone",
"attachment.id",
"attachment.title",
"attachment.type",
"attachment.file",
"user.id",
"user.firstName",
"user.lastName",
])
.where("task.id = :taskId", { taskId })
.getOne();
}
+5 -4
View File
@@ -25,9 +25,10 @@ export enum PermissionEnum {
DPAGE = "dpage",
DMAIL = 'dmail',
RESELLER="reseller",
WORKSPACE="workspace",
PROJECT="project",
WORKSPACE="workspace", // create, update, remove
VIEW_ALL_WORKSPACES="view_all_workspaces",
PROJECT="project", // create, update, remove
VIEW_ALL_PROJECTS="view_all_projects",
TASK="task", // read, create, update, remove
TASK_PHASE="task_phase",
TASK="task",
REMARK="remark"
}