Skip to content

Commit 3716b53

Browse files
authored
Merge pull request #25 from ccaaffee/fix/profileimage
Fix/profileimage
2 parents bfd478f + b5e3ba4 commit 3716b53

File tree

6 files changed

+92
-17
lines changed

6 files changed

+92
-17
lines changed

src/auth/types/userInfo.type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ export type UserInfo = {
22
uuid: string;
33
kakaoId: string;
44
nickname: string;
5-
profileImageUrl?: string;
5+
profileImageUrl: string | null;
66
createdAt: Date;
77
};

src/image/image.service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ export class ImageService {
148148
}
149149
}
150150

151+
/**
152+
* 프로필 이미지 삭제
153+
* @param key string
154+
*/
155+
async deleteProfileImage(key: string): Promise<void> {
156+
await this.deleteFile(key);
157+
}
158+
151159
/**
152160
* 이미지를 WebP 형식으로 변환
153161
* @param file Express.Multer.File

src/user/dto/res/userInfo.dto.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ export class UserInfoDto {
2424

2525
@ApiProperty({
2626
type: String,
27-
description: "User's profile image signed URL",
27+
description: "User's profile image signed URL or null if no image",
2828
example:
2929
'https://your-bucket.s3.region.amazonaws.com/production/profile/1234567890-abcdef.webp?signed-params',
30-
required: false,
3130
})
32-
profileImageUrl?: string;
31+
profileImageUrl: string | null;
3332

3433
@ApiProperty({
3534
type: Date,

src/user/user.controller.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
UseInterceptors,
1010
UploadedFile,
1111
BadRequestException,
12+
Delete,
1213
} from '@nestjs/common';
1314
import { UserService } from './user.service';
1415
import { JwtAuthGuard } from 'src/auth/jwt.auth.strategy';
@@ -147,10 +148,27 @@ export class UserController {
147148
throw new BadRequestException('No file uploaded');
148149
}
149150

150-
const updatedUser = await this.userService.updateProfileImage(
151-
user.uuid,
152-
file,
153-
);
151+
const updatedUser = await this.userService.updateProfileImage(user, file);
152+
return this.userService.formatUserForResponse(updatedUser);
153+
}
154+
155+
@ApiOperation({
156+
summary: 'delete profile image',
157+
description: '프로필 이미지를 삭제합니다.',
158+
})
159+
@ApiOkResponse({
160+
type: UserInfoDto,
161+
description: 'Return updated profile with deleted image',
162+
})
163+
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
164+
@ApiInternalServerErrorResponse({
165+
description: 'Internal Server Error',
166+
})
167+
@ApiBearerAuth('JWT')
168+
@Delete('profile/image')
169+
@UseGuards(JwtAuthGuard)
170+
async deleteProfileImage(@GetUser() user: User): Promise<UserInfo> {
171+
const updatedUser = await this.userService.deleteProfileImage(user);
154172
return this.userService.formatUserForResponse(updatedUser);
155173
}
156174
}

src/user/user.repository.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,12 @@ export class UserRepository {
5757
data: { profileImage },
5858
});
5959
}
60+
61+
// 프로필 이미지 삭제
62+
async deleteProfileImage(uuid: string) {
63+
return this.prismaService.user.update({
64+
where: { uuid },
65+
data: { profileImage: null },
66+
});
67+
}
6068
}

src/user/user.service.ts

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { ConflictException, Injectable } from '@nestjs/common';
1+
import {
2+
ConflictException,
3+
Injectable,
4+
NotFoundException,
5+
} from '@nestjs/common';
26
import { UserRepository } from './user.repository';
37
import { ImageService } from 'src/image/image.service';
48
import { UserInfo } from 'src/auth/types/userInfo.type';
@@ -28,12 +32,18 @@ export class UserService {
2832
return null;
2933
}
3034

31-
const { profileImage, ...userWithoutProfileImage } = user;
32-
const responseUser: UserInfo = userWithoutProfileImage;
35+
const responseUser: UserInfo = {
36+
uuid: user.uuid,
37+
kakaoId: user.kakaoId,
38+
nickname: user.nickname,
39+
createdAt: user.createdAt,
40+
profileImageUrl: null,
41+
};
3342

34-
if (profileImage) {
35-
responseUser.profileImageUrl =
36-
await this.imageService.generateSignedUrl(profileImage);
43+
if (user.profileImage) {
44+
responseUser.profileImageUrl = await this.imageService.generateSignedUrl(
45+
user.profileImage,
46+
);
3747
}
3848

3949
return responseUser;
@@ -58,10 +68,42 @@ export class UserService {
5868

5969
// 프로필 이미지 업데이트
6070
async updateProfileImage(
61-
uuid: string,
71+
user: User,
6272
file: Express.Multer.File,
6373
): Promise<User> {
64-
const imageKey = await this.imageService.uploadProfileImage(file);
65-
return this.userRepository.updateProfileImage(uuid, imageKey);
74+
// 기존 프로필 이미지 키를 저장
75+
const oldProfileImageKey = user.profileImage;
76+
77+
// 새로운 이미지를 먼저 S3에 업로드
78+
const newImageKey = await this.imageService.uploadProfileImage(file);
79+
80+
// DB에 새로운 이미지 키 업데이트
81+
const updatedUser = await this.userRepository.updateProfileImage(
82+
user.uuid,
83+
newImageKey,
84+
);
85+
86+
// 새 이미지 업로드와 DB 업데이트가 성공했을 때만 기존 이미지 삭제
87+
if (oldProfileImageKey) {
88+
try {
89+
await this.imageService.deleteProfileImage(oldProfileImageKey);
90+
} catch (error) {
91+
console.error('기존 프로필 이미지 삭제 실패:', error);
92+
}
93+
}
94+
95+
return updatedUser;
96+
}
97+
98+
// 프로필 이미지 삭제
99+
async deleteProfileImage(user: User): Promise<User> {
100+
// S3에서 기존 프로필 이미지 삭제
101+
if (user.profileImage) {
102+
await this.imageService.deleteProfileImage(user.profileImage);
103+
// DB에서 User의 프로필 이미지 삭제 (null로 설정)
104+
return this.userRepository.deleteProfileImage(user.uuid);
105+
} else {
106+
throw new NotFoundException('프로필 이미지가 없습니다.');
107+
}
66108
}
67109
}

0 commit comments

Comments
 (0)