mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 05:42:27 +00:00
168 lines
6.3 KiB
TypeScript
168 lines
6.3 KiB
TypeScript
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
|
import moment from 'moment';
|
|
import { FindOptionsWhere } from 'typeorm';
|
|
import { Roles } from '~/auth/enums';
|
|
import { IJwtPayload } from '~/auth/interfaces';
|
|
import { DocumentService, OciService } from '~/document/services';
|
|
import { CreateTaskRequestDto, TasksFilterOptions, TaskSubmissionRequestDto } from '../dtos/request';
|
|
import { Task } from '../entities';
|
|
import { SubmissionStatus, TaskStatus } from '../enums';
|
|
import { TaskRepository } from '../repositories';
|
|
|
|
@Injectable()
|
|
export class TaskService {
|
|
private readonly logger = new Logger(TaskService.name);
|
|
constructor(
|
|
private readonly taskRepository: TaskRepository,
|
|
private readonly ociService: OciService,
|
|
private readonly documentService: DocumentService,
|
|
) {}
|
|
async createTask(userId: string, body: CreateTaskRequestDto) {
|
|
this.logger.log(`Creating task for user ${userId}`);
|
|
if (moment(body.dueDate).isBefore(moment(body.startDate))) {
|
|
this.logger.error(`Due date must be after start date`);
|
|
throw new BadRequestException('TASK.DUE_DATE_BEFORE_START_DATE');
|
|
}
|
|
|
|
if (moment(body.dueDate).isBefore(moment())) {
|
|
this.logger.error(`Due date must be in the future`);
|
|
throw new BadRequestException('TASK.DUE_DATE_IN_PAST');
|
|
}
|
|
|
|
await this.validateTaskImage(userId, body.imageId);
|
|
const task = await this.taskRepository.createTask(userId, body);
|
|
|
|
this.logger.log(`Task ${task.id} created successfully`);
|
|
return this.findTask({ id: task.id });
|
|
}
|
|
|
|
findTaskForUser(taskId: string, user: IJwtPayload) {
|
|
this.logger.log(`Finding task ${taskId} for user ${user.sub}`);
|
|
|
|
return this.findTask({
|
|
id: taskId,
|
|
assignedById: user.roles.includes(Roles.GUARDIAN) ? user.sub : undefined,
|
|
assignedToId: user.roles.includes(Roles.JUNIOR) ? user.sub : undefined,
|
|
});
|
|
}
|
|
|
|
async findTask(where: FindOptionsWhere<Task>) {
|
|
this.logger.log(`Finding task with where ${JSON.stringify(where)}`);
|
|
const task = await this.taskRepository.findTask(where);
|
|
|
|
if (!task) {
|
|
this.logger.error(`Task not found`);
|
|
throw new BadRequestException('TASK.NOT_FOUND');
|
|
}
|
|
|
|
await this.prepareTasksPictures([task]);
|
|
|
|
this.logger.log(`Task found successfully`);
|
|
return task;
|
|
}
|
|
|
|
async findTasks(user: IJwtPayload, query: TasksFilterOptions): Promise<[Task[], number]> {
|
|
this.logger.log(`Finding tasks for user ${user.sub} and roles ${user.roles} and filters ${JSON.stringify(query)}`);
|
|
const [tasks, count] = await this.taskRepository.findTasks(user, query);
|
|
await this.prepareTasksPictures(tasks);
|
|
|
|
this.logger.log(`Returning tasks for user ${user.sub}`);
|
|
return [tasks, count];
|
|
}
|
|
async submitTask(userId: string, taskId: string, body: TaskSubmissionRequestDto) {
|
|
this.logger.log(`Submitting task ${taskId} for user ${userId}`);
|
|
const task = await this.findTask({ id: taskId, assignedToId: userId });
|
|
|
|
if (task.status == TaskStatus.COMPLETED) {
|
|
this.logger.error(`Task ${taskId} already completed`);
|
|
throw new BadRequestException('TASK.ALREADY_COMPLETED');
|
|
}
|
|
|
|
if (task.isProofRequired && !body.imageId) {
|
|
this.logger.error(`Proof of completion is required for task ${taskId}`);
|
|
throw new BadRequestException('TASK.PROOF_REQUIRED');
|
|
}
|
|
|
|
await this.validateTaskImage(userId, body.imageId);
|
|
|
|
await this.taskRepository.createSubmission(task, body);
|
|
this.logger.log(`Task ${taskId} submitted successfully`);
|
|
}
|
|
|
|
async approveTaskSubmission(userId: string, taskId: string) {
|
|
this.logger.log(`Approving task submission ${taskId} by user ${userId}`);
|
|
const task = await this.findTask({ id: taskId, assignedById: userId });
|
|
|
|
if (!task.submission) {
|
|
this.logger.error(`No submission found for task ${taskId}`);
|
|
throw new BadRequestException('TASK.NO_SUBMISSION');
|
|
}
|
|
|
|
if (task.submission.status == SubmissionStatus.APPROVED) {
|
|
this.logger.error(`Submission already approved for task ${taskId}`);
|
|
throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED');
|
|
}
|
|
|
|
await this.taskRepository.approveSubmission(task.submission);
|
|
this.logger.log(`Task submission ${taskId} approved successfully`);
|
|
}
|
|
|
|
async rejectTaskSubmission(userId: string, taskId: string) {
|
|
this.logger.log(`Rejecting task submission ${taskId} by user ${userId}`);
|
|
const task = await this.findTask({ id: taskId, assignedById: userId });
|
|
|
|
if (!task.submission) {
|
|
this.logger.error(`No submission found for task ${taskId}`);
|
|
throw new BadRequestException('TASK.NO_SUBMISSION');
|
|
}
|
|
|
|
if (task.submission.status == SubmissionStatus.REJECTED) {
|
|
this.logger.error(`Submission already rejected for task ${taskId}`);
|
|
throw new BadRequestException('TASK.SUBMISSION_ALREADY_REVIEWED');
|
|
}
|
|
|
|
await this.taskRepository.rejectSubmission(task.submission);
|
|
this.logger.log(`Task submission ${taskId} rejected successfully`);
|
|
}
|
|
|
|
async prepareTasksPictures(tasks: Task[]) {
|
|
this.logger.log(`Preparing tasks pictures`);
|
|
await Promise.all(
|
|
tasks.map(async (task) => {
|
|
const [imageUrl, submissionUrl, profilePictureUrl] = await Promise.all([
|
|
this.ociService.generatePreSignedUrl(task.image),
|
|
this.ociService.generatePreSignedUrl(task.submission?.proofOfCompletion),
|
|
this.ociService.generatePreSignedUrl(task.assignedTo.customer.user.profilePicture),
|
|
]);
|
|
|
|
task.image.url = imageUrl;
|
|
|
|
if (task.submission?.proofOfCompletion) {
|
|
task.submission.proofOfCompletion.url = submissionUrl;
|
|
}
|
|
|
|
if (task.assignedTo.customer.user.profilePicture) {
|
|
task.assignedTo.customer.user.profilePicture.url = profilePictureUrl;
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
|
|
private async validateTaskImage(userId: string, imageId?: string) {
|
|
if (!imageId) return;
|
|
|
|
this.logger.log(`Validating task image ${imageId}`);
|
|
const image = await this.documentService.findDocumentById(imageId);
|
|
|
|
if (!image) {
|
|
this.logger.error(`Task image ${imageId} not found`);
|
|
throw new BadRequestException('DOCUMENT.NOT_FOUND');
|
|
}
|
|
|
|
if (image.createdById && image.createdById !== userId) {
|
|
this.logger.error(`Task image ${imageId} not created by user ${userId}`);
|
|
throw new BadRequestException('DOCUMENT.NOT_CREATED_BY_USER');
|
|
}
|
|
}
|
|
}
|