mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-26 06:09:41 +00:00
feat: add validation for documents
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { OciService } from '~/document/services';
|
import { DocumentService, OciService } from '~/document/services';
|
||||||
import { User } from '~/user/entities';
|
import { User } from '~/user/entities';
|
||||||
import { DeviceService } from '~/user/services';
|
import { DeviceService } from '~/user/services';
|
||||||
import { UpdateCustomerRequestDto, UpdateNotificationsSettingsRequestDto } from '../dtos/request';
|
import { UpdateCustomerRequestDto, UpdateNotificationsSettingsRequestDto } from '../dtos/request';
|
||||||
@ -13,6 +13,7 @@ export class CustomerService {
|
|||||||
private readonly customerRepository: CustomerRepository,
|
private readonly customerRepository: CustomerRepository,
|
||||||
private readonly ociService: OciService,
|
private readonly ociService: OciService,
|
||||||
private readonly deviceService: DeviceService,
|
private readonly deviceService: DeviceService,
|
||||||
|
private readonly documentService: DocumentService,
|
||||||
) {}
|
) {}
|
||||||
async updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto, deviceId: string) {
|
async updateNotificationSettings(userId: string, data: UpdateNotificationsSettingsRequestDto, deviceId: string) {
|
||||||
this.logger.log(`Updating notification settings for user ${userId}`);
|
this.logger.log(`Updating notification settings for user ${userId}`);
|
||||||
@ -34,6 +35,8 @@ export class CustomerService {
|
|||||||
|
|
||||||
async updateCustomer(userId: string, data: UpdateCustomerRequestDto): Promise<Customer> {
|
async updateCustomer(userId: string, data: UpdateCustomerRequestDto): Promise<Customer> {
|
||||||
this.logger.log(`Updating customer ${userId}`);
|
this.logger.log(`Updating customer ${userId}`);
|
||||||
|
|
||||||
|
await this.validateProfilePictureForCustomer(userId, data.profilePictureId);
|
||||||
await this.customerRepository.updateCustomer(userId, data);
|
await this.customerRepository.updateCustomer(userId, data);
|
||||||
this.logger.log(`Customer ${userId} updated successfully`);
|
this.logger.log(`Customer ${userId} updated successfully`);
|
||||||
return this.findCustomerById(userId);
|
return this.findCustomerById(userId);
|
||||||
@ -60,4 +63,20 @@ export class CustomerService {
|
|||||||
this.logger.log(`Customer ${id} found successfully`);
|
this.logger.log(`Customer ${id} found successfully`);
|
||||||
return customer;
|
return customer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async validateProfilePictureForCustomer(userId: string, profilePictureId?: string) {
|
||||||
|
if (!profilePictureId) return;
|
||||||
|
|
||||||
|
this.logger.log(`Validating profile picture ${profilePictureId}`);
|
||||||
|
|
||||||
|
const profilePicture = await this.documentService.findDocumentById(profilePictureId);
|
||||||
|
|
||||||
|
if (!profilePicture) {
|
||||||
|
this.logger.error(`Profile picture ${profilePictureId} not found`);
|
||||||
|
throw new BadRequestException('DOCUMENT.NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profilePicture.createdById && profilePicture.createdById !== userId) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddCreatedByToDocumentTable1736753223884 implements MigrationInterface {
|
||||||
|
name = 'AddCreatedByToDocumentTable1736753223884';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "documents" ADD "created_by_id" uuid `);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "documents" ADD CONSTRAINT "FK_7f46f4f77acde1dcedba64cb220" FOREIGN KEY ("created_by_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "documents" DROP CONSTRAINT "FK_7f46f4f77acde1dcedba64cb220"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "documents" DROP COLUMN "created_by_id"`);
|
||||||
|
}
|
||||||
|
}
|
@ -18,3 +18,4 @@ export * from './1734601976591-create-allowance-entities';
|
|||||||
export * from './1734861516657-create-gift-entities';
|
export * from './1734861516657-create-gift-entities';
|
||||||
export * from './1734944692999-create-notification-entity-and-edit-device';
|
export * from './1734944692999-create-notification-entity-and-edit-device';
|
||||||
export * from './1736414850257-add-flags-to-user-entity';
|
export * from './1736414850257-add-flags-to-user-entity';
|
||||||
|
export * from './1736753223884-add_created_by_to_document_table';
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Body, Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
|
import { Body, Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
import { memoryStorage } from 'multer';
|
import { memoryStorage } from 'multer';
|
||||||
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
|
import { AuthenticatedUser } from '~/common/decorators';
|
||||||
|
import { AccessTokenGuard } from '~/common/guards';
|
||||||
import { ResponseFactory } from '~/core/utils';
|
import { ResponseFactory } from '~/core/utils';
|
||||||
import { UploadDocumentRequestDto } from '../dtos/request';
|
import { UploadDocumentRequestDto } from '../dtos/request';
|
||||||
import { DocumentMetaResponseDto } from '../dtos/response';
|
import { DocumentMetaResponseDto } from '../dtos/response';
|
||||||
@ -9,6 +12,8 @@ import { DocumentType } from '../enums';
|
|||||||
import { DocumentService } from '../services';
|
import { DocumentService } from '../services';
|
||||||
@Controller('document')
|
@Controller('document')
|
||||||
@ApiTags('Document')
|
@ApiTags('Document')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(AccessTokenGuard)
|
||||||
export class DocumentController {
|
export class DocumentController {
|
||||||
constructor(private readonly documentService: DocumentService) {}
|
constructor(private readonly documentService: DocumentService) {}
|
||||||
|
|
||||||
@ -36,8 +41,9 @@ export class DocumentController {
|
|||||||
async createDocument(
|
async createDocument(
|
||||||
@UploadedFile() file: Express.Multer.File,
|
@UploadedFile() file: Express.Multer.File,
|
||||||
@Body() uploadedDocumentRequest: UploadDocumentRequestDto,
|
@Body() uploadedDocumentRequest: UploadDocumentRequestDto,
|
||||||
|
@AuthenticatedUser() user: IJwtPayload,
|
||||||
) {
|
) {
|
||||||
const document = await this.documentService.createDocument(file, uploadedDocumentRequest);
|
const document = await this.documentService.createDocument(file, uploadedDocumentRequest, user.sub);
|
||||||
|
|
||||||
return ResponseFactory.data(new DocumentMetaResponseDto(document));
|
return ResponseFactory.data(new DocumentMetaResponseDto(document));
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
import { Column, Entity, OneToMany, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
JoinColumn,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
OneToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
import { Customer } from '~/customer/entities';
|
import { Customer } from '~/customer/entities';
|
||||||
import { Gift } from '~/gift/entities';
|
import { Gift } from '~/gift/entities';
|
||||||
import { Junior, Theme } from '~/junior/entities';
|
import { Junior, Theme } from '~/junior/entities';
|
||||||
@ -22,6 +31,9 @@ export class Document {
|
|||||||
@Column({ type: 'varchar', length: 255, name: 'document_type' })
|
@Column({ type: 'varchar', length: 255, name: 'document_type' })
|
||||||
documentType!: DocumentType;
|
documentType!: DocumentType;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', nullable: true, name: 'created_by_id' })
|
||||||
|
createdById!: string;
|
||||||
|
|
||||||
@OneToOne(() => Customer, (customer) => customer.profilePicture, { onDelete: 'SET NULL' })
|
@OneToOne(() => Customer, (customer) => customer.profilePicture, { onDelete: 'SET NULL' })
|
||||||
customerPicture?: Customer;
|
customerPicture?: Customer;
|
||||||
|
|
||||||
@ -46,6 +58,10 @@ export class Document {
|
|||||||
@OneToMany(() => Gift, (gift) => gift.image)
|
@OneToMany(() => Gift, (gift) => gift.image)
|
||||||
gifts?: Gift[];
|
gifts?: Gift[];
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.createdDocuments, { nullable: true, onDelete: 'SET NULL' })
|
||||||
|
@JoinColumn({ name: 'created_by_id' })
|
||||||
|
createdBy?: User;
|
||||||
|
|
||||||
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
|
@ -8,12 +8,13 @@ import { Document } from '../entities';
|
|||||||
export class DocumentRepository {
|
export class DocumentRepository {
|
||||||
constructor(@InjectRepository(Document) private documentRepository: Repository<Document>) {}
|
constructor(@InjectRepository(Document) private documentRepository: Repository<Document>) {}
|
||||||
|
|
||||||
createDocument(document: UploadResponseDto) {
|
createDocument(userId: string, document: UploadResponseDto) {
|
||||||
return this.documentRepository.save(
|
return this.documentRepository.save(
|
||||||
this.documentRepository.create({
|
this.documentRepository.create({
|
||||||
name: document.name,
|
name: document.name,
|
||||||
documentType: document.documentType,
|
documentType: document.documentType,
|
||||||
extension: document.extension,
|
extension: document.extension,
|
||||||
|
createdById: userId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -21,4 +22,8 @@ export class DocumentRepository {
|
|||||||
findDocuments(where: FindOptionsWhere<Document>) {
|
findDocuments(where: FindOptionsWhere<Document>) {
|
||||||
return this.documentRepository.find({ where });
|
return this.documentRepository.find({ where });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findDocumentById(id: string) {
|
||||||
|
return this.documentRepository.findOne({ where: { id } });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,19 @@ import { OciService } from './oci.service';
|
|||||||
export class DocumentService {
|
export class DocumentService {
|
||||||
private readonly logger = new Logger(DocumentService.name);
|
private readonly logger = new Logger(DocumentService.name);
|
||||||
constructor(private readonly ociService: OciService, private readonly documentRepository: DocumentRepository) {}
|
constructor(private readonly ociService: OciService, private readonly documentRepository: DocumentRepository) {}
|
||||||
async createDocument(file: Express.Multer.File, uploadedDocumentRequest: UploadDocumentRequestDto) {
|
async createDocument(file: Express.Multer.File, uploadedDocumentRequest: UploadDocumentRequestDto, userId: string) {
|
||||||
this.logger.log(`creating document for with type ${uploadedDocumentRequest.documentType}`);
|
this.logger.log(`creating document for with type ${uploadedDocumentRequest.documentType}`);
|
||||||
const uploadedFile = await this.ociService.uploadFile(file, uploadedDocumentRequest);
|
const uploadedFile = await this.ociService.uploadFile(file, uploadedDocumentRequest);
|
||||||
return this.documentRepository.createDocument(uploadedFile);
|
return this.documentRepository.createDocument(userId, uploadedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
findDocuments(where: FindOptionsWhere<Document>) {
|
findDocuments(where: FindOptionsWhere<Document>) {
|
||||||
this.logger.log(`finding documents with where clause ${JSON.stringify(where)}`);
|
this.logger.log(`finding documents with where clause ${JSON.stringify(where)}`);
|
||||||
return this.documentRepository.findDocuments(where);
|
return this.documentRepository.findDocuments(where);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findDocumentById(id: string) {
|
||||||
|
this.logger.log(`finding document with id ${id}`);
|
||||||
|
return this.documentRepository.findDocumentById(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||||
import { Roles } from '~/auth/enums';
|
import { Roles } from '~/auth/enums';
|
||||||
import { IJwtPayload } from '~/auth/interfaces';
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
import { OciService } from '~/document/services';
|
import { DocumentService, OciService } from '~/document/services';
|
||||||
import { JuniorService } from '~/junior/services';
|
import { JuniorService } from '~/junior/services';
|
||||||
import { CreateGiftRequestDto, GiftFiltersRequestDto, GiftReplyRequestDto } from '../dtos/request';
|
import { CreateGiftRequestDto, GiftFiltersRequestDto, GiftReplyRequestDto } from '../dtos/request';
|
||||||
import { Gift } from '../entities';
|
import { Gift } from '../entities';
|
||||||
@ -15,10 +15,13 @@ export class GiftsService {
|
|||||||
private readonly juniorService: JuniorService,
|
private readonly juniorService: JuniorService,
|
||||||
private readonly giftsRepository: GiftsRepository,
|
private readonly giftsRepository: GiftsRepository,
|
||||||
private readonly ociService: OciService,
|
private readonly ociService: OciService,
|
||||||
|
private readonly documentService: DocumentService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createGift(guardianId: string, body: CreateGiftRequestDto) {
|
async createGift(guardianId: string, body: CreateGiftRequestDto) {
|
||||||
this.logger.log(`Creating gift for junior ${body.recipientId} by guardian ${guardianId}`);
|
this.logger.log(`Creating gift for junior ${body.recipientId} by guardian ${guardianId}`);
|
||||||
|
|
||||||
|
await this.validateGiftImage(guardianId, body.imageId);
|
||||||
const doesJuniorBelongToGuardian = await this.juniorService.doesJuniorBelongToGuardian(
|
const doesJuniorBelongToGuardian = await this.juniorService.doesJuniorBelongToGuardian(
|
||||||
guardianId,
|
guardianId,
|
||||||
body.recipientId,
|
body.recipientId,
|
||||||
@ -116,4 +119,21 @@ export class GiftsService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async validateGiftImage(userId: string, imageId?: string) {
|
||||||
|
if (!imageId) return;
|
||||||
|
|
||||||
|
this.logger.log(`Validating gift image ${imageId}`);
|
||||||
|
const image = await this.documentService.findDocumentById(imageId);
|
||||||
|
|
||||||
|
if (!image) {
|
||||||
|
this.logger.error(`Gift image ${imageId} not found`);
|
||||||
|
throw new BadRequestException('DOCUMENT.NOT_FOUND');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.createdById && image.createdById !== userId) {
|
||||||
|
this.logger.error(`Gift image ${imageId} does not belong to user ${userId}`);
|
||||||
|
throw new BadRequestException('DOCUMENT.NOT_BELONG_TO_USER');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,4 +42,10 @@ export class JuniorRepository {
|
|||||||
findThemeForJunior(juniorId: string) {
|
findThemeForJunior(juniorId: string) {
|
||||||
return this.juniorRepository.manager.findOne(Theme, { where: { juniorId }, relations: ['avatar'] });
|
return this.juniorRepository.manager.findOne(Theme, { where: { juniorId }, relations: ['avatar'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findJuniorByCivilId(civilIdFrontId: string, civilIdBackId: string) {
|
||||||
|
return this.juniorRepository.findOne({
|
||||||
|
where: [{ civilIdFrontId, civilIdBackId }, { civilIdFrontId }, { civilIdBackId }],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { Roles } from '~/auth/enums';
|
|||||||
import { PageOptionsRequestDto } from '~/core/dtos';
|
import { PageOptionsRequestDto } from '~/core/dtos';
|
||||||
import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity';
|
import { CustomerNotificationSettings } from '~/customer/entities/customer-notification-settings.entity';
|
||||||
import { CustomerService } from '~/customer/services';
|
import { CustomerService } from '~/customer/services';
|
||||||
|
import { DocumentService, OciService } from '~/document/services';
|
||||||
import { UserService } from '~/user/services';
|
import { UserService } from '~/user/services';
|
||||||
import { CreateJuniorRequestDto, SetThemeRequestDto } from '../dtos/request';
|
import { CreateJuniorRequestDto, SetThemeRequestDto } from '../dtos/request';
|
||||||
import { Junior } from '../entities';
|
import { Junior } from '../entities';
|
||||||
@ -18,6 +19,8 @@ export class JuniorService {
|
|||||||
private readonly juniorTokenService: JuniorTokenService,
|
private readonly juniorTokenService: JuniorTokenService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly customerService: CustomerService,
|
private readonly customerService: CustomerService,
|
||||||
|
private readonly documentService: DocumentService,
|
||||||
|
private readonly ociService: OciService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Transactional()
|
@Transactional()
|
||||||
@ -30,6 +33,8 @@ export class JuniorService {
|
|||||||
throw new BadRequestException('USER.ALREADY_EXISTS');
|
throw new BadRequestException('USER.ALREADY_EXISTS');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.validateJuniorDocuments(guardianId, body.civilIdFrontId, body.civilIdBackId);
|
||||||
|
|
||||||
const user = await this.userService.createUser({
|
const user = await this.userService.createUser({
|
||||||
email: body.email,
|
email: body.email,
|
||||||
countryCode: body.countryCode,
|
countryCode: body.countryCode,
|
||||||
@ -75,6 +80,11 @@ export class JuniorService {
|
|||||||
@Transactional()
|
@Transactional()
|
||||||
async setTheme(body: SetThemeRequestDto, juniorId: string) {
|
async setTheme(body: SetThemeRequestDto, juniorId: string) {
|
||||||
this.logger.log(`Setting theme for junior ${juniorId}`);
|
this.logger.log(`Setting theme for junior ${juniorId}`);
|
||||||
|
const document = await this.documentService.findDocumentById(body.avatarId);
|
||||||
|
if (!document || document.createdById !== juniorId) {
|
||||||
|
this.logger.error(`Document ${body.avatarId} not found or not created by junior ${juniorId}`);
|
||||||
|
throw new BadRequestException('DOCUMENT.NOT_FOUND');
|
||||||
|
}
|
||||||
const junior = await this.findJuniorById(juniorId);
|
const junior = await this.findJuniorById(juniorId);
|
||||||
if (junior.theme) {
|
if (junior.theme) {
|
||||||
this.logger.log(`Removing existing theme for junior ${juniorId}`);
|
this.logger.log(`Removing existing theme for junior ${juniorId}`);
|
||||||
@ -86,9 +96,12 @@ export class JuniorService {
|
|||||||
return this.juniorRepository.findThemeForJunior(juniorId);
|
return this.juniorRepository.findThemeForJunior(juniorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
findJuniorsByGuardianId(guardianId: string, pageOptions: PageOptionsRequestDto) {
|
async findJuniorsByGuardianId(guardianId: string, pageOptions: PageOptionsRequestDto): Promise<[Junior[], number]> {
|
||||||
this.logger.log(`Finding juniors for guardian ${guardianId}`);
|
this.logger.log(`Finding juniors for guardian ${guardianId}`);
|
||||||
return this.juniorRepository.findJuniorsByGuardianId(guardianId, pageOptions);
|
const [juniors, itemCount] = await this.juniorRepository.findJuniorsByGuardianId(guardianId, pageOptions);
|
||||||
|
this.logger.log(`Juniors found for guardian ${guardianId}`);
|
||||||
|
await this.prepareJuniorImages(juniors);
|
||||||
|
return [juniors, itemCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
async validateToken(token: string) {
|
async validateToken(token: string) {
|
||||||
@ -108,4 +121,49 @@ export class JuniorService {
|
|||||||
|
|
||||||
return !!junior;
|
return !!junior;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async validateJuniorDocuments(userId: string, civilIdFrontId: string, civilIdBackId: string) {
|
||||||
|
this.logger.log(`Validating junior documents`);
|
||||||
|
if (!civilIdFrontId || !civilIdBackId) {
|
||||||
|
this.logger.error('Civil id front and back are required');
|
||||||
|
throw new BadRequestException('JUNIOR.CIVIL_ID_REQUIRED');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [civilIdFront, civilIdBack] = await Promise.all([
|
||||||
|
this.documentService.findDocumentById(civilIdFrontId),
|
||||||
|
this.documentService.findDocumentById(civilIdBackId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!civilIdFront || !civilIdBack) {
|
||||||
|
this.logger.error('Civil id front or back not found');
|
||||||
|
throw new BadRequestException('JUNIOR.CIVIL_ID_REQUIRED');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (civilIdFront.createdById !== userId || civilIdBack.createdById !== userId) {
|
||||||
|
this.logger.error(`Civil id front or back not created by user with id ${userId}`);
|
||||||
|
throw new BadRequestException('JUNIOR.CIVIL_ID_NOT_CREATED_BY_GUARDIAN');
|
||||||
|
}
|
||||||
|
|
||||||
|
const juniorWithSameCivilId = await this.juniorRepository.findJuniorByCivilId(civilIdFrontId, civilIdBackId);
|
||||||
|
|
||||||
|
if (juniorWithSameCivilId) {
|
||||||
|
this.logger.error(
|
||||||
|
`Junior with civil id front ${civilIdFrontId} and civil id back ${civilIdBackId} already exists`,
|
||||||
|
);
|
||||||
|
throw new BadRequestException('JUNIOR.CIVIL_ID_ALREADY_EXISTS');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async prepareJuniorImages(juniors: Junior[]) {
|
||||||
|
this.logger.log(`Preparing junior images`);
|
||||||
|
await Promise.all(
|
||||||
|
juniors.map(async (junior) => {
|
||||||
|
const profilePicture = junior.customer.profilePicture;
|
||||||
|
|
||||||
|
if (profilePicture) {
|
||||||
|
profilePicture.url = await this.ociService.generatePreSignedUrl(profilePicture);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { FindOptionsWhere } from 'typeorm';
|
import { FindOptionsWhere } from 'typeorm';
|
||||||
import { IJwtPayload } from '~/auth/interfaces';
|
import { IJwtPayload } from '~/auth/interfaces';
|
||||||
import { OciService } from '~/document/services';
|
import { DocumentService, OciService } from '~/document/services';
|
||||||
import { CreateTaskRequestDto, TasksFilterOptions, TaskSubmissionRequestDto } from '../dtos/request';
|
import { CreateTaskRequestDto, TasksFilterOptions, TaskSubmissionRequestDto } from '../dtos/request';
|
||||||
import { Task } from '../entities';
|
import { Task } from '../entities';
|
||||||
import { SubmissionStatus, TaskStatus } from '../enums';
|
import { SubmissionStatus, TaskStatus } from '../enums';
|
||||||
@ -11,7 +11,11 @@ import { TaskRepository } from '../repositories';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class TaskService {
|
export class TaskService {
|
||||||
private readonly logger = new Logger(TaskService.name);
|
private readonly logger = new Logger(TaskService.name);
|
||||||
constructor(private readonly taskRepository: TaskRepository, private readonly ociService: OciService) {}
|
constructor(
|
||||||
|
private readonly taskRepository: TaskRepository,
|
||||||
|
private readonly ociService: OciService,
|
||||||
|
private readonly documentService: DocumentService,
|
||||||
|
) {}
|
||||||
async createTask(userId: string, body: CreateTaskRequestDto) {
|
async createTask(userId: string, body: CreateTaskRequestDto) {
|
||||||
this.logger.log(`Creating task for user ${userId}`);
|
this.logger.log(`Creating task for user ${userId}`);
|
||||||
if (moment(body.dueDate).isBefore(moment(body.startDate))) {
|
if (moment(body.dueDate).isBefore(moment(body.startDate))) {
|
||||||
@ -23,6 +27,8 @@ export class TaskService {
|
|||||||
this.logger.error(`Due date must be in the future`);
|
this.logger.error(`Due date must be in the future`);
|
||||||
throw new BadRequestException('TASK.DUE_DATE_IN_PAST');
|
throw new BadRequestException('TASK.DUE_DATE_IN_PAST');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.validateTaskImage(userId, body.imageId);
|
||||||
const task = await this.taskRepository.createTask(userId, body);
|
const task = await this.taskRepository.createTask(userId, body);
|
||||||
|
|
||||||
this.logger.log(`Task ${task.id} created successfully`);
|
this.logger.log(`Task ${task.id} created successfully`);
|
||||||
@ -66,6 +72,8 @@ export class TaskService {
|
|||||||
throw new BadRequestException('TASK.PROOF_REQUIRED');
|
throw new BadRequestException('TASK.PROOF_REQUIRED');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.validateTaskImage(userId, body.imageId);
|
||||||
|
|
||||||
await this.taskRepository.createSubmission(task, body);
|
await this.taskRepository.createSubmission(task, body);
|
||||||
this.logger.log(`Task ${taskId} submitted successfully`);
|
this.logger.log(`Task ${taskId} submitted successfully`);
|
||||||
}
|
}
|
||||||
@ -128,4 +136,21 @@ export class TaskService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { Notification } from '~/common/modules/notification/entities';
|
import { Notification } from '~/common/modules/notification/entities';
|
||||||
import { Otp } from '~/common/modules/otp/entities';
|
import { Otp } from '~/common/modules/otp/entities';
|
||||||
import { Customer } from '~/customer/entities/customer.entity';
|
import { Customer } from '~/customer/entities/customer.entity';
|
||||||
|
import { Document } from '~/document/entities';
|
||||||
import { Roles } from '../../auth/enums';
|
import { Roles } from '../../auth/enums';
|
||||||
import { Device } from './device.entity';
|
import { Device } from './device.entity';
|
||||||
|
|
||||||
@ -64,6 +65,9 @@ export class User extends BaseEntity {
|
|||||||
@OneToMany(() => Notification, (notification) => notification.user)
|
@OneToMany(() => Notification, (notification) => notification.user)
|
||||||
notifications!: Notification[];
|
notifications!: Notification[];
|
||||||
|
|
||||||
|
@OneToMany(() => Document, (document) => document.createdBy)
|
||||||
|
createdDocuments!: Document[];
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
|
@CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user