Skip to content

Commit b7eaa6e

Browse files
authored
[TB-31] 회원 탈퇴 기능 추가 (#38)
1 parent b61f385 commit b7eaa6e

File tree

17 files changed

+197
-12
lines changed

17 files changed

+197
-12
lines changed

src/main/java/com/ClubAccount_BE/core/exception/ErrorCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public enum ErrorCode {
3030
RECEIPT_NOT_DELETE("2003", "요청한 영수증을 삭제할 수 없습니다.", HttpStatus.FORBIDDEN),
3131

3232
// S3 관련 에러 코드
33-
S3_UPLOAD_FAIL("3001", "S3에 이미지 업로드 에러입니다.", HttpStatus.INTERNAL_SERVER_ERROR);
33+
S3_UPLOAD_FAIL("3001", "S3에 이미지 업로드 에러입니다.", HttpStatus.INTERNAL_SERVER_ERROR),
34+
S3_DELETE_FAIL("3002", "S3에 이미지 삭제 에러입니다.", HttpStatus.INTERNAL_SERVER_ERROR);
3435

3536
private final String code;
3637
private final String message;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.ClubAccount_BE.core.s3;
2+
3+
import org.springframework.stereotype.Component;
4+
5+
import java.net.URL;
6+
import java.net.URLDecoder;
7+
import java.nio.charset.StandardCharsets;
8+
9+
@Component
10+
public class DefaultS3KeyExtractor implements S3KeyExtractor {
11+
@Override
12+
public String extractKey(String url) {
13+
if (url == null || url.isBlank()) return null;
14+
try {
15+
String path = new URL(url).getPath();
16+
return URLDecoder.decode(path.substring(1), StandardCharsets.UTF_8);
17+
} catch (Exception e) {
18+
return null;
19+
}
20+
}
21+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.ClubAccount_BE.core.s3;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.stereotype.Component;
5+
6+
@Component
7+
public class DefaultS3UrlBuilder implements S3UrlBuilder {
8+
9+
@Value("${cloud.aws.s3.bucket}")
10+
private String bucket;
11+
12+
@Value("${cloud.aws.region.static}")
13+
private String region;
14+
15+
@Override
16+
public String toUrl(String key) {
17+
return "https://" + bucket + ".s3." + region + ".amazonaws.com/" + key;
18+
}
19+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.ClubAccount_BE.core.s3;
2+
3+
public interface S3KeyExtractor {
4+
String extractKey(String url);
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.ClubAccount_BE.core.s3;
2+
3+
public interface S3UrlBuilder {
4+
String toUrl(String key);
5+
}

src/main/java/com/ClubAccount_BE/receipt/adapter/out/ReceiptRepositoryAdapter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ public List<Receipt> getReceiptMonthlyExpenseList(User user, int year) {
6969
.toList();
7070
}
7171

72+
@Override
73+
public List<Receipt> getAllReceipts(User user) {
74+
return receiptRepository.findAllByUserId(user.getId())
75+
.stream()
76+
.map(ReceiptMapper::toDomain)
77+
.toList();
78+
}
79+
7280
@Override
7381
public List<Receipt> getReceiptCategoryList(User user) {
7482
return receiptRepository

src/main/java/com/ClubAccount_BE/receipt/application/port/out/FindReceiptPort.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ Page<Receipt> getReceiptList(
2121
Receipt getReceipt(User user, Long receiptId);
2222

2323
List<Receipt> getReceiptMonthlyExpenseList(User user, int year);
24+
25+
List<Receipt> getAllReceipts(User user);
2426
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.ClubAccount_BE.user.adapter.in.delete;
2+
3+
import com.ClubAccount_BE.user.domain.User;
4+
import io.swagger.v3.oas.annotations.Operation;
5+
import io.swagger.v3.oas.annotations.tags.Tag;
6+
7+
@Tag(name = "User Deletion", description = "회원 탈퇴 API")
8+
public interface DeleteUserApi {
9+
@Operation(summary = "회원 탈퇴", description = "로그인된 사용자가 자신의 계정을 탈퇴합니다.")
10+
void deleteMyAccount(User user);
11+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.ClubAccount_BE.user.adapter.in.delete;
2+
3+
import com.ClubAccount_BE.core.meta.LoginUser;
4+
import com.ClubAccount_BE.user.application.port.in.delete.DeleteUserUseCase;
5+
import com.ClubAccount_BE.user.domain.User;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.web.bind.annotation.DeleteMapping;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
@RestController
12+
@RequestMapping("/api/v1/users")
13+
@RequiredArgsConstructor
14+
public class DeleteUserController implements DeleteUserApi {
15+
16+
private final DeleteUserUseCase deleteUserUseCase;
17+
18+
@DeleteMapping
19+
@Override
20+
public void deleteMyAccount(@LoginUser User user) {
21+
deleteUserUseCase.deleteUser(user);
22+
}
23+
}

src/main/java/com/ClubAccount_BE/user/adapter/out/ProfileImageRepositoryAdapter.java renamed to src/main/java/com/ClubAccount_BE/user/adapter/out/persistence/ProfileImageRepositoryAdapter.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
package com.ClubAccount_BE.user.adapter.out;
1+
package com.ClubAccount_BE.user.adapter.out.persistence;
22

3+
import com.ClubAccount_BE.core.exception.ApiException;
4+
import com.ClubAccount_BE.core.exception.ErrorCode;
5+
import com.ClubAccount_BE.core.s3.S3KeyExtractor;
6+
import com.ClubAccount_BE.core.s3.S3UrlBuilder;
37
import com.ClubAccount_BE.user.application.port.out.UploadProfileImagePort;
8+
import com.ClubAccount_BE.user.application.port.out.delete.DeleteProfileImagePort;
49
import lombok.RequiredArgsConstructor;
510
import org.springframework.beans.factory.annotation.Value;
611
import org.springframework.stereotype.Component;
@@ -16,39 +21,47 @@
1621

1722
@Component
1823
@RequiredArgsConstructor
19-
public class ProfileImageRepositoryAdapter implements UploadProfileImagePort {
24+
public class ProfileImageRepositoryAdapter implements UploadProfileImagePort, DeleteProfileImagePort {
2025

2126
private final S3Client amazonS3;
27+
private final S3KeyExtractor keyExtractor;
28+
private final S3UrlBuilder urlBuilder;
2229

2330
@Value("${cloud.aws.s3.bucket}")
2431
private String bucket;
2532

2633
@Override
2734
public String uploadProfileImage(Long userId, MultipartFile image) {
28-
2935
String imageName = createImageName(image.getOriginalFilename());
30-
3136
try {
3237
PutObjectRequest request = PutObjectRequest.builder()
3338
.bucket(bucket)
3439
.key(imageName)
3540
.contentType(image.getContentType())
3641
.build();
37-
3842
amazonS3.putObject(request,
3943
RequestBody.fromInputStream(image.getInputStream(), image.getSize())
4044
);
41-
4245
} catch (IOException e) {
43-
throw new RuntimeException("S3 업로드 실패", e);
46+
throw new ApiException(ErrorCode.S3_UPLOAD_FAIL, e.getMessage());
4447
}
45-
46-
return amazonS3.utilities()
47-
.getUrl(b -> b.bucket(bucket).key(imageName))
48-
.toExternalForm();
48+
return urlBuilder.toUrl(imageName);
4949
}
5050

5151
private String createImageName(String originalFilename) {
5252
return UUID.randomUUID() + IMAGE_KEY_DELIMITER + originalFilename;
5353
}
54+
55+
@Override
56+
public void deleteImages(String profileImage) {
57+
try {
58+
String key = keyExtractor.extractKey(profileImage);
59+
amazonS3.deleteObject(builder -> builder
60+
.bucket(bucket)
61+
.key(key)
62+
);
63+
} catch (Exception e) {
64+
throw new ApiException(ErrorCode.S3_DELETE_FAIL);
65+
}
66+
}
5467
}

0 commit comments

Comments
 (0)