189 lines
6.6 KiB
TypeScript
189 lines
6.6 KiB
TypeScript
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
|
import { InjectRepository } from "@nestjs/typeorm";
|
|
import { In, Repository } from "typeorm";
|
|
import { TaskRepository } from "../repositories/task.repository";
|
|
import { TMTask } from "../entities/task.entity";
|
|
import { CreateTaskDto } from "../dto/task/create-task.dto";
|
|
import { UpdateTaskDto } from "../dto/task/update-task.dto";
|
|
import { ChangeTaskPhaseDto } from "../dto/task/change-task-phase.dto";
|
|
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";
|
|
import { encodeCursor } from "../../utils/providers/cursor-pagination.utils";
|
|
import { CursorDirection, CursorPaginationDto } from "../../../common/DTO/cursor-pagination.dto";
|
|
|
|
@Injectable()
|
|
export class TaskService {
|
|
constructor(
|
|
private readonly taskRepository: TaskRepository,
|
|
private readonly taskPhaseRepository: TaskPhaseRepository,
|
|
@InjectRepository(User)
|
|
private readonly userRepository: Repository<User>,
|
|
) {}
|
|
|
|
async findOneOrFail(id: string): Promise<TMTask> {
|
|
const task = await this.taskRepository.findOne({
|
|
where: { id },
|
|
relations: {
|
|
taskPhase: true,
|
|
users: true,
|
|
},
|
|
});
|
|
if (!task) throw new NotFoundException(TaskMessage.TASK_NOT_FOUND);
|
|
return task;
|
|
}
|
|
|
|
async findTaskPhaseOrFail(taskPhaseId: string): Promise<TMTaskPhase> {
|
|
const taskPhase = await this.taskPhaseRepository.findOne({
|
|
where: { id: taskPhaseId },
|
|
relations: {
|
|
project: {
|
|
users: true,
|
|
},
|
|
},
|
|
});
|
|
if (!taskPhase) throw new NotFoundException(TaskPhaseMessage.TASK_PHASE_NOT_FOUND);
|
|
|
|
return taskPhase;
|
|
}
|
|
|
|
async create(dto: CreateTaskDto): Promise<TMTask> {
|
|
const { userIds, startDate, endDate, ...rest } = dto;
|
|
|
|
const taskPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
|
|
|
if (userIds?.length) {
|
|
const isUsersValid = userIds.every((userId) => taskPhase.project.users.some((user) => user.id === userId));
|
|
if (!isUsersValid) throw new BadRequestException(TaskMessage.USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER);
|
|
}
|
|
|
|
const task = this.taskRepository.create({
|
|
...rest,
|
|
startDate: startDate ? new Date(startDate) : undefined,
|
|
endDate: endDate ? new Date(endDate) : undefined,
|
|
});
|
|
|
|
if (userIds?.length) {
|
|
task.users = await this.userRepository.findBy({ id: In(userIds) });
|
|
}
|
|
|
|
return this.taskRepository.save(task);
|
|
}
|
|
|
|
async update(id: string, dto: UpdateTaskDto): Promise<TMTask> {
|
|
const task = await this.findOneOrFail(id);
|
|
const { userIds, startDate, endDate, ...rest } = dto;
|
|
|
|
if (dto.taskPhaseId) {
|
|
const taskPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
|
|
|
if (userIds?.length) {
|
|
const isUsersValid = userIds.every((userId) => taskPhase.project.users.some((user) => user.id === userId));
|
|
if (!isUsersValid) throw new BadRequestException(TaskMessage.USERS_ASSIGNED_TO_TASK_NOT_PROJECT_MEMBER);
|
|
}
|
|
}
|
|
|
|
Object.assign(task, {
|
|
...rest,
|
|
startDate: startDate ? new Date(startDate) : task.startDate,
|
|
endDate: endDate ? new Date(endDate) : task.endDate,
|
|
});
|
|
|
|
if (userIds) {
|
|
task.users = userIds.length ? await this.userRepository.findBy({ id: In(userIds) }) : [];
|
|
}
|
|
|
|
return this.taskRepository.save(task);
|
|
}
|
|
|
|
async changePhase(id: string, dto: ChangeTaskPhaseDto): Promise<TMTask> {
|
|
const task = await this.findOneOrFail(id);
|
|
|
|
const newPhase = await this.findTaskPhaseOrFail(dto.taskPhaseId);
|
|
|
|
if (task.taskPhase.projectId !== newPhase.projectId) {
|
|
throw new BadRequestException(TaskPhaseMessage.TASK_PHASE_NOT_SAME_PROJECT);
|
|
}
|
|
|
|
task.taskPhaseId = dto.taskPhaseId;
|
|
task.taskPhase = newPhase;
|
|
|
|
return this.taskRepository.save(task);
|
|
}
|
|
|
|
async remove(id: string): Promise<void> {
|
|
await this.findOneOrFail(id);
|
|
await this.taskRepository.delete(id);
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
async findTasksByTaskPhase(
|
|
taskPhaseId: string,
|
|
pagination: CursorPaginationDto,
|
|
): Promise<{
|
|
data: TMTask[];
|
|
nextCursor: string | null;
|
|
previousCursor: string | null;
|
|
hasNextPage: boolean;
|
|
hasPreviousPage: boolean;
|
|
cursorPaginate: true;
|
|
}> {
|
|
await this.findTaskPhaseOrFail(taskPhaseId);
|
|
|
|
const isBackward = pagination.direction === CursorDirection.PREV;
|
|
const { items, hasMore } = await this.taskRepository.findTasksByTaskPhase(taskPhaseId, pagination);
|
|
|
|
// paging backward always has more ahead (we came from there); paging forward relies on
|
|
// whether a cursor was even supplied to know whether anything precedes this page
|
|
const hasNextPage = items.length > 0 && (isBackward ? true : hasMore);
|
|
const hasPreviousPage = items.length > 0 && (isBackward ? hasMore : !!pagination.cursor);
|
|
|
|
const nextCursor = hasNextPage ? encodeCursor(items[items.length - 1]) : null;
|
|
const previousCursor = hasPreviousPage ? encodeCursor(items[0]) : null;
|
|
|
|
return { data: items, nextCursor, previousCursor, hasNextPage, hasPreviousPage, cursorPaginate: true };
|
|
}
|
|
|
|
async changePriority(srcTaskId: string, destTaskId: string): Promise<TMTask> {
|
|
const srcTask = await this.findOneOrFail(srcTaskId);
|
|
const desTask = await this.findOneOrFail(destTaskId);
|
|
|
|
if (srcTask.taskPhaseId !== desTask.taskPhaseId)
|
|
throw new BadRequestException(TaskMessage.TASKS_NOT_IN_THE_SAME_TASK_PHASE);
|
|
|
|
let movingUp: boolean = false;
|
|
|
|
if(srcTask.priority < desTask.priority) {
|
|
movingUp = true;
|
|
} else if(srcTask.priority < desTask.priority) {
|
|
movingUp = false;
|
|
} else {
|
|
if(srcTask.createdAt > desTask.createdAt) {
|
|
movingUp = true;
|
|
}else {
|
|
movingUp = false;
|
|
}
|
|
}
|
|
|
|
const priorityChange: number = movingUp ? 1 : -1;
|
|
|
|
srcTask.priority = desTask.priority + priorityChange;
|
|
|
|
await this.taskRepository.save(srcTask);
|
|
|
|
return srcTask;
|
|
}
|
|
}
|