From 5d095f61cb08b6f51fe9fc79e651b34ba51a2f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EA=B2=BD=EC=88=98?= Date: Wed, 11 Feb 2026 00:07:05 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(#361):=20sns=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/application/service/UserService.java | 22 ++++++++++++ .../domain/entity/UserMatchingDetail.java | 11 ++++++ .../controller/UserController.java | 14 ++++++++ .../request/MyInstagramUpdateRequestDto.java | 13 +++++++ .../presentation/swagger/UserSwagger.java | 34 ++++++++++++++++--- 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java diff --git a/src/main/java/com/example/RealMatch/user/application/service/UserService.java b/src/main/java/com/example/RealMatch/user/application/service/UserService.java index b634c32b..f2a91998 100644 --- a/src/main/java/com/example/RealMatch/user/application/service/UserService.java +++ b/src/main/java/com/example/RealMatch/user/application/service/UserService.java @@ -20,6 +20,7 @@ import com.example.RealMatch.user.infrastructure.ScrapMockDataProvider; import com.example.RealMatch.user.presentation.code.UserErrorCode; import com.example.RealMatch.user.presentation.dto.request.MyEditInfoRequestDto; +import com.example.RealMatch.user.presentation.dto.request.MyInstagramUpdateRequestDto; import com.example.RealMatch.user.presentation.dto.request.MyProfileCardUpdateRequestDto; import com.example.RealMatch.user.presentation.dto.response.MyEditInfoResponseDto; import com.example.RealMatch.user.presentation.dto.response.MyLoginResponseDto; @@ -171,6 +172,27 @@ public MyProfileCardResponseDto updateMyProfileImage( return MyProfileCardResponseDto.from(user, detail, categories); } + // 인스타 아이디 수정 + @Transactional + public MyProfileCardResponseDto updateSns( + Long userId, + MyInstagramUpdateRequestDto request + ) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND)); + + UserMatchingDetail detail = userMatchingDetailRepository + .findByUserIdAndIsDeprecatedFalse(userId) + .orElseThrow(() -> new CustomException(UserErrorCode.PROFILE_CARD_NOT_FOUND)); + + detail.updateSns(request.getSnsAccount()); + + List categories = + userContentCategoryRepository.findByUserId(userId); + + return MyProfileCardResponseDto.from(user, detail, categories); + } + @Transactional(readOnly = true) public boolean isNicknameAvailable(String nickname) { return nicknameValidator.isAvailable(nickname); diff --git a/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java b/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java index 3d636158..e08fd1a0 100644 --- a/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java +++ b/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java @@ -50,4 +50,15 @@ public void setMatchingResult(String creatorType) { public void deprecated() { this.isDeprecated = true; } + + // 인스타 아이디 업데이트 메서드 + public void updateSns(String snsAccount) { + if (snsAccount != null && !snsAccount.isBlank()) { + // URL 형식으로 저장 (기존 방식 유지) + String cleanAccount = snsAccount.trim().replace("@", ""); + this.snsUrl = "https://www.instagram.com/" + cleanAccount + "/"; + } else { + this.snsUrl = null; + } + } } diff --git a/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java b/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java index 55affa0f..da4936a4 100644 --- a/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java +++ b/src/main/java/com/example/RealMatch/user/presentation/controller/UserController.java @@ -20,6 +20,7 @@ import com.example.RealMatch.user.application.service.UserService; import com.example.RealMatch.user.application.service.UserWithdrawService; import com.example.RealMatch.user.presentation.dto.request.MyEditInfoRequestDto; +import com.example.RealMatch.user.presentation.dto.request.MyInstagramUpdateRequestDto; import com.example.RealMatch.user.presentation.dto.request.MyProfileCardUpdateRequestDto; import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandListResponseDto; import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignListResponseDto; @@ -167,6 +168,8 @@ public CustomResponse getMyFavoriteCampaigns( ) ); } + + @Override @PatchMapping("/me/profile-image") public CustomResponse updateMyProfileImage( @AuthenticationPrincipal CustomUserDetails userDetails, @@ -176,4 +179,15 @@ public CustomResponse updateMyProfileImage( userService.updateMyProfileImage(userDetails.getUserId(), request) ); } + + @Override + @PatchMapping("/me/instagram") + public CustomResponse updateMySns( + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestBody @Valid MyInstagramUpdateRequestDto request + ) { + return CustomResponse.ok( + userService.updateSns(userDetails.getUserId(), request) + ); + } } diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java b/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java new file mode 100644 index 00000000..394a291e --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java @@ -0,0 +1,13 @@ +package com.example.RealMatch.user.presentation.dto.request; + +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class MyInstagramUpdateRequestDto { + + @Size(max = 100) + private String snsAccount; +} diff --git a/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java b/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java index 413a5fd1..ce052db4 100644 --- a/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java +++ b/src/main/java/com/example/RealMatch/user/presentation/swagger/UserSwagger.java @@ -11,6 +11,7 @@ import com.example.RealMatch.match.domain.entity.enums.CampaignSortType; import com.example.RealMatch.match.presentation.dto.request.MatchRequestDto; import com.example.RealMatch.user.presentation.dto.request.MyEditInfoRequestDto; +import com.example.RealMatch.user.presentation.dto.request.MyInstagramUpdateRequestDto; import com.example.RealMatch.user.presentation.dto.request.MyProfileCardUpdateRequestDto; import com.example.RealMatch.user.presentation.dto.response.FavoriteBrandListResponseDto; import com.example.RealMatch.user.presentation.dto.response.FavoriteCampaignListResponseDto; @@ -209,10 +210,35 @@ CustomResponse getMyFavoriteCampaigns( CampaignSortType sort ); - @Operation(summary = "프로필 이미지 수정 API By 고경수", - description = "Attachment API로 업로드된 이미지 URL을 받아 프로필 이미지를 변경합니다") + @Operation( + summary = "프로필 이미지 수정 API By 고경수", + description = """ + 프로필 카드의 profileImageUrl을 변경합니다. + - 이미 업로드된 이미지 URL을 전달받아 저장합니다. + - 파일 업로드/용량 검증은 업로드 API에서 처리합니다. + """ + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "프로필 이미지 수정 성공"), + @ApiResponse(responseCode = "404", description = "USER_NOT_FOUND / PROFILE_CARD_NOT_FOUND") + }) + @PatchMapping("/me/profile-image") CustomResponse updateMyProfileImage( - @Parameter(hidden = true) CustomUserDetails userDetails, - @RequestBody MyProfileCardUpdateRequestDto request + @Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails, + @Valid @RequestBody MyProfileCardUpdateRequestDto request + ); + + @Operation( + summary = "인스타그램 아이디 수정 API By 고경수", + description = "인스타그램 계정 아이디를 변경합니다. (예: @myaccount -> https://www.instagram.com/myaccount/ 형태로 DB에 저장))" + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "인스타 아이디 수정 성공"), + @ApiResponse(responseCode = "404", description = "USER_NOT_FOUND / PROFILE_CARD_NOT_FOUND") + }) + @PatchMapping("/me/instagram") + CustomResponse updateMySns( + @Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails, + @Valid @RequestBody MyInstagramUpdateRequestDto request ); } From 9fe54a54928b8aad66c9074b9d9a25a408ed539d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EA=B2=BD=EC=88=98?= Date: Wed, 11 Feb 2026 00:19:11 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat(#361):=20=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/domain/entity/UserMatchingDetail.java | 15 ++++++++++----- .../dto/request/MyInstagramUpdateRequestDto.java | 4 +++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java b/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java index e08fd1a0..b0dae68a 100644 --- a/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java +++ b/src/main/java/com/example/RealMatch/user/domain/entity/UserMatchingDetail.java @@ -53,12 +53,17 @@ public void deprecated() { // 인스타 아이디 업데이트 메서드 public void updateSns(String snsAccount) { - if (snsAccount != null && !snsAccount.isBlank()) { - // URL 형식으로 저장 (기존 방식 유지) - String cleanAccount = snsAccount.trim().replace("@", ""); - this.snsUrl = "https://www.instagram.com/" + cleanAccount + "/"; - } else { + if (snsAccount == null || snsAccount.isBlank()) { this.snsUrl = null; + return; } + + String cleanAccount = snsAccount.trim() + .replace("@", "") + .replaceAll("[^a-zA-Z0-9._]", ""); // 허용된 문자만 남김 + + this.snsUrl = cleanAccount.isEmpty() + ? null + : "https://www.instagram.com/" + cleanAccount + "/"; } } diff --git a/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java b/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java index 394a291e..5bf0b63f 100644 --- a/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java +++ b/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java @@ -1,5 +1,6 @@ package com.example.RealMatch.user.presentation.dto.request; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,6 +9,7 @@ @NoArgsConstructor public class MyInstagramUpdateRequestDto { - @Size(max = 100) + @Size(max = 30, message = "인스타그램 아이디는 30자를 초과할 수 없습니다") + @Pattern(regexp = "^[a-zA-Z0-9._]*$", message = "인스타그램 아이디는 영문, 숫자, ., _ 만 사용 가능합니다") private String snsAccount; }