From bf98b8b9ced6e57735d0f8ead3737b0503a1e8eb Mon Sep 17 00:00:00 2001 From: realrafi Date: Wed, 1 Jul 2026 12:53:42 +0330 Subject: [PATCH] feat: added task phase APIs --- .../controllers/project.controller.ts | 72 +++++++++---------- .../controllers/task-phase.controller.ts | 56 +++++++++++++++ .../dto/task-phase/create-task-phase.dto.ts | 27 +++++++ .../dto/task-phase/update-task-phase.dto.ts | 4 ++ .../providers/task-phase.service.ts | 53 ++++++++++++++ .../repositories/task-phase.repository.ts | 50 +++++++++++++ 6 files changed, 226 insertions(+), 36 deletions(-) create mode 100644 src/modules/task-manager/controllers/task-phase.controller.ts create mode 100644 src/modules/task-manager/dto/task-phase/create-task-phase.dto.ts create mode 100644 src/modules/task-manager/dto/task-phase/update-task-phase.dto.ts create mode 100644 src/modules/task-manager/providers/task-phase.service.ts create mode 100644 src/modules/task-manager/repositories/task-phase.repository.ts diff --git a/src/modules/task-manager/controllers/project.controller.ts b/src/modules/task-manager/controllers/project.controller.ts index 5f59e4d..3fae2a2 100644 --- a/src/modules/task-manager/controllers/project.controller.ts +++ b/src/modules/task-manager/controllers/project.controller.ts @@ -1,58 +1,58 @@ -import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common"; -import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from "@nestjs/swagger"; -import { ProjectService } from "../providers/project.service"; -import { CreateProjectDto } from "../dto/project/create-project.dto"; -import { UpdateProjectDto } from "../dto/project/update-project.dto"; -import { TMProject } from "../entities/project.entity"; +import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common'; +import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger'; +import { ProjectService } from '../providers/project.service'; +import { CreateProjectDto } from '../dto/project/create-project.dto'; +import { UpdateProjectDto } from '../dto/project/update-project.dto'; +import { TMProject } from '../entities/project.entity'; -@ApiTags("Task Manager - Projects") -@Controller("task-manager/projects") +@ApiTags('Task Manager - Projects') +@Controller('task-manager/projects') export class ProjectController { constructor(private readonly projectService: ProjectService) {} @Get() - @ApiOperation({ summary: "Get all projects, optionally filtered by workspace" }) - @ApiQuery({ name: "workspaceId", required: false, description: "Filter projects by workspace ID" }) - @ApiResponse({ status: 200, description: "List of projects", type: [TMProject] }) - findAll(@Query("workspaceId") workspaceId?: string): Promise { + @ApiOperation({ summary: 'Get all projects, optionally filtered by workspace' }) + @ApiQuery({ name: 'workspaceId', required: false, description: 'Filter projects by workspace ID' }) + @ApiResponse({ status: 200, description: 'List of projects', type: [TMProject] }) + findAll(@Query('workspaceId') workspaceId?: string): Promise { if (workspaceId) return this.projectService.findByWorkspace(workspaceId); return this.projectService.findAll(); } - @Get(":id") - @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 { + @Get(':id') + @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.findOne(id); } @Post() - @ApiOperation({ summary: "Create a new project" }) - @ApiResponse({ status: 201, description: "Project created", type: TMProject }) - @ApiResponse({ status: 400, description: "One or more users are not members of the workspace" }) - @ApiResponse({ status: 404, description: "Workspace not found" }) + @ApiOperation({ summary: 'Create a new project' }) + @ApiResponse({ status: 201, description: 'Project created', type: TMProject }) + @ApiResponse({ status: 400, description: 'One or more users are not members of the workspace' }) + @ApiResponse({ status: 404, description: 'Workspace not found' }) create(@Body() body: CreateProjectDto): Promise { return this.projectService.create(body); } - @Patch(":id") - @ApiOperation({ summary: "Update a project" }) - @ApiParam({ name: "id", description: "Project ID" }) - @ApiResponse({ status: 200, description: "Project updated", type: TMProject }) - @ApiResponse({ status: 400, description: "One or more users are not members of the workspace" }) - @ApiResponse({ status: 404, description: "Project not found" }) - update(@Param("id") id: string, @Body() body: UpdateProjectDto): Promise { + @Patch(':id') + @ApiOperation({ summary: 'Update a project' }) + @ApiParam({ name: 'id', description: 'Project ID' }) + @ApiResponse({ status: 200, description: 'Project updated', type: TMProject }) + @ApiResponse({ status: 400, description: 'One or more users are not members of the workspace' }) + @ApiResponse({ status: 404, description: 'Project not found' }) + update(@Param('id') id: string, @Body() body: UpdateProjectDto): Promise { return this.projectService.update(id, body); } - @Delete(":id") - @ApiOperation({ summary: "Delete a project" }) - @ApiParam({ name: "id", description: "Project ID" }) - @ApiResponse({ status: 200, description: "Project deleted" }) - @ApiResponse({ status: 404, description: "Project not found" }) - remove(@Param("id") id: string): Promise { + @Delete(':id') + @ApiOperation({ summary: 'Delete a project' }) + @ApiParam({ name: 'id', description: 'Project ID' }) + @ApiResponse({ status: 200, description: 'Project deleted' }) + @ApiResponse({ status: 404, description: 'Project not found' }) + remove(@Param('id') id: string): Promise { return this.projectService.remove(id); } -} +} \ No newline at end of file diff --git a/src/modules/task-manager/controllers/task-phase.controller.ts b/src/modules/task-manager/controllers/task-phase.controller.ts new file mode 100644 index 0000000..e38c41c --- /dev/null +++ b/src/modules/task-manager/controllers/task-phase.controller.ts @@ -0,0 +1,56 @@ +import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common"; +import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } 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"; +import { TMTaskPhase } from "../entities/task-phase.entity"; + +@ApiTags("Task Manager - Task Phases") +@Controller("task-manager/task-phases") +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 { + 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 { + return this.taskPhaseService.findOne(id); + } + + @Post() + @ApiOperation({ summary: "Create a new task phase" }) + @ApiResponse({ status: 201, description: "TaskPhase created", type: TMTaskPhase }) + @ApiResponse({ status: 404, description: "Project not found" }) + create(@Body() body: CreateTaskPhaseDto): Promise { + return this.taskPhaseService.create(body); + } + + @Patch(":id") + @ApiOperation({ summary: "Update a task phase" }) + @ApiParam({ name: "id", description: "TaskPhase ID" }) + @ApiResponse({ status: 200, description: "TaskPhase updated", type: TMTaskPhase }) + @ApiResponse({ status: 404, description: "TaskPhase or Project not found" }) + update(@Param("id") id: string, @Body() body: UpdateTaskPhaseDto): Promise { + return this.taskPhaseService.update(id, body); + } + + @Delete(":id") + @ApiOperation({ summary: "Delete a task phase" }) + @ApiParam({ name: "id", description: "TaskPhase ID" }) + @ApiResponse({ status: 200, description: "TaskPhase deleted" }) + @ApiResponse({ status: 404, description: "TaskPhase not found" }) + remove(@Param("id") id: string): Promise { + return this.taskPhaseService.remove(id); + } +} diff --git a/src/modules/task-manager/dto/task-phase/create-task-phase.dto.ts b/src/modules/task-manager/dto/task-phase/create-task-phase.dto.ts new file mode 100644 index 0000000..69dc0f2 --- /dev/null +++ b/src/modules/task-manager/dto/task-phase/create-task-phase.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsString, IsOptional, IsNotEmpty, IsUUID, IsInt, MaxLength, Min } from "class-validator"; + +export class CreateTaskPhaseDto { + @ApiProperty({ description: "Name of the task phase", example: "In Progress" }) + @IsString() + @IsNotEmpty() + @MaxLength(100) + name: string; + + @ApiProperty({ description: "Display order of the task phase", required: false, example: 1 }) + @IsInt() + @IsOptional() + @Min(0) + order?: number; + + @ApiProperty({ description: "Color code for the task phase", required: false, example: "#4F46E5" }) + @IsString() + @IsOptional() + @MaxLength(20) + color?: string; + + @ApiProperty({ description: "ID of the project this phase belongs to", example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }) + @IsUUID() + @IsNotEmpty() + projectId: string; +} \ No newline at end of file diff --git a/src/modules/task-manager/dto/task-phase/update-task-phase.dto.ts b/src/modules/task-manager/dto/task-phase/update-task-phase.dto.ts new file mode 100644 index 0000000..45ebc93 --- /dev/null +++ b/src/modules/task-manager/dto/task-phase/update-task-phase.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from "@nestjs/swagger"; +import { CreateTaskPhaseDto } from "./create-task-phase.dto"; + +export class UpdateTaskPhaseDto extends PartialType(CreateTaskPhaseDto) {} diff --git a/src/modules/task-manager/providers/task-phase.service.ts b/src/modules/task-manager/providers/task-phase.service.ts new file mode 100644 index 0000000..570ac19 --- /dev/null +++ b/src/modules/task-manager/providers/task-phase.service.ts @@ -0,0 +1,53 @@ +import { Injectable, NotFoundException } from "@nestjs/common"; +import { TaskPhaseRepository } from "../repositories/task-phase.repository"; +import { ProjectRepository } from "../repositories/project.repository"; +import { TMTaskPhase } from "../entities/task-phase.entity"; +import { CreateTaskPhaseDto } from "../dto/task-phase/create-task-phase.dto"; +import { UpdateTaskPhaseDto } from "../dto/task-phase/update-task-phase.dto"; + +@Injectable() +export class TaskPhaseService { + constructor( + private readonly taskPhaseRepository: TaskPhaseRepository, + private readonly projectRepository: ProjectRepository, + ) {} + + findAll(): Promise { + return this.taskPhaseRepository.findAll(); + } + + findByProject(projectId: string): Promise { + return this.taskPhaseRepository.findByProject(projectId); + } + + async findOne(id: string): Promise { + const taskPhase = await this.taskPhaseRepository.findOneById(id); + if (!taskPhase) throw new NotFoundException(`TaskPhase #${id} not found`); + return taskPhase; + } + + async create(dto: CreateTaskPhaseDto): Promise { + const project = await this.projectRepository.findOneById(dto.projectId); + if (!project) throw new NotFoundException(`Project #${dto.projectId} not found`); + + const taskPhase = this.taskPhaseRepository.create(dto); + return this.taskPhaseRepository.save(taskPhase); + } + + async update(id: string, dto: UpdateTaskPhaseDto): Promise { + const taskPhase = await this.findOne(id); + + if (dto.projectId) { + const project = await this.projectRepository.findOneById(dto.projectId); + if (!project) throw new NotFoundException(`Project #${dto.projectId} not found`); + } + + Object.assign(taskPhase, dto); + return this.taskPhaseRepository.save(taskPhase); + } + + async remove(id: string): Promise { + await this.findOne(id); + await this.taskPhaseRepository.delete(id); + } +} diff --git a/src/modules/task-manager/repositories/task-phase.repository.ts b/src/modules/task-manager/repositories/task-phase.repository.ts new file mode 100644 index 0000000..1023f9a --- /dev/null +++ b/src/modules/task-manager/repositories/task-phase.repository.ts @@ -0,0 +1,50 @@ +import { Injectable } from "@nestjs/common"; +import { InjectRepository } from "@nestjs/typeorm"; +import { Repository } from "typeorm"; +import { TMTaskPhase } from "../entities/task-phase.entity"; + +@Injectable() +export class TaskPhaseRepository { + constructor( + @InjectRepository(TMTaskPhase) + private readonly repository: Repository, + ) {} + + findAll() { + return this.repository.find({ + relations: ["project", "tasks"], + order: { order: "ASC" }, + }); + } + + findByProject(projectId: string) { + return this.repository.find({ + where: { projectId }, + relations: ["tasks"], + order: { order: "ASC" }, + }); + } + + findOneById(id: string) { + return this.repository.findOne({ + where: { id }, + relations: ["project", "tasks"], + }); + } + + create(data: Partial) { + return this.repository.create(data); + } + + save(taskPhase: TMTaskPhase) { + return this.repository.save(taskPhase); + } + + update(id: string, data: Partial) { + return this.repository.update(id, data); + } + + delete(id: string) { + return this.repository.delete(id); + } +}