This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddTaskCommentEntity1784700000000 implements MigrationInterface {
|
||||
name = "AddTaskCommentEntity1784700000000";
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "tm_task_comments" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "content" text NOT NULL, "userId" uuid NOT NULL, "taskId" uuid NOT NULL, CONSTRAINT "PK_tm_task_comments" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_task_comments" ADD CONSTRAINT "FK_tm_task_comments_user" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE NO ACTION`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "tm_task_comments" ADD CONSTRAINT "FK_tm_task_comments_task" FOREIGN KEY ("taskId") REFERENCES "tm_tasks"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_task_comments" DROP CONSTRAINT "FK_tm_task_comments_task"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_task_comments" DROP CONSTRAINT "FK_tm_task_comments_user"`);
|
||||
await queryRunner.query(`DROP TABLE "tm_task_comments"`);
|
||||
}
|
||||
}
|
||||
@@ -963,3 +963,7 @@ export const enum timeMessage {
|
||||
TIME_ALREADY_STARTED = "برای این تسک یک زمان در حال اجرا وجود دارد!",
|
||||
NO_RUNNING_TIME = "زمان در حال اجرایی برای این تسک پیدا نشد!"
|
||||
}
|
||||
|
||||
export const enum taskCommentMessage {
|
||||
TASK_COMMENT_NOT_FOUND = "نظر مورد نظر پیدا نشد!"
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Controller, Post, Patch, Delete, Param, Body, Get } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
|
||||
import { TaskCommentService } from "../providers/task-comment.service";
|
||||
import { CreateTaskCommentDto } from "../dto/task-comment/create-task-comment.dto";
|
||||
import { UpdateTaskCommentDto } from "../dto/task-comment/update-task-comment.dto";
|
||||
import { TMTaskComment } from "../entities/task-comment.entity";
|
||||
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||
import { UserDec } from "../../../common/decorators/user.decorator";
|
||||
|
||||
@AuthGuards()
|
||||
@AdminRoute()
|
||||
@ApiTags("Task Manager - Comments")
|
||||
@Controller("task-manager/comments")
|
||||
export class TaskCommentController {
|
||||
constructor(private readonly taskCommentService: TaskCommentService) {}
|
||||
|
||||
@Get("task/:taskId")
|
||||
@ApiOperation({ summary: "Get all comments for a task" })
|
||||
@ApiParam({ name: "taskId", description: "Task ID" })
|
||||
getTaskComments(@Param("taskId") taskId: string): Promise<TMTaskComment[]> {
|
||||
return this.taskCommentService.getTaskComments(taskId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Create a new comment" })
|
||||
@ApiResponse({ status: 201, description: "Comment created", type: TMTaskComment })
|
||||
@ApiResponse({ status: 404, description: "Task not found" })
|
||||
create(@UserDec("id") userId: string, @Body() body: CreateTaskCommentDto): Promise<TMTaskComment> {
|
||||
return this.taskCommentService.create(userId, body);
|
||||
}
|
||||
|
||||
@Patch(":id")
|
||||
@ApiOperation({ summary: "Update a comment" })
|
||||
@ApiParam({ name: "id", description: "Comment ID" })
|
||||
@ApiResponse({ status: 200, description: "Comment updated", type: TMTaskComment })
|
||||
@ApiResponse({ status: 404, description: "Comment or Task not found" })
|
||||
update(@Param("id") id: string, @Body() body: UpdateTaskCommentDto): Promise<TMTaskComment> {
|
||||
return this.taskCommentService.update(id, body);
|
||||
}
|
||||
|
||||
@Delete(":id")
|
||||
@ApiOperation({ summary: "Delete a comment" })
|
||||
@ApiParam({ name: "id", description: "Comment ID" })
|
||||
@ApiResponse({ status: 200, description: "Comment deleted" })
|
||||
@ApiResponse({ status: 404, description: "Comment not found" })
|
||||
remove(@Param("id") id: string): Promise<void> {
|
||||
return this.taskCommentService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsString, IsNotEmpty, IsUUID } from "class-validator";
|
||||
|
||||
export class CreateTaskCommentDto {
|
||||
@ApiProperty({ description: "Comment content", example: "Please review this task before closing." })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
content: string;
|
||||
|
||||
@ApiProperty({ description: "ID of the task this comment belongs to", example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
taskId: string;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateTaskCommentDto } from "./create-task-comment.dto";
|
||||
|
||||
export class UpdateTaskCommentDto extends PartialType(CreateTaskCommentDto) {}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { TMTask } from "./task.entity";
|
||||
|
||||
@Entity("tm_task_comments")
|
||||
export class TMTaskComment extends BaseEntity {
|
||||
@Column({ type: "text" })
|
||||
content: string;
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@ManyToOne(() => User, {
|
||||
nullable: false,
|
||||
onDelete: "RESTRICT",
|
||||
})
|
||||
@JoinColumn({ name: "userId" })
|
||||
user: User;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
userId: string;
|
||||
|
||||
@ManyToOne(() => TMTask, (task) => task.comments, {
|
||||
nullable: false,
|
||||
onDelete: "CASCADE",
|
||||
})
|
||||
@JoinColumn({ name: "taskId" })
|
||||
task: TMTask;
|
||||
|
||||
@Column({ type: "uuid" })
|
||||
taskId: string;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { TMRemark } from "./remark.entity";
|
||||
import { TMAttachment } from "./attachment.entity";
|
||||
import { TMCheckListItem } from "./check-list-item.entity";
|
||||
import { TMTime } from "./time.entity";
|
||||
import { TMTaskComment } from "./task-comment.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { Ticket } from "../../tickets/entities/ticket.entity";
|
||||
|
||||
@@ -67,6 +68,11 @@ export class TMTask extends BaseEntity {
|
||||
})
|
||||
times: TMTime[];
|
||||
|
||||
@OneToMany(() => TMTaskComment, (comment) => comment.task, {
|
||||
cascade: ["insert", "update"],
|
||||
})
|
||||
comments: TMTaskComment[];
|
||||
|
||||
@ManyToMany(() => User, (user) => user.tasks)
|
||||
@JoinTable({
|
||||
name: "tm_task_users",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { TaskCommentRepository } from "../repositories/task-comment.repository";
|
||||
import { TMTaskComment } from "../entities/task-comment.entity";
|
||||
import { CreateTaskCommentDto } from "../dto/task-comment/create-task-comment.dto";
|
||||
import { UpdateTaskCommentDto } from "../dto/task-comment/update-task-comment.dto";
|
||||
import { taskCommentMessage } from "../../../common/enums/message.enum";
|
||||
import { TaskService } from "./task.service";
|
||||
|
||||
@Injectable()
|
||||
export class TaskCommentService {
|
||||
constructor(
|
||||
private readonly taskCommentRepository: TaskCommentRepository,
|
||||
private readonly taskService: TaskService,
|
||||
) {}
|
||||
|
||||
async findOneOrFail(id: string): Promise<TMTaskComment> {
|
||||
const comment = await this.taskCommentRepository.findOne({
|
||||
where: { id },
|
||||
relations: { user: true, task: true },
|
||||
});
|
||||
if (!comment) throw new NotFoundException(taskCommentMessage.TASK_COMMENT_NOT_FOUND);
|
||||
return comment;
|
||||
}
|
||||
|
||||
async getTaskComments(taskId: string): Promise<TMTaskComment[]> {
|
||||
await this.taskService.findOneOrFail(taskId);
|
||||
|
||||
return this.taskCommentRepository.find({
|
||||
where: { taskId },
|
||||
relations: { user: true },
|
||||
order: { createdAt: "DESC" },
|
||||
});
|
||||
}
|
||||
|
||||
async create(userId: string, dto: CreateTaskCommentDto): Promise<TMTaskComment> {
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
|
||||
const comment = this.taskCommentRepository.create({
|
||||
...dto,
|
||||
userId,
|
||||
});
|
||||
return this.taskCommentRepository.save(comment);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateTaskCommentDto): Promise<TMTaskComment> {
|
||||
const comment = await this.findOneOrFail(id);
|
||||
|
||||
if (dto.taskId) {
|
||||
await this.taskService.findOneOrFail(dto.taskId);
|
||||
}
|
||||
|
||||
Object.assign(comment, dto);
|
||||
return this.taskCommentRepository.save(comment);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.findOneOrFail(id);
|
||||
await this.taskCommentRepository.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { TMTaskComment } from "../entities/task-comment.entity";
|
||||
|
||||
@Injectable()
|
||||
export class TaskCommentRepository extends Repository<TMTaskComment> {
|
||||
constructor(
|
||||
@InjectRepository(TMTaskComment) taskCommentRepository: Repository<TMTaskComment>,
|
||||
) {
|
||||
super(taskCommentRepository.target, taskCommentRepository.manager, taskCommentRepository.queryRunner);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import { TMRemark } from "./entities/remark.entity";
|
||||
import { TMAttachment } from "./entities/attachment.entity";
|
||||
import { TMCheckListItem } from "./entities/check-list-item.entity";
|
||||
import { TMTime } from "./entities/time.entity";
|
||||
import { TMTaskComment } from "./entities/task-comment.entity";
|
||||
import { WorkspaceRepository } from "./repositories/workspace.repository";
|
||||
import { WorkspaceController } from "./controllers/workspace.controller";
|
||||
import { WorkspaceService } from "./providers/workspace.service";
|
||||
@@ -35,10 +36,13 @@ import { AttachmentService } from "./providers/attachment.service";
|
||||
import { TimeController } from "./controllers/time.controller";
|
||||
import { TimeRepository } from "./repositories/time.repository";
|
||||
import { TimeService } from "./providers/time.service";
|
||||
import { TaskCommentController } from "./controllers/task-comment.controller";
|
||||
import { TaskCommentRepository } from "./repositories/task-comment.repository";
|
||||
import { TaskCommentService } from "./providers/task-comment.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, TMTime, User, Ticket]),
|
||||
TypeOrmModule.forFeature([TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, TMTime, TMTaskComment, User, Ticket]),
|
||||
],
|
||||
controllers: [
|
||||
WorkspaceController,
|
||||
@@ -49,6 +53,7 @@ import { TimeService } from "./providers/time.service";
|
||||
RemarkController,
|
||||
AttachmentController,
|
||||
TimeController,
|
||||
TaskCommentController,
|
||||
],
|
||||
providers: [
|
||||
WorkspaceRepository,
|
||||
@@ -67,6 +72,8 @@ import { TimeService } from "./providers/time.service";
|
||||
AttachmentService,
|
||||
TimeRepository,
|
||||
TimeService,
|
||||
TaskCommentRepository,
|
||||
TaskCommentService,
|
||||
]
|
||||
})
|
||||
export class TaskManagerModule {}
|
||||
|
||||
Reference in New Issue
Block a user