Skip to content

Commit b56c213

Browse files
committed
♻️ refactor: file 업로드 테스트 하기 편한 구조로 변경 및 버그 수정
1 parent dff00b9 commit b56c213

File tree

11 files changed

+126
-168
lines changed

11 files changed

+126
-168
lines changed

server/src/common/disk/diskStorage.ts

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export enum FileUploadType {
2+
PROFILE_IMAGE = 'PROFILE_IMAGE',
3+
// 추후 추가될 타입들 명시
4+
}
5+
6+
export const FILE_SIZE_LIMITS = {
7+
// MB 단위
8+
IMAGE: 5 * 1024 * 1024,
9+
DEFAULT: 10 * 1024 * 1024,
10+
};

server/src/common/disk/fileUtils.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

server/src/common/disk/fileValidator.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

server/src/file/api-docs/uploadProfileFile.api-docs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ApiQuery,
99
ApiUnauthorizedResponse,
1010
} from '@nestjs/swagger';
11-
import { FileUploadType } from '../../common/disk/fileValidator';
11+
import { FileUploadType } from '../../common/disk/file-type';
1212

1313
export function ApiUploadProfileFile() {
1414
return applyDecorators(

server/src/file/controller/file.controller.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,24 @@ import {
66
UploadedFile,
77
Param,
88
UseGuards,
9-
BadRequestException,
109
Query,
1110
HttpStatus,
1211
HttpCode,
12+
FileTypeValidator,
13+
MaxFileSizeValidator,
14+
ParseFilePipe,
1315
} from '@nestjs/common';
1416
import { FileInterceptor } from '@nestjs/platform-express';
1517
import { FileService } from '../service/file.service';
1618
import { ApiTags } from '@nestjs/swagger';
1719
import { JwtGuard, Payload } from '../../common/guard/jwt.guard';
18-
import { createDynamicStorage } from '../../common/disk/diskStorage';
1920
import { ApiResponse } from '../../common/response/common.response';
2021
import { ApiUploadProfileFile } from '../api-docs/uploadProfileFile.api-docs';
2122
import { ApiDeleteFile } from '../api-docs/deleteFile.api-docs';
2223
import { DeleteFileParamRequestDto } from '../dto/request/deleteFile.dto';
2324
import { UploadFileQueryRequestDto } from '../dto/request/uploadFile.dto';
2425
import { CurrentUser } from '../../common/decorator';
26+
import { FILE_SIZE_LIMITS } from '../../common/disk/file-type';
2527

2628
@ApiTags('File')
2729
@Controller('file')
@@ -32,15 +34,29 @@ export class FileController {
3234
@Post('')
3335
@ApiUploadProfileFile()
3436
@HttpCode(HttpStatus.CREATED)
35-
@UseInterceptors(FileInterceptor('file', createDynamicStorage()))
37+
@UseInterceptors(FileInterceptor('file'))
3638
async upload(
37-
@UploadedFile() file: any,
39+
@UploadedFile(
40+
new ParseFilePipe({
41+
validators: [
42+
new MaxFileSizeValidator({
43+
maxSize: FILE_SIZE_LIMITS.IMAGE,
44+
message: 'file size limit',
45+
}),
46+
new FileTypeValidator({
47+
fileType: /image\/(png|jpg|jpeg|webp)/,
48+
skipMagicNumbersValidation: true,
49+
}),
50+
],
51+
}),
52+
)
53+
file: Express.Multer.File,
3854
@Query() query: UploadFileQueryRequestDto,
3955
@CurrentUser() user: Payload,
4056
) {
41-
if (!file) {
42-
throw new BadRequestException('파일이 선택되지 않았습니다.');
43-
}
57+
file.path = (
58+
await this.fileService.handleUpload(file, query.uploadType)
59+
).savedPath;
4460

4561
return ApiResponse.responseWithData(
4662
'파일 업로드에 성공했습니다.',

server/src/file/dto/request/uploadFile.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ApiProperty } from '@nestjs/swagger';
2-
import { FileUploadType } from '../../../common/disk/fileValidator';
2+
import { FileUploadType } from '../../../common/disk/file-type';
33
import { IsEnum } from 'class-validator';
44

55
export class UploadFileQueryRequestDto {

server/src/file/service/file.service.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,65 @@ import { Injectable, NotFoundException } from '@nestjs/common';
22
import { File } from '../entity/file.entity';
33
import { unlink, access } from 'fs/promises';
44
import { FileRepository } from '../repository/file.repository';
5-
import { User } from '../../user/entity/user.entity';
65
import { UploadFileResponseDto } from '../dto/response/uploadFile.dto';
76
import { WinstonLoggerService } from '../../common/logger/logger.service';
7+
import * as fs from 'fs/promises';
8+
import * as path from 'path';
9+
import { FileUploadType } from '../../common/disk/file-type';
810

911
@Injectable()
1012
export class FileService {
13+
private readonly basePath = '/app/objects';
14+
1115
constructor(
1216
private readonly fileRepository: FileRepository,
1317
private readonly logger: WinstonLoggerService,
1418
) {}
1519

16-
async create(file: any, userId: number): Promise<UploadFileResponseDto> {
20+
async handleUpload(file: Express.Multer.File, uploadType: FileUploadType) {
21+
const today = this.getDateString();
22+
const targetDir = path.join(this.basePath, uploadType, today);
23+
24+
await this.ensureDirectory(targetDir);
25+
26+
const filePath = path.join(targetDir, file.originalname);
27+
await fs.writeFile(filePath, file.buffer);
28+
29+
return {
30+
success: true,
31+
uploadType,
32+
savedPath: filePath,
33+
};
34+
}
35+
36+
private async ensureDirectory(dir: string) {
37+
await fs.mkdir(dir, { recursive: true });
38+
}
39+
40+
private getDateString(): string {
41+
const now = new Date();
42+
return now.toISOString().split('T')[0];
43+
}
44+
45+
async create(
46+
file: Express.Multer.File,
47+
userId: number,
48+
): Promise<UploadFileResponseDto> {
1749
const { originalname, mimetype, size, path } = file;
1850
const savedFile = await this.fileRepository.save({
1951
originalName: originalname,
2052
mimetype,
2153
size,
2254
path,
23-
user: { id: userId } as User,
24-
} as File);
55+
user: { id: userId },
56+
});
2557
const accessUrl = this.generateAccessUrl(path);
2658

2759
return UploadFileResponseDto.toResponseDto(savedFile, accessUrl);
2860
}
2961

3062
private generateAccessUrl(filePath: string): string {
31-
const baseUploadPath = '/app/objects';
32-
const relativePath = filePath.replace(baseUploadPath, '');
33-
return `/objects${relativePath}`;
63+
return filePath.replace(this.basePath, '/objects');
3464
}
3565

3666
async findById(id: number): Promise<File> {

server/test/file/dto/upload.dto.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { validate } from 'class-validator';
22
import { UploadFileQueryRequestDto } from '../../../src/file/dto/request/uploadFile.dto';
3-
import { FileUploadType } from '../../../src/common/disk/fileValidator';
3+
import { FileUploadType } from '../../../src/common/disk/file-type';
44

55
describe('UploadFileQueryRequestDto Test', () => {
66
let dto: UploadFileQueryRequestDto;

0 commit comments

Comments
 (0)