From f0cbc378344d0319b05e21dea00fecad49007b5e Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 12:25:46 +0900 Subject: [PATCH 01/27] feat: Font Validation --- .../fontory/fontorybe/font/controller/FontController.java | 5 ++--- .../fontorybe/font/controller/dto/FontCreateDTO.java | 3 +++ .../font/controller/dto/FontProgressUpdateDTO.java | 4 +++- .../fontorybe/font/controller/dto/FontUpdateDTO.java | 7 +++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java index e99fe3c..7a09e43 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java @@ -30,7 +30,6 @@ import org.fontory.fontorybe.font.controller.port.FontService; import org.fontory.fontorybe.font.domain.Font; import org.springframework.data.domain.Page; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -124,7 +123,7 @@ public ResponseEntity getFontProgress(@Login UserPrincipal userPrincipal) { @Parameter(name = "fontId", description = "수정할 폰트 ID") @PutMapping("/{fontId}") public ResponseEntity updateFont( - @RequestBody FontUpdateDTO fontUpdateDTO, + @RequestBody @Valid FontUpdateDTO fontUpdateDTO, @PathVariable Long fontId, @Login UserPrincipal userPrincipal ) { @@ -260,7 +259,7 @@ public ResponseEntity getPopularFonts(@Login(required = false) UserPrincipal @Parameter(name = "fontId", description = "수정할 폰트 ID") @PatchMapping("/progress/{fontId}") public ResponseEntity updateFontProgress( - @RequestBody FontProgressUpdateDTO fontProgressUpdateDTO, + @RequestBody @Valid FontProgressUpdateDTO fontProgressUpdateDTO, @PathVariable Long fontId ) { log.info("Request received: Update font progress ID: {}, request: {}", diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java index 01a1a91..2cc153d 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java @@ -2,6 +2,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -12,9 +13,11 @@ public class FontCreateDTO { @NotBlank(message = "폰트 이름은 필수 입력 값입니다.") + @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") private String name; @NotBlank(message = "폰트 예시는 필수 입력 값입니다.") + @Size(min = 10, max = 255, message = "폰트 예시는 10자 이상 255자 이하로 입력해주세요.") private String example; @Pattern(regexp = "^$|^01[016-9]\\d{7,8}$", message = "휴대폰 번호 형식이 올바르지 않습니다.") diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontProgressUpdateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontProgressUpdateDTO.java index 2a26840..dbd43e5 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontProgressUpdateDTO.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontProgressUpdateDTO.java @@ -1,6 +1,7 @@ package org.fontory.fontorybe.font.controller.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; import org.fontory.fontorybe.font.infrastructure.entity.FontStatus; @@ -8,6 +9,7 @@ @Getter @Builder public class FontProgressUpdateDTO { - @Schema(description = "폰트의 상태 (PROGRESS, DONE)") + @NotNull(message = "폰트 상태는 필수입니다.") + @Schema(description = "폰트의 상태 (PROGRESS, DONE, FAILED)") private FontStatus status; } diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java index 3e01b08..81a373e 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java @@ -1,11 +1,18 @@ package org.fontory.fontorybe.font.controller.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.Getter; @Getter @Builder public class FontUpdateDTO { + @NotBlank(message = "폰트 이름은 필수 입력 값입니다.") + @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") private String name; + + @NotBlank(message = "폰트 예시는 필수 입력 값입니다.") + @Size(min = 10, max = 255, message = "폰트 예시는 10자 이상 255자 이하로 입력해주세요.") private String example; } From 0bb4eec6b252279e2405dbb5e750e294299e610c Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 12:35:35 +0900 Subject: [PATCH 02/27] feat: Member Validation --- .../member/controller/ProfileController.java | 3 ++- .../member/controller/RegistrationController.java | 3 ++- .../controller/dto/InitMemberInfoRequest.java | 13 +++++++++++++ .../member/controller/dto/MemberUpdateRequest.java | 7 +++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java index b651cee..ab095e6 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import lombok.Builder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -71,7 +72,7 @@ public ResponseEntity getMyProfile( @PatchMapping(consumes = MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateMember( @Login UserPrincipal userPrincipal, - @RequestPart MemberUpdateRequest req, + @RequestPart @Valid MemberUpdateRequest req, @SingleFileUpload @RequestPart("file") List files ) { Long requestMemberId = userPrincipal.getId(); diff --git a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java index 3d054b2..7fa1c17 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.Builder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -58,7 +59,7 @@ public ResponseEntity checkDuplicate( @PostMapping(consumes = MULTIPART_FORM_DATA_VALUE) public ResponseEntity register( @Login UserPrincipal user, - @RequestPart InitMemberInfoRequest req, + @RequestPart @Valid InitMemberInfoRequest req, @SingleFileUpload @RequestPart("file") List files ) { Long requestMemberId = user.getId(); diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java index 3bb9513..ff9e248 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java @@ -1,5 +1,9 @@ package org.fontory.fontorybe.member.controller.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Past; +import jakarta.validation.constraints.Size; import java.time.LocalDate; import lombok.AllArgsConstructor; @@ -13,8 +17,17 @@ @ToString @AllArgsConstructor public class InitMemberInfoRequest { + @NotBlank(message = "닉네임은 필수 입력 값입니다.") + @Size(min = 2, max = 20, message = "닉네임은 2자 이상 20자 이하로 입력해주세요.") private String nickname; + + @NotNull(message = "성별을 선택해주세요.") private Gender gender; + + @NotNull(message = "생년월일을 입력해주세요.") + @Past(message = "생년월일은 과거 날짜만 가능합니다.") private LocalDate birth; + + @NotNull(message = "이용약관 동의 여부를 선택해주세요.") private Boolean terms; } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java index 63179cd..b658a46 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java @@ -1,5 +1,8 @@ package org.fontory.fontorybe.member.controller.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -10,6 +13,10 @@ @ToString @AllArgsConstructor public class MemberUpdateRequest { + @NotBlank(message = "닉네임은 필수 입력 값입니다.") + @Size(min = 2, max = 20, message = "닉네임은 2자 이상 20자 이하로 입력해주세요.") private String nickname; + + @NotNull(message = "이용약관 동의 여부를 선택해주세요.") private Boolean terms; } From 42c464eac32f02bc0f19bf44cab93a653dc1dc0d Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 14:12:59 +0900 Subject: [PATCH 03/27] =?UTF-8?q?feat:=20=ED=8F=B0=ED=8A=B8=20=EC=98=81?= =?UTF-8?q?=EC=96=B4=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../font/controller/dto/FontCreateDTO.java | 4 ++ .../font/controller/dto/FontUpdateDTO.java | 4 -- .../fontory/fontorybe/font/domain/Font.java | 49 +++++++------------ .../infrastructure/entity/FontEntity.java | 4 ++ .../font/service/FontServiceImpl.java | 15 ++++-- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java index 2cc153d..5558a44 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java @@ -16,6 +16,10 @@ public class FontCreateDTO { @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") private String name; + @NotBlank(message = "폰트 영어 이름은 필수 입력 값입니다.") + @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") + private String engName; + @NotBlank(message = "폰트 예시는 필수 입력 값입니다.") @Size(min = 10, max = 255, message = "폰트 예시는 10자 이상 255자 이하로 입력해주세요.") private String example; diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java index 81a373e..cf04ee9 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java @@ -8,10 +8,6 @@ @Getter @Builder public class FontUpdateDTO { - @NotBlank(message = "폰트 이름은 필수 입력 값입니다.") - @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") - private String name; - @NotBlank(message = "폰트 예시는 필수 입력 값입니다.") @Size(min = 10, max = 255, message = "폰트 예시는 10자 이상 255자 이하로 입력해주세요.") private String example; diff --git a/src/main/java/org/fontory/fontorybe/font/domain/Font.java b/src/main/java/org/fontory/fontorybe/font/domain/Font.java index 3626303..8bf2a3f 100644 --- a/src/main/java/org/fontory/fontorybe/font/domain/Font.java +++ b/src/main/java/org/fontory/fontorybe/font/domain/Font.java @@ -22,6 +22,8 @@ public class Font { private String name; + private String engName; + private FontStatus status; private String example; @@ -53,6 +55,7 @@ public void increaseDownloadCount() { public static Font from(FontCreateDTO fontCreateDTO, Long memberId, String key) { return Font.builder() .name(fontCreateDTO.getName()) + .engName(fontCreateDTO.getEngName()) .status(FontStatus.PROGRESS) .example(fontCreateDTO.getExample()) .key(key) @@ -64,7 +67,8 @@ public static Font from(FontCreateDTO fontCreateDTO, Long memberId, String key) public Font update(FontUpdateDTO fontUpdateDTO) { return Font.builder() - .name(fontUpdateDTO.getName()) + .name(this.name) + .engName(this.engName) .example(fontUpdateDTO.getExample()) .id(this.id) .status(this.status) @@ -77,34 +81,19 @@ public Font update(FontUpdateDTO fontUpdateDTO) { .build(); } - public Font updateProgress(FontProgressUpdateDTO fontProgressUpdateDTO, Long fontId) { - - if (fontProgressUpdateDTO.getStatus() == FontStatus.DONE) { - return Font.builder() - .name(this.name) - .example(this.example) - .id(this.id) - .status(fontProgressUpdateDTO.getStatus()) - .downloadCount(this.downloadCount) - .bookmarkCount(this.bookmarkCount) - .key(this.key) - .memberId(this.memberId) - .createdAt(this.createdAt) - .updatedAt(this.updatedAt) - .build(); - } else { - return Font.builder() - .name(this.name) - .example(this.example) - .id(this.id) - .status(fontProgressUpdateDTO.getStatus()) - .downloadCount(this.downloadCount) - .bookmarkCount(this.bookmarkCount) - .key(this.key) - .memberId(this.memberId) - .createdAt(this.createdAt) - .updatedAt(this.updatedAt) - .build(); - } + public Font updateProgress(FontProgressUpdateDTO fontProgressUpdateDTO) { + return Font.builder() + .name(this.name) + .engName(this.engName) + .example(this.example) + .id(this.id) + .status(fontProgressUpdateDTO.getStatus()) + .downloadCount(this.downloadCount) + .bookmarkCount(this.bookmarkCount) + .key(this.key) + .memberId(this.memberId) + .createdAt(this.createdAt) + .updatedAt(this.updatedAt) + .build(); } } diff --git a/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java b/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java index 4707eed..f6eaf03 100644 --- a/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java +++ b/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java @@ -31,6 +31,8 @@ public class FontEntity extends BaseEntity { private String name; + private String engName; + @Enumerated(EnumType.STRING) private FontStatus status; @@ -49,6 +51,7 @@ public Font toModel() { return Font.builder() .id(id) .name(name) + .engName(engName) .status(status) .example(example) .downloadCount(downloadCount) @@ -64,6 +67,7 @@ public static FontEntity from(Font font) { return FontEntity.builder() .id(font.getId()) .name(font.getName()) + .engName(font.getEngName()) .status(font.getStatus()) .example(font.getExample()) .downloadCount(font.getDownloadCount()) diff --git a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java index 958aa85..060a058 100644 --- a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java @@ -105,8 +105,8 @@ public FontUpdateResponse update(Long memberId, Long fontId, FontUpdateDTO fontU Font targetFont = getOrThrowById(fontId); checkFontOwnership(member.getId(), targetFont.getMemberId()); - checkContainsBadWord(fontUpdateDTO.getName(), fontUpdateDTO.getExample()); - + checkContainsBadWord(fontUpdateDTO.getExample()); + Font updatedFont = fontRepository.save(targetFont.update(fontUpdateDTO)); String woff2Url = cloudStorageService.getWoff2Url(updatedFont.getKey()); @@ -316,7 +316,7 @@ public FontUpdateResponse updateProgress(Long fontId, FontProgressUpdateDTO font log.info("Service executing: Updating font ID: {}", fontId); Font targetFont = getOrThrowById(fontId); - Font updatedFont = fontRepository.save(targetFont.updateProgress(fontProgressUpdateDTO, fontId)); + Font updatedFont = fontRepository.save(targetFont.updateProgress(fontProgressUpdateDTO)); String woff2Url = cloudStorageService.getWoff2Url(updatedFont.getKey()); if (fontProgressUpdateDTO.getStatus() == FontStatus.DONE) { @@ -383,4 +383,13 @@ private void checkContainsBadWord(String name, String example) { throw new FontContainsBadWordException(); } } + + private void checkContainsBadWord(String example) { + log.debug("Service detail: Checking bad word: example={}", example); + + if (badWordFiltering.blankCheck(example)) { + log.warn("Service warning: Font contains bad word: example={}", example); + throw new FontContainsBadWordException(); + } + } } From 9ef074d6f6f6429dde2bc10bc0d5fab35f7cb880 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 14:18:45 +0900 Subject: [PATCH 04/27] =?UTF-8?q?feat:=20=ED=8F=B0=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../font/controller/FontController.java | 22 --------- .../font/controller/dto/FontUpdateDTO.java | 14 ------ .../font/controller/port/FontService.java | 1 - .../fontory/fontorybe/font/domain/Font.java | 18 ------- .../font/service/FontServiceImpl.java | 18 ------- .../font/FontControllerIntegrationTest.java | 47 ------------------- .../font/FontServiceIntegrationTest.java | 41 ---------------- 7 files changed, 161 deletions(-) delete mode 100644 src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java diff --git a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java index 7a09e43..7f29903 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java @@ -25,7 +25,6 @@ import org.fontory.fontorybe.font.controller.dto.FontProgressResponse; import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO; import org.fontory.fontorybe.font.controller.dto.FontResponse; -import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO; import org.fontory.fontorybe.font.controller.dto.FontUpdateResponse; import org.fontory.fontorybe.font.controller.port.FontService; import org.fontory.fontorybe.font.domain.Font; @@ -119,27 +118,6 @@ public ResponseEntity getFontProgress(@Login UserPrincipal userPrincipal) { .body(fontsProgress); } - @Operation(summary = "폰트 정보 수정") - @Parameter(name = "fontId", description = "수정할 폰트 ID") - @PutMapping("/{fontId}") - public ResponseEntity updateFont( - @RequestBody @Valid FontUpdateDTO fontUpdateDTO, - @PathVariable Long fontId, - @Login UserPrincipal userPrincipal - ) { - Long memberId = userPrincipal.getId(); - log.info("Request received: Update font ID: {} by member ID: {}, request: {}", - fontId, memberId, toJson(fontUpdateDTO)); - - FontUpdateResponse fontUpdateResponse = fontService.update(memberId, fontId, fontUpdateDTO); - log.info("Response sent: Font ID: {} updated successfully, name: {}", - fontUpdateResponse.getId(), fontUpdateResponse.getName()); - - return ResponseEntity - .status(HttpStatus.OK) - .body(fontUpdateResponse); - } - @Operation(summary = "내가 제작한 폰트") @GetMapping("/members") public ResponseEntity getFonts( diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java deleted file mode 100644 index cf04ee9..0000000 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontUpdateDTO.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.fontory.fontorybe.font.controller.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class FontUpdateDTO { - @NotBlank(message = "폰트 예시는 필수 입력 값입니다.") - @Size(min = 10, max = 255, message = "폰트 예시는 10자 이상 255자 이하로 입력해주세요.") - private String example; -} diff --git a/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java b/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java index 63e2dc8..eaa4e6f 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java @@ -9,7 +9,6 @@ public interface FontService { Font create(Long memberId, FontCreateDTO fontCreateDTO, FileUploadResult fileDetails); List getFontProgress(Long memberId); - FontUpdateResponse update(Long memberId, Long fontId, FontUpdateDTO fontUpdateDTO); Font getOrThrowById(Long id); Page getFonts(Long memberId, int page, int size); FontResponse getFont(Long fondId, Long memberId); diff --git a/src/main/java/org/fontory/fontorybe/font/domain/Font.java b/src/main/java/org/fontory/fontorybe/font/domain/Font.java index 8bf2a3f..5120156 100644 --- a/src/main/java/org/fontory/fontorybe/font/domain/Font.java +++ b/src/main/java/org/fontory/fontorybe/font/domain/Font.java @@ -6,10 +6,8 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.fontory.fontorybe.file.domain.FileUploadResult; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO; -import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO; import org.fontory.fontorybe.font.infrastructure.entity.FontStatus; @Getter @@ -65,22 +63,6 @@ public static Font from(FontCreateDTO fontCreateDTO, Long memberId, String key) .build(); } - public Font update(FontUpdateDTO fontUpdateDTO) { - return Font.builder() - .name(this.name) - .engName(this.engName) - .example(fontUpdateDTO.getExample()) - .id(this.id) - .status(this.status) - .downloadCount(this.downloadCount) - .bookmarkCount(this.bookmarkCount) - .key(this.key) - .memberId(this.memberId) - .createdAt(this.createdAt) - .updatedAt(this.updatedAt) - .build(); - } - public Font updateProgress(FontProgressUpdateDTO fontProgressUpdateDTO) { return Font.builder() .name(this.name) diff --git a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java index 060a058..94cc746 100644 --- a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java @@ -17,7 +17,6 @@ import org.fontory.fontorybe.font.controller.dto.FontProgressResponse; import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO; import org.fontory.fontorybe.font.controller.dto.FontResponse; -import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO; import org.fontory.fontorybe.font.controller.dto.FontUpdateResponse; import org.fontory.fontorybe.font.controller.port.FontService; import org.fontory.fontorybe.font.domain.Font; @@ -97,23 +96,6 @@ public List getFontProgress(Long memberId) { return result; } - @Override - @Transactional - public FontUpdateResponse update(Long memberId, Long fontId, FontUpdateDTO fontUpdateDTO) { - log.info("Service executing: Updating font ID: {} for member ID: {}", fontId, memberId); - Member member = memberLookupService.getOrThrowById(memberId); - Font targetFont = getOrThrowById(fontId); - - checkFontOwnership(member.getId(), targetFont.getMemberId()); - checkContainsBadWord(fontUpdateDTO.getExample()); - - Font updatedFont = fontRepository.save(targetFont.update(fontUpdateDTO)); - String woff2Url = cloudStorageService.getWoff2Url(updatedFont.getKey()); - - log.info("Service completed: Font ID: {} updated successfully", fontId); - return FontUpdateResponse.from(updatedFont, woff2Url); - } - @Override @Transactional(readOnly = true) public Font getOrThrowById(Long id) { diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java index 2eb3a9f..0aad20c 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java @@ -18,7 +18,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; -import java.util.UUID; import jakarta.servlet.http.Cookie; import org.fontory.fontorybe.authentication.application.port.JwtTokenProvider; @@ -30,7 +29,6 @@ import org.fontory.fontorybe.file.domain.FileUploadResult; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO; -import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO; import org.fontory.fontorybe.font.infrastructure.entity.FontStatus; import org.fontory.fontorybe.font.service.port.FontRequestProducer; import org.junit.jupiter.api.BeforeEach; @@ -203,51 +201,6 @@ void getFontProgressWithoutAuthHeader() throws Exception { .andExpect(jsonPath("$.errorMessage").value("Authentication Required.")); } - @Test - @DisplayName("PUT /fonts/{fontId} - update font success with valid Authorization header") - void updateFontSuccess() throws Exception { - // given - FontUpdateDTO updateDTO = FontUpdateDTO.builder() - .name(updateFontName) - .example(updateFontExample) - .build(); - - String jsonRequest = objectMapper.writeValueAsString(updateDTO); - - // when & then - mockMvc.perform(put("/fonts/{fontId}", existFontId) - .cookie(new Cookie("accessToken", validAccessToken)) - .contentType(MediaType.APPLICATION_JSON) - .content(jsonRequest)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.id", is(existFontId.intValue()))) - .andExpect(jsonPath("$.name", is(updateFontName))) - .andExpect(jsonPath("$.status").isNotEmpty()) - .andExpect(jsonPath("$.example", is(updateFontExample))) - .andExpect(jsonPath("$.memberId").isNotEmpty()) - .andExpect(jsonPath("$.createdAt").isNotEmpty()); - } - - @Test - @DisplayName("PUT /fonts/{fontId} - update font without Authorization header returns 401") - void updateFontWithoutAuthHeader() throws Exception { - // given - FontUpdateDTO updateDTO = FontUpdateDTO.builder() - .name(updateFontName) - .example(updateFontExample) - .build(); - - String jsonRequest = objectMapper.writeValueAsString(updateDTO); - - // when & then - mockMvc.perform(put("/fonts/{fontId}", existFontId) - .contentType(MediaType.APPLICATION_JSON) - .content(jsonRequest)) - .andExpect(status().isUnauthorized()) - .andExpect(content().contentType("application/json;charset=UTF-8")) - .andExpect(jsonPath("$.errorMessage").value("Authentication Required.")); - } - @Test @DisplayName("GET /fonts/members - success with valid Authorization header") void getMyFontsSuccess() throws Exception { diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java index 6844014..00166a3 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java @@ -138,47 +138,6 @@ void getFontProgressSuccess() { result.forEach(font -> assertThat(font.getStatus()).isEqualTo(FontStatus.PROGRESS)); } - @Test - @DisplayName("font - update success test") - void updateFontSuccess() { - // given - FontUpdateDTO dto = FontUpdateDTO.builder() - .name("수정폰트") - .example("수정 폰트 예시입니다.") - .build(); - - // when - FontUpdateResponse updated = fontService.update(existMemberId, existFontId, dto); - - // then - assertAll( - () -> assertThat(updated.getId()).isEqualTo(existFontId), - () -> assertThat(updated.getName()).isEqualTo("수정폰트"), - () -> assertThat(updated.getExample()).isEqualTo("수정 폰트 예시입니다.") - ); - } - - @Test - @DisplayName("font - update fail test caused by access denied") - void updateFontAccessDeniedFail() { - // given - FontCreateDTO createDTO = FontCreateDTO.builder() - .name("다른사람폰트") - .example("다른예제") - .build(); - - Font elseFont = fontService.create(createdMemberId, createDTO, fileDetails); - - FontUpdateDTO updateDTO = FontUpdateDTO.builder() - .name("수정시도") - .example("예제수정") - .build(); - - // when & then - assertThatThrownBy(() -> fontService.update(existMemberId, elseFont.getId(), updateDTO)) - .isExactlyInstanceOf(FontOwnerMismatchException.class); - } - @Test @DisplayName("font - getOrThrowById success test") void getOrThrowByIdSuccess() { From ab558e518fc75816bcc34e042292d6c9d1ab40c3 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 17:06:23 +0900 Subject: [PATCH 05/27] =?UTF-8?q?feat:=20=ED=8F=B0=ED=8A=B8=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=A0=95=EA=B7=9C=20=ED=91=9C=ED=98=84=EC=8B=9D=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fontory/fontorybe/font/controller/dto/FontCreateDTO.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java index 5558a44..9d03b09 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java @@ -14,10 +14,12 @@ public class FontCreateDTO { @NotBlank(message = "폰트 이름은 필수 입력 값입니다.") @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") + @Pattern(regexp = "^[가-힣]{2,30}$", message = "한글만 입력할 수 있습니다. (예: 가나다체)") private String name; @NotBlank(message = "폰트 영어 이름은 필수 입력 값입니다.") @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") + @Pattern(regexp = "^[a-zA-Z]{2,30}$", message = "폰트 영어 이름은 영문 대소문자만 입력할 수 있습니다. (예: ABCD)") private String engName; @NotBlank(message = "폰트 예시는 필수 입력 값입니다.") From 4e74a78412a33d49fed7ff79d955734b8fa7731b Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 17:08:10 +0900 Subject: [PATCH 06/27] feat: Validation Exception Handler --- .../adapter/inbound/GlobalExceptionHandler.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java b/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java index f014499..70fa9c0 100644 --- a/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java +++ b/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java @@ -3,6 +3,7 @@ import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.MalformedJwtException; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.fontory.fontorybe.authentication.domain.exception.InvalidRefreshTokenException; import org.fontory.fontorybe.authentication.domain.exception.TokenNotFoundException; @@ -28,7 +29,9 @@ import org.fontory.fontorybe.member.domain.exception.MemberNotFoundException; import org.fontory.fontorybe.member.domain.exception.MemberOwnerMismatchException; import org.fontory.fontorybe.provide.domain.exception.ProvideNotFoundException; +import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -38,6 +41,16 @@ @RequiredArgsConstructor public class GlobalExceptionHandler { + @ExceptionHandler(MethodArgumentNotValidException.class) + public BaseErrorResponse validationException(MethodArgumentNotValidException e) { + String message = e.getBindingResult() + .getFieldErrors() + .stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .collect(Collectors.joining(", ")); + return new BaseErrorResponse(message); + } + @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler({MemberNotFoundException.class, FontNotFoundException.class, BookmarkNotFoundException.class}) public BaseErrorResponse notFoundException(Exception e) { From 9b7f610e02ecb7b81ebb131a55c744beab3e1ce3 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 17:08:18 +0900 Subject: [PATCH 07/27] chore: Spring Validation --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 4d1c7b6..9af4de8 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ dependencies { // Spring Boot Starters: implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' // OAuth2 implementation 'org.springframework.session:spring-session-data-redis' From 823d8b28c670584ed960ca7a1825a23df3accfc0 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 17:11:11 +0900 Subject: [PATCH 08/27] =?UTF-8?q?feat:=20=ED=8F=B0=ED=8A=B8=20=EC=98=81?= =?UTF-8?q?=EC=96=B4=20=EC=9D=B4=EB=A6=84=EB=8F=84=20=EB=B9=84=EC=86=8D?= =?UTF-8?q?=EC=96=B4=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../font/service/FontServiceImpl.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java index 94cc746..1527097 100644 --- a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java @@ -64,7 +64,7 @@ public Font create(Long memberId, FontCreateDTO fontCreateDTO, FileUploadResult throw new FontDuplicateNameExistsException(); } - checkContainsBadWord(fontCreateDTO.getName(), fontCreateDTO.getExample()); + checkFontNameAndExampleContainsBadWord(fontCreateDTO.getName(), fontCreateDTO.getEngName(), fontCreateDTO.getExample()); FileMetadata fileMetadata = fileService.getOrThrowById(fileDetails.getId()); @@ -357,20 +357,11 @@ private void checkFontStatusIsDone(Font targetFont) { } } - private void checkContainsBadWord(String name, String example) { - log.debug("Service detail: Checking bad word: name={}, example={}", name, example); + private void checkFontNameAndExampleContainsBadWord(String name, String engName, String example) { + log.debug("Service detail: Checking bad word: name={}, engName={} example={}", name, engName, example); - if (badWordFiltering.blankCheck(name) || badWordFiltering.blankCheck(example)) { - log.warn("Service warning: Font contains bad word: name={}, example={}", name, example); - throw new FontContainsBadWordException(); - } - } - - private void checkContainsBadWord(String example) { - log.debug("Service detail: Checking bad word: example={}", example); - - if (badWordFiltering.blankCheck(example)) { - log.warn("Service warning: Font contains bad word: example={}", example); + if (badWordFiltering.blankCheck(name) || badWordFiltering.blankCheck(engName) || badWordFiltering.blankCheck(example)) { + log.warn("Service warning: Font contains bad word: name={}, engName={}, example={}", name, engName, example); throw new FontContainsBadWordException(); } } From 541f97099537b30d05776c2802fa5af7df7c5090 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 17:20:10 +0900 Subject: [PATCH 09/27] =?UTF-8?q?refactor:=20=ED=8F=B0=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=A0=95=EA=B7=9C=ED=91=9C=ED=98=84?= =?UTF-8?q?=EC=8B=9D=EC=97=90=20=EC=88=AB=EC=9E=90=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fontory/fontorybe/font/controller/dto/FontCreateDTO.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java index 9d03b09..a5cbc75 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateDTO.java @@ -14,12 +14,12 @@ public class FontCreateDTO { @NotBlank(message = "폰트 이름은 필수 입력 값입니다.") @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") - @Pattern(regexp = "^[가-힣]{2,30}$", message = "한글만 입력할 수 있습니다. (예: 가나다체)") + @Pattern(regexp = "^[가-힣0-9]{2,30}$", message = "한글과 숫자만 입력할 수 있습니다. (예: 가나다체123)") private String name; @NotBlank(message = "폰트 영어 이름은 필수 입력 값입니다.") @Size(min = 2, max = 30, message = "폰트 이름은 2자 이상 30자 이하로 입력해주세요.") - @Pattern(regexp = "^[a-zA-Z]{2,30}$", message = "폰트 영어 이름은 영문 대소문자만 입력할 수 있습니다. (예: ABCD)") + @Pattern(regexp = "^[a-zA-Z0-9]{2,30}$", message = "영문 대소문자와 숫자만 입력할 수 있습니다. (예: ABCD123)") private String engName; @NotBlank(message = "폰트 예시는 필수 입력 값입니다.") From 560fff1caeef660ec7416a3201afc070dd714d76 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Fri, 16 May 2025 17:20:23 +0900 Subject: [PATCH 10/27] =?UTF-8?q?test:=20=EC=98=81=EC=96=B4=20=ED=8F=B0?= =?UTF-8?q?=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EC=B6=94=EA=B0=80=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../font/FontControllerIntegrationTest.java | 4 +++- .../font/FontServiceIntegrationTest.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java index 0aad20c..97861d6 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java @@ -81,7 +81,8 @@ class FontControllerIntegrationTest { private final String existFontKey = "key"; private final String existFontTemplateExtension = "jpg"; - private final String newFontName = "newFontName"; + private final String newFontName = "제작한글폰트1"; + private final String newFontEngName = "newFontEngName"; private final String newFontExample = "newFontExample"; private final String updateFontName = "updateFontName"; @@ -125,6 +126,7 @@ void addFontSuccess() throws Exception { // given FontCreateDTO createDTO = FontCreateDTO.builder() .name(newFontName) + .engName(newFontEngName) .example(newFontExample) .build(); diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java index 00166a3..626ba03 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java @@ -93,6 +93,7 @@ void createFontSuccess() { // given FontCreateDTO dto = FontCreateDTO.builder() .name("생성폰트") + .engName("ENG1") .example("생성 폰트 예제입니다.") .build(); @@ -122,6 +123,7 @@ void getFontProgressSuccess() { existMemberId, FontCreateDTO.builder() .name("진행중폰트" + i) + .engName("ENG" + i) .example("예제" + i) .build(), fileDetails @@ -176,6 +178,7 @@ void getFontsSuccess() { existMemberId, FontCreateDTO.builder() .name("폰트" + i) + .engName("ENG" + i) .example("예제" + i) .build(), fileDetails @@ -239,6 +242,7 @@ void deleteFontAccessDeniedFail() { // given FontCreateDTO createDTO = FontCreateDTO.builder() .name("다른사람폰트") + .engName("ENG1") .example("다른예제") .build(); @@ -258,6 +262,7 @@ void getFontPageSuccess() { existMemberId, FontCreateDTO.builder() .name("페이지폰트1") + .engName("ENG1") .example("예제1") .build(), fileDetails @@ -267,6 +272,7 @@ void getFontPageSuccess() { existMemberId, FontCreateDTO.builder() .name("페이지폰트2") + .engName("ENG2") .example("예제2") .build(), fileDetails @@ -276,6 +282,7 @@ void getFontPageSuccess() { existMemberId, FontCreateDTO.builder() .name("페이지폰트") + .engName("ENG3") .example("예제3") .build(), fileDetails @@ -337,6 +344,7 @@ void getMyPopularFontsSuccess() { existMemberId, FontCreateDTO.builder() .name("폰트1") + .engName("ENG1") .example("예1") .build(), fileDetails @@ -346,6 +354,7 @@ void getMyPopularFontsSuccess() { existMemberId, FontCreateDTO.builder() .name("폰트2") + .engName("ENG2") .example("예2") .build(), fileDetails @@ -355,6 +364,7 @@ void getMyPopularFontsSuccess() { existMemberId, FontCreateDTO.builder() .name("폰트3") + .engName("ENG3") .example("예3") .build(), fileDetails @@ -399,6 +409,7 @@ void getPopularFontsSuccess() { existMemberId, FontCreateDTO.builder() .name("폰트1") + .engName("ENG1") .example("예1") .build(), fileDetails @@ -408,6 +419,7 @@ void getPopularFontsSuccess() { existMemberId, FontCreateDTO.builder() .name("폰트2") + .engName("ENG2") .example("예2") .build(), fileDetails @@ -417,6 +429,7 @@ void getPopularFontsSuccess() { existMemberId, FontCreateDTO.builder() .name("폰트3") + .engName("ENG3") .example("예3") .build(), fileDetails @@ -462,6 +475,7 @@ void updateFontProgressSuccess() { // given FontCreateDTO dto = FontCreateDTO.builder() .name("진행중폰트") + .engName("ENG1") .example("예제입니다") .build(); From e444c3729d29f43785ef91a9d7ca44ad8de61d51 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Fri, 16 May 2025 18:28:27 +0900 Subject: [PATCH 11/27] fix bug empty files sent when onboarding & update memebr --- .../member/controller/ProfileController.java | 17 +++++++++----- .../controller/RegistrationController.java | 22 +++++++++++++------ .../controller/port/MemberOnboardService.java | 1 + .../fontorybe/member/domain/Member.java | 16 ++++++++++++++ .../service/MemberOnboardServiceImpl.java | 16 ++++++++++++++ src/main/resources/application.properties | 4 ++++ .../ProfileControllerIntegrationTest.java | 3 ++- .../fontorybe/unit/mock/TestContainer.java | 1 + 8 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java index b651cee..ed2fdae 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java @@ -72,21 +72,26 @@ public ResponseEntity getMyProfile( public ResponseEntity updateMember( @Login UserPrincipal userPrincipal, @RequestPart MemberUpdateRequest req, - @SingleFileUpload @RequestPart("file") List files + @SingleFileUpload @RequestPart(value = "file", required = false) List files ) { Long requestMemberId = userPrincipal.getId(); - MultipartFile file = extractSingleMultipartFile(files); - log.info("Request received: update member ID: {} with request: {}", requestMemberId, req); - logFileDetails(file, "Member profile image upload"); - FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, requestMemberId); + if (files != null && !files.isEmpty()) { + MultipartFile file = extractSingleMultipartFile(files); + logFileDetails(file, "Member profile image upload"); + FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, requestMemberId); + log.info("fileUploadResult: {}", fileUploadResult); + } else { + log.info("No profile image upload found"); + } + Member updatedMember = memberUpdateService.update(requestMemberId, req); log.info("Updated : Member ID: {} Updated successfully with nickname: {}, terms : {}", updatedMember.getId(), updatedMember.getNickname(), updatedMember.getTerms()); - MyProfileResponse myProfileResponse = MyProfileResponse.from(updatedMember, fileUploadResult.getFileUrl()); + MyProfileResponse myProfileResponse = MyProfileResponse.from(updatedMember, cloudStorageService.getProfileImageUrl(updatedMember.getProfileImageKey())); log.info("Response sent: MyProfileDto : {}", myProfileResponse); return ResponseEntity diff --git a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java index 3d054b2..631c496 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java @@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.fontory.fontorybe.authentication.adapter.inbound.annotation.Login; import org.fontory.fontorybe.authentication.domain.UserPrincipal; +import org.fontory.fontorybe.file.application.port.CloudStorageService; import org.fontory.fontorybe.file.application.port.FileService; import org.fontory.fontorybe.file.domain.FileUploadResult; import org.fontory.fontorybe.file.application.annotation.SingleFileUpload; @@ -17,6 +18,7 @@ import org.fontory.fontorybe.member.domain.Member; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.parameters.P; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -32,6 +34,7 @@ @RequestMapping("/register") @Tag(name = "사용자 - 신규", description = "회원가입/온보딩") public class RegistrationController { + private final CloudStorageService cloudStorageService; private final MemberLookupService memberLookupService; private final MemberOnboardService memberOnboardService; private final FileService fileService; @@ -59,24 +62,29 @@ public ResponseEntity checkDuplicate( public ResponseEntity register( @Login UserPrincipal user, @RequestPart InitMemberInfoRequest req, - @SingleFileUpload @RequestPart("file") List files + @SingleFileUpload @RequestPart(value = "file", required = false) List files ) { Long requestMemberId = user.getId(); - MultipartFile file = extractSingleMultipartFile(files); - log.info("Request received: Create member ID: {} with request: {}", requestMemberId, req); - logFileDetails(file, "Member profile image upload"); - FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, requestMemberId); - Member updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req, fileUploadResult); + Member updatedMember; + if (files != null && !files.isEmpty()) { + MultipartFile file = extractSingleMultipartFile(files); + logFileDetails(file, "Member profile image upload"); + FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, requestMemberId); + updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req, fileUploadResult); + } else { + log.info("No profile image upload found"); + updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req); + } log.info("Response sent: Member ID: {} Created successfully with nickname: {}", updatedMember.getId(), updatedMember.getNickname()); return ResponseEntity .status(HttpStatus.CREATED) - .body(MemberCreateResponse.from(updatedMember, fileUploadResult.getFileUrl())); + .body(MemberCreateResponse.from(updatedMember, cloudStorageService.getProfileImageUrl(updatedMember.getProfileImageKey()))); } private void logFileDetails(MultipartFile file, String context) { diff --git a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java index 8441560..65b55e8 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java @@ -8,4 +8,5 @@ public interface MemberOnboardService { Member fetchOrCreateMember(Provide p); Member initNewMemberInfo(Long requestMemberId, InitMemberInfoRequest initMemberInfoRequest, FileUploadResult fileUploadResult); + Member initNewMemberInfo(Long requestMemberId, InitMemberInfoRequest initMemberInfoRequest); } diff --git a/src/main/java/org/fontory/fontorybe/member/domain/Member.java b/src/main/java/org/fontory/fontorybe/member/domain/Member.java index dd1d166..3e0d323 100644 --- a/src/main/java/org/fontory/fontorybe/member/domain/Member.java +++ b/src/main/java/org/fontory/fontorybe/member/domain/Member.java @@ -65,6 +65,22 @@ public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo, String .build(); } + public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo) { + return Member.builder() + .id(this.id) + .nickname(initNewMemberInfo.getNickname()) + .gender(initNewMemberInfo.getGender()) + .birth(initNewMemberInfo.getBirth()) + .terms(initNewMemberInfo.getTerms()) + .profileImageKey(this.profileImageKey) + .createdAt(this.createdAt) + .provideId(this.provideId) + .deletedAt(this.deletedAt) + .provideId(this.provideId) + .status(MemberStatus.ACTIVATE) + .build(); + } + public Member update(MemberUpdateRequest memberUpdateRequest) { return Member.builder() //tobe update diff --git a/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java b/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java index 07c615f..ec9fd37 100644 --- a/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java @@ -58,6 +58,22 @@ public Member initNewMemberInfo(Long requestMemberId, return memberRepository.save(targetMember.initNewMemberInfo(initNewMemberInfoRequest, fileMetadata.getKey())); } + @Override + @Transactional + public Member initNewMemberInfo(Long requestMemberId, + InitMemberInfoRequest initNewMemberInfoRequest) { + Member targetMember = memberLookupService.getOrThrowById(requestMemberId); + if (targetMember.getStatus() == MemberStatus.ACTIVATE) { + throw new MemberAlreadyJoinedException(); + } else if (memberLookupService.existsByNickname(initNewMemberInfoRequest.getNickname())) { + throw new MemberDuplicateNameExistsException(); + } + + checkContainsBadWord(initNewMemberInfoRequest.getNickname()); + + return memberRepository.save(targetMember.initNewMemberInfo(initNewMemberInfoRequest)); + } + private void checkContainsBadWord(String nickname) { if (badWordFiltering.blankCheck(nickname)) { throw new MemberContainsBadWordException(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1ad54b1..b6e7e95 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,3 +34,7 @@ member.default.birth=1999-12-31 member.default.profile-image-key=default member.default.terms-agree=false +# max file size +spring.servlet.multipart.maxFileSize=5MB +# max total file size +spring.servlet.multipart.maxRequestSize=10MB \ No newline at end of file diff --git a/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java index bb20fbc..895cb5f 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java @@ -27,6 +27,7 @@ import static org.fontory.fontorybe.TestConstants.*; import static org.fontory.fontorybe.TestConstants.UPDATE_MEMBER_TERMS; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -106,7 +107,7 @@ void updateMemberSuccessTest() throws Exception { .andExpect(jsonPath("$.memberId", is(TEST_MEMBER_ID.intValue()))) .andExpect(jsonPath("$.nickname", is(UPDATE_MEMBER_NICKNAME))) .andExpect(jsonPath("$.terms", is(UPDATE_MEMBER_TERMS))) - .andExpect(jsonPath("$.profileImageUrl", is(testMember.getProfileImageKey()))) + .andExpect(jsonPath("$.profileImageUrl", containsString(testMember.getProfileImageKey()))) .andExpect(jsonPath("$.gender", is(testMember.getGender().name()))) .andExpect(jsonPath("$.birth", is(testMember.getBirth().toString()))); } diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java index 064f1a8..8e556b7 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java @@ -191,6 +191,7 @@ public TestContainer() { registrationController = RegistrationController.builder() .memberLookupService(memberLookupService) .memberOnboardService(memberOnboardService) + .cloudStorageService(cloudStorageService) .fileService(fileService) .build(); } From 11da0c63505e0b3b0f2ca4f31f509966845e5485 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Sat, 17 May 2025 00:21:13 +0900 Subject: [PATCH 12/27] fix: /member/me with onboarding user return 401 Unauthorized --- .../exception/AuthenticationRequiredException.java | 10 ++++++++++ .../common/adapter/inbound/GlobalExceptionHandler.java | 7 +++++++ .../fontorybe/member/controller/ProfileController.java | 5 +++++ 3 files changed, 22 insertions(+) create mode 100644 src/main/java/org/fontory/fontorybe/authentication/domain/exception/AuthenticationRequiredException.java diff --git a/src/main/java/org/fontory/fontorybe/authentication/domain/exception/AuthenticationRequiredException.java b/src/main/java/org/fontory/fontorybe/authentication/domain/exception/AuthenticationRequiredException.java new file mode 100644 index 0000000..26a9f8f --- /dev/null +++ b/src/main/java/org/fontory/fontorybe/authentication/domain/exception/AuthenticationRequiredException.java @@ -0,0 +1,10 @@ +package org.fontory.fontorybe.authentication.domain.exception; + +import org.fontory.fontorybe.common.domain.SkipDiscordNotification; + +@SkipDiscordNotification +public class AuthenticationRequiredException extends RuntimeException { + public AuthenticationRequiredException() { + super("Authentication required"); + } +} diff --git a/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java b/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java index f014499..534c869 100644 --- a/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java +++ b/src/main/java/org/fontory/fontorybe/common/adapter/inbound/GlobalExceptionHandler.java @@ -4,6 +4,7 @@ import io.jsonwebtoken.JwtException; import io.jsonwebtoken.MalformedJwtException; import lombok.RequiredArgsConstructor; +import org.fontory.fontorybe.authentication.domain.exception.AuthenticationRequiredException; import org.fontory.fontorybe.authentication.domain.exception.InvalidRefreshTokenException; import org.fontory.fontorybe.authentication.domain.exception.TokenNotFoundException; import org.fontory.fontorybe.bookmark.domain.exception.BookmarkAlreadyException; @@ -169,4 +170,10 @@ public BaseErrorResponse fileNotFoundException(FileNotFoundException e) { public BaseErrorResponse containsBadWordException(Exception e) { return new BaseErrorResponse(e.getMessage()); } + + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ExceptionHandler(AuthenticationRequiredException.class) + public BaseErrorResponse authenticationRequiredException(AuthenticationRequiredException e) { + return new BaseErrorResponse(e.getMessage()); + } } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java index 6281204..4c2c12d 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java @@ -10,6 +10,7 @@ import org.fontory.fontorybe.authentication.adapter.inbound.annotation.Login; import org.fontory.fontorybe.authentication.application.AuthService; import org.fontory.fontorybe.authentication.domain.UserPrincipal; +import org.fontory.fontorybe.authentication.domain.exception.AuthenticationRequiredException; import org.fontory.fontorybe.file.application.port.CloudStorageService; import org.fontory.fontorybe.file.application.port.FileService; import org.fontory.fontorybe.file.domain.FileUploadResult; @@ -20,6 +21,7 @@ import org.fontory.fontorybe.member.controller.port.MemberLookupService; import org.fontory.fontorybe.member.controller.port.MemberUpdateService; import org.fontory.fontorybe.member.domain.Member; +import org.fontory.fontorybe.member.infrastructure.entity.MemberStatus; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -54,6 +56,9 @@ public ResponseEntity getMyProfile( log.info("Request received: getMyInfo member ID: {}", requestMemberId); Member lookupMember = memberLookupService.getOrThrowById(requestMemberId); + if (lookupMember.getStatus().equals(MemberStatus.ONBOARDING)) { + throw new AuthenticationRequiredException(); + } String fileUrl = cloudStorageService.getProfileImageUrl(lookupMember.getProfileImageKey()); log.info("ProfileImageUrl generated : {}", fileUrl); From 26b86e40fb48ba8b2d4747e3895d570f1a2a8cb0 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Mon, 19 May 2025 13:34:16 +0900 Subject: [PATCH 13/27] =?UTF-8?q?feat:=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=ED=81=90=EC=97=90=20=ED=8F=B0=ED=8A=B8=20=EC=98=81=EC=96=B4=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=A0=84=EB=8B=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fontorybe/font/service/dto/FontRequestProduceDto.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java b/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java index 555919d..255fa9f 100644 --- a/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java +++ b/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java @@ -15,6 +15,7 @@ public class FontRequestProduceDto { private Long fontId; private String fileKey; private String fontName; + private String fontEngName; private String templateURL; private String author; private String requestUUID; @@ -25,6 +26,7 @@ public static FontRequestProduceDto from(Font font, Member member, String templa .fileKey(font.getKey()) .fontId(font.getId()) .fontName(font.getName()) + .fontEngName(font.getEngName()) .templateURL(templateUrl) .author(member.getNickname()) .requestUUID(MDC.get("requestId")) From 522b69270a126d322d1e21cee8fe61f4de29d33a Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Mon, 19 May 2025 18:21:04 +0900 Subject: [PATCH 14/27] refactor: remove terms properties in member entity --- .../application/DevTokenInitializer.java | 1 - .../member/controller/ProfileController.java | 3 +- .../controller/dto/InitMemberInfoRequest.java | 3 -- .../controller/dto/MemberCreateResponse.java | 2 -- .../controller/dto/MemberUpdateRequest.java | 4 +-- .../controller/dto/MemberUpdateResponse.java | 2 -- .../controller/dto/MyProfileResponse.java | 6 ---- .../fontorybe/member/domain/Member.java | 11 ------- .../member/domain/MemberDefaults.java | 8 ----- .../infrastructure/entity/MemberEntity.java | 4 --- src/main/resources/application.properties | 1 - .../org/fontory/fontorybe/TestConstants.java | 3 -- .../ProfileControllerIntegrationTest.java | 6 ++-- ...RegistrationControllerIntegrationTest.java | 2 +- .../MemberUpdateServiceIntegrationTest.java | 23 ++++++------- .../unit/file/FileRequestMapperTest.java | 3 +- .../fontorybe/unit/file/FileServiceTest.java | 3 +- .../controller/ProfileControllerTest.java | 7 ++-- .../service/MemberUpdateServiceTest.java | 32 ++++++++----------- .../unit/mock/FakeMemberRepository.java | 2 -- .../fontorybe/unit/mock/TestContainer.java | 7 ++-- src/test/resources/sql/createFileTestData.sql | 4 +-- src/test/resources/sql/createFontTestData.sql | 8 ++--- .../resources/sql/createMemberTestData.sql | 4 +-- 24 files changed, 44 insertions(+), 105 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java b/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java index 132b4f6..4312f1a 100644 --- a/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java +++ b/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java @@ -95,7 +95,6 @@ public void initTokens() { Member m = Member.builder() .gender(Gender.MALE) .provideId(provide.getId()) - .terms(true) .birth(LocalDate.now()) .nickname("Tester") .profileImageKey(memberDefaults.getProfileImageKey()) diff --git a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java index 4c2c12d..26f24ba 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java @@ -94,8 +94,7 @@ public ResponseEntity updateMember( } Member updatedMember = memberUpdateService.update(requestMemberId, req); - log.info("Updated : Member ID: {} Updated successfully with nickname: {}, terms : {}", - updatedMember.getId(), updatedMember.getNickname(), updatedMember.getTerms()); + log.info("Updated : Member ID: {} Updated successfully with nickname: {}", updatedMember.getId(), updatedMember.getNickname()); MyProfileResponse myProfileResponse = MyProfileResponse.from(updatedMember, cloudStorageService.getProfileImageUrl(updatedMember.getProfileImageKey())); log.info("Response sent: MyProfileDto : {}", myProfileResponse); diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java index ff9e248..9217ebb 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/InitMemberInfoRequest.java @@ -27,7 +27,4 @@ public class InitMemberInfoRequest { @NotNull(message = "생년월일을 입력해주세요.") @Past(message = "생년월일은 과거 날짜만 가능합니다.") private LocalDate birth; - - @NotNull(message = "이용약관 동의 여부를 선택해주세요.") - private Boolean terms; } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java index 510988c..ef241c9 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java @@ -15,7 +15,6 @@ public class MemberCreateResponse { private final Gender gender; private final String profileImageUrl; private final LocalDate birth; - private final boolean terms; private final LocalDateTime createdAt; public static MemberCreateResponse from(Member member, String url) { @@ -24,7 +23,6 @@ public static MemberCreateResponse from(Member member, String url) { .gender(member.getGender()) .profileImageUrl(url) .birth(member.getBirth()) - .terms(member.getTerms()) .createdAt(member.getCreatedAt()) .build(); } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java index b658a46..3a9780a 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateRequest.java @@ -16,7 +16,5 @@ public class MemberUpdateRequest { @NotBlank(message = "닉네임은 필수 입력 값입니다.") @Size(min = 2, max = 20, message = "닉네임은 2자 이상 20자 이하로 입력해주세요.") private String nickname; - - @NotNull(message = "이용약관 동의 여부를 선택해주세요.") - private Boolean terms; } + diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java index 676718f..157b57d 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java @@ -11,14 +11,12 @@ public class MemberUpdateResponse { private String nickname; private String profileImage; - private Boolean terms; private LocalDateTime updatedAt; public static MemberUpdateResponse from(Member member) { return MemberUpdateResponse.builder() .nickname(member.getNickname()) .profileImage(member.getProfileImageKey()) - .terms(member.getTerms()) .updatedAt(member.getUpdatedAt()) .build(); } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java index f396acc..7c93aab 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java @@ -16,7 +16,6 @@ public class MyProfileResponse { private String nickname; private Gender gender; private LocalDate birth; - private boolean terms; private String profileImageUrl; public static MyProfileResponse from(Member member, String url) { @@ -25,12 +24,7 @@ public static MyProfileResponse from(Member member, String url) { .nickname(member.getNickname()) .birth(member.getBirth()) .gender(member.getGender()) - .terms(member.getTerms()) .profileImageUrl(url) .build(); } - - public boolean getTerms() { - return this.terms; - } } diff --git a/src/main/java/org/fontory/fontorybe/member/domain/Member.java b/src/main/java/org/fontory/fontorybe/member/domain/Member.java index 3e0d323..e84790e 100644 --- a/src/main/java/org/fontory/fontorybe/member/domain/Member.java +++ b/src/main/java/org/fontory/fontorybe/member/domain/Member.java @@ -23,8 +23,6 @@ public class Member { private LocalDate birth; - private boolean terms; - private String profileImageKey; private LocalDateTime createdAt; @@ -42,7 +40,6 @@ public static Member fromDefaults(MemberDefaults memberDefaults, String nickname .nickname(nickname) .gender(memberDefaults.getGender()) .birth(memberDefaults.getBirth()) - .terms(memberDefaults.getTerms()) .profileImageKey(memberDefaults.getProfileImageKey()) .provideId(provide.getId()) .status(MemberStatus.ONBOARDING) @@ -55,7 +52,6 @@ public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo, String .nickname(initNewMemberInfo.getNickname()) .gender(initNewMemberInfo.getGender()) .birth(initNewMemberInfo.getBirth()) - .terms(initNewMemberInfo.getTerms()) .profileImageKey(profileImageKey) .createdAt(this.createdAt) .provideId(this.provideId) @@ -71,7 +67,6 @@ public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo) { .nickname(initNewMemberInfo.getNickname()) .gender(initNewMemberInfo.getGender()) .birth(initNewMemberInfo.getBirth()) - .terms(initNewMemberInfo.getTerms()) .profileImageKey(this.profileImageKey) .createdAt(this.createdAt) .provideId(this.provideId) @@ -85,7 +80,6 @@ public Member update(MemberUpdateRequest memberUpdateRequest) { return Member.builder() //tobe update .nickname(memberUpdateRequest.getNickname()) - .terms(memberUpdateRequest.getTerms()) //not to be update .id(this.id) @@ -104,10 +98,6 @@ public void disable() { this.deletedAt = LocalDateTime.now(); } - public boolean getTerms() { - return this.terms; - } - public Member setProfileImageKey(String profileImageKey) { return Member.builder() .profileImageKey(profileImageKey) @@ -119,7 +109,6 @@ public Member setProfileImageKey(String profileImageKey) { .createdAt(this.createdAt) .provideId(this.provideId) .deletedAt(this.deletedAt) - .terms(this.terms) .provideId(this.provideId) .status(this.status) .build(); diff --git a/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java b/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java index b9ff27f..fef9010 100644 --- a/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java +++ b/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java @@ -13,22 +13,14 @@ @ConfigurationProperties(prefix="member.default") public class MemberDefaults { private LocalDate birth; - private boolean termsAgree; private String profileImageKey; private Gender gender; @ConstructorBinding public MemberDefaults(LocalDate birth, - boolean termsAgree, String profileImageKey) { this.birth = birth; - this.termsAgree = termsAgree; this.profileImageKey = profileImageKey; this.gender = Gender.NONE; } - - public boolean getTerms() { - return this.termsAgree; - } - } diff --git a/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java b/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java index 8fe3678..d62e7d5 100644 --- a/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java +++ b/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java @@ -36,8 +36,6 @@ public class MemberEntity extends BaseEntity { private LocalDate birth; - private Boolean terms; - private String profileImageKey; private Long provideId; @@ -53,7 +51,6 @@ public Member toModel() { .nickname(nickname) .gender(gender) .birth(birth) - .terms(terms) .profileImageKey(profileImageKey) .provideId(provideId) .status(status) @@ -69,7 +66,6 @@ public static MemberEntity from(Member member) { .nickname(member.getNickname()) .gender(member.getGender()) .birth(member.getBirth()) - .terms(member.getTerms()) .profileImageKey(member.getProfileImageKey()) .provideId(member.getProvideId()) .status(member.getStatus()) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d51fff4..fcc6af4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -32,7 +32,6 @@ jwt.refresh-token-validity-ms=604800000 member.default.birth=1999-12-31 member.default.profile-image-key=default -member.default.terms-agree=false # max file size spring.servlet.multipart.maxFileSize=5MB diff --git a/src/test/java/org/fontory/fontorybe/TestConstants.java b/src/test/java/org/fontory/fontorybe/TestConstants.java index a96bde7..4a1d60b 100644 --- a/src/test/java/org/fontory/fontorybe/TestConstants.java +++ b/src/test/java/org/fontory/fontorybe/TestConstants.java @@ -11,7 +11,6 @@ private TestConstants() {} // 인스턴스 생성 방지 // NOT created in SQL public static final Gender NEW_MEMBER_GENDER = Gender.FEMALE; - public static final boolean NEW_MEMBER_TERMS = false; public static final LocalDate NEW_MEMBER_BIRTH = LocalDate.of(2025, 1, 22); public static final String NEW_MEMBER_NICKNAME = "newMemberNickName"; public static final String NEW_MEMBER_PROFILE_KEY = "newMemberProfileImage"; @@ -23,7 +22,6 @@ private TestConstants() {} // 인스턴스 생성 방지 public static final String DEFAULT_PROFILE_KEY = "defaultProfileImage"; // Created in SQL public static final Gender TEST_MEMBER_GENDER = Gender.MALE; - public static final boolean TEST_MEMBER_TERMS = true; public static final LocalDate TEST_MEMBER_BIRTH = LocalDate.of(2025, 1, 26); public static final String TEST_MEMBER_NICKNAME = "testMemberNickName"; public static final String TEST_MEMBER_PROFILE_KEY = "testMemberProfileImage"; @@ -36,7 +34,6 @@ private TestConstants() {} // 인스턴스 생성 방지 public static final Long TEST_PROVIDE_ID = 1L; public static final Long NON_EXIST_ID = -1L; - public static final boolean UPDATE_MEMBER_TERMS = Boolean.FALSE; public static final String UPDATE_MEMBER_NICKNAME = "updateMemberNickName"; public static final String UPDATE_MEMBER_PROFILE_KEY= "updateMemberProfileImage"; diff --git a/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java index 895cb5f..78b3945 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java @@ -26,7 +26,6 @@ import java.util.UUID; import static org.fontory.fontorybe.TestConstants.*; -import static org.fontory.fontorybe.TestConstants.UPDATE_MEMBER_TERMS; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.mockito.ArgumentMatchers.any; @@ -80,7 +79,7 @@ void checkDuplicateFalseTest() throws Exception { @Test @DisplayName("PUT /member - update member success with valid Authorization JWT Cookie") void updateMemberSuccessTest() throws Exception { - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME); String jsonRequest = objectMapper.writeValueAsString(memberUpdateRequest); MockMultipartFile jsonPart = new MockMultipartFile( "req", @@ -106,7 +105,6 @@ void updateMemberSuccessTest() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.memberId", is(TEST_MEMBER_ID.intValue()))) .andExpect(jsonPath("$.nickname", is(UPDATE_MEMBER_NICKNAME))) - .andExpect(jsonPath("$.terms", is(UPDATE_MEMBER_TERMS))) .andExpect(jsonPath("$.profileImageUrl", containsString(testMember.getProfileImageKey()))) .andExpect(jsonPath("$.gender", is(testMember.getGender().name()))) .andExpect(jsonPath("$.birth", is(testMember.getBirth().toString()))); @@ -115,7 +113,7 @@ void updateMemberSuccessTest() throws Exception { @Test @DisplayName("PUT /member without Authorization JWT Cookie returns 401") void putMemberWithoutAuthHeader() throws Exception { - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME); String jsonRequest = objectMapper.writeValueAsString(memberUpdateRequest); mockMvc.perform(put("/member/me") diff --git a/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java index 79e4fa8..da4f5a8 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java @@ -105,7 +105,7 @@ void checkDuplicateTrueTest() throws Exception { @Test @DisplayName("POST /register - add member success with valid Authorization header") void addMemberSuccessTest() throws Exception { - InitMemberInfoRequest initMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); String jsonRequest = objectMapper.writeValueAsString(initMemberInfoRequest); MockMultipartFile jsonPart = new MockMultipartFile( "req", diff --git a/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java index b0b7391..c4b5b30 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java @@ -85,7 +85,6 @@ void getOrThrowByIdTest() { () -> assertThat(foundMember.getNickname()).isEqualTo(TEST_MEMBER_NICKNAME), () -> assertThat(foundMember.getGender()).isEqualTo(TEST_MEMBER_GENDER), () -> assertThat(foundMember.getProvideId()).isEqualTo(TEST_PROVIDE_ID), - () -> assertThat(foundMember.getTerms()).isEqualTo(TEST_MEMBER_TERMS), () -> assertThat(foundMember.getProfileImageKey()).isEqualTo(TEST_MEMBER_PROFILE_KEY), () -> assertThat(foundMember.getCreatedAt()).isNotNull(), () -> assertThat(foundMember.getUpdatedAt()).isNotNull(), @@ -104,7 +103,7 @@ void getOrThrowByIdTestX() { @Test @DisplayName("member - create success test") void createTest() { - InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); ProvideCreateDto newProvideCreateDto = new ProvideCreateDto(NEW_MEMBER_PROVIDER, NEW_MEMBER_PROVIDED_ID, NEW_MEMBER_EMAIL); Provide createdProvide = provideService.create(newProvideCreateDto); Member createdMember = create(initNewMemberRequest, createdProvide); @@ -115,7 +114,6 @@ void createTest() { () -> assertThat(createdMember.getNickname()).isEqualTo(NEW_MEMBER_NICKNAME), () -> assertThat(createdMember.getGender()).isEqualTo(NEW_MEMBER_GENDER), () -> assertThat(createdMember.getBirth()).isEqualTo(NEW_MEMBER_BIRTH), - () -> assertThat(createdMember.getTerms()).isEqualTo(NEW_MEMBER_TERMS), () -> assertThat(createdMember.getProfileImageKey()).isEqualTo(NEW_MEMBER_PROFILE_KEY), () -> assertThat(createdMember.getCreatedAt()).isNotNull(), () -> assertThat(createdMember.getUpdatedAt()).isNotNull() @@ -130,11 +128,11 @@ void createDuplicateNicknameTest() { Provide createdProvide1 = provideService.create(provideCreateDto1); Provide createdProvide2 = provideService.create(provideCreateDto2); // 첫 번째 회원 생성 - InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); create(initNewMemberRequest, createdProvide1); // 동일 닉네임으로 또 회원 생성 시 예외 발생 - InitMemberInfoRequest duplicateInitNewMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest duplicateInitNewMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); assertThatThrownBy( () -> create(duplicateInitNewMemberInfoRequest, createdProvide2)) .isExactlyInstanceOf(MemberDuplicateNameExistsException.class); @@ -145,7 +143,7 @@ void createDuplicateNicknameTest() { void updateTest() { // 기존에 존재하는 회원(TEST_MEMBER_ID) 조회 Member member = memberLookupService.getOrThrowById(TEST_MEMBER_ID); - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME); // 업데이트 요청: 단일 회원 ID만 전달 Member updatedMember = memberUpdateService.update(TEST_MEMBER_ID, memberUpdateRequest); @@ -157,7 +155,6 @@ void updateTest() { () -> assertThat(updatedMember.getProvideId()).isEqualTo(member.getProvideId()), () -> assertThat(updatedMember.getNickname()).isEqualTo(UPDATE_MEMBER_NICKNAME), () -> assertThat(updatedMember.getProfileImageKey()).isEqualTo(member.getProfileImageKey()), - () -> assertThat(updatedMember.getTerms()).isEqualTo(UPDATE_MEMBER_TERMS), () -> assertThat(updatedMember.getUpdatedAt()).isAfter(member.getUpdatedAt()) ); } @@ -173,13 +170,13 @@ void updateDuplicateNicknameTest() { // 두 회원을 각각 생성 String uniqueNickname1 = UUID.randomUUID().toString(); String uniqueNickname2 = UUID.randomUUID().toString(); - InitMemberInfoRequest initNewMemberInfoRequestDto1 = new InitMemberInfoRequest(uniqueNickname1, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); - InitMemberInfoRequest initNewMemberInfoRequestDto2 = new InitMemberInfoRequest(uniqueNickname2, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberInfoRequestDto1 = new InitMemberInfoRequest(uniqueNickname1, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); + InitMemberInfoRequest initNewMemberInfoRequestDto2 = new InitMemberInfoRequest(uniqueNickname2, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); Member member1 = create(initNewMemberInfoRequestDto1, createdProvide1); create(initNewMemberInfoRequestDto2, createdProvide2); // member1의 닉네임을 이미 존재하는 이름(uniqueNickname2)으로 업데이트 시도하면 예외 발생 - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(uniqueNickname2, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(uniqueNickname2); Long member1Id = member1.getId(); assertThatThrownBy( @@ -190,7 +187,7 @@ void updateDuplicateNicknameTest() { @Test @DisplayName("member - update fail test caused by member not found") void updateNonExistentMemberTest() { - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME); assertThatThrownBy( () -> memberUpdateService.update(nonExistentId, memberUpdateRequest)) .isExactlyInstanceOf(MemberNotFoundException.class); @@ -203,7 +200,7 @@ void disableTest() { ProvideCreateDto provideCreateDto = new ProvideCreateDto(Provider.GOOGLE, UUID.randomUUID().toString(), UUID.randomUUID().toString()); Provide createdProvide = provideService.create(provideCreateDto); - InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); Member member = create(initNewMemberRequest, createdProvide); // 회원 비활성화 요청 (단일 ID) @@ -218,7 +215,7 @@ void disableAlreadyDisabledTest() { ProvideCreateDto provideCreateDto = new ProvideCreateDto(Provider.GOOGLE, UUID.randomUUID().toString(), UUID.randomUUID().toString()); Provide createdProvide = provideService.create(provideCreateDto); - InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); Member member = create(initNewMemberRequest, createdProvide); // 최초 비활성화 처리 diff --git a/src/test/java/org/fontory/fontorybe/unit/file/FileRequestMapperTest.java b/src/test/java/org/fontory/fontorybe/unit/file/FileRequestMapperTest.java index da1a3e3..6868b60 100644 --- a/src/test/java/org/fontory/fontorybe/unit/file/FileRequestMapperTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/file/FileRequestMapperTest.java @@ -108,8 +108,7 @@ private static InitMemberInfoRequest createMemberRequest(String nickname) { return new InitMemberInfoRequest( nickname, Gender.MALE, - LocalDate.of(2025, 1, 26), - true + LocalDate.of(2025, 1, 26) ); } diff --git a/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java b/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java index c9057cc..58daa13 100644 --- a/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java @@ -71,8 +71,7 @@ private static InitMemberInfoRequest createMemberRequest(String nickname) { return new InitMemberInfoRequest( nickname, Gender.MALE, - LocalDate.of(2025, 1, 26), - true + LocalDate.of(2025, 1, 26) ); } diff --git a/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java b/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java index 0871ca4..ba3706b 100644 --- a/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java @@ -31,7 +31,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.fontory.fontorybe.TestConstants.*; -import static org.fontory.fontorybe.TestConstants.UPDATE_MEMBER_TERMS; import static org.junit.jupiter.api.Assertions.assertAll; class ProfileControllerTest { @@ -111,8 +110,7 @@ void disableAlreadyDisabledMemberTest() { void testUpdateMember() { //given MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest( - UPDATE_MEMBER_NICKNAME, - UPDATE_MEMBER_TERMS + UPDATE_MEMBER_NICKNAME ); //when @@ -124,7 +122,6 @@ void testUpdateMember() { () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK), () -> assertThat(body).isNotNull(), () -> assertThat(body.getNickname()).isEqualTo(UPDATE_MEMBER_NICKNAME), - () -> assertThat(body.getTerms()).isEqualTo(UPDATE_MEMBER_TERMS), () -> assertThat(body.getProfileImageUrl()).isEqualTo(cloudStorageService.getProfileImageUrl(testMember.getProfileImageKey())) ); } @@ -134,7 +131,7 @@ void testUpdateMember() { void updateMemberNonExistentTest() { // given UserPrincipal nonExistentUserPrincipal = new UserPrincipal(NON_EXIST_ID); - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(TestConstants.UPDATE_MEMBER_NICKNAME, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(TestConstants.UPDATE_MEMBER_NICKNAME); // when & then assertThatThrownBy(() -> profileController.updateMember(nonExistentUserPrincipal, memberUpdateRequest, mockFiles)) diff --git a/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java b/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java index f119132..a2a7e0f 100644 --- a/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java @@ -71,7 +71,6 @@ void getOrThrownByIdTest() { () -> assertThat(foundMember.getNickname()).isEqualTo(TEST_MEMBER_NICKNAME), () -> assertThat(foundMember.getGender()).isEqualTo(TEST_MEMBER_GENDER), () -> assertThat(foundMember.getProvideId()).isEqualTo(testMemberProvideId), - () -> assertThat(foundMember.getTerms()).isEqualTo(TEST_MEMBER_TERMS), () -> assertThat(foundMember.getProfileImageKey()).isNotEqualTo(DEFAULT_PROFILE_KEY), () -> assertThat(foundMember.getCreatedAt()).isNotNull(), () -> assertThat(foundMember.getUpdatedAt()).isNotNull(), @@ -114,7 +113,7 @@ void createTest() { ProvideCreateDto provideCreateDto = new ProvideCreateDto(NEW_MEMBER_PROVIDER, NEW_MEMBER_PROVIDED_ID, NEW_MEMBER_EMAIL); Provide createdProvide = testContainer.provideService.create(provideCreateDto); - InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); Member createdMember = testContainer.create(initNewMemberRequestDto, createdProvide); assertAll( @@ -123,7 +122,6 @@ void createTest() { () -> assertThat(createdMember.getNickname()).isEqualTo(NEW_MEMBER_NICKNAME), () -> assertThat(createdMember.getGender()).isEqualTo(NEW_MEMBER_GENDER), () -> assertThat(createdMember.getBirth()).isEqualTo(NEW_MEMBER_BIRTH), - () -> assertThat(createdMember.getTerms()).isEqualTo(NEW_MEMBER_TERMS), () -> assertThat(createdMember.getProfileImageKey()).isNotEqualTo(DEFAULT_PROFILE_KEY), () -> assertThat(createdMember.getCreatedAt()).isNotNull(), () -> assertThat(createdMember.getUpdatedAt()).isNotNull() @@ -139,11 +137,11 @@ void createDuplicateNicknameTest() { Provide createdProvide1 = testContainer.provideService.create(provideCreateDto1); Provide createdProvide2 = testContainer.provideService.create(provideCreateDto2); - InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); testContainer.create(initNewMemberRequestDto, createdProvide1); // 동일 닉네임으로 또 회원 생성 시 예외 발생 - InitMemberInfoRequest duplicateInitNewMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest duplicateInitNewMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); assertThatThrownBy( () -> testContainer.create(duplicateInitNewMemberInfoRequest, createdProvide2)) .isExactlyInstanceOf(MemberDuplicateNameExistsException.class); @@ -154,7 +152,7 @@ void createDuplicateNicknameTest() { void updateTest() { Long requestMemberId = testMemberId; Member member = memberLookupService.getOrThrowById(testMemberId); - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME); Member updatedMember = memberUpdateService.update(requestMemberId, memberUpdateRequest); assertAll( @@ -165,7 +163,6 @@ void updateTest() { () -> assertThat(updatedMember.getProvideId()).isEqualTo(member.getProvideId()), () -> assertThat(updatedMember.getNickname()).isEqualTo(UPDATE_MEMBER_NICKNAME), () -> assertThat(updatedMember.getProfileImageKey()).isEqualTo(member.getProfileImageKey()), - () -> assertThat(updatedMember.getTerms()).isEqualTo(UPDATE_MEMBER_TERMS), () -> assertThat(updatedMember.getUpdatedAt()).isAfter(member.getUpdatedAt()) ); } @@ -181,13 +178,13 @@ void updateDuplicateNicknameTest() { String uniqueNickname1 = UUID.randomUUID().toString(); String uniqueNickname2 = UUID.randomUUID().toString(); - InitMemberInfoRequest initNewMemberRequestDto1 = new InitMemberInfoRequest(uniqueNickname1, Gender.MALE, NEW_MEMBER_BIRTH, TEST_MEMBER_TERMS); - InitMemberInfoRequest initNewMemberRequestDto2 = new InitMemberInfoRequest(uniqueNickname2, Gender.FEMALE, NEW_MEMBER_BIRTH, TEST_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequestDto1 = new InitMemberInfoRequest(uniqueNickname1, Gender.MALE, NEW_MEMBER_BIRTH); + InitMemberInfoRequest initNewMemberRequestDto2 = new InitMemberInfoRequest(uniqueNickname2, Gender.FEMALE, NEW_MEMBER_BIRTH); Member member1 = testContainer.create(initNewMemberRequestDto1, createdProvide1); testContainer.create(initNewMemberRequestDto2, createdProvide2); // member1의 닉네임을 이미 존재하는 이름으로 업데이트 시도하면 예외 발생 - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(uniqueNickname2, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(uniqueNickname2); assertThatThrownBy( () -> memberUpdateService.update(member1.getId(), memberUpdateRequest)) .isExactlyInstanceOf(MemberDuplicateNameExistsException.class); @@ -201,8 +198,8 @@ void createProvideMemberMoreThanOneTest() { String uniqueNickname1 = UUID.randomUUID().toString(); String uniqueNickname2 = UUID.randomUUID().toString(); - InitMemberInfoRequest initNewMemberRequestDto1 = new InitMemberInfoRequest(uniqueNickname1, Gender.MALE, NEW_MEMBER_BIRTH, TEST_MEMBER_TERMS); - InitMemberInfoRequest initNewMemberRequestDto2 = new InitMemberInfoRequest(uniqueNickname2, Gender.MALE, NEW_MEMBER_BIRTH, TEST_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequestDto1 = new InitMemberInfoRequest(uniqueNickname1, Gender.MALE, NEW_MEMBER_BIRTH); + InitMemberInfoRequest initNewMemberRequestDto2 = new InitMemberInfoRequest(uniqueNickname2, Gender.MALE, NEW_MEMBER_BIRTH); testContainer.create(initNewMemberRequestDto1, createdProvide); assertThatThrownBy( @@ -214,7 +211,7 @@ void createProvideMemberMoreThanOneTest() { @DisplayName("member - update fail test caused by member not found") void updateNonExistentMemberTest() { // 존재하지 않는 회원(-1L)을 대상으로 업데이트 시도 시 예외 발생 - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME, UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME); assertThatThrownBy( () -> memberUpdateService.update(nonExistentId, memberUpdateRequest)) .isExactlyInstanceOf(MemberNotFoundException.class); @@ -226,15 +223,14 @@ void updateNoNicknameChangeTest() { // 닉네임 변경 없이 다른 정보만 업데이트하는 경우 Member member = memberLookupService.getOrThrowById(testMemberId); // 업데이트 DTO에서 기존 닉네임 그대로 사용 - MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(member.getNickname(), UPDATE_MEMBER_TERMS); + MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(member.getNickname()); // 같은 닉네임을 사용하더라도 본인의 정보이므로 중복 체크가 통과되어 업데이트 성공해야 함 Member updatedMember = memberUpdateService.update(member.getId(), memberUpdateRequest); assertAll( () -> assertThat(updatedMember.getId()).isEqualTo(member.getId()), () -> assertThat(updatedMember.getNickname()).isEqualTo(member.getNickname()), - () -> assertThat(updatedMember.getProfileImageKey()).isEqualTo(member.getProfileImageKey()), - () -> assertThat(updatedMember.getTerms()).isEqualTo(UPDATE_MEMBER_TERMS) + () -> assertThat(updatedMember.getProfileImageKey()).isEqualTo(member.getProfileImageKey()) ); } @@ -254,7 +250,7 @@ void disableTest() { ProvideCreateDto provideCreateDto = new ProvideCreateDto(NEW_MEMBER_PROVIDER, NEW_MEMBER_PROVIDED_ID, NEW_MEMBER_EMAIL); Provide createdProvide = testContainer.provideService.create(provideCreateDto); - InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); Member member = testContainer.create(initNewMemberRequestDto, createdProvide); // 회원 비활성화 @@ -272,7 +268,7 @@ void disableAlreadyDisabledTest() { ProvideCreateDto provideCreateDto = new ProvideCreateDto(NEW_MEMBER_PROVIDER, NEW_MEMBER_PROVIDED_ID, NEW_MEMBER_EMAIL); Provide createdProvide = testContainer.provideService.create(provideCreateDto); - InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberRequestDto = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); Member member = testContainer.create(initNewMemberRequestDto, createdProvide); // 회원 비활성화 diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java b/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java index 49fec4f..82c33f4 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java @@ -23,7 +23,6 @@ public Member save(Member member) { .nickname(member.getNickname()) .gender(member.getGender()) .birth(member.getBirth()) - .terms(member.getTerms()) .profileImageKey(member.getProfileImageKey()) .provideId(member.getProvideId()) .status(member.getStatus()) @@ -39,7 +38,6 @@ public Member save(Member member) { .nickname(member.getNickname()) .gender(member.getGender()) .birth(member.getBirth()) - .terms(member.getTerms()) .profileImageKey(member.getProfileImageKey()) .provideId(member.getProvideId()) .status(member.getStatus()) diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java index 8e556b7..786e652 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java @@ -139,7 +139,6 @@ public TestContainer() { memberDefaults = new MemberDefaults( LocalDate.of(1999, 12, 31), - false, DEFAULT_PROFILE_KEY); fileService = FileServiceImpl.builder() @@ -231,7 +230,7 @@ public Member create(InitMemberInfoRequest initNewMemberInfoRequest, Provide pro public Provide testMemberProvide; public Provide newMemberProvide; - public final InitMemberInfoRequest newInitMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + public final InitMemberInfoRequest newInitMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); public final ProvideCreateDto newMemberProvideCreateDto = new ProvideCreateDto(NEW_MEMBER_PROVIDER, NEW_MEMBER_PROVIDED_ID, NEW_MEMBER_EMAIL); public Member createNotInitedMember() { @@ -241,13 +240,13 @@ public Member createNotInitedMember() { public Member createTestMember() { testMemberProvide = provideService.create(testMemberProvideCreateDto); - InitMemberInfoRequest initMemberInfoRequest = new InitMemberInfoRequest(TEST_MEMBER_NICKNAME, TEST_MEMBER_GENDER, TEST_MEMBER_BIRTH, TEST_MEMBER_TERMS); + InitMemberInfoRequest initMemberInfoRequest = new InitMemberInfoRequest(TEST_MEMBER_NICKNAME, TEST_MEMBER_GENDER, TEST_MEMBER_BIRTH); return create(initMemberInfoRequest, testMemberProvide); } public Member createNewMember() { newMemberProvide = provideService.create(newMemberProvideCreateDto); - InitMemberInfoRequest initNewMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH, NEW_MEMBER_TERMS); + InitMemberInfoRequest initNewMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); return create(initNewMemberInfoRequest, newMemberProvide); } } diff --git a/src/test/resources/sql/createFileTestData.sql b/src/test/resources/sql/createFileTestData.sql index 2f048e6..69c7524 100644 --- a/src/test/resources/sql/createFileTestData.sql +++ b/src/test/resources/sql/createFileTestData.sql @@ -3,8 +3,8 @@ truncate table `member`; truncate table `provide`; -- Test member data -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `terms`, `profile_image_key`, `provide_id`, `status`,`created_at`, `updated_at`) -values (999, 'existMemberNickName', 'MALE', '2025-01-26', 1, 'existMemberProfileImage', 999, 'ACTIVATE', NOW(), NOW()); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`,`created_at`, `updated_at`) +values (999, 'existMemberNickName', 'MALE', '2025-01-26', 'existMemberProfileImage', 999, 'ACTIVATE', NOW(), NOW()); -- Test provide data insert into `provide` (`provide_id`, `provider`, `provided_id`, `email`, `member_id`) diff --git a/src/test/resources/sql/createFontTestData.sql b/src/test/resources/sql/createFontTestData.sql index 58d0018..6e42ddc 100644 --- a/src/test/resources/sql/createFontTestData.sql +++ b/src/test/resources/sql/createFontTestData.sql @@ -26,11 +26,11 @@ INSERT INTO `font` ( '2025-04-08 10:00:00' ); -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `terms`, `profile_image_key`, `provide_id`, `status`, `created_at`, `updated_at`) -values (999, 'existMemberNickName', 'MALE', '2025-01-26', 1, 'existMemberProfileImage', 999, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`, `created_at`, `updated_at`) +values (999, 'existMemberNickName', 'MALE', '2025-01-26', 'existMemberProfileImage', 999, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `terms`, `profile_image_key`, `provide_id`, `status`, `created_at`, `updated_at`) -values (1, 'createdMemberNickName', 'MALE', '2025-01-26', 1, 'existMemberProfileImage', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`, `created_at`, `updated_at`) +values (1, 'createdMemberNickName', 'MALE', '2025-01-26', 'existMemberProfileImage', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); insert into `provide` (`provide_id`, `provider`, `provided_id`, `email`, `member_id`) values (999, 'GOOGLE', 'test_provided_id', 'test_email', 999); diff --git a/src/test/resources/sql/createMemberTestData.sql b/src/test/resources/sql/createMemberTestData.sql index 224330d..eebb305 100644 --- a/src/test/resources/sql/createMemberTestData.sql +++ b/src/test/resources/sql/createMemberTestData.sql @@ -1,8 +1,8 @@ truncate table `member`; truncate table `provide`; -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `terms`, `profile_image_key`, `provide_id`, `status`,`created_at`, `updated_at`) -values (999, 'testMemberNickName', 'MALE', '2025-01-26', 1, 'testMemberProfileImage', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`,`created_at`, `updated_at`) +values (999, 'testMemberNickName', 'MALE', '2025-01-26', 'testMemberProfileImage', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); insert into `provide` (`provide_id`, `provider`, `provided_id`, `email`, `member_id`) values (1, 'GOOGLE', 'testMemberProvidedId', 'testMemberEmail', 999); \ No newline at end of file From c30f862fba30e3e63e06837087f720f226b03158 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Mon, 19 May 2025 21:35:46 +0900 Subject: [PATCH 15/27] hotfix: fix error when /member/me for test token --- .../fontorybe/common/application/DevTokenInitializer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java b/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java index 4312f1a..aa0f0a6 100644 --- a/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java +++ b/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java @@ -14,6 +14,7 @@ import org.fontory.fontorybe.member.domain.Member; import org.fontory.fontorybe.member.domain.MemberDefaults; import org.fontory.fontorybe.member.infrastructure.entity.Gender; +import org.fontory.fontorybe.member.infrastructure.entity.MemberStatus; import org.fontory.fontorybe.member.service.port.MemberRepository; import org.fontory.fontorybe.provide.domain.Provide; import org.fontory.fontorybe.provide.infrastructure.entity.Provider; @@ -97,6 +98,7 @@ public void initTokens() { .provideId(provide.getId()) .birth(LocalDate.now()) .nickname("Tester") + .status(MemberStatus.ONBOARDING) .profileImageKey(memberDefaults.getProfileImageKey()) .build(); return memberRepository.save(m); From ca93f48841c90e1cbfde5e9fe1e8149feb8e6b0f Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Sun, 25 May 2025 12:09:09 +0900 Subject: [PATCH 16/27] change signp url --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fcc6af4..64e434b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,7 +13,7 @@ api.server.url=http://localhost:8080 spring.config.import=optional:classpath:application-infrastructure.properties url.base=http://localhost -url.path.signup=/signup +url.path.signup=/signup/step-one url.path.auth= url.cdn=https://cdn.fontory.co.kr From c552c60732b786dcafb90bd366a0113e58ed5ef1 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Sun, 25 May 2025 12:26:28 +0900 Subject: [PATCH 17/27] /member/me return 404 when miss auth --- .../member/controller/ProfileController.java | 11 ++++++++--- src/main/resources/application-prod.properties | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java index 26f24ba..e8f1376 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java @@ -10,7 +10,6 @@ import org.fontory.fontorybe.authentication.adapter.inbound.annotation.Login; import org.fontory.fontorybe.authentication.application.AuthService; import org.fontory.fontorybe.authentication.domain.UserPrincipal; -import org.fontory.fontorybe.authentication.domain.exception.AuthenticationRequiredException; import org.fontory.fontorybe.file.application.port.CloudStorageService; import org.fontory.fontorybe.file.application.port.FileService; import org.fontory.fontorybe.file.domain.FileUploadResult; @@ -21,6 +20,7 @@ import org.fontory.fontorybe.member.controller.port.MemberLookupService; import org.fontory.fontorybe.member.controller.port.MemberUpdateService; import org.fontory.fontorybe.member.domain.Member; +import org.fontory.fontorybe.member.domain.exception.MemberNotFoundException; import org.fontory.fontorybe.member.infrastructure.entity.MemberStatus; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -51,13 +51,18 @@ public class ProfileController { ) @GetMapping public ResponseEntity getMyProfile( - @Login UserPrincipal me) { + @Login(required = false) UserPrincipal me) { + if (me == null) { + log.info("Request received: getMyInfo - no login"); + throw new MemberNotFoundException(); + } Long requestMemberId = me.getId(); log.info("Request received: getMyInfo member ID: {}", requestMemberId); Member lookupMember = memberLookupService.getOrThrowById(requestMemberId); if (lookupMember.getStatus().equals(MemberStatus.ONBOARDING)) { - throw new AuthenticationRequiredException(); + log.info("Request received: getMyInfo - member is onboarding"); + throw new MemberNotFoundException(); } String fileUrl = cloudStorageService.getProfileImageUrl(lookupMember.getProfileImageKey()); log.info("ProfileImageUrl generated : {}", fileUrl); diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 9351369..c593eef 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -1,3 +1,3 @@ -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update cookies.domain=.fontory.co.kr From 4dacd9ecec56921cbc65c6a2df60e7841b2004f1 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Mon, 26 May 2025 13:12:01 +0900 Subject: [PATCH 18/27] =?UTF-8?q?refactor:=20=ED=8F=B0=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?HTTP=20METHOD=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/fontory/fontorybe/font/controller/FontController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java index 7f29903..0987b73 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java @@ -273,7 +273,7 @@ public ResponseEntity downloadFont( summary = "폰트 이름 중복 검사", description = "이름이 중복이면 true를 반환합니다." ) - @PostMapping("/verify-name") + @GetMapping("/verify-name") public ResponseEntity verifyFontName( @Login UserPrincipal userPrincipal, @RequestParam String fontName From 58201bfb45eb205f19c9978a0793805ddb1f9906 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Wed, 4 Jun 2025 01:59:44 +0900 Subject: [PATCH 19/27] =?UTF-8?q?feat:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=95=8C=EB=A6=BC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85=20=EB=B0=8F=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FontServiceImpl 내 폰트 생성 및 완료 시점에 이벤트 발행 로직 추가 - FontCreateRequestNotificationEvent - FontCreateCompleteNotificationEvent - SmsService 인터페이스 및 구현체에 관련 메서드 추가 - 통합 테스트(FontServiceIntegrationTest, FontControllerIntegrationTest)에서 이벤트 및 알림 로직 검증 추가 비동기 이벤트 기반으로 알림 로직을 분리하여 서비스 응답 속도 개선 및 관심사 분리 달성 --- .../fontorybe/font/CoolSmsEventListener.java | 48 +++++++++++++++++++ .../FontCreateCompleteNotificationEvent.java | 11 +++++ .../FontCreateRequestNotificationEvent.java | 12 +++++ .../font/service/FontServiceImpl.java | 17 +++---- .../sms/application/SmsServiceImpl.java | 16 +++---- .../sms/application/port/SmsService.java | 4 +- .../font/FontControllerIntegrationTest.java | 3 ++ .../font/FontServiceIntegrationTest.java | 4 ++ 8 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/fontory/fontorybe/font/CoolSmsEventListener.java create mode 100644 src/main/java/org/fontory/fontorybe/font/FontCreateCompleteNotificationEvent.java create mode 100644 src/main/java/org/fontory/fontorybe/font/FontCreateRequestNotificationEvent.java diff --git a/src/main/java/org/fontory/fontorybe/font/CoolSmsEventListener.java b/src/main/java/org/fontory/fontorybe/font/CoolSmsEventListener.java new file mode 100644 index 0000000..ff276af --- /dev/null +++ b/src/main/java/org/fontory/fontorybe/font/CoolSmsEventListener.java @@ -0,0 +1,48 @@ +package org.fontory.fontorybe.font; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.fontory.fontorybe.font.domain.Font; +import org.fontory.fontorybe.sms.application.port.PhoneNumberStorage; +import org.fontory.fontorybe.sms.application.port.SmsService; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CoolSmsEventListener { + + private final PhoneNumberStorage phoneNumberStorage; + private final SmsService smsService; + + @Async + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void sendFontCreateRequestNotificationAndSavePhoneNumber(FontCreateRequestNotificationEvent e) { + Font font = e.getFont(); + String phoneNumber = e.getPhoneNumber(); + + log.info("sms send start & save phone number in redis - fontId={}, phoneNumber={}", font.getId(), phoneNumber); + + phoneNumberStorage.savePhoneNumber(font, phoneNumber); + smsService.sendFontCreateRequestNotification(phoneNumber, font.getName()); + + log.info("sms sent & phone number saved in redis - fontId={}, phoneNumber={}", font.getId(), phoneNumber); + } + + @Async + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void sendFontCreateCompleteNotificationAndRemovePhoneNumber(FontCreateCompleteNotificationEvent e) { + Font font = e.getFont(); + String phoneNumber = phoneNumberStorage.getPhoneNumber(font); + + log.info("sms send start & save phone number in redis - fontId={}, phoneNumber={}", font.getId(), phoneNumber); + + smsService.sendFontCreateCompleteNotification(phoneNumber, font.getName()); + phoneNumberStorage.removePhoneNumber(font); + + log.info("sms sent & phone number saved in redis - fontId={}, phoneNumber={}", font.getId(), phoneNumber); + } +} diff --git a/src/main/java/org/fontory/fontorybe/font/FontCreateCompleteNotificationEvent.java b/src/main/java/org/fontory/fontorybe/font/FontCreateCompleteNotificationEvent.java new file mode 100644 index 0000000..4c927c4 --- /dev/null +++ b/src/main/java/org/fontory/fontorybe/font/FontCreateCompleteNotificationEvent.java @@ -0,0 +1,11 @@ +package org.fontory.fontorybe.font; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.fontory.fontorybe.font.domain.Font; + +@Getter +@RequiredArgsConstructor +public class FontCreateCompleteNotificationEvent { + private final Font font; +} diff --git a/src/main/java/org/fontory/fontorybe/font/FontCreateRequestNotificationEvent.java b/src/main/java/org/fontory/fontorybe/font/FontCreateRequestNotificationEvent.java new file mode 100644 index 0000000..1995bcb --- /dev/null +++ b/src/main/java/org/fontory/fontorybe/font/FontCreateRequestNotificationEvent.java @@ -0,0 +1,12 @@ +package org.fontory.fontorybe.font; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.fontory.fontorybe.font.domain.Font; + +@Getter +@RequiredArgsConstructor +public class FontCreateRequestNotificationEvent { + private final Font font; + private final String phoneNumber; +} diff --git a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java index 1527097..60c77e6 100644 --- a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java @@ -10,6 +10,8 @@ import org.fontory.fontorybe.file.application.port.FileService; import org.fontory.fontorybe.file.domain.FileMetadata; import org.fontory.fontorybe.file.domain.FileUploadResult; +import org.fontory.fontorybe.font.FontCreateCompleteNotificationEvent; +import org.fontory.fontorybe.font.FontCreateRequestNotificationEvent; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontDeleteResponse; import org.fontory.fontorybe.font.controller.dto.FontDownloadResponse; @@ -33,6 +35,7 @@ import org.fontory.fontorybe.member.domain.Member; import org.fontory.fontorybe.sms.application.port.PhoneNumberStorage; import org.fontory.fontorybe.sms.application.port.SmsService; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -51,8 +54,7 @@ public class FontServiceImpl implements FontService { private final FontRequestProducer fontRequestProducer; private final CloudStorageService cloudStorageService; private final BadWordFiltering badWordFiltering; - private final SmsService smsService; - private final PhoneNumberStorage phoneNumberStorage; + private final ApplicationEventPublisher eventPublisher; @Override @Transactional @@ -73,8 +75,8 @@ public Font create(Long memberId, FontCreateDTO fontCreateDTO, FileUploadResult fontRequestProducer.sendFontRequest(FontRequestProduceDto.from(savedFont, member, fontPaperUrl)); if (fontCreateDTO.getPhoneNumber() != null && !fontCreateDTO.getPhoneNumber().isBlank()) { - phoneNumberStorage.savePhoneNumber(savedFont, fontCreateDTO.getPhoneNumber()); - smsService.sendFontCreationNotification(fontCreateDTO.getPhoneNumber(), fontCreateDTO.getName()); + String notificationPhoneNumber = fontCreateDTO.getPhoneNumber(); + eventPublisher.publishEvent(new FontCreateRequestNotificationEvent(savedFont, notificationPhoneNumber)); } log.info("Service completed: Font created with ID: {} and Font template image uploaded successfully", savedFont.getId()); @@ -302,12 +304,7 @@ public FontUpdateResponse updateProgress(Long fontId, FontProgressUpdateDTO font String woff2Url = cloudStorageService.getWoff2Url(updatedFont.getKey()); if (fontProgressUpdateDTO.getStatus() == FontStatus.DONE) { - String phoneNumber = phoneNumberStorage.getPhoneNumber(targetFont); - - if (phoneNumber != null && !phoneNumber.isBlank()) { - smsService.sendFontProgressNotification(phoneNumber, updatedFont.getName()); - phoneNumberStorage.removePhoneNumber(targetFont); - } + eventPublisher.publishEvent(new FontCreateCompleteNotificationEvent(updatedFont)); } log.info("Service completed: Font ID: {} updated successfully", fontId); diff --git a/src/main/java/org/fontory/fontorybe/sms/application/SmsServiceImpl.java b/src/main/java/org/fontory/fontorybe/sms/application/SmsServiceImpl.java index 0e7b430..e703b14 100644 --- a/src/main/java/org/fontory/fontorybe/sms/application/SmsServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/sms/application/SmsServiceImpl.java @@ -15,7 +15,7 @@ public class SmsServiceImpl implements SmsService { @Value("${coolsms.phone-number}") - private String phoneNumber; + private String fromPhoneNumber; private final DefaultMessageService messageService; @@ -23,25 +23,25 @@ public class SmsServiceImpl implements SmsService { private static final String FONT_PROGRESS_MESSAGE_FORMAT = "[Fontory]\n\"%s\" 폰트 제작이 완료되었습니다."; @Override - public void sendFontCreationNotification(String to, String fontName) { + public void sendFontCreateRequestNotification(String to, String fontName) { sendSms(to, String.format(FONT_CREATION_MESSAGE_FORMAT, fontName)); } @Override - public void sendFontProgressNotification(String to, String fontName) { + public void sendFontCreateCompleteNotification(String to, String fontName) { sendSms(to, String.format(FONT_PROGRESS_MESSAGE_FORMAT, fontName)); } - private void sendSms(String to, String text) { - if (to == null || to.isBlank()) { + private void sendSms(String toPhoneNumber, String content) { + if (toPhoneNumber == null || toPhoneNumber.isBlank()) { log.warn("Service warning: recipient phone number is empty"); return; } Message message = new Message(); - message.setFrom(phoneNumber); - message.setTo(to); - message.setText(text); + message.setFrom(fromPhoneNumber); + message.setTo(toPhoneNumber); + message.setText(content); messageService.sendOne(new SingleMessageSendingRequest(message)); } diff --git a/src/main/java/org/fontory/fontorybe/sms/application/port/SmsService.java b/src/main/java/org/fontory/fontorybe/sms/application/port/SmsService.java index 408229d..9dac948 100644 --- a/src/main/java/org/fontory/fontorybe/sms/application/port/SmsService.java +++ b/src/main/java/org/fontory/fontorybe/sms/application/port/SmsService.java @@ -1,6 +1,6 @@ package org.fontory.fontorybe.sms.application.port; public interface SmsService { - void sendFontCreationNotification(String to, String fontName); - void sendFontProgressNotification(String to, String fontName); + void sendFontCreateRequestNotification(String to, String fontName); + void sendFontCreateCompleteNotification(String to, String fontName); } diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java index 97861d6..b209b41 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java @@ -37,6 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.bean.override.mockito.MockitoBean; @@ -66,6 +67,8 @@ class FontControllerIntegrationTest { private FileService fileService; @MockitoBean private FontRequestProducer fontRequestProducer; + @MockitoBean + private ApplicationEventPublisher eventPublisher; private final Long existMemberId = 999L; private final String existMemberName = "existMemberNickName"; diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java index 626ba03..9a124c9 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java @@ -28,6 +28,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.jdbc.Sql; @@ -43,6 +45,8 @@ class FontServiceIntegrationTest { private FileService fileService; @MockitoBean private FontRequestProducer fontRequestProducer; + @MockitoBean + private ApplicationEventPublisher eventPublisher; private final Long existMemberId = 999L; private final String existMemberName = "existMemberNickName"; From 0becd8ae978a8d643b5b1a26c0a9cf225fa05813 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Mon, 7 Jul 2025 02:52:26 +0900 Subject: [PATCH 20/27] chore: Rename S3 event listener phase from BEFORE_COMMIT to AFTER_COMMIT --- .../file/adapter/outbound/s3/ProfileImageUpdateListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java index ecfc80f..46c254b 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java @@ -28,8 +28,8 @@ void init() { profileImagePrefix = s3Config.getPrefix(FileType.PROFILE_IMAGE); } - @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) - public void beforeCommit(ProfileImageUpdatedEvent event) { + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void afterCommit(ProfileImageUpdatedEvent event) { s3.copyObject(CopyObjectRequest.builder() .sourceBucket(profileImageBucketName) .sourceKey(profileImagePrefix + "/" + event.getTempKey()) From 57b713fa42a7a36323aeb04fa68f4faf34ee5072 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Mon, 7 Jul 2025 02:54:30 +0900 Subject: [PATCH 21/27] =?UTF-8?q?fix:=20Add=20try=E2=80=93catch=20around?= =?UTF-8?q?=20S3=20copy/delete=20in=20afterCommit=20listener?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Log errors with tempKey and fixedKey - Rethrow exception so @AfterThrowing aspect can pick it up --- .../s3/ProfileImageUpdateListener.java | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java index 46c254b..17b58cc 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java @@ -30,17 +30,23 @@ void init() { @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void afterCommit(ProfileImageUpdatedEvent event) { - s3.copyObject(CopyObjectRequest.builder() - .sourceBucket(profileImageBucketName) - .sourceKey(profileImagePrefix + "/" + event.getTempKey()) - .destinationBucket(profileImageBucketName) - .destinationKey(profileImagePrefix + "/" + event.getFixedKey()) - .build()); + try { + s3.copyObject(CopyObjectRequest.builder() + .sourceBucket(profileImageBucketName) + .sourceKey(profileImagePrefix + "/" + event.getTempKey()) + .destinationBucket(profileImageBucketName) + .destinationKey(profileImagePrefix + "/" + event.getFixedKey()) + .build()); - s3.deleteObject(DeleteObjectRequest.builder() - .bucket(profileImageBucketName) - .key(profileImagePrefix + "/" + event.getTempKey()) - .build()); + s3.deleteObject(DeleteObjectRequest.builder() + .bucket(profileImageBucketName) + .key(profileImagePrefix + "/" + event.getTempKey()) + .build()); + } catch (Exception e) { + log.error("Error while copying/deleting profile image to s3: tempKey={}, fixedKey={}", + event.getTempKey(), event.getFixedKey(), e); + throw e; + } } @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) From 3989d23ae085ef70de505a079c84eaca626eba0e Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Mon, 7 Jul 2025 03:10:07 +0900 Subject: [PATCH 22/27] =?UTF-8?q?fix:=20Wrap=20sendToDiscord=20in=20try?= =?UTF-8?q?=E2=80=93catch=20to=20log=20notification=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Catch RestTemplate/IO errors and log warning - Prevent async exceptions from bubbling up --- .../DiscordExceptionLoggingAspect.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/common/adapter/outbound/DiscordExceptionLoggingAspect.java b/src/main/java/org/fontory/fontorybe/common/adapter/outbound/DiscordExceptionLoggingAspect.java index 5280b43..ed397da 100644 --- a/src/main/java/org/fontory/fontorybe/common/adapter/outbound/DiscordExceptionLoggingAspect.java +++ b/src/main/java/org/fontory/fontorybe/common/adapter/outbound/DiscordExceptionLoggingAspect.java @@ -156,24 +156,28 @@ private String buildStackTrace(Throwable ex) { */ @Async protected void sendToDiscord(String payloadJson, String fullStackTrace) { - byte[] stackTraceBytes = fullStackTrace.getBytes(StandardCharsets.UTF_8); - ByteArrayResource stackTraceResource = new ByteArrayResource(stackTraceBytes) { - @Override - public String getFilename() { - return "stacktrace.txt"; - } - }; + try { + byte[] stackTraceBytes = fullStackTrace.getBytes(StandardCharsets.UTF_8); + ByteArrayResource stackTraceResource = new ByteArrayResource(stackTraceBytes) { + @Override + public String getFilename() { + return "stacktrace.txt"; + } + }; - LinkedMultiValueMap multipartBody = new LinkedMultiValueMap<>(); - multipartBody.add("payload_json", payloadJson); - multipartBody.add("file", stackTraceResource); + LinkedMultiValueMap multipartBody = new LinkedMultiValueMap<>(); + multipartBody.add("payload_json", payloadJson); + multipartBody.add("file", stackTraceResource); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); - HttpEntity> requestEntity = - new HttpEntity<>(multipartBody, headers); + HttpEntity> requestEntity = + new HttpEntity<>(multipartBody, headers); - restTemplate.postForEntity(discordWebhookUrl, requestEntity, String.class); + restTemplate.postForEntity(discordWebhookUrl, requestEntity, String.class); + } catch (Exception e) { + log.warn("Exception occured while sending stacktrace to Discord: {}", e); + } } } \ No newline at end of file From e5c5b33ac59002c30c5988b9826aeab4a875faac Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Tue, 8 Jul 2025 00:53:33 +0900 Subject: [PATCH 23/27] feat: add asynchronous retryable S3 promotion with DLQ support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - enable Spring Retry (`@EnableRetry`) and Async processing (`@EnableAsync`) - extend `ProfileImageUpdatedEvent` to include `memberId` - implement `ProfileImageUpdateListener`: * annotate `afterCommit` with `@Async`, `@Retryable(maxAttempts=3, backoff=2000)` and `@Transactional(propagation=REQUIRES_NEW)` * log current attempt/count via `RetrySynchronizationManager` * copy S3 object from `tempKey` → `fixedKey`, delete `tempKey`, then update member’s `profileImageKey` * add `@Recover` to record permanent failures in `ProfileImageDlqRepository` and emit error logs (Discord alert TODO) * add `afterRollback` listener to clean up orphan `tempKey` on transaction rollback - create `ProfileImageDlq` JPA entity and `ProfileImageDlqRepository` for DLQ persistence - refactor `FileServiceImpl`: * remove inline member update * stage upload to S3 under `tempKey`, save metadata with `fixedKey` placeholder * publish `ProfileImageUpdatedEvent` for AFTER_COMMIT promotion * enhance logging and add method-level Javadoc - update `RegistrationController` to defer member onboarding until after file upload event - refactor `MemberUpdateService` interface to accept `memberId` instead of `Member` object - adjust unit test container to remove `MemberUpdateService` from `FileServiceImpl` builder --- .../fontorybe/FonToryBeApplication.java | 2 + .../s3/ProfileImageUpdateListener.java | 118 +++++++++++++++--- .../s3/dto/ProfileImageUpdatedEvent.java | 1 + .../file/application/FileServiceImpl.java | 36 ++++-- .../ProfileImageDlqRepository.java | 7 ++ .../entity/ProfileImageDlq.java | 22 ++++ .../controller/RegistrationController.java | 3 +- .../controller/port/MemberUpdateService.java | 2 +- .../service/MemberUpdateServiceImpl.java | 5 +- .../fontorybe/unit/mock/TestContainer.java | 1 - 10 files changed, 165 insertions(+), 32 deletions(-) create mode 100644 src/main/java/org/fontory/fontorybe/file/infrastructure/ProfileImageDlqRepository.java create mode 100644 src/main/java/org/fontory/fontorybe/file/infrastructure/entity/ProfileImageDlq.java diff --git a/src/main/java/org/fontory/fontorybe/FonToryBeApplication.java b/src/main/java/org/fontory/fontorybe/FonToryBeApplication.java index 33b4b0b..01c1686 100644 --- a/src/main/java/org/fontory/fontorybe/FonToryBeApplication.java +++ b/src/main/java/org/fontory/fontorybe/FonToryBeApplication.java @@ -10,12 +10,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.retry.annotation.EnableRetry; import org.springframework.scheduling.annotation.EnableAsync; import lombok.extern.slf4j.Slf4j; @Slf4j @EnableAsync +@EnableRetry @EnableJpaAuditing @SpringBootApplication @EnableConfigurationProperties({JwtProperties.class, MemberDefaults.class}) diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java index 17b58cc..d85bb5d 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java @@ -6,19 +6,37 @@ import org.fontory.fontorybe.config.S3Config; import org.fontory.fontorybe.file.adapter.outbound.s3.dto.ProfileImageUpdatedEvent; import org.fontory.fontorybe.file.domain.FileType; +import org.fontory.fontorybe.file.infrastructure.ProfileImageDlqRepository; +import org.fontory.fontorybe.file.infrastructure.entity.ProfileImageDlq; +import org.fontory.fontorybe.member.controller.port.MemberUpdateService; +import org.fontory.fontorybe.member.domain.Member; +import org.springframework.retry.RetryContext; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Recover; +import org.springframework.retry.annotation.Retryable; +import org.springframework.retry.support.RetrySynchronizationManager; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.CopyObjectRequest; import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import java.time.LocalDateTime; + +import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; + @Slf4j @Component @RequiredArgsConstructor public class ProfileImageUpdateListener { private final S3Client s3; private final S3Config s3Config; + private final ProfileImageDlqRepository profileImageDlqRepository; + private final MemberUpdateService memberUpdateService; + private String profileImageBucketName; private String profileImagePrefix; @@ -28,36 +46,102 @@ void init() { profileImagePrefix = s3Config.getPrefix(FileType.PROFILE_IMAGE); } + /** + * 1. S3 copy(temp -> fixed) + * 2. S3 delete(temp) + * 3. Member profileImageKey update + */ + @Async + @Retryable( + maxAttempts = 3, + backoff = @Backoff(delay = 2000) + ) + @Transactional(propagation = REQUIRES_NEW) @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) public void afterCommit(ProfileImageUpdatedEvent event) { - try { - s3.copyObject(CopyObjectRequest.builder() - .sourceBucket(profileImageBucketName) - .sourceKey(profileImagePrefix + "/" + event.getTempKey()) - .destinationBucket(profileImageBucketName) - .destinationKey(profileImagePrefix + "/" + event.getFixedKey()) - .build()); - - s3.deleteObject(DeleteObjectRequest.builder() - .bucket(profileImageBucketName) - .key(profileImagePrefix + "/" + event.getTempKey()) - .build()); - } catch (Exception e) { - log.error("Error while copying/deleting profile image to s3: tempKey={}, fixedKey={}", - event.getTempKey(), event.getFixedKey(), e); - throw e; + RetryContext ctx = RetrySynchronizationManager.getContext(); + int attempt = ctx.getRetryCount() + 1; // 0-based retryCount + Integer maxAttempts = (Integer) ctx.getAttribute(ctx.MAX_ATTEMPTS); + if (maxAttempts == null) { + maxAttempts = 3; } + + String tempKey = event.getTempKey(); + String fixedKey = event.getFixedKey(); + Long memberId = event.getMemberId(); + String srcKey = profileImagePrefix + "/" + tempKey; + String dstKey = profileImagePrefix + "/" + fixedKey; + + log.info("Promotion attempt {}/{} for memberId={}, tempKey={}, fixedKey={}", + attempt, maxAttempts, memberId, tempKey, fixedKey); + + // 1. S3 copy(temp -> fixed) + s3.copyObject(CopyObjectRequest.builder() + .sourceBucket(profileImageBucketName) + .sourceKey(srcKey) + .destinationBucket(profileImageBucketName) + .destinationKey(dstKey) + .build()); + log.info("S3 copy succeeded: {}/{} → {}/{}", + profileImageBucketName, srcKey, profileImageBucketName, dstKey); + + // 2. S3 delete(temp) + s3.deleteObject(DeleteObjectRequest.builder() + .bucket(profileImageBucketName) + .key(srcKey) + .build()); + log.info("S3 temp object deleted: {}/{}", profileImageBucketName, srcKey); + + // 3. DB update + Member updatedMember = memberUpdateService.setProfileImageKey(event.getMemberId(), event.getFixedKey()); + log.info("Member profileImageKey updated: memberId={}, newKey={}", + updatedMember.getId(), updatedMember.getProfileImageKey()); + } + + /** + * When afterCommit methods fails maxAttempts(3) times, recover process begin + * 1. record failed event to dql repository + * 2. discord alert + */ + @Recover + public void recover(ProfileImageUpdatedEvent event, Exception e) { + String tempKey = event.getTempKey(); + String fixedKey = event.getFixedKey(); + Long memberId = event.getMemberId(); + + log.error("Promotion retries exhausted for profile image: memberId={}, tempKey={}, fixedKey={}", + memberId, tempKey, fixedKey, e); + + log.info("Recording DLQ entry for failed promotion: memberId={}, tempKey={}, fixedKey={}", + memberId, tempKey, fixedKey); + profileImageDlqRepository.save(ProfileImageDlq.builder() + .memberId(event.getMemberId()) + .fixKey(event.getFixedKey()) + .tempKey(event.getTempKey()) + .time(LocalDateTime.now()) + .build()); + log.error("Profile image promotion permanently failed, moved to DLQ. " + + "memberId={}, tempKey={}, fixedKey={}", memberId, tempKey, fixedKey); + } @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) public void afterRollback(ProfileImageUpdatedEvent event) { + String tempKey = event.getTempKey(); + String srcKey = profileImagePrefix + "/" + tempKey; + + log.warn("Transaction rolled back; cleaning up orphan tempKey: {}/{}", + profileImageBucketName, srcKey); try { s3.deleteObject(DeleteObjectRequest.builder() .bucket(profileImageBucketName) .key(profileImagePrefix + "/" + event.getTempKey()) .build()); + log.info("Orphan tempKey deleted after rollback: {}/{}", + profileImageBucketName, srcKey); } catch (Exception e) { - log.warn("Rollback profile image failed", e); + log.warn("Failed to delete orphan tempKey after rollback: {}/{} – {}", + profileImageBucketName, srcKey, e.getMessage(), e); } } } diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java index 9515bba..2a4942b 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java @@ -6,6 +6,7 @@ @Getter @RequiredArgsConstructor public class ProfileImageUpdatedEvent { + private final Long memberId; private final String tempKey; private final String fixedKey; } diff --git a/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java b/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java index 7c66391..4b5a14d 100644 --- a/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java @@ -35,7 +35,6 @@ public class FileServiceImpl implements FileService { private final FileRequestMapper fileRequestMapper; private final CloudStorageService cloudStorageService; private final MemberLookupService memberLookupService; - private final MemberUpdateService memberUpdateService; private final ApplicationEventPublisher eventPublisher; @Override @@ -46,32 +45,51 @@ public FileMetadata getOrThrowById(Long id) { .orElseThrow(() -> new FileNotFoundException(id)); } + /** + * Upload profile image in two phase + * 1) Stage the file in S3 under tempKey and save metadata in DB + * 2) Publish an event for AFTER_COMMIT listener to promote and update DB. + */ @Override @Transactional public FileUploadResult uploadProfileImage(MultipartFile file, Long memberId) { - log.info("Processing profile image upload: fileName={}, memberId={}", file.getOriginalFilename(), memberId); + log.info("Start profile image upload: fileName={}, memberId={}", file.getOriginalFilename(), memberId); + // Lookup member and build FileCreate DTO Member requestMember = memberLookupService.getOrThrowById(memberId); + log.debug("Fetched member for upload: memberId={}, currentProfileKey={}", + memberId, requestMember.getProfileImageKey()); FileCreate profileImageFileCreate = fileRequestMapper.toProfileImageFileCreate(file, requestMember); + + // Determine fixedKey: new UUID for first upload, else reuse boolean isInitial = memberDefaults.getProfileImageKey().equals(requestMember.getProfileImageKey()); String fixedKey = isInitial ? UUID.randomUUID().toString() : requestMember.getProfileImageKey(); + log.debug("Determined fixedKey: {} (initialUpload={})", fixedKey, isInitial); + // Stage upload to S3 under tempKey String tempKey = UUID.randomUUID().toString(); - - log.info("Uploading profile image to cloud storage: memberId={}, tempKey={}", memberId, tempKey); + log.info("Staging profile image to S3: memberId={}, tempKey={}", memberId, tempKey); FileMetadata metadata = cloudStorageService.uploadProfileImage(profileImageFileCreate, tempKey); + log.debug("S3 upload complete, metadata key updated: tempKey={}, metadataKey={}", + tempKey, metadata.getKey()); + + // Save metadata with the final key placeholder metadata.updateKey(fixedKey); FileMetadata savedMetaData = fileRepository.save(metadata); - Member updated = memberUpdateService.setProfileImageKey(requestMember, fixedKey); + log.info("File metadata saved: id={}, key={}", savedMetaData.getId(), savedMetaData.getKey()); - log.info("Updating member profile image key: memberId={}, memberProfileImageKey={}", updated.getId(), updated.getProfileImageKey()); - log.info("Publishing image update event: tempKey={}, fixedKey={}", tempKey, fixedKey); - eventPublisher.publishEvent(new ProfileImageUpdatedEvent(tempKey, fixedKey)); + // Publish event for AFTER_COMMIT promotion listener + log.info("Publishing ProfileImageUpdatedEvent: memberId={}, tempKey={}, fixedKey={}", + memberId, tempKey, fixedKey); + eventPublisher.publishEvent(new ProfileImageUpdatedEvent(memberId, tempKey, fixedKey)); + + // 6) Build result URL from fixedKey (will be live after promotion) String fileUrl = cloudStorageService.getProfileImageUrl(fixedKey); FileUploadResult result = FileUploadResult.from(savedMetaData, fileUrl); - log.info("Profile image upload completed successfully: memberId={}, fileUrl={}", memberId, fileUrl); + log.info("Profile image upload request processed: memberId={}, resultUrl={}", memberId, fileUrl); + return result; } diff --git a/src/main/java/org/fontory/fontorybe/file/infrastructure/ProfileImageDlqRepository.java b/src/main/java/org/fontory/fontorybe/file/infrastructure/ProfileImageDlqRepository.java new file mode 100644 index 0000000..5ae966b --- /dev/null +++ b/src/main/java/org/fontory/fontorybe/file/infrastructure/ProfileImageDlqRepository.java @@ -0,0 +1,7 @@ +package org.fontory.fontorybe.file.infrastructure; + +import org.fontory.fontorybe.file.infrastructure.entity.ProfileImageDlq; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProfileImageDlqRepository extends JpaRepository { +} diff --git a/src/main/java/org/fontory/fontorybe/file/infrastructure/entity/ProfileImageDlq.java b/src/main/java/org/fontory/fontorybe/file/infrastructure/entity/ProfileImageDlq.java new file mode 100644 index 0000000..33ff794 --- /dev/null +++ b/src/main/java/org/fontory/fontorybe/file/infrastructure/entity/ProfileImageDlq.java @@ -0,0 +1,22 @@ +package org.fontory.fontorybe.file.infrastructure.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@Table(name = "dlq") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +public class ProfileImageDlq{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private Long memberId; + private String tempKey; + private String fixKey; + private LocalDateTime time; +} diff --git a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java index 28cd31d..ca2141a 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java @@ -74,11 +74,10 @@ public ResponseEntity register( MultipartFile file = extractSingleMultipartFile(files); logFileDetails(file, "Member profile image upload"); FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, requestMemberId); - updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req, fileUploadResult); } else { log.info("No profile image upload found"); - updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req); } + updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req); log.info("Response sent: Member ID: {} Created successfully with nickname: {}", updatedMember.getId(), updatedMember.getNickname()); diff --git a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java index 0ca979c..89b4680 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java @@ -5,6 +5,6 @@ public interface MemberUpdateService { Member update(Long requestMemberId, MemberUpdateRequest memberUpdateRequest); - Member setProfileImageKey(Member requetMember, String profileImageKey); + Member setProfileImageKey(Long requestMemberId, String profileImageKey); Member disable(Long requestMemberId); } diff --git a/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java b/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java index e40b866..e93c3a0 100644 --- a/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java @@ -44,8 +44,9 @@ public Member update(Long requestMemberId, MemberUpdateRequest memberUpdateReque @Override @Transactional - public Member setProfileImageKey(Member requetMember, String profileImageKey) { - Member member = requetMember.setProfileImageKey(profileImageKey); + public Member setProfileImageKey(Long requestMemberId, String profileImageKey) { + Member targetMember = memberLookupService.getOrThrowById(requestMemberId); + Member member = targetMember.setProfileImageKey(profileImageKey); return memberRepository.save(member); } diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java index 786e652..b7a30a6 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java @@ -144,7 +144,6 @@ public TestContainer() { fileService = FileServiceImpl.builder() .memberLookupService(memberLookupService) .memberDefaults(memberDefaults) - .memberUpdateService(memberUpdateService) .fileRepository(fileRepository) .fileRequestMapper(fileRequestMapper) .eventPublisher(eventPublisher) From a92428250d86be9f4ad527e5eb1621b0365797b6 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Tue, 8 Jul 2025 22:16:53 +0900 Subject: [PATCH 24/27] chore: Remove profile image upload feature and related artifacts - Remove uploadProfileImage API and service method references - Delete ProfileImageUpdatedEvent DTO and ProfileImageUpdateListener listener - Remove uploadProfileImage/ getProfileImageUrl methods from CloudStorageService/AmazonS3BucketService - Strip out profileImageKey fields from Member domain, entity, DTOs, controllers, tests - Clean up DevTokenInitializer default profileImageKey initialization - Update integration and unit tests to remove profile image upload and URL assertions - Simplify MemberController/ProfileController/RegistrationController to no longer expect multipart file handling --- .../application/DevTokenInitializer.java | 1 - .../outbound/s3/AmazonS3BucketService.java | 25 --- .../s3/ProfileImageUpdateListener.java | 147 ------------------ .../s3/dto/ProfileImageUpdatedEvent.java | 12 -- .../file/application/FileServiceImpl.java | 50 ------ .../application/port/CloudStorageService.java | 2 - .../file/application/port/FileService.java | 2 - .../member/controller/MemberController.java | 4 +- .../member/controller/ProfileController.java | 20 +-- .../controller/RegistrationController.java | 26 +--- .../controller/dto/MemberCreateResponse.java | 4 +- .../controller/dto/MemberUpdateResponse.java | 23 --- .../controller/dto/MyProfileResponse.java | 6 +- .../controller/dto/ProfileResponse.java | 4 +- .../controller/port/MemberUpdateService.java | 1 - .../fontorybe/member/domain/Member.java | 22 --- .../infrastructure/entity/MemberEntity.java | 4 - .../service/MemberUpdateServiceImpl.java | 8 - .../file/FileServiceIntegrationTest.java | 45 +----- .../MemberControllerIntegrationTest.java | 6 +- .../ProfileControllerIntegrationTest.java | 28 +--- ...RegistrationControllerIntegrationTest.java | 32 +--- .../MemberUpdateServiceIntegrationTest.java | 14 +- .../fontorybe/unit/file/FileServiceTest.java | 63 ++------ .../controller/MemberControllerTest.java | 1 - .../controller/ProfileControllerTest.java | 7 +- .../RegistrationControllerTest.java | 9 +- .../service/MemberUpdateServiceTest.java | 7 +- .../unit/mock/FakeCloudStorageService.java | 16 -- .../unit/mock/FakeMemberRepository.java | 2 - .../fontorybe/unit/mock/TestContainer.java | 3 +- src/test/resources/sql/createFileTestData.sql | 4 +- src/test/resources/sql/createFontTestData.sql | 8 +- .../resources/sql/createMemberTestData.sql | 4 +- 34 files changed, 50 insertions(+), 560 deletions(-) delete mode 100644 src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java delete mode 100644 src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java delete mode 100644 src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java diff --git a/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java b/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java index aa0f0a6..7de323e 100644 --- a/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java +++ b/src/main/java/org/fontory/fontorybe/common/application/DevTokenInitializer.java @@ -99,7 +99,6 @@ public void initTokens() { .birth(LocalDate.now()) .nickname("Tester") .status(MemberStatus.ONBOARDING) - .profileImageKey(memberDefaults.getProfileImageKey()) .build(); return memberRepository.save(m); }); diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/AmazonS3BucketService.java b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/AmazonS3BucketService.java index a18b806..21e446e 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/AmazonS3BucketService.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/AmazonS3BucketService.java @@ -27,40 +27,15 @@ public class AmazonS3BucketService implements CloudStorageService { private final S3Client s3; private final S3Config s3Config; private final ClockHolder clockHolder; - private String profileImageBucketName; private String fontPaperBucketName; - private String profileImagePrefix; private String fontPaperPrefix; @PostConstruct void init() { - profileImageBucketName = s3Config.getBucketName(FileType.PROFILE_IMAGE); fontPaperBucketName = s3Config.getBucketName(FileType.FONT_PAPER); - profileImagePrefix = s3Config.getPrefix(FileType.PROFILE_IMAGE); fontPaperPrefix = s3Config.getPrefix(FileType.FONT_PAPER); } - @Override - public FileMetadata uploadProfileImage(FileCreate request, String key) { - log.info("Uploading profile image: fileName={}, contentType={}, size={} bytes, fileKey={}", - request.getFileName(), request.getFile().getContentType(), request.getFile().getSize(), key); - - AmazonS3PutRequest amazonS3PutRequest = AmazonS3PutRequest.from( - request, - key, - profileImageBucketName, - profileImagePrefix, - clockHolder.getCurrentTimeStamp()); - - log.info("Profile image uploaded successfully: fileKey={}, bucket={}", key, profileImageBucketName); - return uploadFile(amazonS3PutRequest).toModel(); - } - - @Override - public String getProfileImageUrl(String key) { - return getFileUrl(FileType.PROFILE_IMAGE, key); - } - @Override public String getFontPaperUrl(String key) { return getFileUrl(FileType.FONT_PAPER, key); diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java deleted file mode 100644 index d85bb5d..0000000 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/ProfileImageUpdateListener.java +++ /dev/null @@ -1,147 +0,0 @@ -package org.fontory.fontorybe.file.adapter.outbound.s3; - -import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.fontory.fontorybe.config.S3Config; -import org.fontory.fontorybe.file.adapter.outbound.s3.dto.ProfileImageUpdatedEvent; -import org.fontory.fontorybe.file.domain.FileType; -import org.fontory.fontorybe.file.infrastructure.ProfileImageDlqRepository; -import org.fontory.fontorybe.file.infrastructure.entity.ProfileImageDlq; -import org.fontory.fontorybe.member.controller.port.MemberUpdateService; -import org.fontory.fontorybe.member.domain.Member; -import org.springframework.retry.RetryContext; -import org.springframework.retry.annotation.Backoff; -import org.springframework.retry.annotation.Recover; -import org.springframework.retry.annotation.Retryable; -import org.springframework.retry.support.RetrySynchronizationManager; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.event.TransactionPhase; -import org.springframework.transaction.event.TransactionalEventListener; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.CopyObjectRequest; -import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; - -import java.time.LocalDateTime; - -import static org.springframework.transaction.annotation.Propagation.REQUIRES_NEW; - -@Slf4j -@Component -@RequiredArgsConstructor -public class ProfileImageUpdateListener { - private final S3Client s3; - private final S3Config s3Config; - private final ProfileImageDlqRepository profileImageDlqRepository; - private final MemberUpdateService memberUpdateService; - - private String profileImageBucketName; - private String profileImagePrefix; - - @PostConstruct - void init() { - profileImageBucketName = s3Config.getBucketName(FileType.PROFILE_IMAGE); - profileImagePrefix = s3Config.getPrefix(FileType.PROFILE_IMAGE); - } - - /** - * 1. S3 copy(temp -> fixed) - * 2. S3 delete(temp) - * 3. Member profileImageKey update - */ - @Async - @Retryable( - maxAttempts = 3, - backoff = @Backoff(delay = 2000) - ) - @Transactional(propagation = REQUIRES_NEW) - @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) - public void afterCommit(ProfileImageUpdatedEvent event) { - RetryContext ctx = RetrySynchronizationManager.getContext(); - int attempt = ctx.getRetryCount() + 1; // 0-based retryCount - Integer maxAttempts = (Integer) ctx.getAttribute(ctx.MAX_ATTEMPTS); - if (maxAttempts == null) { - maxAttempts = 3; - } - - String tempKey = event.getTempKey(); - String fixedKey = event.getFixedKey(); - Long memberId = event.getMemberId(); - String srcKey = profileImagePrefix + "/" + tempKey; - String dstKey = profileImagePrefix + "/" + fixedKey; - - log.info("Promotion attempt {}/{} for memberId={}, tempKey={}, fixedKey={}", - attempt, maxAttempts, memberId, tempKey, fixedKey); - - // 1. S3 copy(temp -> fixed) - s3.copyObject(CopyObjectRequest.builder() - .sourceBucket(profileImageBucketName) - .sourceKey(srcKey) - .destinationBucket(profileImageBucketName) - .destinationKey(dstKey) - .build()); - log.info("S3 copy succeeded: {}/{} → {}/{}", - profileImageBucketName, srcKey, profileImageBucketName, dstKey); - - // 2. S3 delete(temp) - s3.deleteObject(DeleteObjectRequest.builder() - .bucket(profileImageBucketName) - .key(srcKey) - .build()); - log.info("S3 temp object deleted: {}/{}", profileImageBucketName, srcKey); - - // 3. DB update - Member updatedMember = memberUpdateService.setProfileImageKey(event.getMemberId(), event.getFixedKey()); - log.info("Member profileImageKey updated: memberId={}, newKey={}", - updatedMember.getId(), updatedMember.getProfileImageKey()); - } - - /** - * When afterCommit methods fails maxAttempts(3) times, recover process begin - * 1. record failed event to dql repository - * 2. discord alert - */ - @Recover - public void recover(ProfileImageUpdatedEvent event, Exception e) { - String tempKey = event.getTempKey(); - String fixedKey = event.getFixedKey(); - Long memberId = event.getMemberId(); - - log.error("Promotion retries exhausted for profile image: memberId={}, tempKey={}, fixedKey={}", - memberId, tempKey, fixedKey, e); - - log.info("Recording DLQ entry for failed promotion: memberId={}, tempKey={}, fixedKey={}", - memberId, tempKey, fixedKey); - profileImageDlqRepository.save(ProfileImageDlq.builder() - .memberId(event.getMemberId()) - .fixKey(event.getFixedKey()) - .tempKey(event.getTempKey()) - .time(LocalDateTime.now()) - .build()); - log.error("Profile image promotion permanently failed, moved to DLQ. " + - "memberId={}, tempKey={}, fixedKey={}", memberId, tempKey, fixedKey); - - } - - @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK) - public void afterRollback(ProfileImageUpdatedEvent event) { - String tempKey = event.getTempKey(); - String srcKey = profileImagePrefix + "/" + tempKey; - - log.warn("Transaction rolled back; cleaning up orphan tempKey: {}/{}", - profileImageBucketName, srcKey); - try { - s3.deleteObject(DeleteObjectRequest.builder() - .bucket(profileImageBucketName) - .key(profileImagePrefix + "/" + event.getTempKey()) - .build()); - log.info("Orphan tempKey deleted after rollback: {}/{}", - profileImageBucketName, srcKey); - } catch (Exception e) { - log.warn("Failed to delete orphan tempKey after rollback: {}/{} – {}", - profileImageBucketName, srcKey, e.getMessage(), e); - } - } -} diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java b/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java deleted file mode 100644 index 2a4942b..0000000 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outbound/s3/dto/ProfileImageUpdatedEvent.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.fontory.fontorybe.file.adapter.outbound.s3.dto; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class ProfileImageUpdatedEvent { - private final Long memberId; - private final String tempKey; - private final String fixedKey; -} diff --git a/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java b/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java index 4b5a14d..36e0a59 100644 --- a/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java @@ -4,7 +4,6 @@ import java.util.UUID; import org.fontory.fontorybe.file.adapter.inbound.FileRequestMapper; -import org.fontory.fontorybe.file.adapter.outbound.s3.dto.ProfileImageUpdatedEvent; import org.fontory.fontorybe.file.application.port.CloudStorageService; import org.fontory.fontorybe.file.application.port.FileRepository; import org.fontory.fontorybe.file.application.port.FileService; @@ -13,7 +12,6 @@ import org.fontory.fontorybe.file.domain.FileUploadResult; import org.fontory.fontorybe.file.domain.exception.FileNotFoundException; import org.fontory.fontorybe.member.controller.port.MemberLookupService; -import org.fontory.fontorybe.member.controller.port.MemberUpdateService; import org.fontory.fontorybe.member.domain.Member; import org.fontory.fontorybe.member.domain.MemberDefaults; import org.springframework.context.ApplicationEventPublisher; @@ -45,54 +43,6 @@ public FileMetadata getOrThrowById(Long id) { .orElseThrow(() -> new FileNotFoundException(id)); } - /** - * Upload profile image in two phase - * 1) Stage the file in S3 under tempKey and save metadata in DB - * 2) Publish an event for AFTER_COMMIT listener to promote and update DB. - */ - @Override - @Transactional - public FileUploadResult uploadProfileImage(MultipartFile file, Long memberId) { - log.info("Start profile image upload: fileName={}, memberId={}", file.getOriginalFilename(), memberId); - - // Lookup member and build FileCreate DTO - Member requestMember = memberLookupService.getOrThrowById(memberId); - log.debug("Fetched member for upload: memberId={}, currentProfileKey={}", - memberId, requestMember.getProfileImageKey()); - FileCreate profileImageFileCreate = fileRequestMapper.toProfileImageFileCreate(file, requestMember); - - // Determine fixedKey: new UUID for first upload, else reuse - boolean isInitial = memberDefaults.getProfileImageKey().equals(requestMember.getProfileImageKey()); - String fixedKey = isInitial - ? UUID.randomUUID().toString() - : requestMember.getProfileImageKey(); - log.debug("Determined fixedKey: {} (initialUpload={})", fixedKey, isInitial); - - // Stage upload to S3 under tempKey - String tempKey = UUID.randomUUID().toString(); - log.info("Staging profile image to S3: memberId={}, tempKey={}", memberId, tempKey); - FileMetadata metadata = cloudStorageService.uploadProfileImage(profileImageFileCreate, tempKey); - log.debug("S3 upload complete, metadata key updated: tempKey={}, metadataKey={}", - tempKey, metadata.getKey()); - - // Save metadata with the final key placeholder - metadata.updateKey(fixedKey); - FileMetadata savedMetaData = fileRepository.save(metadata); - log.info("File metadata saved: id={}, key={}", savedMetaData.getId(), savedMetaData.getKey()); - - // Publish event for AFTER_COMMIT promotion listener - log.info("Publishing ProfileImageUpdatedEvent: memberId={}, tempKey={}, fixedKey={}", - memberId, tempKey, fixedKey); - eventPublisher.publishEvent(new ProfileImageUpdatedEvent(memberId, tempKey, fixedKey)); - - // 6) Build result URL from fixedKey (will be live after promotion) - String fileUrl = cloudStorageService.getProfileImageUrl(fixedKey); - FileUploadResult result = FileUploadResult.from(savedMetaData, fileUrl); - log.info("Profile image upload request processed: memberId={}, resultUrl={}", memberId, fileUrl); - - return result; - } - @Override @Transactional public FileUploadResult uploadFontTemplateImage(MultipartFile file, Long memberId) { diff --git a/src/main/java/org/fontory/fontorybe/file/application/port/CloudStorageService.java b/src/main/java/org/fontory/fontorybe/file/application/port/CloudStorageService.java index e143ec6..19b98d2 100644 --- a/src/main/java/org/fontory/fontorybe/file/application/port/CloudStorageService.java +++ b/src/main/java/org/fontory/fontorybe/file/application/port/CloudStorageService.java @@ -4,9 +4,7 @@ import org.fontory.fontorybe.file.domain.FileMetadata; public interface CloudStorageService { - FileMetadata uploadProfileImage(FileCreate fileCreate, String key); FileMetadata uploadFontTemplateImage(FileCreate request); - String getProfileImageUrl(String key); String getFontPaperUrl(String key); String getWoff2Url(String key); String getTtfUrl(String key); diff --git a/src/main/java/org/fontory/fontorybe/file/application/port/FileService.java b/src/main/java/org/fontory/fontorybe/file/application/port/FileService.java index 604b00c..47ca951 100644 --- a/src/main/java/org/fontory/fontorybe/file/application/port/FileService.java +++ b/src/main/java/org/fontory/fontorybe/file/application/port/FileService.java @@ -5,8 +5,6 @@ import org.springframework.web.multipart.MultipartFile; public interface FileService { - - FileUploadResult uploadProfileImage(MultipartFile file, Long memberId); FileMetadata getOrThrowById(Long id); FileUploadResult uploadFontTemplateImage(MultipartFile file, Long memberId); } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/MemberController.java b/src/main/java/org/fontory/fontorybe/member/controller/MemberController.java index 08e7a44..daf2fc2 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/MemberController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/MemberController.java @@ -36,10 +36,8 @@ public ResponseEntity getInfoMember( log.info("Request received: Get member info ID: {} by member ID: {}", id, requestMemberId); Member targetMember = memberLookupService.getOrThrowById(id); - String fileUrl = cloudStorageService.getProfileImageUrl(targetMember.getProfileImageKey()); - log.info("ProfileImageUrl generated : {}", fileUrl); - ProfileResponse profileResponse = ProfileResponse.from(targetMember, fileUrl); + ProfileResponse profileResponse = ProfileResponse.from(targetMember); log.info("Response sent: ProfileResponse : {}", profileResponse); return ResponseEntity diff --git a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java index e8f1376..4d55230 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/ProfileController.java @@ -64,10 +64,8 @@ public ResponseEntity getMyProfile( log.info("Request received: getMyInfo - member is onboarding"); throw new MemberNotFoundException(); } - String fileUrl = cloudStorageService.getProfileImageUrl(lookupMember.getProfileImageKey()); - log.info("ProfileImageUrl generated : {}", fileUrl); - MyProfileResponse myProfileResponse = MyProfileResponse.from(lookupMember, fileUrl); + MyProfileResponse myProfileResponse = MyProfileResponse.from(lookupMember); log.info("Response sent: MyProfileDto : {}", myProfileResponse); return ResponseEntity @@ -79,29 +77,19 @@ public ResponseEntity getMyProfile( @Operation( summary = "내정보 수정" ) - @PatchMapping(consumes = MULTIPART_FORM_DATA_VALUE) + @PatchMapping public ResponseEntity updateMember( @Login UserPrincipal userPrincipal, - @RequestPart @Valid MemberUpdateRequest req, - @SingleFileUpload @RequestPart(value = "file", required = false) List files + @RequestBody @Valid MemberUpdateRequest req ) { Long requestMemberId = userPrincipal.getId(); log.info("Request received: update member ID: {} with request: {}", requestMemberId, req); - if (files != null && !files.isEmpty()) { - MultipartFile file = extractSingleMultipartFile(files); - logFileDetails(file, "Member profile image upload"); - FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, requestMemberId); - log.info("fileUploadResult: {}", fileUploadResult); - } else { - log.info("No profile image upload found"); - } - Member updatedMember = memberUpdateService.update(requestMemberId, req); log.info("Updated : Member ID: {} Updated successfully with nickname: {}", updatedMember.getId(), updatedMember.getNickname()); - MyProfileResponse myProfileResponse = MyProfileResponse.from(updatedMember, cloudStorageService.getProfileImageUrl(updatedMember.getProfileImageKey())); + MyProfileResponse myProfileResponse = MyProfileResponse.from(updatedMember); log.info("Response sent: MyProfileDto : {}", myProfileResponse); return ResponseEntity diff --git a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java index ca2141a..a6a2ac4 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/RegistrationController.java @@ -59,40 +59,22 @@ public ResponseEntity checkDuplicate( @Operation( summary = "회원가입" ) - @PostMapping(consumes = MULTIPART_FORM_DATA_VALUE) + @PostMapping public ResponseEntity register( @Login UserPrincipal user, - @RequestPart @Valid InitMemberInfoRequest req, - @SingleFileUpload @RequestPart(value = "file", required = false) List files + @RequestBody @Valid InitMemberInfoRequest req ) { Long requestMemberId = user.getId(); log.info("Request received: Create member ID: {} with request: {}", requestMemberId, req); - Member updatedMember; - if (files != null && !files.isEmpty()) { - MultipartFile file = extractSingleMultipartFile(files); - logFileDetails(file, "Member profile image upload"); - FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, requestMemberId); - } else { - log.info("No profile image upload found"); - } - updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req); + Member updatedMember = memberOnboardService.initNewMemberInfo(requestMemberId, req); log.info("Response sent: Member ID: {} Created successfully with nickname: {}", updatedMember.getId(), updatedMember.getNickname()); return ResponseEntity .status(HttpStatus.CREATED) - .body(MemberCreateResponse.from(updatedMember, cloudStorageService.getProfileImageUrl(updatedMember.getProfileImageKey()))); - } - - private void logFileDetails(MultipartFile file, String context) { - log.debug("{} - File details: name='{}', original name='{}', size={} bytes, contentType='{}'", - context, - file.getName(), - file.getOriginalFilename(), - file.getSize(), - file.getContentType()); + .body(MemberCreateResponse.from(updatedMember)); } } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java index ef241c9..1f22fce 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberCreateResponse.java @@ -13,15 +13,13 @@ public class MemberCreateResponse { private final String nickname; private final Gender gender; - private final String profileImageUrl; private final LocalDate birth; private final LocalDateTime createdAt; - public static MemberCreateResponse from(Member member, String url) { + public static MemberCreateResponse from(Member member) { return MemberCreateResponse.builder() .nickname(member.getNickname()) .gender(member.getGender()) - .profileImageUrl(url) .birth(member.getBirth()) .createdAt(member.getCreatedAt()) .build(); diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java deleted file mode 100644 index 157b57d..0000000 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MemberUpdateResponse.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.fontory.fontorybe.member.controller.dto; - -import lombok.Builder; -import lombok.Getter; -import org.fontory.fontorybe.member.domain.Member; - -import java.time.LocalDateTime; - -@Getter -@Builder -public class MemberUpdateResponse { - private String nickname; - private String profileImage; - private LocalDateTime updatedAt; - - public static MemberUpdateResponse from(Member member) { - return MemberUpdateResponse.builder() - .nickname(member.getNickname()) - .profileImage(member.getProfileImageKey()) - .updatedAt(member.getUpdatedAt()) - .build(); - } -} diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java index 7c93aab..12f8b82 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/MyProfileResponse.java @@ -12,19 +12,15 @@ @Builder @ToString public class MyProfileResponse { - private Long memberId; private String nickname; private Gender gender; private LocalDate birth; - private String profileImageUrl; - public static MyProfileResponse from(Member member, String url) { + public static MyProfileResponse from(Member member) { return MyProfileResponse.builder() - .memberId(member.getId()) .nickname(member.getNickname()) .birth(member.getBirth()) .gender(member.getGender()) - .profileImageUrl(url) .build(); } } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/dto/ProfileResponse.java b/src/main/java/org/fontory/fontorybe/member/controller/dto/ProfileResponse.java index 96b0336..596f044 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/dto/ProfileResponse.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/dto/ProfileResponse.java @@ -11,13 +11,11 @@ public class ProfileResponse { private Long memberId; private String nickname; - private String profileImageUrl; - public static ProfileResponse from(Member member, String url) { + public static ProfileResponse from(Member member) { return ProfileResponse.builder() .memberId(member.getId()) .nickname(member.getNickname()) - .profileImageUrl(url) .build(); } } diff --git a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java index 89b4680..21423e0 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberUpdateService.java @@ -5,6 +5,5 @@ public interface MemberUpdateService { Member update(Long requestMemberId, MemberUpdateRequest memberUpdateRequest); - Member setProfileImageKey(Long requestMemberId, String profileImageKey); Member disable(Long requestMemberId); } diff --git a/src/main/java/org/fontory/fontorybe/member/domain/Member.java b/src/main/java/org/fontory/fontorybe/member/domain/Member.java index e84790e..b7e852f 100644 --- a/src/main/java/org/fontory/fontorybe/member/domain/Member.java +++ b/src/main/java/org/fontory/fontorybe/member/domain/Member.java @@ -23,8 +23,6 @@ public class Member { private LocalDate birth; - private String profileImageKey; - private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -40,7 +38,6 @@ public static Member fromDefaults(MemberDefaults memberDefaults, String nickname .nickname(nickname) .gender(memberDefaults.getGender()) .birth(memberDefaults.getBirth()) - .profileImageKey(memberDefaults.getProfileImageKey()) .provideId(provide.getId()) .status(MemberStatus.ONBOARDING) .build(); @@ -52,7 +49,6 @@ public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo, String .nickname(initNewMemberInfo.getNickname()) .gender(initNewMemberInfo.getGender()) .birth(initNewMemberInfo.getBirth()) - .profileImageKey(profileImageKey) .createdAt(this.createdAt) .provideId(this.provideId) .deletedAt(this.deletedAt) @@ -67,7 +63,6 @@ public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo) { .nickname(initNewMemberInfo.getNickname()) .gender(initNewMemberInfo.getGender()) .birth(initNewMemberInfo.getBirth()) - .profileImageKey(this.profileImageKey) .createdAt(this.createdAt) .provideId(this.provideId) .deletedAt(this.deletedAt) @@ -90,27 +85,10 @@ public Member update(MemberUpdateRequest memberUpdateRequest) { .deletedAt(this.deletedAt) .provideId(this.provideId) .status(this.status) - .profileImageKey(this.profileImageKey) .build(); } public void disable() { this.status = MemberStatus.DEACTIVATE; this.deletedAt = LocalDateTime.now(); } - - public Member setProfileImageKey(String profileImageKey) { - return Member.builder() - .profileImageKey(profileImageKey) - - .id(this.id) - .nickname(this.nickname) - .gender(this.gender) - .birth(this.birth) - .createdAt(this.createdAt) - .provideId(this.provideId) - .deletedAt(this.deletedAt) - .provideId(this.provideId) - .status(this.status) - .build(); - } } diff --git a/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java b/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java index d62e7d5..56b7f55 100644 --- a/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java +++ b/src/main/java/org/fontory/fontorybe/member/infrastructure/entity/MemberEntity.java @@ -36,8 +36,6 @@ public class MemberEntity extends BaseEntity { private LocalDate birth; - private String profileImageKey; - private Long provideId; @Enumerated(EnumType.STRING) @@ -51,7 +49,6 @@ public Member toModel() { .nickname(nickname) .gender(gender) .birth(birth) - .profileImageKey(profileImageKey) .provideId(provideId) .status(status) .createdAt(getCreatedAt()) @@ -66,7 +63,6 @@ public static MemberEntity from(Member member) { .nickname(member.getNickname()) .gender(member.getGender()) .birth(member.getBirth()) - .profileImageKey(member.getProfileImageKey()) .provideId(member.getProvideId()) .status(member.getStatus()) .createdAt(member.getCreatedAt()) diff --git a/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java b/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java index e93c3a0..21afb92 100644 --- a/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/member/service/MemberUpdateServiceImpl.java @@ -42,14 +42,6 @@ public Member update(Long requestMemberId, MemberUpdateRequest memberUpdateReque return memberRepository.save(targetMember.update(memberUpdateRequest)); } - @Override - @Transactional - public Member setProfileImageKey(Long requestMemberId, String profileImageKey) { - Member targetMember = memberLookupService.getOrThrowById(requestMemberId); - Member member = targetMember.setProfileImageKey(profileImageKey); - return memberRepository.save(member); - } - @Override @Transactional public Member disable(Long requestMemberId) { diff --git a/src/test/java/org/fontory/fontorybe/integration/file/FileServiceIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/file/FileServiceIntegrationTest.java index d2a7cc1..394a7b1 100644 --- a/src/test/java/org/fontory/fontorybe/integration/file/FileServiceIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/file/FileServiceIntegrationTest.java @@ -57,23 +57,6 @@ void setup() { given(s3Client.deleteObject(any(DeleteObjectRequest.class))) .willReturn(DeleteObjectResponse.builder().build()); - // Stub cloud storage upload for profile image - given(cloudStorageService.uploadProfileImage(any(FileCreate.class), anyString())) - .willAnswer(invocation -> { - FileCreate fc = invocation.getArgument(0); - String key = invocation.getArgument(1); - return FileMetadata.builder() - .fileName(fc.getFileName()) - .fileType(fc.getFileType()) - .extension(fc.getExtension()) - .key(key) - .uploaderId(fc.getUploaderId()) - .size(fc.getFile().getSize()) - .uploadedAt(LocalDateTime.now()) - .createdAt(LocalDateTime.now()) - .updatedAt(LocalDateTime.now()) - .build(); - }); // Stub cloud storage upload for font template given(cloudStorageService.uploadFontTemplateImage(any(FileCreate.class))) .willAnswer(invocation -> { @@ -91,10 +74,8 @@ void setup() { .updatedAt(LocalDateTime.now()) .build(); }); - // Stub getting file URL - given(cloudStorageService.getProfileImageUrl(anyString())) - .willReturn(mockedFileUrl); + // Stub getting file URL given(cloudStorageService.getFontPaperUrl(anyString())) .willReturn(mockedFileUrl); } @@ -116,30 +97,6 @@ void getNonExistentFileMetadataTest() { .isInstanceOf(FileNotFoundException.class); } - @Test - @DisplayName("uploadProfileImage - uploads successfully and updates member") - void uploadProfileImageTest() { - MockMultipartFile mockFile = createMockImageFile("profileTest.jpg", "image/jpeg"); - - FileUploadResult result = fileService.uploadProfileImage(mockFile, existMemberId); - - assertThat(result).isNotNull(); - assertThat(result.getFileUrl()).isEqualTo(mockedFileUrl); - assertThat(result.getFileName()).isEqualTo(existMemberId + ".jpg"); - assertThat(result.getSize()).isEqualTo(mockFile.getSize()); - - Member member = memberLookupService.getOrThrowById(existMemberId); - assertThat(member.getProfileImageKey()).isNotNull(); - } - - @Test - @DisplayName("uploadProfileImage - non-existent member throws") - void uploadProfileImageNonExistentMemberTest() { - MockMultipartFile mockFile = createMockImageFile("profileTest.jpg", "image/jpeg"); - assertThatThrownBy(() -> fileService.uploadProfileImage(mockFile, nonExistentId)) - .isInstanceOf(MemberNotFoundException.class); - } - @Test @DisplayName("uploadFontTemplateImage - uploads successfully") void uploadFontTemplateImageTest() { diff --git a/src/test/java/org/fontory/fontorybe/integration/member/controller/MemberControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/controller/MemberControllerIntegrationTest.java index df709a5..8fcf209 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/controller/MemberControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/controller/MemberControllerIntegrationTest.java @@ -47,9 +47,6 @@ void setUp() { UserPrincipal userPrincipal = new UserPrincipal(TEST_MEMBER_ID); validAccessToken = jwtTokenProvider.generateAccessToken(userPrincipal); testMember = memberLookupService.getOrThrowById(TEST_MEMBER_ID); - - given(cloudStorageService.getProfileImageUrl(testMember.getProfileImageKey())) - .willReturn(TEST_FILE_URL); } @Test @@ -60,8 +57,7 @@ void getInfoMemberSuccess() throws Exception { .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.memberId").value(TEST_MEMBER_ID.intValue())) - .andExpect(jsonPath("$.nickname").value(TEST_MEMBER_NICKNAME)) - .andExpect(jsonPath("$.profileImageUrl").value(TEST_FILE_URL)); + .andExpect(jsonPath("$.nickname").value(TEST_MEMBER_NICKNAME)); } @Test diff --git a/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java index 78b3945..4aaaa49 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/controller/ProfileControllerIntegrationTest.java @@ -62,8 +62,6 @@ void setUp() { .fileUploadTime(TEST_FILE_UPLOAD_TIME) .size(TEST_FILE_SIZE) .build(); - - given(fileService.uploadProfileImage(any(), any())).willReturn(mockFileUploadResult); } @Test @@ -81,31 +79,13 @@ void checkDuplicateFalseTest() throws Exception { void updateMemberSuccessTest() throws Exception { MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(UPDATE_MEMBER_NICKNAME); String jsonRequest = objectMapper.writeValueAsString(memberUpdateRequest); - MockMultipartFile jsonPart = new MockMultipartFile( - "req", - null, - "application/json", - jsonRequest.getBytes(StandardCharsets.UTF_8) - ); - MockMultipartFile filePart = new MockMultipartFile( - "file", - UPDDATE_FILE_NAME, - "image/jpeg", - "fileBytes".getBytes(StandardCharsets.UTF_8) - ); - - MockMultipartHttpServletRequestBuilder builder = multipart("/member/me"); - builder.with(request -> { request.setMethod("PATCH"); return request; }); - - mockMvc.perform(builder - .file(jsonPart) - .file(filePart) + + mockMvc.perform(patch("/member/me") .cookie(new Cookie("accessToken", validAccessToken)) - .contentType(MediaType.MULTIPART_FORM_DATA)) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.memberId", is(TEST_MEMBER_ID.intValue()))) .andExpect(jsonPath("$.nickname", is(UPDATE_MEMBER_NICKNAME))) - .andExpect(jsonPath("$.profileImageUrl", containsString(testMember.getProfileImageKey()))) .andExpect(jsonPath("$.gender", is(testMember.getGender().name()))) .andExpect(jsonPath("$.birth", is(testMember.getBirth().toString()))); } diff --git a/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java index da4f5a8..e1cce3f 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/controller/RegistrationControllerIntegrationTest.java @@ -31,8 +31,7 @@ import static org.fontory.fontorybe.TestConstants.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -58,12 +57,6 @@ void setUp() { UserPrincipal userPrincipal = new UserPrincipal(TEST_MEMBER_ID); validAccessToken = jwtTokenProvider.generateAccessToken(userPrincipal); - FileUploadResult mockFileUploadResult = FileUploadResult.builder() - .fileName(TEST_FILE_NAME) - .fileUrl(TEST_FILE_URL) - .fileUploadTime(TEST_FILE_UPLOAD_TIME) - .size(TEST_FILE_SIZE) - .build(); FileMetadata fileMetadata = FileMetadata.builder() .id(TEST_FILE_ID) .fileName(TEST_FILE_NAME) @@ -79,7 +72,6 @@ void setUp() { notInitializedAccessToken = jwtTokenProvider.generateAccessToken(UserPrincipal.from(notInitedMember)); given(fileService.getOrThrowById(any())).willReturn(fileMetadata); - given(fileService.uploadProfileImage(any(), any())).willReturn(mockFileUploadResult); } @Test @@ -107,25 +99,11 @@ void checkDuplicateTrueTest() throws Exception { void addMemberSuccessTest() throws Exception { InitMemberInfoRequest initMemberInfoRequest = new InitMemberInfoRequest(NEW_MEMBER_NICKNAME, NEW_MEMBER_GENDER, NEW_MEMBER_BIRTH); String jsonRequest = objectMapper.writeValueAsString(initMemberInfoRequest); - MockMultipartFile jsonPart = new MockMultipartFile( - "req", - null, - "application/json", - jsonRequest.getBytes(StandardCharsets.UTF_8) - ); - MockMultipartFile filePart = new MockMultipartFile( - "file", - TEST_FILE_NAME, - "image/jpeg", - "fileBytes".getBytes(StandardCharsets.UTF_8) - ); - - - mockMvc.perform(multipart("/register") - .file(jsonPart) - .file(filePart) + + mockMvc.perform(post("/register") .cookie(new Cookie("accessToken", notInitializedAccessToken)) - .contentType(MediaType.MULTIPART_FORM_DATA)) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonRequest)) .andExpect(status().isCreated()); } } diff --git a/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java index c4b5b30..6c58ed6 100644 --- a/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/member/service/MemberUpdateServiceIntegrationTest.java @@ -72,7 +72,6 @@ void setUp() { .build(); given(fileService.getOrThrowById(any())).willReturn(profileImageFileMetadata); - given(fileService.uploadProfileImage(any(), any())).willReturn(profileImageFileUploadResult); } @Test @@ -85,7 +84,6 @@ void getOrThrowByIdTest() { () -> assertThat(foundMember.getNickname()).isEqualTo(TEST_MEMBER_NICKNAME), () -> assertThat(foundMember.getGender()).isEqualTo(TEST_MEMBER_GENDER), () -> assertThat(foundMember.getProvideId()).isEqualTo(TEST_PROVIDE_ID), - () -> assertThat(foundMember.getProfileImageKey()).isEqualTo(TEST_MEMBER_PROFILE_KEY), () -> assertThat(foundMember.getCreatedAt()).isNotNull(), () -> assertThat(foundMember.getUpdatedAt()).isNotNull(), () -> assertThat(foundMember.getDeletedAt()).isNull() @@ -114,7 +112,6 @@ void createTest() { () -> assertThat(createdMember.getNickname()).isEqualTo(NEW_MEMBER_NICKNAME), () -> assertThat(createdMember.getGender()).isEqualTo(NEW_MEMBER_GENDER), () -> assertThat(createdMember.getBirth()).isEqualTo(NEW_MEMBER_BIRTH), - () -> assertThat(createdMember.getProfileImageKey()).isEqualTo(NEW_MEMBER_PROFILE_KEY), () -> assertThat(createdMember.getCreatedAt()).isNotNull(), () -> assertThat(createdMember.getUpdatedAt()).isNotNull() ); @@ -154,7 +151,6 @@ void updateTest() { () -> assertThat(updatedMember.getCreatedAt()).isEqualTo(member.getCreatedAt()), () -> assertThat(updatedMember.getProvideId()).isEqualTo(member.getProvideId()), () -> assertThat(updatedMember.getNickname()).isEqualTo(UPDATE_MEMBER_NICKNAME), - () -> assertThat(updatedMember.getProfileImageKey()).isEqualTo(member.getProfileImageKey()), () -> assertThat(updatedMember.getUpdatedAt()).isAfter(member.getUpdatedAt()) ); } @@ -230,14 +226,6 @@ void disableAlreadyDisabledTest() { private Member create(InitMemberInfoRequest initNewMemberInfoRequest, Provide provide) { Member defaultMember = memberCreationService.createDefaultMember(provide); - MockMultipartFile file = new MockMultipartFile( - TEST_FILE_NAME, // RequestPart 이름 - TEST_FILE_NAME, // 원본 파일명 - "image/png", // Content-Type - "dummy-image-data".getBytes() // 파일 내용 - ); - FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, defaultMember.getId()); - System.out.println("fileUploadResult = " + fileUploadResult); - return memberOnboardService.initNewMemberInfo(defaultMember.getId(), initNewMemberInfoRequest, fileUploadResult); + return memberOnboardService.initNewMemberInfo(defaultMember.getId(), initNewMemberInfoRequest); } } \ No newline at end of file diff --git a/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java b/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java index 58daa13..e6b84ad 100644 --- a/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/file/FileServiceTest.java @@ -83,7 +83,7 @@ void getOrThrowByIdTest() { MultipartFile mockFile = createValidImageFile(profileImageFilename, "image/jpeg"); // Upload a file to get its ID - FileUploadResult uploadResult = fileService.uploadProfileImage(mockFile, existMemberId); + FileUploadResult uploadResult = fileService.uploadFontTemplateImage(mockFile, existMemberId); FileMetadata savedMetadata = testContainer.fileRepository.findById(uploadResult.getId()).get(); Long fileId = savedMetadata.getId(); @@ -95,7 +95,7 @@ void getOrThrowByIdTest() { () -> assertThat(retrievedMetadata).isNotNull(), () -> assertThat(retrievedMetadata.getId()).isEqualTo(fileId), () -> assertThat(retrievedMetadata.getFileName()).isEqualTo(existMemberId + ".jpg"), - () -> assertThat(retrievedMetadata.getFileType()).isEqualTo(FileType.PROFILE_IMAGE), + () -> assertThat(retrievedMetadata.getFileType()).isEqualTo(FileType.FONT_PAPER), () -> assertThat(retrievedMetadata.getUploaderId()).isEqualTo(existMemberId), () -> assertThat(retrievedMetadata.getSize()).isEqualTo(fileContent.length), () -> assertThat(retrievedMetadata.getUploadedAt()).isNotNull(), @@ -116,41 +116,6 @@ void getOrThrowByIdNonExistentTest() { ).isExactlyInstanceOf(FileNotFoundException.class); } - @Test - @DisplayName("uploadProfileImage - should upload profile image successfully") - void uploadProfileImageTest() { - // given - MultipartFile mockFile = createValidImageFile(profileImageFilename, "image/jpeg"); - - // when - FileUploadResult result = fileService.uploadProfileImage(mockFile, existMemberId); - - // then - assertAll( - () -> assertThat(result).isNotNull(), - () -> assertThat(result.getFileName()).isEqualTo(existMemberId + ".jpg"), - () -> assertThat(result.getFileUrl()).isNotNull(), - () -> assertThat(result.getFileUploadTime()).isNotNull(), - () -> assertThat(result.getSize()).isEqualTo(fileContent.length) - ); - - // 멤버의 프로필 이미지가 업데이트되었는지 확인 - Member updatedMember = memberLookupService.getOrThrowById(existMemberId); - assertThat(updatedMember.getProfileImageKey()).isNotNull(); - } - - @Test - @DisplayName("uploadProfileImage - should throw exception when member doesn't exist") - void uploadProfileImageNonExistentMemberTest() { - // given - MultipartFile mockFile = createValidImageFile(profileImageFilename, "image/jpeg"); - - // when & then - assertThatThrownBy( - () -> fileService.uploadProfileImage(mockFile, nonExistentId) - ).isExactlyInstanceOf(MemberNotFoundException.class); - } - @Test @DisplayName("uploadFontTemplateImage - should upload font template image successfully") void uploadFontTemplateImageTest() { @@ -186,10 +151,10 @@ void uploadFontTemplateImageNonExistentMemberTest() { @DisplayName("upload and retrieve file metadata should work together") void uploadAndRetrieveTest() { // given - MockMultipartFile mockFile = createValidImageFile("profile.jpg", "image/jpeg"); + MockMultipartFile mockFile = createValidImageFile("fontPaper.jpg", "image/jpeg"); // when - upload file - FileUploadResult uploadResult = fileService.uploadProfileImage(mockFile, existMemberId); + FileUploadResult uploadResult = fileService.uploadFontTemplateImage(mockFile, existMemberId); // Find the file ID from repository FileMetadata savedMetadata = testContainer.fileRepository.findById(uploadResult.getId()).get(); @@ -203,7 +168,7 @@ void uploadAndRetrieveTest() { () -> assertThat(retrievedMetadata).isNotNull(), () -> assertThat(retrievedMetadata.getId()).isEqualTo(fileId), () -> assertThat(retrievedMetadata.getFileName()).isEqualTo(existMemberId + ".jpg"), - () -> assertThat(retrievedMetadata.getFileType()).isEqualTo(FileType.PROFILE_IMAGE), + () -> assertThat(retrievedMetadata.getFileType()).isEqualTo(FileType.FONT_PAPER), () -> assertThat(uploadResult.getFileUrl()).contains(retrievedMetadata.getKey()) ); } @@ -212,24 +177,20 @@ void uploadAndRetrieveTest() { @DisplayName("uploading different types of images should update FileMetadata correctly") void uploadDifferentImagesTest() { // given - MockMultipartFile profileFile = createValidImageFile("profile.jpg", "image/jpeg"); - MockMultipartFile templateFile = createValidImageFile("template.png", "image/png"); + MockMultipartFile jpgFile = createValidImageFile("profile.jpg", "image/jpeg"); + MockMultipartFile pngFile = createValidImageFile("template.png", "image/png"); // when - upload profile image - FileUploadResult profileResult = fileService.uploadProfileImage(profileFile, existMemberId); + FileUploadResult jpgResult = fileService.uploadFontTemplateImage(jpgFile, existMemberId); // when - upload template image - FileUploadResult templateResult = fileService.uploadFontTemplateImage(templateFile, existMemberId); + FileUploadResult pngResult = fileService.uploadFontTemplateImage(pngFile, existMemberId); // then assertAll( - () -> assertThat(profileResult.getFileName()).contains("jpg"), - () -> assertThat(templateResult.getFileName()).contains("png"), - () -> assertThat(((FakeFileRepository)testContainer.fileRepository).findAll()).hasSize(3) + () -> assertThat(jpgResult.getFileName()).contains("jpg"), + () -> assertThat(pngResult.getFileName()).contains("png"), + () -> assertThat(((FakeFileRepository)testContainer.fileRepository).findAll()).hasSize(2) ); - - // Member should have profile image updated - Member updatedMember = memberLookupService.getOrThrowById(existMemberId); - assertThat(updatedMember.getProfileImageKey()).isNotNull(); } } \ No newline at end of file diff --git a/src/test/java/org/fontory/fontorybe/unit/member/controller/MemberControllerTest.java b/src/test/java/org/fontory/fontorybe/unit/member/controller/MemberControllerTest.java index 41a2653..8da1d3f 100644 --- a/src/test/java/org/fontory/fontorybe/unit/member/controller/MemberControllerTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/member/controller/MemberControllerTest.java @@ -69,7 +69,6 @@ void getInfoMemberSuccessTest() { assertAll( () -> assertThat(body.getMemberId()).isEqualTo(testMember.getId()), - () -> assertThat(body.getProfileImageUrl()).isEqualTo(cloudStorageService.getProfileImageUrl(testMember.getProfileImageKey())), () -> assertThat(body.getNickname()).isEqualTo(testMember.getNickname()) ); } diff --git a/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java b/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java index ba3706b..74f50e1 100644 --- a/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/member/controller/ProfileControllerTest.java @@ -114,15 +114,14 @@ void testUpdateMember() { ); //when - ResponseEntity response = profileController.updateMember(testMemberUserPrincipal, memberUpdateRequest, mockFiles); + ResponseEntity response = profileController.updateMember(testMemberUserPrincipal, memberUpdateRequest); MyProfileResponse body = response.getBody(); //then assertAll( () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK), () -> assertThat(body).isNotNull(), - () -> assertThat(body.getNickname()).isEqualTo(UPDATE_MEMBER_NICKNAME), - () -> assertThat(body.getProfileImageUrl()).isEqualTo(cloudStorageService.getProfileImageUrl(testMember.getProfileImageKey())) + () -> assertThat(body.getNickname()).isEqualTo(UPDATE_MEMBER_NICKNAME) ); } @@ -134,7 +133,7 @@ void updateMemberNonExistentTest() { MemberUpdateRequest memberUpdateRequest = new MemberUpdateRequest(TestConstants.UPDATE_MEMBER_NICKNAME); // when & then - assertThatThrownBy(() -> profileController.updateMember(nonExistentUserPrincipal, memberUpdateRequest, mockFiles)) + assertThatThrownBy(() -> profileController.updateMember(nonExistentUserPrincipal, memberUpdateRequest)) .isInstanceOf(MemberNotFoundException.class); } } diff --git a/src/test/java/org/fontory/fontorybe/unit/member/controller/RegistrationControllerTest.java b/src/test/java/org/fontory/fontorybe/unit/member/controller/RegistrationControllerTest.java index dc17926..c29a4e4 100644 --- a/src/test/java/org/fontory/fontorybe/unit/member/controller/RegistrationControllerTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/member/controller/RegistrationControllerTest.java @@ -73,17 +73,10 @@ void testCheckDuplicateFalse() { @DisplayName("addMember returns created member response") void testAddMember() { //given - MockMultipartFile file = new MockMultipartFile( - "file", // RequestPart 이름 - "test.png", // 원본 파일명 - "image/png", // Content-Type - "dummy-image-data".getBytes() // 파일 내용 - ); - List files = Collections.singletonList(file); Member notInitedMember = testContainer.createNotInitedMember(); //when - ResponseEntity response = registrationController.register(UserPrincipal.from(notInitedMember), newInitMemberInfoRequest, files); + ResponseEntity response = registrationController.register(UserPrincipal.from(notInitedMember), newInitMemberInfoRequest); MemberCreateResponse body = response.getBody(); //then diff --git a/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java b/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java index a2a7e0f..af1efd2 100644 --- a/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java +++ b/src/test/java/org/fontory/fontorybe/unit/member/service/MemberUpdateServiceTest.java @@ -71,7 +71,6 @@ void getOrThrownByIdTest() { () -> assertThat(foundMember.getNickname()).isEqualTo(TEST_MEMBER_NICKNAME), () -> assertThat(foundMember.getGender()).isEqualTo(TEST_MEMBER_GENDER), () -> assertThat(foundMember.getProvideId()).isEqualTo(testMemberProvideId), - () -> assertThat(foundMember.getProfileImageKey()).isNotEqualTo(DEFAULT_PROFILE_KEY), () -> assertThat(foundMember.getCreatedAt()).isNotNull(), () -> assertThat(foundMember.getUpdatedAt()).isNotNull(), () -> assertThat(foundMember.getDeletedAt()).isNull() @@ -101,7 +100,6 @@ void makeDefaultMember() { () -> assertThat(defaultMember.getNickname()).isNotNull(), () -> assertThat(defaultMember.getGender()).isEqualTo(memberDefaults.getGender()), () -> assertThat(defaultMember.getBirth()).isEqualTo(memberDefaults.getBirth()), - () -> assertThat(defaultMember.getProfileImageKey()).isEqualTo(memberDefaults.getProfileImageKey()), () -> assertThat(defaultMember.getCreatedAt()).isNotNull(), () -> assertThat(defaultMember.getUpdatedAt()).isNotNull() ); @@ -122,7 +120,6 @@ void createTest() { () -> assertThat(createdMember.getNickname()).isEqualTo(NEW_MEMBER_NICKNAME), () -> assertThat(createdMember.getGender()).isEqualTo(NEW_MEMBER_GENDER), () -> assertThat(createdMember.getBirth()).isEqualTo(NEW_MEMBER_BIRTH), - () -> assertThat(createdMember.getProfileImageKey()).isNotEqualTo(DEFAULT_PROFILE_KEY), () -> assertThat(createdMember.getCreatedAt()).isNotNull(), () -> assertThat(createdMember.getUpdatedAt()).isNotNull() ); @@ -162,7 +159,6 @@ void updateTest() { () -> assertThat(updatedMember.getCreatedAt()).isEqualTo(member.getCreatedAt()), () -> assertThat(updatedMember.getProvideId()).isEqualTo(member.getProvideId()), () -> assertThat(updatedMember.getNickname()).isEqualTo(UPDATE_MEMBER_NICKNAME), - () -> assertThat(updatedMember.getProfileImageKey()).isEqualTo(member.getProfileImageKey()), () -> assertThat(updatedMember.getUpdatedAt()).isAfter(member.getUpdatedAt()) ); } @@ -229,8 +225,7 @@ void updateNoNicknameChangeTest() { Member updatedMember = memberUpdateService.update(member.getId(), memberUpdateRequest); assertAll( () -> assertThat(updatedMember.getId()).isEqualTo(member.getId()), - () -> assertThat(updatedMember.getNickname()).isEqualTo(member.getNickname()), - () -> assertThat(updatedMember.getProfileImageKey()).isEqualTo(member.getProfileImageKey()) + () -> assertThat(updatedMember.getNickname()).isEqualTo(member.getNickname()) ); } diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/FakeCloudStorageService.java b/src/test/java/org/fontory/fontorybe/unit/mock/FakeCloudStorageService.java index 896c90d..0c0faae 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/FakeCloudStorageService.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/FakeCloudStorageService.java @@ -18,17 +18,6 @@ public FakeCloudStorageService(S3Config s3Config) { this.s3Config = s3Config; } - @Override - public FileMetadata uploadProfileImage(FileCreate fileCreate, String key) { - AmazonS3PutRequest amazonS3PutRequest = AmazonS3PutRequest.from( - fileCreate, - key, - s3Config.getBucketName(FileType.PROFILE_IMAGE), - s3Config.getPrefix(FileType.PROFILE_IMAGE), - LocalDateTime.now()); - return uploadFile(amazonS3PutRequest).toModel(); - } - @Override public FileMetadata uploadFontTemplateImage(FileCreate request) { String key = UUID.randomUUID().toString(); @@ -45,11 +34,6 @@ private String getFileUrl(FileType fileType, String key) { return String.format("%s/%s/%s", s3Config.getCdnUrl(), s3Config.getPrefix(fileType), key); } - @Override - public String getProfileImageUrl(String key) { - return getFileUrl(FileType.PROFILE_IMAGE, key); - } - @Override public String getFontPaperUrl(String key) { return getFileUrl(FileType.FONT_PAPER, key); diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java b/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java index 82c33f4..145c273 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/FakeMemberRepository.java @@ -23,7 +23,6 @@ public Member save(Member member) { .nickname(member.getNickname()) .gender(member.getGender()) .birth(member.getBirth()) - .profileImageKey(member.getProfileImageKey()) .provideId(member.getProvideId()) .status(member.getStatus()) .createdAt(now) @@ -38,7 +37,6 @@ public Member save(Member member) { .nickname(member.getNickname()) .gender(member.getGender()) .birth(member.getBirth()) - .profileImageKey(member.getProfileImageKey()) .provideId(member.getProvideId()) .status(member.getStatus()) .createdAt(member.getCreatedAt()) diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java index b7a30a6..cdf3edf 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java @@ -221,8 +221,7 @@ public Member create(InitMemberInfoRequest initNewMemberInfoRequest, Provide pro "image/png", // Content-Type "dummy-image-data".getBytes() // 파일 내용 ); - FileUploadResult fileUploadResult = fileService.uploadProfileImage(file, defaultMember.getId()); - return memberOnboardService.initNewMemberInfo(defaultMember.getId(), initNewMemberInfoRequest, fileUploadResult); + return memberOnboardService.initNewMemberInfo(defaultMember.getId(), initNewMemberInfoRequest); } public final ProvideCreateDto testMemberProvideCreateDto = new ProvideCreateDto(TEST_MEMBER_PROVIDER, TEST_MEMBER_PROVIDED_ID, TEST_MEMBER_EMAIL); diff --git a/src/test/resources/sql/createFileTestData.sql b/src/test/resources/sql/createFileTestData.sql index 69c7524..96e61b3 100644 --- a/src/test/resources/sql/createFileTestData.sql +++ b/src/test/resources/sql/createFileTestData.sql @@ -3,8 +3,8 @@ truncate table `member`; truncate table `provide`; -- Test member data -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`,`created_at`, `updated_at`) -values (999, 'existMemberNickName', 'MALE', '2025-01-26', 'existMemberProfileImage', 999, 'ACTIVATE', NOW(), NOW()); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `provide_id`, `status`,`created_at`, `updated_at`) +values (999, 'existMemberNickName', 'MALE', '2025-01-26', 999, 'ACTIVATE', NOW(), NOW()); -- Test provide data insert into `provide` (`provide_id`, `provider`, `provided_id`, `email`, `member_id`) diff --git a/src/test/resources/sql/createFontTestData.sql b/src/test/resources/sql/createFontTestData.sql index 6e42ddc..8b055fa 100644 --- a/src/test/resources/sql/createFontTestData.sql +++ b/src/test/resources/sql/createFontTestData.sql @@ -26,11 +26,11 @@ INSERT INTO `font` ( '2025-04-08 10:00:00' ); -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`, `created_at`, `updated_at`) -values (999, 'existMemberNickName', 'MALE', '2025-01-26', 'existMemberProfileImage', 999, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `provide_id`, `status`, `created_at`, `updated_at`) +values (999, 'existMemberNickName', 'MALE', '2025-01-26', 999, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`, `created_at`, `updated_at`) -values (1, 'createdMemberNickName', 'MALE', '2025-01-26', 'existMemberProfileImage', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `provide_id`, `status`, `created_at`, `updated_at`) +values (1, 'createdMemberNickName', 'MALE', '2025-01-26', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); insert into `provide` (`provide_id`, `provider`, `provided_id`, `email`, `member_id`) values (999, 'GOOGLE', 'test_provided_id', 'test_email', 999); diff --git a/src/test/resources/sql/createMemberTestData.sql b/src/test/resources/sql/createMemberTestData.sql index eebb305..ae0eedd 100644 --- a/src/test/resources/sql/createMemberTestData.sql +++ b/src/test/resources/sql/createMemberTestData.sql @@ -1,8 +1,8 @@ truncate table `member`; truncate table `provide`; -insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `profile_image_key`, `provide_id`, `status`,`created_at`, `updated_at`) -values (999, 'testMemberNickName', 'MALE', '2025-01-26', 'testMemberProfileImage', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); +insert into `member` (`member_id`, `nickname`, `gender`, `birth`, `provide_id`, `status`,`created_at`, `updated_at`) +values (999, 'testMemberNickName', 'MALE', '2025-01-26', 1, 'ACTIVATE','1922-09-18 19:11:00.000000', '1922-09-18 19:11:00.000000'); insert into `provide` (`provide_id`, `provider`, `provided_id`, `email`, `member_id`) values (1, 'GOOGLE', 'testMemberProvidedId', 'testMemberEmail', 999); \ No newline at end of file From c3f6c4825e2ee7b12390dcf66f4ea472538f8963 Mon Sep 17 00:00:00 2001 From: JuHyuk Lim Date: Sat, 12 Jul 2025 16:44:49 +0900 Subject: [PATCH 25/27] chore: Add cors url --- src/main/java/org/fontory/fontorybe/config/WebConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/fontory/fontorybe/config/WebConfig.java b/src/main/java/org/fontory/fontorybe/config/WebConfig.java index ea37b98..0b4cb6e 100644 --- a/src/main/java/org/fontory/fontorybe/config/WebConfig.java +++ b/src/main/java/org/fontory/fontorybe/config/WebConfig.java @@ -40,6 +40,7 @@ public void addInterceptors(InterceptorRegistry registry) { public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(List.of( + "http://localhost:3000", "http://localhost:5173", "https://www.fontory.co.kr", "https://fontory.co.kr", From 8e23de6a1fd61fada1a7726f3859da8ec1472d90 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Sun, 14 Sep 2025 09:18:01 -0400 Subject: [PATCH 26/27] fix: remove profile image logics when login --- .../controller/port/MemberOnboardService.java | 1 - .../fontorybe/member/domain/Member.java | 14 -------------- .../member/domain/MemberDefaults.java | 5 +---- .../service/MemberOnboardServiceImpl.java | 18 ------------------ src/main/resources/application.properties | 2 +- .../fontorybe/unit/mock/TestContainer.java | 5 +---- 6 files changed, 3 insertions(+), 42 deletions(-) diff --git a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java index 65b55e8..ee48e88 100644 --- a/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java +++ b/src/main/java/org/fontory/fontorybe/member/controller/port/MemberOnboardService.java @@ -7,6 +7,5 @@ public interface MemberOnboardService { Member fetchOrCreateMember(Provide p); - Member initNewMemberInfo(Long requestMemberId, InitMemberInfoRequest initMemberInfoRequest, FileUploadResult fileUploadResult); Member initNewMemberInfo(Long requestMemberId, InitMemberInfoRequest initMemberInfoRequest); } diff --git a/src/main/java/org/fontory/fontorybe/member/domain/Member.java b/src/main/java/org/fontory/fontorybe/member/domain/Member.java index b7e852f..d4750ac 100644 --- a/src/main/java/org/fontory/fontorybe/member/domain/Member.java +++ b/src/main/java/org/fontory/fontorybe/member/domain/Member.java @@ -43,20 +43,6 @@ public static Member fromDefaults(MemberDefaults memberDefaults, String nickname .build(); } - public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo, String profileImageKey) { - return Member.builder() - .id(this.id) - .nickname(initNewMemberInfo.getNickname()) - .gender(initNewMemberInfo.getGender()) - .birth(initNewMemberInfo.getBirth()) - .createdAt(this.createdAt) - .provideId(this.provideId) - .deletedAt(this.deletedAt) - .provideId(this.provideId) - .status(MemberStatus.ACTIVATE) - .build(); - } - public Member initNewMemberInfo(InitMemberInfoRequest initNewMemberInfo) { return Member.builder() .id(this.id) diff --git a/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java b/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java index fef9010..1bdb16d 100644 --- a/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java +++ b/src/main/java/org/fontory/fontorybe/member/domain/MemberDefaults.java @@ -13,14 +13,11 @@ @ConfigurationProperties(prefix="member.default") public class MemberDefaults { private LocalDate birth; - private String profileImageKey; private Gender gender; @ConstructorBinding - public MemberDefaults(LocalDate birth, - String profileImageKey) { + public MemberDefaults(LocalDate birth) { this.birth = birth; - this.profileImageKey = profileImageKey; this.gender = Gender.NONE; } } diff --git a/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java b/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java index ec9fd37..b189b7c 100644 --- a/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/member/service/MemberOnboardServiceImpl.java @@ -40,24 +40,6 @@ public Member fetchOrCreateMember(Provide p) { } } - @Override - @Transactional - public Member initNewMemberInfo(Long requestMemberId, - InitMemberInfoRequest initNewMemberInfoRequest, - FileUploadResult fileUploadResult) { - Member targetMember = memberLookupService.getOrThrowById(requestMemberId); - FileMetadata fileMetadata = fileService.getOrThrowById(fileUploadResult.getId()); - if (targetMember.getStatus() == MemberStatus.ACTIVATE) { - throw new MemberAlreadyJoinedException(); - } else if (memberLookupService.existsByNickname(initNewMemberInfoRequest.getNickname())) { - throw new MemberDuplicateNameExistsException(); - } - - checkContainsBadWord(initNewMemberInfoRequest.getNickname()); - - return memberRepository.save(targetMember.initNewMemberInfo(initNewMemberInfoRequest, fileMetadata.getKey())); - } - @Override @Transactional public Member initNewMemberInfo(Long requestMemberId, diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 64e434b..fcc6af4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -13,7 +13,7 @@ api.server.url=http://localhost:8080 spring.config.import=optional:classpath:application-infrastructure.properties url.base=http://localhost -url.path.signup=/signup/step-one +url.path.signup=/signup url.path.auth= url.cdn=https://cdn.fontory.co.kr diff --git a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java index cdf3edf..7802c19 100644 --- a/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java +++ b/src/test/java/org/fontory/fontorybe/unit/mock/TestContainer.java @@ -15,7 +15,6 @@ import org.fontory.fontorybe.file.application.port.CloudStorageService; import org.fontory.fontorybe.file.application.port.FileRepository; import org.fontory.fontorybe.file.application.port.FileService; -import org.fontory.fontorybe.file.domain.FileUploadResult; import org.fontory.fontorybe.member.controller.MemberController; import org.fontory.fontorybe.member.controller.ProfileController; import org.fontory.fontorybe.member.controller.RegistrationController; @@ -38,7 +37,6 @@ import org.fontory.fontorybe.provide.service.port.ProvideRepository; import org.springframework.context.ApplicationEventPublisher; import org.springframework.mock.web.MockMultipartFile; -import org.springframework.web.multipart.MultipartFile; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @@ -138,8 +136,7 @@ public TestContainer() { .build(); memberDefaults = new MemberDefaults( - LocalDate.of(1999, 12, 31), - DEFAULT_PROFILE_KEY); + LocalDate.of(1999, 12, 31)); fileService = FileServiceImpl.builder() .memberLookupService(memberLookupService) From 17633d5db4ca03c47a8ab7ce8b35c20842b8d702 Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Tue, 7 Oct 2025 06:21:53 +0900 Subject: [PATCH 27/27] Add logs on oAuth2 login logics --- docker-compose.yml | 2 +- .../inbound/CustomOauth2FailureHandler.java | 12 ++++ .../inbound/CustomOauth2SuccessHandler.java | 26 +++++++- .../inbound/CustomOauth2UserService.java | 25 +++++++- .../outbound/JwtTokenProviderImpl.java | 64 ++++++++++++++++--- .../application/AuthService.java | 31 ++++++++- 6 files changed, 145 insertions(+), 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3fb6e4e..8a4253a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: container_name: fontory-mariadb environment: MYSQL_ROOT_PASSWORD: rootPW - MYSQL_DATRABASE: FONTORY + MYSQL_DATABASE: FONTORY MYSQL_USER: fontory MYSQL_PASSWORD: fontoryPW ports: diff --git a/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2FailureHandler.java b/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2FailureHandler.java index 2173d76..be035f3 100644 --- a/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2FailureHandler.java +++ b/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2FailureHandler.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.stereotype.Component; @@ -13,6 +14,7 @@ import java.util.HashMap; import java.util.Map; +@Slf4j @Component @RequiredArgsConstructor public class CustomOauth2FailureHandler implements AuthenticationFailureHandler { @@ -22,8 +24,18 @@ public class CustomOauth2FailureHandler implements AuthenticationFailureHandler public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + log.warn("OAuth2 authentication failed: errorMessage={}, exceptionType={}", + exception.getMessage(), exception.getClass().getSimpleName()); + + String requestUrl = request.getRequestURL().toString(); + String queryString = request.getQueryString(); + log.debug("Failed OAuth2 request details: url={}, queryString={}", requestUrl, queryString); + Map attributes = new HashMap<>(); attributes.put("message", "error occurred during authentication"); + + log.info("Sending OAuth2 authentication failure response: status={}", HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write(objectMapper.writeValueAsString(attributes)); diff --git a/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2SuccessHandler.java b/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2SuccessHandler.java index 134b0ef..57a5f5f 100644 --- a/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2SuccessHandler.java +++ b/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2SuccessHandler.java @@ -3,6 +3,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.fontory.fontorybe.authentication.application.dto.ResponseCookies; import org.fontory.fontorybe.authentication.application.AuthService; import org.fontory.fontorybe.authentication.application.port.CookieUtils; @@ -21,6 +22,7 @@ import java.io.IOException; import java.util.Objects; +@Slf4j @Component @RequiredArgsConstructor public class CustomOauth2SuccessHandler implements AuthenticationSuccessHandler { @@ -37,19 +39,39 @@ public class CustomOauth2SuccessHandler implements AuthenticationSuccessHandler public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + log.info("OAuth2 authentication success handler triggered"); + OAuth2User authUser = (OAuth2User) authentication.getPrincipal(); Provide provide = authUser.getAttribute("provide"); Objects.requireNonNull(provide, "OAuth2User must have 'provide' attribute"); + + log.info("Processing successful OAuth2 login: provideId={}, provider={}, email={}", + provide.getId(), provide.getProvider(), provide.getEmail()); Member member = memberOnboardService.fetchOrCreateMember(provide); + log.info("Member fetched/created: memberId={}, status={}", + member.getId(), member.getStatus()); + ResponseCookies cookies = authService.issueAuthCookies(member); + log.debug("Auth cookies issued for member: memberId={}", member.getId()); + cookieUtils.addCookies(response, cookies); + log.debug("Auth cookies added to response: memberId={}", member.getId()); - redirectStrategy.sendRedirect(request, response, buildRedirectUrl(member)); + String redirectUrl = buildRedirectUrl(member); + log.info("Redirecting user after successful OAuth2 login: memberId={}, status={}, redirectUrl={}", + member.getId(), member.getStatus(), redirectUrl); + + redirectStrategy.sendRedirect(request, response, redirectUrl); } private String buildRedirectUrl(Member member) { String path = (member.getStatus() == MemberStatus.ONBOARDING) ? signUpPath : authPath; - return baseUrl + path; + String redirectUrl = baseUrl + path; + + log.debug("Building redirect URL: memberStatus={}, path={}, fullUrl={}", + member.getStatus(), path, redirectUrl); + + return redirectUrl; } } diff --git a/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2UserService.java b/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2UserService.java index 9c847a8..3e756ed 100644 --- a/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2UserService.java +++ b/src/main/java/org/fontory/fontorybe/authentication/adapter/inbound/CustomOauth2UserService.java @@ -1,6 +1,7 @@ package org.fontory.fontorybe.authentication.adapter.inbound; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.fontory.fontorybe.authentication.domain.Auth2UserInfo; import org.fontory.fontorybe.provide.domain.Provide; import org.fontory.fontorybe.provide.infrastructure.entity.Provider; @@ -22,6 +23,7 @@ import static org.fontory.fontorybe.authentication.domain.Auth2UserInfo.getOAuth2UserInfo; +@Slf4j @Service @RequiredArgsConstructor public class CustomOauth2UserService implements OAuth2UserService { @@ -30,16 +32,26 @@ public class CustomOauth2UserService implements OAuth2UserService delegate = new DefaultOAuth2UserService(); OAuth2User oAuth2User = delegate.loadUser(userRequest); + log.debug("OAuth2 user loaded from provider: provider={}", registrationId); String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); Provider provider = Provider.from(userRequest.getClientRegistration().getRegistrationId()); Map attributes = new HashMap<>(oAuth2User.getAttributes()); Auth2UserInfo oAuth2UserInfo = getOAuth2UserInfo(provider, attributes, userNameAttributeName); + + log.info("OAuth2 user info extracted: provider={}, userIdentifier={}, email={}", + provider, oAuth2UserInfo.getUserIdentifier(), oAuth2UserInfo.getEmail()); Provide provide = getProvide(oAuth2UserInfo); attributes.put("provide", provide); + + log.info("OAuth2 authentication successful: provider={}, provideId={}", + provider, provide.getId()); return new DefaultOAuth2User( Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")), @@ -51,17 +63,28 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic private Provide getProvide(Auth2UserInfo oAuth2UserInfo) { String userIdentifier = oAuth2UserInfo.getUserIdentifier(); Provider provider = oAuth2UserInfo.getProvider(); + + log.debug("Looking up provide info: userIdentifier={}, provider={}", userIdentifier, provider); Optional oAuthInfo = provideRepository.findByOAuthInfo(userIdentifier, provider); if (oAuthInfo.isEmpty() || oAuthInfo.get().getId() == null) { + log.info("Creating new provide entry: userIdentifier={}, provider={}, email={}", + userIdentifier, provider, oAuth2UserInfo.getEmail()); + Provide provide = Provide.builder() .providedId(userIdentifier) .email(oAuth2UserInfo.getEmail()) .provider(provider) .build(); - return provideRepository.save(provide); + Provide savedProvide = provideRepository.save(provide); + + log.info("New provide created: provideId={}, userIdentifier={}, provider={}", + savedProvide.getId(), userIdentifier, provider); + return savedProvide; } else { + log.info("Existing provide found: provideId={}, userIdentifier={}, provider={}", + oAuthInfo.get().getId(), userIdentifier, provider); return oAuthInfo.get(); } } diff --git a/src/main/java/org/fontory/fontorybe/authentication/adapter/outbound/JwtTokenProviderImpl.java b/src/main/java/org/fontory/fontorybe/authentication/adapter/outbound/JwtTokenProviderImpl.java index e635770..53db259 100644 --- a/src/main/java/org/fontory/fontorybe/authentication/adapter/outbound/JwtTokenProviderImpl.java +++ b/src/main/java/org/fontory/fontorybe/authentication/adapter/outbound/JwtTokenProviderImpl.java @@ -11,10 +11,12 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; import javax.crypto.SecretKey; import java.util.Date; +@Slf4j @Component public class JwtTokenProviderImpl implements JwtTokenProvider { @@ -36,6 +38,8 @@ private SecretKey getSigningKey(String key) { public JwtTokenProviderImpl( JwtProperties props) { + log.info("Initializing JWT token provider"); + this.accessSecretKey = getSigningKey(props.getAccessSecretKey()); this.refreshSecretKey = getSigningKey(props.getRefreshSecretKey()); this.provideSecretKey = getSigningKey(props.getProvideSecretKey()); @@ -46,75 +50,119 @@ public JwtTokenProviderImpl( this.provideJwtParser = Jwts.parserBuilder().setSigningKey(provideSecretKey).build(); this.fontCreateJwtParser = Jwts.parserBuilder().setSigningKey(fontCreateSecretKey).build(); this.props = props; + + log.debug("JWT token provider initialized with token validities - access: {}ms, refresh: {}ms", + props.getAccessTokenValidityMs(), props.getRefreshTokenValidityMs()); } public String generateTemporalProvideToken(String id) { + log.debug("Generating temporal provide token for id: {}", id); + Date now = new Date(); Date expiryDate = new Date(now.getTime() + props.getTempTokenValidityMs()); - return Jwts.builder() + String token = Jwts.builder() .setSubject(String.valueOf(id)) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(this.provideSecretKey) .compact(); + + log.info("Temporal provide token generated: id={}, expiresAt={}", id, expiryDate); + return token; } public Long getProvideId(String token) { + log.debug("Extracting provide ID from token"); + Claims claims = provideJwtParser .parseClaimsJws(token) .getBody(); - return Long.valueOf(claims.getSubject()); + Long provideId = Long.valueOf(claims.getSubject()); + + log.debug("Provide ID extracted: {}", provideId); + return provideId; } public String generateAccessToken(UserPrincipal user) { + log.debug("Generating access token for user: {}", user.getId()); + Date now = new Date(); Date expiryDate = new Date(now.getTime() + props.getAccessTokenValidityMs()); - return Jwts.builder() + String token = Jwts.builder() .setSubject(String.valueOf(user.getId())) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(this.accessSecretKey) .compact(); + + log.info("Access token generated: userId={}, expiresAt={}", user.getId(), expiryDate); + return token; } public String generateRefreshToken(UserPrincipal user) { + log.debug("Generating refresh token for user: {}", user.getId()); + Date now = new Date(); Date expiryDate = new Date(now.getTime() + props.getRefreshTokenValidityMs()); - return Jwts.builder() + String token = Jwts.builder() .setSubject(String.valueOf(user.getId())) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(refreshSecretKey) .compact(); + + log.info("Refresh token generated: userId={}, expiresAt={}", user.getId(), expiryDate); + return token; } public Long getMemberIdFromAccessToken(String token) { + log.debug("Extracting member ID from access token"); + Claims claims = accessJwtParser .parseClaimsJws(token) .getBody(); - return Long.valueOf(claims.getSubject()); + Long memberId = Long.valueOf(claims.getSubject()); + + log.debug("Member ID extracted from access token: {}", memberId); + return memberId; } public Long getMemberIdFromRefreshToken(String token) { + log.debug("Extracting member ID from refresh token"); + Claims claims = refreshJwtParser .parseClaimsJws(token) .getBody(); - return Long.valueOf(claims.getSubject()); + Long memberId = Long.valueOf(claims.getSubject()); + + log.debug("Member ID extracted from refresh token: {}", memberId); + return memberId; } public Authentication getAuthenticationFromAccessToken(String token) { + log.debug("Creating authentication from access token"); + Claims claims = accessJwtParser .parseClaimsJws(token) .getBody(); Long id = Long.valueOf(claims.getSubject()); UserPrincipal principal = new UserPrincipal(id); - return new UsernamePasswordAuthenticationToken(principal, token, principal.getAuthorities()); + Authentication auth = new UsernamePasswordAuthenticationToken(principal, token, principal.getAuthorities()); + + log.info("Authentication created from access token: userId={}, authorities={}", + id, principal.getAuthorities()); + return auth; } public String getFontCreateServer(String token) { + log.debug("Validating font create server token"); + Claims claims = fontCreateJwtParser .parseClaimsJws(token) .getBody(); - return claims.getSubject(); + String subject = claims.getSubject(); + + log.debug("Font create server token validated: subject={}", subject); + return subject; } } diff --git a/src/main/java/org/fontory/fontorybe/authentication/application/AuthService.java b/src/main/java/org/fontory/fontorybe/authentication/application/AuthService.java index 03d456b..a6f6c12 100644 --- a/src/main/java/org/fontory/fontorybe/authentication/application/AuthService.java +++ b/src/main/java/org/fontory/fontorybe/authentication/application/AuthService.java @@ -3,6 +3,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.Builder; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.fontory.fontorybe.authentication.application.dto.ResponseCookies; import org.fontory.fontorybe.authentication.application.port.CookieUtils; import org.fontory.fontorybe.authentication.application.port.JwtTokenProvider; @@ -14,6 +15,7 @@ import org.fontory.fontorybe.member.domain.Member; import org.springframework.stereotype.Service; +@Slf4j @Builder @Service @RequiredArgsConstructor @@ -29,12 +31,16 @@ public class AuthService { * 기존에 토큰이 존재한다면 제거, 기존 토큰이 존재할 필요 X */ private TokenResponse issueNewTokens(Member member) { + log.info("Issuing new token pair for member: memberId={}, provideId={}", + member.getId(), member.getProvideId()); + UserPrincipal user = UserPrincipal.from(member); String accessToken = jwtTokenProvider.generateAccessToken(user); String refreshToken = jwtTokenProvider.generateRefreshToken(user); tokenStorage.saveRefreshToken(member, refreshToken); + log.debug("Refresh token stored in Redis for member: memberId={}", member.getId()); return TokenResponse.from(accessToken, refreshToken); } @@ -43,30 +49,49 @@ private TokenResponse issueNewTokens(Member member) { * 쿠키 발급 */ public ResponseCookies issueAuthCookies(Member member) { + log.info("Creating auth cookies for member: memberId={}, status={}", + member.getId(), member.getStatus()); + TokenResponse tokenResponse = issueNewTokens(member); - return ResponseCookies.from( + ResponseCookies cookies = ResponseCookies.from( cookieUtils.createAccessTokenCookie(tokenResponse.getAccessToken()), cookieUtils.createRefreshTokenCookie(tokenResponse.getRefreshToken()) ); + + log.debug("Auth cookies created successfully for member: memberId={}", member.getId()); + return cookies; } /** * 쿠키 재발급 */ public ResponseCookies refreshAuthCookies(Long memberId, String providedRefreshToken) { + log.info("Refreshing auth cookies for member: memberId={}", memberId); + Member member = memberLookupService.getOrThrowById(memberId); String storedRefreshToken = tokenStorage.getRefreshToken(member); - if (storedRefreshToken == null || !storedRefreshToken.equals(providedRefreshToken)) { + if (storedRefreshToken == null) { + log.warn("No stored refresh token found for member: memberId={}", memberId); throw new InvalidRefreshTokenException(); } - + + if (!storedRefreshToken.equals(providedRefreshToken)) { + log.warn("Refresh token mismatch for member: memberId={}", memberId); + throw new InvalidRefreshTokenException(); + } + + log.info("Refresh token validated successfully, issuing new tokens: memberId={}", memberId); return issueAuthCookies(member); } public void clearAuthCookies(HttpServletResponse res, Long memberId) { + log.info("Clearing auth cookies for member: memberId={}", memberId); + Member member = memberLookupService.getOrThrowById(memberId); cookieUtils.clearAuthCookies(res); tokenStorage.removeRefreshToken(member); + + log.info("Auth cookies and refresh token cleared successfully: memberId={}", memberId); } }