feat(uploads): add file upload and retrieval capabilities
Introduced a new uploads module with controllers and service methods for handling file uploads and retrievals. Includes route guards, file validations, and error handling for both avatar and product image uploads.
This commit is contained in:
parent
27f79d4e46
commit
314625bd9c
33
src/uploads/uploads.controller.ts
Normal file
33
src/uploads/uploads.controller.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Controller, Get, Param, Post, Res, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import { join } from 'node:path';
|
||||
import { AdminGuard, UserGuard } from "src/auth/auth.guard";
|
||||
|
||||
@Controller('uploads')
|
||||
export class UploadsController {
|
||||
@Post("p/new")
|
||||
@UseGuards(AdminGuard)
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
uploadProductImage(@UploadedFile() file) {
|
||||
|
||||
}
|
||||
|
||||
@Post("avatar")
|
||||
@UseGuards(UserGuard)
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
uploadAvatar(@UploadedFile() file) {
|
||||
console.log(file);
|
||||
}
|
||||
|
||||
@Get('avatar/:userId')
|
||||
seeUploadedAvatar(@Param('filepath') file: string, @Res() res: Response) {
|
||||
const filePath = join(__dirname, '..', 'avatars', file);
|
||||
res.sendFile(filePath);
|
||||
}
|
||||
|
||||
@Get('p/:filepath')
|
||||
seeUploadedFile(@Param('filepath') file: string, @Res() res: Response) {
|
||||
const filePath = join(__dirname, '..', 'files', file);
|
||||
res.sendFile(filePath);
|
||||
}
|
||||
}
|
15
src/uploads/uploads.module.ts
Normal file
15
src/uploads/uploads.module.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UploadsService } from './uploads.service';
|
||||
import { UploadsController } from './uploads.controller';
|
||||
import { MulterModule } from "@nestjs/platform-express";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MulterModule.register({
|
||||
dest: '/files',
|
||||
}),
|
||||
],
|
||||
providers: [UploadsService],
|
||||
controllers: [UploadsController]
|
||||
})
|
||||
export class UploadsModule {}
|
67
src/uploads/uploads.service.ts
Normal file
67
src/uploads/uploads.service.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common";
|
||||
import * as crypto from "node:crypto";
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { join } from "node:path";
|
||||
import * as console from "node:console";
|
||||
import FileType from 'file-type';
|
||||
|
||||
@Injectable()
|
||||
export class UploadsService {
|
||||
|
||||
//TODO save avatar under avatar-{userId}-{checksum}
|
||||
private async saveAvatar(userId: string, file: Buffer) {
|
||||
const checksum = await this.checkConditions(file)
|
||||
const fileType = await FileType.fileTypeFromBuffer(file)
|
||||
const fileName = `avatar-${userId}.${fileType.ext}`;
|
||||
await this.saveFile(fileName, file)
|
||||
}
|
||||
//TODO get
|
||||
private async getAvatar(userId: string) {
|
||||
|
||||
}
|
||||
|
||||
//TODO save product image under product-{productId}-{checksum}
|
||||
async saveProductImage(productId: string, imageBuffer: Buffer): Promise<string> {
|
||||
|
||||
}
|
||||
//TODO get
|
||||
async getProductImage(imageId: string): Promise<Buffer> {
|
||||
|
||||
}
|
||||
|
||||
private getChecksum(file: Buffer) {
|
||||
return crypto.createHash('sha256').update(file).digest('hex').toLowerCase();
|
||||
}
|
||||
|
||||
private async saveFile(fileName: string, file: Buffer) {
|
||||
try {
|
||||
await writeFile(join(process.cwd(), 'files/', fileName), file, 'utf8');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new InternalServerErrorException("File save failed !")
|
||||
}
|
||||
}
|
||||
|
||||
private async checkConditions(file: Buffer) {
|
||||
const checksum = this.getChecksum(file)
|
||||
const fileType = await FileType.fileTypeFromBuffer(file)
|
||||
if (!fileType || !fileType.mime.startsWith('image/')) {
|
||||
throw new BadRequestException('Invalid file type. Only images are allowed.');
|
||||
}
|
||||
//check file size is less than 2mb
|
||||
const fileSize = file.byteLength;
|
||||
if (fileSize > 2 * 1024 * 1024) {
|
||||
throw new BadRequestException('File size exceeds the limit. Maximum file size allowed is 2MB.');
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private async getFile(fileName: string){
|
||||
try {
|
||||
return await readFile(join(process.cwd(), 'files/', fileName));
|
||||
} catch (err) {
|
||||
throw new NotFoundException("File not found")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user