feat: added project section of TM with some refactors
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class TaskmanagerProject1782889623829 implements MigrationInterface {
|
||||
name = 'TaskmanagerProject1782889623829'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "tm_project_users" ("projectId" uuid NOT NULL, "userId" uuid NOT NULL, CONSTRAINT "PK_5969064e19a1efa76b91de6a065" PRIMARY KEY ("projectId", "userId"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_fcc8e135e4891925d288dbb14e" ON "tm_project_users" ("projectId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_8e2d2c9d639d5fd806159f8698" ON "tm_project_users" ("userId") `);
|
||||
await queryRunner.query(`ALTER TABLE "tm_projects" ADD "backgroundPicture" text`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_projects" ADD "backgroundColor" character varying(20)`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_projects" ADD "isActive" boolean NOT NULL DEFAULT true`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_project_users" ADD CONSTRAINT "FK_fcc8e135e4891925d288dbb14e5" FOREIGN KEY ("projectId") REFERENCES "tm_projects"("id") ON DELETE CASCADE ON UPDATE CASCADE`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_project_users" ADD CONSTRAINT "FK_8e2d2c9d639d5fd806159f86980" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "tm_project_users" DROP CONSTRAINT "FK_8e2d2c9d639d5fd806159f86980"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_project_users" DROP CONSTRAINT "FK_fcc8e135e4891925d288dbb14e5"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_projects" DROP COLUMN "isActive"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_projects" DROP COLUMN "backgroundColor"`);
|
||||
await queryRunner.query(`ALTER TABLE "tm_projects" DROP COLUMN "backgroundPicture"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_8e2d2c9d639d5fd806159f8698"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_fcc8e135e4891925d288dbb14e"`);
|
||||
await queryRunner.query(`DROP TABLE "tm_project_users"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +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";
|
||||
|
||||
@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<TMProject[]> {
|
||||
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<TMProject> {
|
||||
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" })
|
||||
create(@Body() body: CreateProjectDto): Promise<TMProject> {
|
||||
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<TMProject> {
|
||||
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<TMProject> {
|
||||
return this.projectService.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsString, IsOptional, IsNotEmpty, IsUUID, IsDateString, MaxLength } from "class-validator";
|
||||
import { IsString, IsOptional, IsNotEmpty, IsUUID, IsDateString, IsBoolean, MaxLength } from "class-validator";
|
||||
|
||||
export class CreateProjectDto {
|
||||
@ApiProperty({ description: "Name of the project", example: "Mobile App Redesign" })
|
||||
@@ -33,8 +33,29 @@ export class CreateProjectDto {
|
||||
@IsOptional()
|
||||
backgroundPicture?: string;
|
||||
|
||||
@ApiProperty({ description: "Background color of the project (hex or named)", required: false, example: "#4F46E5" })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(20)
|
||||
backgroundColor?: string;
|
||||
|
||||
@ApiProperty({ description: "Whether the project is active", required: false, default: true, example: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isActive?: boolean;
|
||||
|
||||
@ApiProperty({ description: "ID of the workspace this project belongs to", example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" })
|
||||
@IsUUID()
|
||||
@IsNotEmpty()
|
||||
workspaceId: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: "IDs of users to assign to the project (must be members of the workspace)",
|
||||
required: false,
|
||||
type: [String],
|
||||
example: ["b2c3d4e5-f6a7-8901-bcde-f12345678901"],
|
||||
})
|
||||
@IsUUID("4", { each: true })
|
||||
@IsOptional()
|
||||
userIds?: string[];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateProjectDto } from './create-project.dto';
|
||||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateProjectDto } from "./create-project.dto";
|
||||
|
||||
export class UpdateProjectDto extends PartialType(CreateProjectDto) {}
|
||||
export class UpdateProjectDto extends PartialType(CreateProjectDto) {}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm";
|
||||
import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany } from "typeorm";
|
||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||
import { TMWorkspace } from "./workspace.entity";
|
||||
import { TMTaskPhase } from "./task-phase.entity";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
|
||||
@Entity("tm_projects")
|
||||
export class TMProject extends BaseEntity {
|
||||
@@ -20,6 +21,12 @@ export class TMProject extends BaseEntity {
|
||||
@Column({ type: "text", nullable: true })
|
||||
backgroundPicture: string;
|
||||
|
||||
@Column({ type: "varchar", length: 20, nullable: true })
|
||||
backgroundColor: string;
|
||||
|
||||
@Column({ type: "boolean", default: true })
|
||||
isActive: boolean;
|
||||
|
||||
// --- Relations ---
|
||||
|
||||
@ManyToOne(() => TMWorkspace, (workspace) => workspace.projects, {
|
||||
@@ -36,4 +43,12 @@ export class TMProject extends BaseEntity {
|
||||
cascade: ["insert", "update"],
|
||||
})
|
||||
taskPhases: TMTaskPhase[];
|
||||
|
||||
@ManyToMany(() => User, (user) => user.projects)
|
||||
@JoinTable({
|
||||
name: "tm_project_users",
|
||||
joinColumn: { name: "projectId", referencedColumnName: "id" },
|
||||
inverseJoinColumn: { name: "userId", referencedColumnName: "id" },
|
||||
})
|
||||
users: User[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { In, Repository } from "typeorm";
|
||||
import { ProjectRepository } from "../repositories/project.repository";
|
||||
import { TMProject } from "../entities/project.entity";
|
||||
import { CreateProjectDto } from "../dto/project/create-project.dto";
|
||||
import { UpdateProjectDto } from "../dto/project/update-project.dto";
|
||||
import { User } from "../../users/entities/user.entity";
|
||||
import { WorkspaceRepository } from "../repositories/workspace.repository";
|
||||
|
||||
@Injectable()
|
||||
export class ProjectService {
|
||||
constructor(
|
||||
private readonly projectRepository: ProjectRepository,
|
||||
private readonly workspaceRepository: WorkspaceRepository,
|
||||
@InjectRepository(User)
|
||||
private readonly userRepository: Repository<User>,
|
||||
) {}
|
||||
|
||||
findAll(): Promise<TMProject[]> {
|
||||
return this.projectRepository.findAll();
|
||||
}
|
||||
|
||||
findByWorkspace(workspaceId: string): Promise<TMProject[]> {
|
||||
return this.projectRepository.findByWorkspace(workspaceId);
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<TMProject> {
|
||||
const project = await this.projectRepository.findOneById(id);
|
||||
if (!project) throw new NotFoundException(`Project #${id} not found`);
|
||||
return project;
|
||||
}
|
||||
|
||||
async create(dto: CreateProjectDto): Promise<TMProject> {
|
||||
const { userIds, startDate, ...rest } = dto;
|
||||
|
||||
const workspace = await this.workspaceRepository.findOneById(dto.workspaceId);
|
||||
if (!workspace) throw new NotFoundException(`Workspace #${dto.workspaceId} not found`);
|
||||
|
||||
const project = this.projectRepository.create({
|
||||
...rest,
|
||||
startDate: new Date(startDate),
|
||||
});
|
||||
|
||||
if (userIds?.length) {
|
||||
const workspaceUserIds = workspace.users.map((u) => u.id);
|
||||
const invalidUsers = userIds.filter((id) => !workspaceUserIds.includes(id));
|
||||
if (invalidUsers.length) {
|
||||
throw new BadRequestException(`Users [${invalidUsers.join(", ")}] are not members of this workspace`);
|
||||
}
|
||||
project.users = await this.userRepository.findBy({ id: In(userIds) });
|
||||
}
|
||||
|
||||
return this.projectRepository.save(project);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateProjectDto): Promise<TMProject> {
|
||||
const project = await this.findOne(id);
|
||||
const { userIds, startDate, ...rest } = dto;
|
||||
|
||||
Object.assign(project, {
|
||||
...rest,
|
||||
startDate: startDate ? new Date(startDate) : project.startDate,
|
||||
});
|
||||
|
||||
if (userIds) {
|
||||
if (userIds.length) {
|
||||
const workspaceId = dto.workspaceId ?? project.workspaceId;
|
||||
const workspace = await this.workspaceRepository.findOneById(workspaceId);
|
||||
if (!workspace) throw new NotFoundException(`Workspace #${workspaceId} not found`);
|
||||
|
||||
const workspaceUserIds = workspace.users.map((u) => u.id);
|
||||
const invalidUsers = userIds.filter((uid) => !workspaceUserIds.includes(uid));
|
||||
if (invalidUsers.length) {
|
||||
throw new BadRequestException(`Users [${invalidUsers.join(", ")}] are not members of this workspace`);
|
||||
}
|
||||
project.users = await this.userRepository.findBy({ id: In(userIds) });
|
||||
} else {
|
||||
project.users = [];
|
||||
}
|
||||
}
|
||||
|
||||
return this.projectRepository.save(project);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<TMProject> {
|
||||
const project = await this.findOne(id);
|
||||
await this.projectRepository.delete(id);
|
||||
|
||||
return project;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InjectRepository } from "@nestjs/typeorm";
|
||||
import { Repository } from "typeorm";
|
||||
import { TMProject } from "../entities/project.entity";
|
||||
|
||||
@Injectable()
|
||||
export class ProjectRepository {
|
||||
constructor(
|
||||
@InjectRepository(TMProject)
|
||||
private readonly repository: Repository<TMProject>,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
return this.repository.find({
|
||||
relations: ["workspace", "taskPhases", "users"],
|
||||
});
|
||||
}
|
||||
|
||||
findByWorkspace(workspaceId: string) {
|
||||
return this.repository.find({
|
||||
where: { workspaceId },
|
||||
relations: ["taskPhases", "users"],
|
||||
});
|
||||
}
|
||||
|
||||
findOneById(id: string) {
|
||||
return this.repository.findOne({
|
||||
where: { id },
|
||||
relations: ["workspace", "taskPhases", "users"],
|
||||
});
|
||||
}
|
||||
|
||||
create(data: Partial<TMProject>) {
|
||||
return this.repository.create(data);
|
||||
}
|
||||
|
||||
save(project: TMProject) {
|
||||
return this.repository.save(project);
|
||||
}
|
||||
|
||||
update(id: string, data: Partial<TMProject>) {
|
||||
return this.repository.update(id, data);
|
||||
}
|
||||
|
||||
delete(id: string) {
|
||||
return this.repository.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -16,29 +16,25 @@ import { User } from "../users/entities/user.entity";
|
||||
import { WorkspaceTypeService } from "./providers/workspace-type.service";
|
||||
import { WorkspaceTypeRepository } from "./repositories/workspace-type.repository";
|
||||
import { WorkspaceTypeController } from "./controllers/workspace-type.controller";
|
||||
|
||||
// import { WorkspaceTypeService } from './providers/workspace-type.service';
|
||||
// import { WorkspaceService } from './providers/workspace.service';
|
||||
// import { ProjectService } from './providers/project.service';
|
||||
// import { TaskPhaseService } from './providers/task-phase.service';
|
||||
// import { TaskService } from './providers/task.service';
|
||||
|
||||
// import { TaskManagerController } from './task-manager.controller';
|
||||
import { ProjectController } from "./controllers/project.controller";
|
||||
import { ProjectRepository } from "./repositories/project.repository";
|
||||
import { ProjectService } from "./providers/project.service";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([TMWorkspaceType, TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, User]),
|
||||
],
|
||||
controllers: [WorkspaceController, WorkspaceTypeController],
|
||||
controllers: [WorkspaceController, WorkspaceTypeController, ProjectController],
|
||||
providers: [
|
||||
WorkspaceRepository,
|
||||
WorkspaceService,
|
||||
WorkspaceTypeRepository,
|
||||
WorkspaceTypeService,
|
||||
// ProjectService,
|
||||
ProjectRepository,
|
||||
ProjectService,
|
||||
// TaskPhaseService,
|
||||
// TaskService,
|
||||
],
|
||||
exports: [WorkspaceService, WorkspaceRepository, WorkspaceTypeService, WorkspaceTypeRepository],
|
||||
exports: [WorkspaceService, WorkspaceRepository, WorkspaceTypeService, WorkspaceTypeRepository, ProjectService, ProjectRepository],
|
||||
})
|
||||
export class TaskManagerModule {}
|
||||
|
||||
@@ -34,6 +34,7 @@ import { Reseller } from "../../reseller/entities/reseller.entity";
|
||||
import { UserBankAccount } from "../../reseller/entities/user-bank-account.entity";
|
||||
import { TMWorkspace } from "../../task-manager/entities/workspace.entity";
|
||||
import { TMTask } from "../../task-manager/entities/task.entity";
|
||||
import { TMProject } from "../../task-manager/entities/project.entity";
|
||||
|
||||
@Entity()
|
||||
export class User extends BaseEntity {
|
||||
@@ -185,4 +186,7 @@ export class User extends BaseEntity {
|
||||
|
||||
@ManyToMany(() => TMTask, (task) => task.users)
|
||||
tasks: TMTask[];
|
||||
|
||||
@ManyToMany(() => TMProject, (project) => project.users)
|
||||
projects: TMProject[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user