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..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 @@ -50,4 +50,20 @@ public void setMatchingResult(String creatorType) { public void deprecated() { this.isDeprecated = true; } + + // 인스타 아이디 업데이트 메서드 + public void updateSns(String snsAccount) { + 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/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..5bf0b63f --- /dev/null +++ b/src/main/java/com/example/RealMatch/user/presentation/dto/request/MyInstagramUpdateRequestDto.java @@ -0,0 +1,15 @@ +package com.example.RealMatch.user.presentation.dto.request; + +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class MyInstagramUpdateRequestDto { + + @Size(max = 30, message = "인스타그램 아이디는 30자를 초과할 수 없습니다") + @Pattern(regexp = "^[a-zA-Z0-9._]*$", message = "인스타그램 아이디는 영문, 숫자, ., _ 만 사용 가능합니다") + 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 ); }