mirror of
https://github.com/hknsh/project-knedita.git
synced 2024-11-28 17:41:15 +00:00
feat: added create kweek route & multi file upload
This commit is contained in:
parent
ac9026aea6
commit
cab4a0d9f1
21 changed files with 225 additions and 103 deletions
|
@ -3,6 +3,9 @@
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
|
"files": {
|
||||||
|
"ignore": ["dist/**", "node_modules"]
|
||||||
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Kweek" ADD COLUMN "attachments" TEXT[];
|
|
@ -27,14 +27,15 @@ model User {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Kweek {
|
model Kweek {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
content String
|
content String
|
||||||
authorId String
|
authorId String
|
||||||
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||||
likes KweekLike[]
|
likes KweekLike[]
|
||||||
comments Comments[]
|
comments Comments[]
|
||||||
createdAt DateTime @default(now())
|
attachments String[]
|
||||||
updatedAt DateTime @updatedAt
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
model KweekLike {
|
model KweekLike {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
ApiTags,
|
ApiTags,
|
||||||
ApiUnauthorizedResponse,
|
ApiUnauthorizedResponse,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
import { Public } from "src/public.decorator";
|
import { Public } from "src/decorators/public.decorator";
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
import { LoginUserDTO } from "./dto/login.dto";
|
import { LoginUserDTO } from "./dto/login.dto";
|
||||||
import { LocalAuthGuard } from "./local-auth.guard";
|
import { LocalAuthGuard } from "./local-auth.guard";
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ExecutionContext, Injectable } from "@nestjs/common";
|
||||||
import { Reflector } from "@nestjs/core";
|
import { Reflector } from "@nestjs/core";
|
||||||
import { AuthGuard } from "@nestjs/passport";
|
import { AuthGuard } from "@nestjs/passport";
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
import { IS_PUBLIC_KEY } from "src/public.decorator";
|
import { IS_PUBLIC_KEY } from "src/decorators/public.decorator";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtAuthGuard extends AuthGuard("jwt") {
|
export class JwtAuthGuard extends AuthGuard("jwt") {
|
||||||
|
|
27
src/decorators/create-kweek.decorator.ts
Normal file
27
src/decorators/create-kweek.decorator.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Thanks sandeepsuvit @ https://github.com/nestjs/swagger/issues/417
|
||||||
|
|
||||||
|
import { ApiBody } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
export const ApiCreateKweek =
|
||||||
|
(fieldName: string): MethodDecorator =>
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: idk typing for target
|
||||||
|
(target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
||||||
|
ApiBody({
|
||||||
|
required: true,
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
content: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
[fieldName]: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "string",
|
||||||
|
format: "binary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})(target, propertyKey, descriptor);
|
||||||
|
};
|
|
@ -1,11 +0,0 @@
|
||||||
import { createZodDto } from "nestjs-zod";
|
|
||||||
import { z } from "nestjs-zod/z";
|
|
||||||
|
|
||||||
export const CreateKweekSchema = z
|
|
||||||
.object({
|
|
||||||
content: z.string({ required_error: "Kweek content is required" }).max(300),
|
|
||||||
files: z.array(z.object({})),
|
|
||||||
})
|
|
||||||
.required();
|
|
||||||
|
|
||||||
export class CreateKweekDTO extends createZodDto(CreateKweekSchema) {}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { FilesInterceptor } from "@nest-lab/fastify-multer";
|
import { File, FilesInterceptor } from "@nest-lab/fastify-multer";
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
@ -17,8 +17,9 @@ import {
|
||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiTags,
|
ApiTags,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
import { Public } from "src/public.decorator";
|
import { ApiCreateKweek } from "src/decorators/create-kweek.decorator";
|
||||||
import { CreateKweekDTO } from "./dto/create-kweek.dto";
|
import { Public } from "src/decorators/public.decorator";
|
||||||
|
import { MultiFileValidation } from "src/validators/multi-file.validator";
|
||||||
import { UpdateKweekDTO } from "./dto/update-kweek.dto";
|
import { UpdateKweekDTO } from "./dto/update-kweek.dto";
|
||||||
import { KweeksService } from "./kweeks.service";
|
import { KweeksService } from "./kweeks.service";
|
||||||
|
|
||||||
|
@ -28,17 +29,17 @@ export class KweeksController {
|
||||||
constructor(private readonly kweeksService: KweeksService) {}
|
constructor(private readonly kweeksService: KweeksService) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@ApiConsumes("multipart/form-data")
|
||||||
|
@ApiCreateKweek("attachments")
|
||||||
|
@UseInterceptors(FilesInterceptor("attachments", 4))
|
||||||
@ApiOperation({ summary: "Creates a kweek" })
|
@ApiOperation({ summary: "Creates a kweek" })
|
||||||
@ApiBearerAuth("JWT")
|
@ApiBearerAuth("JWT")
|
||||||
@ApiConsumes("multipart/form-data")
|
|
||||||
@UseInterceptors(FilesInterceptor("attachments", 4))
|
|
||||||
create(
|
create(
|
||||||
@Body() createKweekDto: CreateKweekDTO,
|
@UploadedFiles(new MultiFileValidation()) attachments: Array<File>,
|
||||||
@UploadedFiles() attachments: File,
|
@Body() body,
|
||||||
@Request() req,
|
@Request() req,
|
||||||
) {
|
) {
|
||||||
// TODO: Find a way to handle multiple files with Swagger
|
return this.kweeksService.create(body.content, req.user.id, attachments);
|
||||||
return this.kweeksService.create(createKweekDto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Public()
|
@Public()
|
||||||
|
@ -51,7 +52,7 @@ export class KweeksController {
|
||||||
@Patch(":id")
|
@Patch(":id")
|
||||||
@ApiOperation({ summary: "Updates a kweek content" })
|
@ApiOperation({ summary: "Updates a kweek content" })
|
||||||
@ApiBearerAuth("JWT")
|
@ApiBearerAuth("JWT")
|
||||||
update(@Param("id") id: string, @Body() updateKweekDto: UpdateKweekDTO ) {
|
update(@Param("id") id: string, @Body() updateKweekDto: UpdateKweekDTO) {
|
||||||
return this.kweeksService.update(+id, updateKweekDto);
|
return this.kweeksService.update(+id, updateKweekDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { PrismaModule } from "src/prisma/prisma.module";
|
import { PrismaModule } from "src/services/prisma/prisma.module";
|
||||||
|
import { S3Service } from "src/services/s3/s3.service";
|
||||||
import { KweeksController } from "./kweeks.controller";
|
import { KweeksController } from "./kweeks.controller";
|
||||||
import { KweeksService } from "./kweeks.service";
|
import { KweeksService } from "./kweeks.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaModule],
|
||||||
controllers: [KweeksController],
|
controllers: [KweeksController],
|
||||||
providers: [KweeksService],
|
providers: [KweeksService, S3Service],
|
||||||
})
|
})
|
||||||
export class KweeksModule {}
|
export class KweeksModule {}
|
||||||
|
|
|
@ -1,13 +1,35 @@
|
||||||
|
import { File } from "@nest-lab/fastify-multer";
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/services/prisma/prisma.service";
|
||||||
import { CreateKweekDTO } from "./dto/create-kweek.dto";
|
import { S3Service } from "src/services/s3/s3.service";
|
||||||
import { UpdateKweekDTO } from "./dto/update-kweek.dto";
|
import { UpdateKweekDTO } from "./dto/update-kweek.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KweeksService {
|
export class KweeksService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(
|
||||||
create(createKweekDto: CreateKweekDTO) {
|
private readonly prisma: PrismaService,
|
||||||
return "This action adds a new kweek";
|
private readonly s3: S3Service,
|
||||||
|
) {}
|
||||||
|
async create(content: string, authorId: string, files: Array<File>) {
|
||||||
|
const post = await this.prisma.kweek.create({
|
||||||
|
data: {
|
||||||
|
content,
|
||||||
|
authorId,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const attachments = await this.s3.multiImageUploadToMinio(post.id, files);
|
||||||
|
|
||||||
|
await this.prisma.kweek.updateMany({
|
||||||
|
where: {
|
||||||
|
id: post.id
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
attachments
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.prisma.kweek.findUnique({where: {id: post.id}});
|
||||||
}
|
}
|
||||||
|
|
||||||
findOne(id: number) {
|
findOne(id: number) {
|
||||||
|
|
74
src/services/s3/s3.service.ts
Normal file
74
src/services/s3/s3.service.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import { PutObjectCommand, PutObjectCommandInput } from "@aws-sdk/client-s3";
|
||||||
|
import { BadRequestException, Injectable, InternalServerErrorException } from "@nestjs/common";
|
||||||
|
import { InjectS3, S3 } from "nestjs-s3";
|
||||||
|
import { File } from "@nest-lab/fastify-multer";
|
||||||
|
import sharp from "sharp";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class S3Service {
|
||||||
|
constructor(@InjectS3() private readonly s3: S3) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the image url if the upload to minio was successful.
|
||||||
|
*/
|
||||||
|
async uploadImageToMinio(userID: string, buffer: Buffer): Promise<string> {
|
||||||
|
const compressedBuffer = await sharp(buffer)
|
||||||
|
.resize(200, 200)
|
||||||
|
.webp({ quality: 70 })
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const params: PutObjectCommandInput = {
|
||||||
|
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
|
||||||
|
Key: `profile_images/${userID}.webp`,
|
||||||
|
Body: compressedBuffer,
|
||||||
|
ContentType: "image/webp",
|
||||||
|
ContentDisposition: "inline",
|
||||||
|
ACL: "public-read",
|
||||||
|
};
|
||||||
|
|
||||||
|
const { ETag } = await this.s3.send(new PutObjectCommand(params));
|
||||||
|
|
||||||
|
if (ETag !== null) {
|
||||||
|
return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/profile_images/${userID}.webp`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InternalServerErrorException(
|
||||||
|
"Failed to upload the profile image",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async multiImageUploadToMinio(id: string, files: Array<File>) {
|
||||||
|
const buffers: Buffer[] = [];
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const { buffer } = files[i];
|
||||||
|
|
||||||
|
const compressedBuffers = await sharp(buffer).webp({quality: 70}).toBuffer();
|
||||||
|
buffers.push(compressedBuffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadPromises = buffers.map(async (buffer, index) => {
|
||||||
|
return await this.multiUploadToMinio(buffer, id, index + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(uploadPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async multiUploadToMinio(buffer: Buffer, id: string, index: number) {
|
||||||
|
const Key = `posts/${id}/${index}.webp`;
|
||||||
|
|
||||||
|
const params: PutObjectCommandInput = {
|
||||||
|
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
|
||||||
|
Key,
|
||||||
|
Body: buffer,
|
||||||
|
ContentType: 'image/webp',
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.s3.send(new PutObjectCommand(params))
|
||||||
|
return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/${Key}`
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
import { PutObjectCommand, PutObjectCommandInput } from "@aws-sdk/client-s3";
|
|
||||||
import { Injectable, InternalServerErrorException } from "@nestjs/common";
|
|
||||||
import { InjectS3, S3 } from "nestjs-s3";
|
|
||||||
import sharp from "sharp";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class S3Service {
|
|
||||||
constructor(@InjectS3() private readonly s3: S3) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the image url if the upload to minio was successful.
|
|
||||||
*/
|
|
||||||
async uploadImageToMinio(userID: string, buffer: Buffer): Promise<string> {
|
|
||||||
const compressedBuffer = await sharp(buffer)
|
|
||||||
.resize(200, 200)
|
|
||||||
.webp({ quality: 70 })
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
const uploadParams: PutObjectCommandInput = {
|
|
||||||
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
|
|
||||||
Key: `profile_images/${userID}.webp`,
|
|
||||||
Body: compressedBuffer,
|
|
||||||
ContentType: "image/webp",
|
|
||||||
ContentDisposition: "inline",
|
|
||||||
ACL: "public-read",
|
|
||||||
};
|
|
||||||
|
|
||||||
const { ETag } = await this.s3.send(new PutObjectCommand(uploadParams));
|
|
||||||
|
|
||||||
if (ETag !== null) {
|
|
||||||
return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/profile_images/${userID}.webp`;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InternalServerErrorException(
|
|
||||||
"Failed to upload the profile image",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,8 +23,8 @@ import {
|
||||||
ApiTags,
|
ApiTags,
|
||||||
ApiUnauthorizedResponse,
|
ApiUnauthorizedResponse,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
import { Public } from "src/public.decorator";
|
import { Public } from "src/decorators/public.decorator";
|
||||||
import { BufferValidator } from "src/validators/buffer-validator.pipe";
|
import { BufferValidator } from "src/validators/buffer.validator";
|
||||||
import UploadImageValidator from "src/validators/upload-image.validator";
|
import UploadImageValidator from "src/validators/upload-image.validator";
|
||||||
import { CreateUserDTO } from "./dto/create-user.dto";
|
import { CreateUserDTO } from "./dto/create-user.dto";
|
||||||
import { UpdateEmailDTO } from "./dto/update-email.dto";
|
import { UpdateEmailDTO } from "./dto/update-email.dto";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { PrismaModule } from "src/prisma/prisma.module";
|
import { PrismaModule } from "src/services/prisma/prisma.module";
|
||||||
import { S3Service } from "./s3.service";
|
import { S3Service } from "src/services/s3/s3.service";
|
||||||
import { UserController } from "./users.controller";
|
import { UserController } from "./users.controller";
|
||||||
import { UserService } from "./users.service";
|
import { UserService } from "./users.service";
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,10 @@ import {
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from "bcrypt";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/services/prisma/prisma.service";
|
||||||
|
import { S3Service } from "src/services/s3/s3.service";
|
||||||
import { CreateUserDTO } from "./dto/create-user.dto";
|
import { CreateUserDTO } from "./dto/create-user.dto";
|
||||||
import { UserModel } from "./models/user.model";
|
import { UserModel } from "./models/user.model";
|
||||||
import { S3Service } from "./s3.service";
|
|
||||||
import { User } from "./types/user.type";
|
import { User } from "./types/user.type";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -53,6 +53,7 @@ export class UserService {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
content: true,
|
content: true,
|
||||||
|
attachments: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
},
|
},
|
||||||
|
@ -106,10 +107,7 @@ export class UserService {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateEmail(
|
async updateEmail(id: string, email: string): Promise<{ message: string }> {
|
||||||
id: string,
|
|
||||||
email: string,
|
|
||||||
): Promise<{ message: string }> {
|
|
||||||
const user = await this.prisma.user.findFirst({
|
const user = await this.prisma.user.findFirst({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
@ -155,7 +153,7 @@ export class UserService {
|
||||||
|
|
||||||
return await this.prisma.user.update({
|
return await this.prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
id
|
id,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
displayName,
|
displayName,
|
||||||
|
@ -199,10 +197,7 @@ export class UserService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async uploadImage(id: string, image: File) {
|
async uploadImage(id: string, image: File) {
|
||||||
const url = await this.s3.uploadImageToMinio(
|
const url = await this.s3.uploadImageToMinio(id, image.buffer);
|
||||||
id,
|
|
||||||
image.buffer,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await this.prisma.user.update({
|
return await this.prisma.user.update({
|
||||||
where: {
|
where: {
|
||||||
|
@ -217,13 +212,13 @@ export class UserService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
async delete(id: string) {
|
||||||
// TODO: Add validation for safety (like e-mail confirmation or password)
|
// TODO: Add validation for safety (like e-mail confirmation or password)
|
||||||
try {
|
try {
|
||||||
await this.prisma.user.deleteMany({where: {id}});;
|
await this.prisma.user.deleteMany({ where: { id } });
|
||||||
return { message: "User deleted"}
|
return { message: "User deleted" };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new BadRequestException('Error while trying to delete user')
|
throw new BadRequestException("Error while trying to delete user");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,9 @@ export class BufferValidator implements PipeTransform {
|
||||||
) as Promise<typeof import("file-type")>);
|
) as Promise<typeof import("file-type")>);
|
||||||
|
|
||||||
const ALLOWED_MIMES = ["image/jpeg", "image/png", "image/webp"];
|
const ALLOWED_MIMES = ["image/jpeg", "image/png", "image/webp"];
|
||||||
const Type = await fileTypeFromBuffer(value.buffer);
|
const buffer_type = await fileTypeFromBuffer(value.buffer);
|
||||||
|
|
||||||
if (!Type || !ALLOWED_MIMES.includes(Type.mime)) {
|
if (!buffer_type || !ALLOWED_MIMES.includes(buffer_type.mime)) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Invalid file type. Should be jpeg, png or webp",
|
"Invalid file type. Should be jpeg, png or webp",
|
||||||
);
|
);
|
45
src/validators/multi-file.validator.ts
Normal file
45
src/validators/multi-file.validator.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { File } from "@nest-lab/fastify-multer";
|
||||||
|
import { BadRequestException, Injectable, PipeTransform } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MultiFileValidation implements PipeTransform {
|
||||||
|
async transform(value: Array<File>) {
|
||||||
|
const ALLOWED_MIMES = ["image/jpeg", "image/png", "image/webp"];
|
||||||
|
|
||||||
|
const errors = [];
|
||||||
|
|
||||||
|
const { fileTypeFromBuffer } = await (eval(
|
||||||
|
'import("file-type")',
|
||||||
|
) as Promise<typeof import("file-type")>);
|
||||||
|
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const file = value[i];
|
||||||
|
const { buffer, size } = file;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const buffer_type = await fileTypeFromBuffer(buffer);
|
||||||
|
|
||||||
|
if (!buffer_type || !ALLOWED_MIMES.includes(buffer_type.mime)) {
|
||||||
|
errors.push({
|
||||||
|
file: i + 1,
|
||||||
|
error: "Invalid file type. Should be jpeg, png or webp",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxFileSize = 1024 * 1024;
|
||||||
|
|
||||||
|
if (size > maxFileSize) {
|
||||||
|
errors.push({ file: i + 1, error: "File is too big. Max 1MB" });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
errors.push({ file: i + 1, error: e.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length === 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestException(errors);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import {
|
||||||
const UploadImageValidator = new ParseFilePipe({
|
const UploadImageValidator = new ParseFilePipe({
|
||||||
validators: [
|
validators: [
|
||||||
new MaxFileSizeValidator({
|
new MaxFileSizeValidator({
|
||||||
maxSize: 15 * 1024 * 1024,
|
maxSize: 1024 * 1024,
|
||||||
message: "File too big. Max 1MB.",
|
message: "File too big. Max 1MB.",
|
||||||
}),
|
}),
|
||||||
new FileTypeValidator({ fileType: /^image\/(jpeg|png|webp)$/ }), // File extension validation
|
new FileTypeValidator({ fileType: /^image\/(jpeg|png|webp)$/ }), // File extension validation
|
||||||
|
|
Loading…
Reference in a new issue