Compare commits

4 Commits

23 changed files with 262 additions and 359 deletions
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Taskmanager1783150480192 implements MigrationInterface {
name = 'Taskmanager1783150480192'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "tm_workspaces" DROP CONSTRAINT "FK_253e36f065cfc1eca58abaefb22"`);
await queryRunner.query(`ALTER TABLE "tm_workspaces" DROP COLUMN "workspaceTypeId"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "tm_workspaces" ADD "workspaceTypeId" uuid NOT NULL`);
await queryRunner.query(`ALTER TABLE "tm_workspaces" ADD CONSTRAINT "FK_253e36f065cfc1eca58abaefb22" FOREIGN KEY ("workspaceTypeId") REFERENCES "tm_workspace_types"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
}
}
+18
View File
@@ -0,0 +1,18 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsInt, IsISO8601, IsOptional, Max, Min } from "class-validator";
export class CursorPaginationDto {
@IsOptional()
@IsISO8601()
@ApiPropertyOptional({ type: "string", description: "Cursor (createdAt of last seen item)", example: "2026-07-01T10:00:00.000Z" })
cursor?: string;
@IsOptional()
@IsInt()
@Min(1)
@Max(50)
@Type(() => Number)
@ApiPropertyOptional({ type: "number", required: false, default: 10 })
limit?: number;
}
+4
View File
@@ -915,3 +915,7 @@ export const enum UploaderMessage {
export const enum AccessLogMessage {
ACCESS_LOG_NOT_FOUND = "لاگ دسترسی مورد نظر یافت نشد",
}
export const enum WorkSpaceMessage {
WORKSPACE_EXISTS = "فضای کار با این اسم وجود دارد!"
}
@@ -0,0 +1,25 @@
import { CursorPaginatedResult } from "../interfaces/cursor-paginated-result.interface";
export function buildCursorPageFormat<T extends { createdAt: Date }>(
data: T[],
limit: number,
previousCursor?: Date,
): CursorPaginatedResult<T> {
const hasMore = data.length > limit;
if (hasMore) data.pop();
const nextCursor = hasMore ? data[data.length - 1].createdAt.toISOString() : null;
const prevCursor = previousCursor ? previousCursor.toISOString() : null;
return {
data,
meta: {
nextCursor,
prevCursor,
hasMore,
limit,
},
};
}
@@ -0,0 +1,9 @@
export interface CursorPaginatedResult<T> {
data: T[];
meta: {
nextCursor: string | null;
prevCursor: string | null;
hasMore: boolean;
limit: number;
};
}
@@ -1,56 +1,59 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } 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 { TMCheckListItem } from '../entities/check-list-item.entity';
import { Controller, Get, Post, Patch, Delete, Param, Body, Query } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } 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";
@ApiTags('Task Manager - Check List Items')
@Controller('task-manager/check-list-items')
@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' })
@ApiResponse({ status: 200, description: 'List of check list items', type: [TMCheckListItem] })
findAll(@Query('taskId') taskId?: string): Promise<TMCheckListItem[]> {
if (taskId) return this.checkListItemService.findByTask(taskId);
return this.checkListItemService.findAll();
@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> {
@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 })
@ApiResponse({ status: 404, description: 'Task not found' })
@ApiOperation({ summary: "Create a new check list item" })
@ApiResponse({ status: 201, description: "CheckListItem created", type: TMCheckListItem })
@ApiResponse({ status: 404, description: "Task not found" })
create(@Body() body: CreateCheckListItemDto): Promise<TMCheckListItem> {
return this.checkListItemService.create(body);
}
@Patch(':id')
@ApiOperation({ summary: 'Update a check list item' })
@ApiParam({ name: 'id', description: 'CheckListItem ID' })
@ApiResponse({ status: 200, description: 'CheckListItem updated', type: TMCheckListItem })
@ApiResponse({ status: 404, description: 'CheckListItem or Task not found' })
update(@Param('id') id: string, @Body() body: UpdateCheckListItemDto): Promise<TMCheckListItem> {
@Patch(":id")
@ApiOperation({ summary: "Update a check list item" })
@ApiParam({ name: "id", description: "CheckListItem ID" })
@ApiResponse({ status: 200, description: "CheckListItem updated", type: TMCheckListItem })
@ApiResponse({ status: 404, description: "CheckListItem or Task not found" })
update(@Param("id") id: string, @Body() body: UpdateCheckListItemDto): Promise<TMCheckListItem> {
return this.checkListItemService.update(id, body);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a check list item' })
@ApiParam({ name: 'id', description: 'CheckListItem ID' })
@ApiResponse({ status: 200, description: 'CheckListItem deleted' })
@ApiResponse({ status: 404, description: 'CheckListItem not found' })
remove(@Param('id') id: string): Promise<void> {
@Delete(":id")
@ApiOperation({ summary: "Delete a check list item" })
@ApiParam({ name: "id", description: "CheckListItem ID" })
@ApiResponse({ status: 200, description: "CheckListItem deleted", type: TMCheckListItem })
@ApiResponse({ status: 404, description: "CheckListItem not found" })
remove(@Param("id") id: string): Promise<TMCheckListItem> {
return this.checkListItemService.remove(id);
}
}
}
@@ -1,65 +1,66 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Req } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
import { Request } from 'express';
import { ProjectService } from '../providers/project.service';
import { CreateProjectDto } from '../dto/project/create-project.dto';
import { UpdateProjectDto } from '../dto/project/update-project.dto';
import { PaginationDto } from '../../../common/DTO/pagination.dto';
import { TMProject } from '../entities/project.entity';
import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Headers } 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 { PaginationDto } from "../../../common/DTO/pagination.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: 'Paginated list of projects' })
@ApiOperation({ summary: "Get all projects, optionally filtered by workspace" })
@ApiQuery({ name: "workspaceId", required: false, description: "Filter projects by workspace ID" })
@ApiResponse({ status: 200, description: "Paginated list of projects" })
findAll(
@Query() pagination: PaginationDto,
@Query('workspaceId') workspaceId: string,
@Req() req: Request,
@Query("workspaceId") workspaceId: string,
@Headers("host") host: string,
@Headers("x-forwarded-proto") proto?: string,
) {
const baseUrl = `${req.protocol}://${req.get('host')}${req.path}`;
const protocol = proto ?? "http";
const baseUrl = `${protocol}://${host}/task-manager/projects`;
if (workspaceId) return this.projectService.findByWorkspace(workspaceId, pagination, baseUrl);
return this.projectService.findAll(pagination, baseUrl);
}
@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> {
@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' })
@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> {
@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', type: TMProject })
@ApiResponse({ status: 404, description: 'Project not found' })
remove(@Param('id') id: string): Promise<TMProject> {
@Delete(":id")
@ApiOperation({ summary: "Delete a project" })
@ApiParam({ name: "id", description: "Project ID" })
@ApiResponse({ status: 200, description: "Project deleted", type: TMProject })
@ApiResponse({ status: 404, description: "Project not found" })
remove(@Param("id") id: string): Promise<TMProject> {
return this.projectService.remove(id);
}
}
}
@@ -1,55 +0,0 @@
import { Controller, Get, Post, Patch, Delete, Param, Body } from "@nestjs/common";
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from "@nestjs/swagger";
import { WorkspaceTypeService } from "../providers/workspace-type.service";
import { CreateWorkspaceTypeDto } from "../dto/workspace-type/create-workspace-type.dto";
import { UpdateWorkspaceTypeDto } from "../dto/workspace-type/update-workspace-type.dto";
import { TMWorkspaceType } from "../entities/workspace-type.entity";
@ApiTags("Task Manager")
@Controller("task-manager/workspace-types")
export class WorkspaceTypeController {
constructor(private readonly workspaceTypeService: WorkspaceTypeService) {}
@Get()
@ApiOperation({ summary: "Get all workspace types" })
@ApiResponse({ status: 200, description: "List of workspace types", type: [TMWorkspaceType] })
findAll(): Promise<TMWorkspaceType[]> {
return this.workspaceTypeService.findAll();
}
@Get(":id")
@ApiOperation({ summary: "Get a workspace type by ID" })
@ApiParam({ name: "id", description: "WorkspaceType ID" })
@ApiResponse({ status: 200, description: "WorkspaceType found", type: TMWorkspaceType })
@ApiResponse({ status: 404, description: "WorkspaceType not found" })
findOne(@Param("id") id: string): Promise<TMWorkspaceType> {
return this.workspaceTypeService.findOne(id);
}
@Post()
@ApiOperation({ summary: "Create a new workspace type" })
@ApiResponse({ status: 201, description: "WorkspaceType created", type: TMWorkspaceType })
@ApiResponse({ status: 409, description: "WorkspaceType with this name already exists" })
create(@Body() body: CreateWorkspaceTypeDto): Promise<TMWorkspaceType> {
return this.workspaceTypeService.create(body);
}
@Patch(":id")
@ApiOperation({ summary: "Update a workspace type" })
@ApiParam({ name: "id", description: "WorkspaceType ID" })
@ApiResponse({ status: 200, description: "WorkspaceType updated", type: TMWorkspaceType })
@ApiResponse({ status: 404, description: "WorkspaceType not found" })
@ApiResponse({ status: 409, description: "WorkspaceType with this name already exists" })
update(@Param("id") id: string, @Body() body: UpdateWorkspaceTypeDto): Promise<TMWorkspaceType> {
return this.workspaceTypeService.update(id, body);
}
@Delete(":id")
@ApiOperation({ summary: "Delete a workspace type" })
@ApiParam({ name: "id", description: "WorkspaceType ID" })
@ApiResponse({ status: 200, description: "WorkspaceType deleted" })
@ApiResponse({ status: 404, description: "WorkspaceType not found" })
remove(@Param("id") id: string): Promise<TMWorkspaceType> {
return this.workspaceTypeService.remove(id);
}
}
@@ -4,13 +4,22 @@ 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';
@ApiTags('Task Manager')
@AuthGuards()
@AdminRoute()
@ApiTags('Task Manager - Workspaces')
@Controller('task-manager/workspaces')
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[]> {
@@ -18,6 +27,7 @@ export class WorkspaceController {
}
@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 })
@@ -27,6 +37,7 @@ export class WorkspaceController {
}
@Post()
@PermissionsDec(PermissionEnum.WORKSPACE)
@ApiOperation({ summary: 'Create a new workspace' })
@ApiResponse({ status: 201, description: 'Workspace created', type: TMWorkspace })
create(@Body() body: CreateWorkspaceDto): Promise<TMWorkspace> {
@@ -34,6 +45,7 @@ export class WorkspaceController {
}
@Patch(':id')
@PermissionsDec(PermissionEnum.WORKSPACE)
@ApiOperation({ summary: 'Update a workspace' })
@ApiParam({ name: 'id', description: 'Workspace ID' })
@ApiResponse({ status: 200, description: 'Workspace updated', type: TMWorkspace })
@@ -43,6 +55,7 @@ export class WorkspaceController {
}
@Delete(':id')
@PermissionsDec(PermissionEnum.WORKSPACE)
@ApiOperation({ summary: 'Delete a workspace' })
@ApiParam({ name: 'id', description: 'Workspace ID' })
@ApiResponse({ status: 200, description: 'Workspace deleted' })
@@ -50,4 +63,12 @@ export class WorkspaceController {
remove(@Param('id') id: string): Promise<TMWorkspace> {
return this.workspaceService.remove(id);
}
@Get('/byuser')
@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);
}
}
@@ -1,25 +0,0 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsString, IsOptional, IsInt, IsNotEmpty, MaxLength, Min } from "class-validator";
export class CreateWorkspaceTypeDto {
@ApiProperty({ description: "Name of the workspace type", example: "Engineering" })
@IsString()
@IsNotEmpty()
@MaxLength(100)
name: string;
@ApiProperty({
description: "Description of the workspace type",
required: false,
example: "Workspace type for engineering related work",
})
@IsString()
@IsOptional()
description?: string;
@ApiProperty({ description: "Display order of the workspace type", example: 1 })
@IsInt()
@IsNotEmpty()
@Min(0)
order: number;
}
@@ -1,4 +0,0 @@
import { PartialType } from '@nestjs/swagger';
import { CreateWorkspaceTypeDto } from './create-workspace-type.dto';
export class UpdateWorkspaceTypeDto extends PartialType(CreateWorkspaceTypeDto) {}
@@ -8,11 +8,6 @@ export class CreateWorkspaceDto {
@MaxLength(100)
name: string;
@ApiProperty({ description: "ID of the workspace type", example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890" })
@IsUUID()
@IsNotEmpty()
workspaceTypeId: string;
@ApiProperty({ description: "Description of the workspace", required: false, example: "Workspace for the engineering team" })
@IsString()
@IsOptional()
@@ -1,20 +0,0 @@
import { Column, Entity, OneToMany } from "typeorm";
import { BaseEntity } from "../../../common/entities/base.entity";
import { TMWorkspace } from "./workspace.entity";
@Entity("tm_workspace_types")
export class TMWorkspaceType extends BaseEntity {
@Column({ type: "varchar", length: 100, unique: true })
name: string;
@Column({ type: "text", nullable: true })
description: string;
@Column({ type: "int", default: 0 })
order: number;
// --- Relations ---
@OneToMany(() => TMWorkspace, (workspace) => workspace.workspaceType)
workspaces: TMWorkspace[];
}
@@ -1,12 +1,11 @@
import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany } from "typeorm";
import { Column, Entity, JoinTable, ManyToMany, OneToMany } from "typeorm";
import { BaseEntity } from "../../../common/entities/base.entity";
import { TMWorkspaceType } from "./workspace-type.entity";
import { TMProject } from "./project.entity";
import { User } from "../../users/entities/user.entity";
@Entity("tm_workspaces")
export class TMWorkspace extends BaseEntity {
@Column({ type: "varchar", length: 100 })
@Column({ type: "varchar", length: 100, unique: true })
name: string;
@Column({ type: "text", nullable: true })
@@ -20,13 +19,6 @@ export class TMWorkspace extends BaseEntity {
// --- Relations ---
@ManyToOne(() => TMWorkspaceType, { nullable: false, eager: true })
@JoinColumn({ name: "workspaceTypeId" })
workspaceType: TMWorkspaceType;
@Column({ type: "uuid" })
workspaceTypeId: string;
@OneToMany(() => TMProject, (project) => project.workspace, {
cascade: ["insert", "update"],
})
@@ -1,9 +1,12 @@
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 { 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';
@Injectable()
export class CheckListItemService {
@@ -12,12 +15,25 @@ export class CheckListItemService {
private readonly taskRepository: TaskRepository,
) {}
findAll(): Promise<TMCheckListItem[]> {
return this.checkListItemRepository.findAll();
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);
}
findByTask(taskId: string): Promise<TMCheckListItem[]> {
return this.checkListItemRepository.findByTask(taskId);
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> {
@@ -46,8 +62,9 @@ export class CheckListItemService {
return this.checkListItemRepository.save(checkListItem);
}
async remove(id: string): Promise<void> {
await this.findOne(id);
async remove(id: string): Promise<TMCheckListItem> {
const checkListItem = await this.findOne(id);
await this.checkListItemRepository.delete(id);
return checkListItem;
}
}
}
@@ -1,47 +0,0 @@
import { Injectable, NotFoundException, ConflictException } from "@nestjs/common";
import { WorkspaceTypeRepository } from "../repositories/workspace-type.repository";
import { TMWorkspaceType } from "../entities/workspace-type.entity";
import { CreateWorkspaceTypeDto } from "../dto/workspace-type/create-workspace-type.dto";
import { UpdateWorkspaceTypeDto } from "../dto/workspace-type/update-workspace-type.dto";
@Injectable()
export class WorkspaceTypeService {
constructor(private readonly workspaceTypeRepository: WorkspaceTypeRepository) {}
findAll(): Promise<TMWorkspaceType[]> {
return this.workspaceTypeRepository.findAll();
}
async findOne(id: string): Promise<TMWorkspaceType> {
const workspaceType = await this.workspaceTypeRepository.findOneById(id);
if (!workspaceType) throw new NotFoundException(`WorkspaceType #${id} not found`);
return workspaceType;
}
async create(dto: CreateWorkspaceTypeDto): Promise<TMWorkspaceType> {
const existing = await this.workspaceTypeRepository.findOneByName(dto.name);
if (existing) throw new ConflictException(`WorkspaceType with name "${dto.name}" already exists`);
const workspaceType = this.workspaceTypeRepository.create(dto);
return this.workspaceTypeRepository.save(workspaceType);
}
async update(id: string, dto: UpdateWorkspaceTypeDto): Promise<TMWorkspaceType> {
await this.findOne(id);
if (dto.name) {
const existing = await this.workspaceTypeRepository.findOneByName(dto.name);
if (existing && existing.id !== id) throw new ConflictException(`WorkspaceType with name "${dto.name}" already exists`);
}
await this.workspaceTypeRepository.update(id, dto);
return this.findOne(id);
}
async remove(id: string): Promise<TMWorkspaceType> {
const workspaceType = await this.findOne(id);
await this.workspaceTypeRepository.delete(id);
return workspaceType;
}
}
@@ -1,4 +1,4 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { In, Repository } from "typeorm";
import { WorkspaceRepository } from "../repositories/workspace.repository";
@@ -6,32 +6,35 @@ import { TMWorkspace } from "../entities/workspace.entity";
import { CreateWorkspaceDto } from "../dto/workspace/create-workspace.dto";
import { UpdateWorkspaceDto } from "../dto/workspace/update-workspace.dto";
import { User } from "../../users/entities/user.entity";
import { WorkspaceTypeRepository } from "../repositories/workspace-type.repository";
import { WorkSpaceMessage } from "../../../common/enums/message.enum";
@Injectable()
export class WorkspaceService {
constructor(
private readonly workspaceRepository: WorkspaceRepository,
private readonly workspaceTypeRepository: WorkspaceTypeRepository,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
findAll(): Promise<TMWorkspace[]> {
return this.workspaceRepository.findAll();
return this.workspaceRepository.find({
relations: ["projects", "users"],
});
}
async findOneOrFail(id: string): Promise<TMWorkspace> {
const workspace = await this.workspaceRepository.findOneById(id);
const workspace = await this.workspaceRepository.findOne({ where: { id } });
if (!workspace) throw new NotFoundException(`Workspace #${id} not found`);
return workspace;
}
async create(dto: CreateWorkspaceDto): Promise<TMWorkspace> {
const { userIds, workspaceTypeId, ...rest } = dto;
const { userIds, ...rest } = dto;
const workspaceType = await this.workspaceTypeRepository.findOneById(workspaceTypeId);
if (!workspaceType) throw new NotFoundException(`WorkSpaceType #${workspaceTypeId} not found!`);
const existingWorkspace = await this.workspaceRepository.findOne({where: {name: rest.name}});
if(existingWorkspace) {
throw new BadRequestException(WorkSpaceMessage.WORKSPACE_EXISTS);
}
const workspace = this.workspaceRepository.create(rest);
@@ -44,12 +47,7 @@ export class WorkspaceService {
async update(id: string, dto: UpdateWorkspaceDto): Promise<TMWorkspace> {
const workspace = await this.findOneOrFail(id);
const { userIds, workspaceTypeId, ...rest } = dto;
if (workspaceTypeId) {
const workspaceType = await this.workspaceTypeRepository.findOneById(workspaceTypeId);
if (!workspaceType) throw new NotFoundException(`WorkSpaceType #${workspaceTypeId} not found!`);
}
const { userIds, ...rest } = dto;
Object.assign(workspace, rest);
@@ -66,4 +64,8 @@ export class WorkspaceService {
return workspace;
}
async findByUser(userId: string): Promise<TMWorkspace[]> {
return await this.workspaceRepository.findByUser(userId);
}
}
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Repository, MoreThan } from 'typeorm';
import { TMCheckListItem } from '../entities/check-list-item.entity';
@Injectable()
@@ -10,15 +10,23 @@ export class CheckListItemRepository {
private readonly repository: Repository<TMCheckListItem>,
) {}
findAll() {
return this.repository.find({
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) {
return this.repository.find({
where: { taskId },
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'],
});
}
@@ -29,6 +37,13 @@ export class CheckListItemRepository {
});
}
findOneByCreatedAt(createdAt: Date) {
return this.repository.findOne({
where: { createdAt },
order: { createdAt: 'ASC' },
});
}
create(data: Partial<TMCheckListItem>) {
return this.repository.create(data);
}
@@ -1,42 +0,0 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { TMWorkspaceType } from "../entities/workspace-type.entity";
@Injectable()
export class WorkspaceTypeRepository {
constructor(
@InjectRepository(TMWorkspaceType)
private readonly repository: Repository<TMWorkspaceType>,
) {}
findAll() {
return this.repository.find({
order: { order: "ASC" },
});
}
findOneById(id: string) {
return this.repository.findOne({ where: { id } });
}
findOneByName(name: string) {
return this.repository.findOne({ where: { name } });
}
create(data: Partial<TMWorkspaceType>) {
return this.repository.create(data);
}
save(workspaceType: TMWorkspaceType) {
return this.repository.save(workspaceType);
}
update(id: string, data: Partial<TMWorkspaceType>) {
return this.repository.update(id, data);
}
delete(id: string) {
return this.repository.delete(id);
}
}
@@ -4,37 +4,15 @@ import { Repository } from "typeorm";
import { TMWorkspace } from "../entities/workspace.entity";
@Injectable()
export class WorkspaceRepository {
constructor(
@InjectRepository(TMWorkspace)
private readonly repository: Repository<TMWorkspace>,
) {}
findAll() {
return this.repository.find({
relations: ["workspaceType", "projects", "users"],
});
export class WorkspaceRepository extends Repository<TMWorkspace> {
constructor(@InjectRepository(TMWorkspace) workspaceRepository: Repository<TMWorkspace>) {
super(workspaceRepository.target, workspaceRepository.manager, workspaceRepository.queryRunner);
}
findOneById(id: string) {
return this.repository.findOne({
where: { id },
});
}
create(data: Partial<TMWorkspace>) {
return this.repository.create(data);
}
save(workspace: TMWorkspace) {
return this.repository.save(workspace);
}
update(id: string, data: Partial<TMWorkspace>) {
return this.repository.update(id, data);
}
delete(id: string) {
return this.repository.delete(id);
async findByUser(userId: string): Promise<TMWorkspace[]> {
return this.createQueryBuilder("workspace")
.innerJoin("workspace.users", "users")
.where("user.id = :userId", { userId })
.getMany();
}
}
@@ -1,7 +1,6 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { TMWorkspaceType } from "./entities/workspace-type.entity";
import { TMWorkspace } from "./entities/workspace.entity";
import { TMProject } from "./entities/project.entity";
import { TMTaskPhase } from "./entities/task-phase.entity";
@@ -13,9 +12,6 @@ import { WorkspaceRepository } from "./repositories/workspace.repository";
import { WorkspaceController } from "./controllers/workspace.controller";
import { WorkspaceService } from "./providers/workspace.service";
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 { ProjectController } from "./controllers/project.controller";
import { ProjectRepository } from "./repositories/project.repository";
import { ProjectService } from "./providers/project.service";
@@ -31,25 +27,26 @@ import { CheckListItemController } from "./controllers/check-list-item.controlle
import { RemarkController } from "./controllers/remark.controller";
import { RemarkRepository } from "./repositories/remark.repository";
import { RemarkService } from "./providers/remark.service";
import { AttachmentController } from "./controllers/attachment.controller";
import { AttachmentRepository } from "./repositories/attachment.repository";
import { AttachmentService } from "./providers/attachment.service";
@Module({
imports: [
TypeOrmModule.forFeature([TMWorkspaceType, TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, User]),
TypeOrmModule.forFeature([TMWorkspace, TMProject, TMTaskPhase, TMTask, TMRemark, TMAttachment, TMCheckListItem, User]),
],
controllers: [
WorkspaceController,
WorkspaceTypeController,
ProjectController,
TaskPhaseController,
TaskController,
CheckListItemController,
RemarkController
RemarkController,
AttachmentController
],
providers: [
WorkspaceRepository,
WorkspaceService,
WorkspaceTypeRepository,
WorkspaceTypeService,
ProjectRepository,
ProjectService,
TaskPhaseRepository,
@@ -59,7 +56,9 @@ import { RemarkService } from "./providers/remark.service";
CheckListItemRepository,
CheckListItemService,
RemarkRepository,
RemarkService
RemarkService,
AttachmentRepository,
AttachmentService
]
})
export class TaskManagerModule {}
+2 -1
View File
@@ -24,5 +24,6 @@ export enum PermissionEnum {
DKALA = "dkala",
DPAGE = "dpage",
DMAIL = 'dmail',
RESELLER="reseller"
RESELLER="reseller",
WORKSPACE="workspace"
}