From 5784451f6b3cf1c4968848fe306e1a2cdbcc6c65 Mon Sep 17 00:00:00 2001 From: asn6878 Date: Mon, 11 Aug 2025 20:47:15 +0900 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9C=A8=20feat:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=EC=8B=9C=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/file/service/file.service.ts | 25 ++++++++++++++++++++++--- server/src/user/module/user.module.ts | 3 ++- server/src/user/service/user.service.ts | 10 +++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/server/src/file/service/file.service.ts b/server/src/file/service/file.service.ts index 180c6068..862657ac 100644 --- a/server/src/file/service/file.service.ts +++ b/server/src/file/service/file.service.ts @@ -1,6 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { File } from '../entity/file.entity'; -import { unlinkSync, existsSync } from 'fs'; +import { unlink, access } from 'fs/promises'; import { FileRepository } from '../repository/file.repository'; import { User } from '../../user/entity/user.entity'; import { FileUploadResponseDto } from '../dto/createFile.dto'; @@ -50,8 +50,11 @@ export class FileService { async deleteFile(id: number): Promise { const file = await this.findById(id); - if (existsSync(file.path)) { - unlinkSync(file.path); + try { + await access(file.path); + await unlink(file.path); + } catch (error) { + console.warn(`파일 삭제 실패: ${file.path}`, error); } await this.fileRepository.delete(id); @@ -60,4 +63,20 @@ export class FileService { async getFileInfo(id: number): Promise { return this.findById(id); } + + async deleteByPath(path: string): Promise { + const file = await this.fileRepository.findOne({ where: { path } }); + if (file) { + try { + await access(file.path); + await unlink(file.path); + } catch (error) { + console.warn(`파일 삭제 실패: ${file.path}`, error); + } + + await this.fileRepository.delete(file.id); + } else { + throw new NotFoundException('파일을 찾을 수 없습니다.'); + } + } } diff --git a/server/src/user/module/user.module.ts b/server/src/user/module/user.module.ts index bb0c2475..24020eec 100644 --- a/server/src/user/module/user.module.ts +++ b/server/src/user/module/user.module.ts @@ -11,9 +11,10 @@ import { AdminModule } from '../../admin/module/admin.module'; import { GoogleOAuthProvider } from '../provider/google.provider'; import { GithubOAuthProvider } from '../provider/github.provider'; import { UserScheduler } from '../scheduler/user.scheduler'; +import { FileModule } from '../../file/module/file.module'; @Module({ - imports: [JwtAuthModule, AdminModule, ScheduleModule.forRoot()], + imports: [JwtAuthModule, AdminModule, FileModule, ScheduleModule.forRoot()], controllers: [UserController, OAuthController], providers: [ UserService, diff --git a/server/src/user/service/user.service.ts b/server/src/user/service/user.service.ts index 0734f51a..ef159586 100644 --- a/server/src/user/service/user.service.ts +++ b/server/src/user/service/user.service.ts @@ -18,6 +18,7 @@ import { ConfigService } from '@nestjs/config'; import { cookieConfig } from '../../common/cookie/cookie.config'; import { Payload } from '../../common/guard/jwt.guard'; import { UpdateUserDto } from '../dto/request/update-user.dto'; +import { FileService } from '../../file/service/file.service'; @Injectable() export class UserService { @@ -27,6 +28,7 @@ export class UserService { private readonly emailService: EmailService, private readonly jwtService: JwtService, private readonly configService: ConfigService, + private readonly fileService: FileService, ) {} async checkEmailDuplication(email: string): Promise { @@ -166,9 +168,15 @@ export class UserService { if (updateData.userName !== undefined) { user.userName = updateData.userName; } - if (updateData.profileImage !== undefined) { + + if ( + updateData.profileImage !== undefined && + user.profileImage !== updateData.profileImage + ) { + await this.fileService.deleteByPath(user.profileImage); user.profileImage = updateData.profileImage; } + if (updateData.introduction !== undefined) { user.introduction = updateData.introduction; } From b7d8fb4d43ae23289473c6c1bcb579e09041d5da Mon Sep 17 00:00:00 2001 From: asn6878 Date: Mon, 11 Aug 2025 20:56:01 +0900 Subject: [PATCH 02/11] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=20=EC=B6=9C=EB=A0=A5=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/file/service/file.service.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server/src/file/service/file.service.ts b/server/src/file/service/file.service.ts index 862657ac..6ba82928 100644 --- a/server/src/file/service/file.service.ts +++ b/server/src/file/service/file.service.ts @@ -4,10 +4,14 @@ import { unlink, access } from 'fs/promises'; import { FileRepository } from '../repository/file.repository'; import { User } from '../../user/entity/user.entity'; import { FileUploadResponseDto } from '../dto/createFile.dto'; +import { WinstonLoggerService } from '../../common/logger/logger.service'; @Injectable() export class FileService { - constructor(private readonly fileRepository: FileRepository) {} + constructor( + private readonly fileRepository: FileRepository, + private readonly logger: WinstonLoggerService, + ) {} async create(file: any, userId: number): Promise { const { originalName, mimetype, size, path } = file; @@ -54,7 +58,7 @@ export class FileService { await access(file.path); await unlink(file.path); } catch (error) { - console.warn(`파일 삭제 실패: ${file.path}`, error); + this.logger.warn(`파일 삭제 실패: ${file.path}`, 'FileService'); } await this.fileRepository.delete(id); @@ -71,7 +75,7 @@ export class FileService { await access(file.path); await unlink(file.path); } catch (error) { - console.warn(`파일 삭제 실패: ${file.path}`, error); + this.logger.warn(`파일 삭제 실패: ${file.path}`, 'FileService'); } await this.fileRepository.delete(file.id); From ff85e482a0c92ec946b8765e2a987ef59d445991 Mon Sep 17 00:00:00 2001 From: DinoDeveloper Date: Sat, 16 Aug 2025 00:53:09 +0900 Subject: [PATCH 03/11] =?UTF-8?q?=E2=9C=A8=20feat:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=B6=94=EB=A1=A0=20=EB=B0=A9=EB=B2=95=20?= =?UTF-8?q?Query=20Params=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/common/disk/diskStorage.ts | 40 ++++++++----------------- server/src/common/disk/fileUtils.ts | 23 ++++++++++++-- server/src/common/disk/fileValidator.ts | 25 ++++++++-------- 3 files changed, 45 insertions(+), 43 deletions(-) diff --git a/server/src/common/disk/diskStorage.ts b/server/src/common/disk/diskStorage.ts index acb0c9c4..e81eaefb 100644 --- a/server/src/common/disk/diskStorage.ts +++ b/server/src/common/disk/diskStorage.ts @@ -1,25 +1,22 @@ import { diskStorage } from 'multer'; -import { createDirectoryIfNotExists, getFileName } from './fileUtils'; import { - validateFile, - ALLOWED_MIME_TYPES, - FILE_SIZE_LIMITS, - FileUploadType, -} from './fileValidator'; + createDirectoryIfNotExists, + getFileName, + validateAndGetUploadType, +} from './fileUtils'; +import { validateFile, FILE_SIZE_LIMITS } from './fileValidator'; export const createDynamicStorage = () => { return { storage: diskStorage({ destination: (req: any, file, cb) => { - const uploadType: FileUploadType = - // TODO: 파일 업로드 타입 추론부 확정나면 변경하기 - req.body?.uploadType || - req.query?.uploadType || - req.uploadType || - 'PROFILE_IMAGE'; - - const uploadPath = createDirectoryIfNotExists(uploadType); - cb(null, uploadPath); + try { + const uploadType = validateAndGetUploadType(req.query.uploadType); + const uploadPath = createDirectoryIfNotExists(uploadType); + cb(null, uploadPath); + } catch (error) { + cb(error, null); + } }, filename: (req, file, cb) => { cb(null, getFileName(file)); @@ -27,18 +24,7 @@ export const createDynamicStorage = () => { }), fileFilter: (req: any, file: any, cb: any) => { try { - const uploadType: FileUploadType = - req.body?.uploadType || - req.query?.uploadType || - req.uploadType || - 'PROFILE_IMAGE'; - - // uploadType에 따른 허용 타입 결정 - let allowedTypes: string[] = []; - if (uploadType === 'PROFILE_IMAGE') { - allowedTypes = ALLOWED_MIME_TYPES.IMAGE; - } // else if 로 업로드 타입별 허용 MIME TYPE 결정 구문 추가 하기 - + const uploadType = validateAndGetUploadType(req.query.uploadType); validateFile(file, uploadType); cb(null, true); } catch (error) { diff --git a/server/src/common/disk/fileUtils.ts b/server/src/common/disk/fileUtils.ts index a2e58812..03f1a034 100644 --- a/server/src/common/disk/fileUtils.ts +++ b/server/src/common/disk/fileUtils.ts @@ -3,10 +3,10 @@ import { ensureDirSync } from 'fs-extra'; import { promises as fs } from 'fs'; import { existsSync } from 'fs'; import { v4 as uuidv4 } from 'uuid'; +import { BadRequestException } from '@nestjs/common'; +import { FileUploadType } from './fileValidator'; -// TODO: 테스트 후 기본 경로 제거 하기. -const BASE_UPLOAD_PATH = - process.env.UPLOAD_BASE_PATH || '/var/web05-Denamu/objects'; +const BASE_UPLOAD_PATH = '/var/web05-Denamu/objects'; export const generateFilePath = (originalPath: string): string => { const now = new Date(); @@ -34,3 +34,20 @@ export const deleteFileIfExists = async (filePath: string): Promise => { await fs.unlink(filePath); } }; + +// Interceptor가 Pipes보다 먼저 실행되기에, 타입 유효성 검사 필요함 +export const validateAndGetUploadType = (uploadType: any): FileUploadType => { + if (!uploadType) { + throw new BadRequestException( + `uploadType이 필요합니다. 허용된 타입: ${Object.values(FileUploadType).join(', ')}`, + ); + } + + if (!Object.values(FileUploadType).includes(uploadType)) { + throw new BadRequestException( + `유효하지 않은 파일 업로드 타입입니다. 허용된 타입: ${Object.values(FileUploadType).join(', ')}`, + ); + } + + return uploadType as FileUploadType; +}; diff --git a/server/src/common/disk/fileValidator.ts b/server/src/common/disk/fileValidator.ts index 2b4f874d..95b30fd7 100644 --- a/server/src/common/disk/fileValidator.ts +++ b/server/src/common/disk/fileValidator.ts @@ -5,11 +5,10 @@ export const ALLOWED_MIME_TYPES = { ALL: [] as string[], }; -export const FILE_UPLOAD_TYPE = { - PROFILE_IMAGE: 'profileImg', -} as const; - -export type FileUploadType = keyof typeof FILE_UPLOAD_TYPE; +export enum FileUploadType { + PROFILE_IMAGE = 'PROFILE_IMAGE', + // 추후 추가될 타입들 명시 +} ALLOWED_MIME_TYPES.ALL = [...ALLOWED_MIME_TYPES.IMAGE]; @@ -19,30 +18,30 @@ export const FILE_SIZE_LIMITS = { DEFAULT: 10 * 1024 * 1024, }; -export const validateFile = (file: any, uploadType: string) => { +export const validateFile = (file: any, uploadType: FileUploadType) => { let allowedTypes: string[] = []; - if (uploadType === 'PROFILE_IMAGE') { + if (uploadType === FileUploadType.PROFILE_IMAGE) { allowedTypes = ALLOWED_MIME_TYPES.IMAGE; - } + } // else if 구문 이나 switch 써서 타입 추가되면 유효성 ALLOWED TYPES 매핑해주기! - validateFileType(file, allowedTypes); + validateFileType(file, uploadType, allowedTypes); validateFileSize(file, uploadType); }; -const validateFileType = (file: any, allowedTypes?: string[]) => { +const validateFileType = (file: any, t, allowedTypes?: string[]) => { const types = allowedTypes || []; if (!types.includes(file.mimetype)) { throw new BadRequestException( - `지원하지 않는 파일 형식입니다. 지원 형식: ${types.join(', ')}`, + `받은 파일 타입 ${t}}지원하지 않는 파일 형식입니다. 지원 형식: ${types.join(', ')}`, ); } }; -const validateFileSize = (file: any, uploadType: string) => { +const validateFileSize = (file: any, uploadType: FileUploadType) => { let sizeLimit: number; - if (uploadType === 'PROFILE_IMAGE') { + if (uploadType === FileUploadType.PROFILE_IMAGE) { sizeLimit = FILE_SIZE_LIMITS.IMAGE; } else { sizeLimit = FILE_SIZE_LIMITS.DEFAULT; From bc6e7eb4d1f1831a6443728dce0a7f077e017389 Mon Sep 17 00:00:00 2001 From: DinoDeveloper Date: Sat, 16 Aug 2025 00:54:09 +0900 Subject: [PATCH 04/11] =?UTF-8?q?=E2=9C=A8=20feat:=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EB=90=9C=20=ED=83=80=EC=9E=85=20=EC=9E=85=EB=A0=A5=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=EC=97=90=20=EB=A7=9E=EC=B6=94=EC=96=B4=20Controller?= =?UTF-8?q?=20=EB=B0=8F=20=EB=AC=B8=EC=84=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api-docs/uploadProfileFile.api-docs.ts | 48 +++++++++++++++---- server/src/file/controller/file.controller.ts | 11 +++-- server/src/file/dto/fileUpload.dto.ts | 16 +++++++ 3 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 server/src/file/dto/fileUpload.dto.ts diff --git a/server/src/file/api-docs/uploadProfileFile.api-docs.ts b/server/src/file/api-docs/uploadProfileFile.api-docs.ts index 88219874..9708c4b8 100644 --- a/server/src/file/api-docs/uploadProfileFile.api-docs.ts +++ b/server/src/file/api-docs/uploadProfileFile.api-docs.ts @@ -5,16 +5,25 @@ import { ApiConsumes, ApiOkResponse, ApiOperation, + ApiQuery, ApiUnauthorizedResponse, } from '@nestjs/swagger'; +import { FileUploadType } from '../../common/disk/fileValidator'; export function ApiUploadProfileFile() { return applyDecorators( ApiOperation({ - summary: '프로필 이미지 업로드 API', - description: '사용자의 프로필 이미지를 업로드합니다.', + summary: '파일 업로드 API', + description: '사용자의 파일을 업로드합니다.', }), ApiConsumes('multipart/form-data'), + ApiQuery({ + name: 'uploadType', + description: '파일 업로드 타입', + enum: FileUploadType, + example: FileUploadType.PROFILE_IMAGE, + required: true, + }), ApiBody({ description: '업로드할 파일', schema: { @@ -23,7 +32,7 @@ export function ApiUploadProfileFile() { file: { type: 'string', format: 'binary', - description: '업로드할 이미지 파일 (JPG, PNG, GIF 등)', + description: '업로드할 파일 (uploadType별 허용 형식 다름!)', }, }, required: ['file'], @@ -62,7 +71,8 @@ export function ApiUploadProfileFile() { }, url: { type: 'string', - example: '/objects/profile/2024/01/profile-image.jpg', + example: + '/objects/PROFILE_IMAGE/20241215/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg', description: '파일 접근 URL', }, userId: { @@ -84,10 +94,32 @@ export function ApiUploadProfileFile() { ApiBadRequestResponse({ description: '잘못된 요청', schema: { - properties: { - message: { - type: 'string', - example: '파일이 선택되지 않았습니다.', + examples: { + fileNotSelected: { + summary: '파일 미선택', + value: { + message: '파일이 선택되지 않았습니다.', + }, + }, + invalidUploadType: { + summary: '잘못된 업로드 타입', + value: { + message: + '유효하지 않은 파일 업로드 타입입니다. 허용된 타입: PROFILE_IMAGE', + }, + }, + invalidFileType: { + summary: '지원하지 않는 파일 형식', + value: { + message: + '지원하지 않는 파일 형식입니다. 지원 형식: image/jpeg, image/png, image/gif, image/webp', + }, + }, + fileSizeExceeded: { + summary: '파일 크기 초과', + value: { + message: '파일 크기가 너무 큽니다. 최대 5MB까지 허용됩니다.', + }, }, }, }, diff --git a/server/src/file/controller/file.controller.ts b/server/src/file/controller/file.controller.ts index 359e355e..b6647370 100644 --- a/server/src/file/controller/file.controller.ts +++ b/server/src/file/controller/file.controller.ts @@ -8,6 +8,7 @@ import { Param, UseGuards, BadRequestException, + Query, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { FileService } from '../service/file.service'; @@ -18,7 +19,7 @@ import { createDynamicStorage } from '../../common/disk/diskStorage'; import { ApiResponse } from '../../common/response/common.response'; import { ApiUploadProfileFile } from '../api-docs/uploadProfileFile.api-docs'; import { ApiDeleteFile } from '../api-docs/deleteFile.api-docs'; -import { FileUploadResponseDto } from '../dto/createFile.dto'; +import { FileUploadQueryDto } from '../dto/fileUpload.dto'; @ApiTags('File') @Controller('file') @@ -26,10 +27,14 @@ import { FileUploadResponseDto } from '../dto/createFile.dto'; export class FileController { constructor(private readonly fileService: FileService) {} - @Post('upload/profile') + @Post('') @ApiUploadProfileFile() @UseInterceptors(FileInterceptor('file', createDynamicStorage())) - async upload(@UploadedFile() file: any, @Req() req) { + async upload( + @UploadedFile() file: any, + @Query() query: FileUploadQueryDto, + @Req() req, + ) { if (!file) { throw new BadRequestException('파일이 선택되지 않았습니다.'); } diff --git a/server/src/file/dto/fileUpload.dto.ts b/server/src/file/dto/fileUpload.dto.ts new file mode 100644 index 00000000..22efb0f9 --- /dev/null +++ b/server/src/file/dto/fileUpload.dto.ts @@ -0,0 +1,16 @@ +import { IsEnum, IsOptional } from 'class-validator'; +import { Transform } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; +import { FileUploadType } from '../../common/disk/fileValidator'; + +export class FileUploadQueryDto { + @ApiProperty({ + description: '파일 업로드 타입', + enum: FileUploadType, + example: FileUploadType.PROFILE_IMAGE, + required: false, + }) + @IsOptional() + @Transform(({ value }) => value) + uploadType: FileUploadType; +} From e29b787d819624072881159de0884f4c616c68fc Mon Sep 17 00:00:00 2001 From: DinoDeveloper Date: Sat, 16 Aug 2025 00:55:38 +0900 Subject: [PATCH 05/11] =?UTF-8?q?=F0=9F=90=9B=20fix:=20originalName=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EC=98=A4=ED=83=88=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/file/service/file.service.ts | 7 +++---- server/src/user/dto/request/update-user.dto.ts | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/server/src/file/service/file.service.ts b/server/src/file/service/file.service.ts index 6ba82928..0de1fab0 100644 --- a/server/src/file/service/file.service.ts +++ b/server/src/file/service/file.service.ts @@ -14,10 +14,10 @@ export class FileService { ) {} async create(file: any, userId: number): Promise { - const { originalName, mimetype, size, path } = file; + const { originalname, mimetype, size, path } = file; const savedFile = await this.fileRepository.save({ - originalName, + originalName: originalname, mimetype, size, path, @@ -37,8 +37,7 @@ export class FileService { } private generateAccessUrl(filePath: string): string { - const baseUploadPath = - process.env.UPLOAD_BASE_PATH || '/var/web05-Denamu/objects'; + const baseUploadPath = '/var/web05-Denamu/objects'; const relativePath = filePath.replace(baseUploadPath, ''); return `/objects${relativePath}`; } diff --git a/server/src/user/dto/request/update-user.dto.ts b/server/src/user/dto/request/update-user.dto.ts index 2326d1e1..7c9ccefe 100644 --- a/server/src/user/dto/request/update-user.dto.ts +++ b/server/src/user/dto/request/update-user.dto.ts @@ -17,8 +17,8 @@ export class UpdateUserDto { userName?: string; @ApiProperty({ - example: 'uuid', - description: '변경할 프로필 이미지 key', + example: 'https://denamu.site/objects/PROFILE_IMAGE/20250816/uuid.png', + description: '변경할 프로필 이미지 path', required: false, }) @IsOptional() From cb868f911adf133256e43b984821531bc5cafe05 Mon Sep 17 00:00:00 2001 From: DinoDeveloper Date: Sat, 16 Aug 2025 00:56:05 +0900 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=EC=9E=98=EB=AA=BB?= =?UTF-8?q?=EB=90=9C=20=EC=A0=95=EC=A0=81=ED=8C=8C=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.conf b/nginx.conf index 08d95c05..35b3eb85 100644 --- a/nginx.conf +++ b/nginx.conf @@ -31,7 +31,7 @@ server { # 파일 업로드 서비스에 의해 관리되는 정적 파일 서빙 location /objects { - alias /var/denamu_objects/; + alias /var/web05-Denamu/objects/; try_files $uri $uri/ =404; } From b0a0e9a69f408bd302cfbfa43c1e6a0c6d88e699 Mon Sep 17 00:00:00 2001 From: DinoDeveloper Date: Sat, 16 Aug 2025 00:59:37 +0900 Subject: [PATCH 07/11] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=82=B4=20=EC=9E=98=EB=AA=BB=EB=90=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/test/user/e2e/update-profile.e2e-spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/test/user/e2e/update-profile.e2e-spec.ts b/server/test/user/e2e/update-profile.e2e-spec.ts index 39312990..a0b0bacb 100644 --- a/server/test/user/e2e/update-profile.e2e-spec.ts +++ b/server/test/user/e2e/update-profile.e2e-spec.ts @@ -15,7 +15,8 @@ describe('PATCH /api/user/profile E2E Test', () => { const testUpdateData = { complete: { userName: '변경된이름', - profileImage: 'new-profile-uuid', + profileImage: + 'https://denamu.site/objects/PROFILE_IMAGE/20000902/uuid.png', introduction: '변경된 소개글입니다.', }, partial: { From 7988cf70c30aa8cd0ffdb7530a381b81590fa423 Mon Sep 17 00:00:00 2001 From: DinoDeveloper Date: Sat, 16 Aug 2025 01:14:01 +0900 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=90=9B=20fix:=20=EB=94=94=EB=B2=84?= =?UTF-8?q?=EA=B9=85=EC=9A=A9=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/common/disk/fileValidator.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/common/disk/fileValidator.ts b/server/src/common/disk/fileValidator.ts index 95b30fd7..b6f55465 100644 --- a/server/src/common/disk/fileValidator.ts +++ b/server/src/common/disk/fileValidator.ts @@ -24,16 +24,16 @@ export const validateFile = (file: any, uploadType: FileUploadType) => { allowedTypes = ALLOWED_MIME_TYPES.IMAGE; } // else if 구문 이나 switch 써서 타입 추가되면 유효성 ALLOWED TYPES 매핑해주기! - validateFileType(file, uploadType, allowedTypes); + validateFileType(file, allowedTypes); validateFileSize(file, uploadType); }; -const validateFileType = (file: any, t, allowedTypes?: string[]) => { +const validateFileType = (file: any, allowedTypes?: string[]) => { const types = allowedTypes || []; if (!types.includes(file.mimetype)) { throw new BadRequestException( - `받은 파일 타입 ${t}}지원하지 않는 파일 형식입니다. 지원 형식: ${types.join(', ')}`, + `지원하지 않는 파일 형식입니다. 지원 형식: ${types.join(', ')}`, ); } }; From d83229beb65df3897aa5791200a15ac1d32137f5 Mon Sep 17 00:00:00 2001 From: DinoDeveloper Date: Sat, 16 Aug 2025 01:15:01 +0900 Subject: [PATCH 09/11] =?UTF-8?q?=E2=9C=85=20test:=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=94=94=EB=B2=84=EA=B9=85=EC=9A=A9=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/test/user/e2e/update-profile.e2e-spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/test/user/e2e/update-profile.e2e-spec.ts b/server/test/user/e2e/update-profile.e2e-spec.ts index a0b0bacb..727751d1 100644 --- a/server/test/user/e2e/update-profile.e2e-spec.ts +++ b/server/test/user/e2e/update-profile.e2e-spec.ts @@ -88,6 +88,10 @@ describe('PATCH /api/user/profile E2E Test', () => { .set('Authorization', `Bearer ${accessToken}`) .send(testUpdateData.partial); + console.log('Response status:', response.status); + console.log('Response body:', response.body); + console.log('Response text:', response.text); + // then expect(response.status).toBe(200); From ac32da7334cef3306b954ed3f27ba8e27c450670 Mon Sep 17 00:00:00 2001 From: asn6878 Date: Thu, 21 Aug 2025 16:43:43 +0900 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=85=20test:=20deletefile=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=AA=A8=ED=82=B9=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/test/user/e2e/update-profile.e2e-spec.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/server/test/user/e2e/update-profile.e2e-spec.ts b/server/test/user/e2e/update-profile.e2e-spec.ts index 727751d1..455935f1 100644 --- a/server/test/user/e2e/update-profile.e2e-spec.ts +++ b/server/test/user/e2e/update-profile.e2e-spec.ts @@ -4,12 +4,13 @@ import { UserService } from '../../../src/user/service/user.service'; import { UserRepository } from '../../../src/user/repository/user.repository'; import { UserFixture } from '../../fixture/user.fixture'; import { User } from '../../../src/user/entity/user.entity'; -import { UpdateUserDto } from '../../../src/user/dto/request/update-user.dto'; +import { FileService } from '../../../src/file/service/file.service'; describe('PATCH /api/user/profile E2E Test', () => { let app: INestApplication; let userService: UserService; let userRepository: UserRepository; + let fileService: FileService; let testUser: User; const testUpdateData = { @@ -28,16 +29,24 @@ describe('PATCH /api/user/profile E2E Test', () => { app = global.testApp; userService = app.get(UserService); userRepository = app.get(UserRepository); + fileService = app.get(FileService); + + jest.spyOn(fileService, 'deleteByPath').mockResolvedValue(undefined); testUser = await userRepository.save( await UserFixture.createUserCryptFixture({ userName: '기존이름', - profileImage: 'old-profile-uuid', + profileImage: + 'https://denamu.site/objects/PROFILE_IMAGE/20000902/uuid_old.png', introduction: '기존 소개글입니다.', }), ); }); + afterAll(async () => { + jest.restoreAllMocks(); + }); + it('로그인한 사용자가 프로필 정보를 성공적으로 수정한다.', async () => { // given const accessToken = userService.createToken( @@ -88,10 +97,6 @@ describe('PATCH /api/user/profile E2E Test', () => { .set('Authorization', `Bearer ${accessToken}`) .send(testUpdateData.partial); - console.log('Response status:', response.status); - console.log('Response body:', response.body); - console.log('Response text:', response.text); - // then expect(response.status).toBe(200); From 52a4a5f8f17ccb708ef618be923cd7afc45d2317 Mon Sep 17 00:00:00 2001 From: asn6878 Date: Thu, 21 Aug 2025 22:33:52 +0900 Subject: [PATCH 11/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/common/disk/{diskStorage.ts => disk-storage.ts} | 4 ++-- server/src/common/disk/{fileUtils.ts => file-utils.ts} | 2 +- .../src/common/disk/{fileValidator.ts => file-validator.ts} | 0 server/src/file/api-docs/uploadProfileFile.api-docs.ts | 2 +- server/src/file/controller/file.controller.ts | 2 +- server/src/file/dto/fileUpload.dto.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename server/src/common/disk/{diskStorage.ts => disk-storage.ts} (92%) rename server/src/common/disk/{fileUtils.ts => file-utils.ts} (97%) rename server/src/common/disk/{fileValidator.ts => file-validator.ts} (100%) diff --git a/server/src/common/disk/diskStorage.ts b/server/src/common/disk/disk-storage.ts similarity index 92% rename from server/src/common/disk/diskStorage.ts rename to server/src/common/disk/disk-storage.ts index e81eaefb..39079c92 100644 --- a/server/src/common/disk/diskStorage.ts +++ b/server/src/common/disk/disk-storage.ts @@ -3,8 +3,8 @@ import { createDirectoryIfNotExists, getFileName, validateAndGetUploadType, -} from './fileUtils'; -import { validateFile, FILE_SIZE_LIMITS } from './fileValidator'; +} from './file-utils'; +import { validateFile, FILE_SIZE_LIMITS } from './file-validator'; export const createDynamicStorage = () => { return { diff --git a/server/src/common/disk/fileUtils.ts b/server/src/common/disk/file-utils.ts similarity index 97% rename from server/src/common/disk/fileUtils.ts rename to server/src/common/disk/file-utils.ts index 03f1a034..d9ac2877 100644 --- a/server/src/common/disk/fileUtils.ts +++ b/server/src/common/disk/file-utils.ts @@ -4,7 +4,7 @@ import { promises as fs } from 'fs'; import { existsSync } from 'fs'; import { v4 as uuidv4 } from 'uuid'; import { BadRequestException } from '@nestjs/common'; -import { FileUploadType } from './fileValidator'; +import { FileUploadType } from './file-validator'; const BASE_UPLOAD_PATH = '/var/web05-Denamu/objects'; diff --git a/server/src/common/disk/fileValidator.ts b/server/src/common/disk/file-validator.ts similarity index 100% rename from server/src/common/disk/fileValidator.ts rename to server/src/common/disk/file-validator.ts diff --git a/server/src/file/api-docs/uploadProfileFile.api-docs.ts b/server/src/file/api-docs/uploadProfileFile.api-docs.ts index 9708c4b8..33463bb1 100644 --- a/server/src/file/api-docs/uploadProfileFile.api-docs.ts +++ b/server/src/file/api-docs/uploadProfileFile.api-docs.ts @@ -8,7 +8,7 @@ import { ApiQuery, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { FileUploadType } from '../../common/disk/fileValidator'; +import { FileUploadType } from '../../common/disk/file-validator'; export function ApiUploadProfileFile() { return applyDecorators( diff --git a/server/src/file/controller/file.controller.ts b/server/src/file/controller/file.controller.ts index b6647370..e20ee5f5 100644 --- a/server/src/file/controller/file.controller.ts +++ b/server/src/file/controller/file.controller.ts @@ -15,7 +15,7 @@ import { FileService } from '../service/file.service'; import { ApiTags } from '@nestjs/swagger'; import { JwtGuard } from '../../common/guard/jwt.guard'; -import { createDynamicStorage } from '../../common/disk/diskStorage'; +import { createDynamicStorage } from '../../common/disk/disk-storage'; import { ApiResponse } from '../../common/response/common.response'; import { ApiUploadProfileFile } from '../api-docs/uploadProfileFile.api-docs'; import { ApiDeleteFile } from '../api-docs/deleteFile.api-docs'; diff --git a/server/src/file/dto/fileUpload.dto.ts b/server/src/file/dto/fileUpload.dto.ts index 22efb0f9..0d2132db 100644 --- a/server/src/file/dto/fileUpload.dto.ts +++ b/server/src/file/dto/fileUpload.dto.ts @@ -1,7 +1,7 @@ import { IsEnum, IsOptional } from 'class-validator'; import { Transform } from 'class-transformer'; import { ApiProperty } from '@nestjs/swagger'; -import { FileUploadType } from '../../common/disk/fileValidator'; +import { FileUploadType } from '../../common/disk/file-validator'; export class FileUploadQueryDto { @ApiProperty({