From 703156474a0bcdc1cde343ff96216ce4ae88eca6 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Tue, 8 Apr 2025 15:14:49 +0900 Subject: [PATCH 1/5] [SPOT-259][MODIFY] Update Constructor of StudyPostImage --- .../com/example/spot/domain/mapping/StudyPostImage.java | 9 +++------ .../java/com/example/spot/domain/study/StudyPost.java | 8 ++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/example/spot/domain/mapping/StudyPostImage.java b/src/main/java/com/example/spot/domain/mapping/StudyPostImage.java index afa63038..507251f1 100644 --- a/src/main/java/com/example/spot/domain/mapping/StudyPostImage.java +++ b/src/main/java/com/example/spot/domain/mapping/StudyPostImage.java @@ -16,14 +16,17 @@ @Entity @Getter +@Builder @DynamicUpdate @DynamicInsert +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) public class StudyPostImage extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Setter @Column(nullable = false) private String url; @@ -31,10 +34,4 @@ public class StudyPostImage extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "study_post_id", nullable = false) private StudyPost studyPost; - -/* ----------------------------- 생성자 ------------------------------------- */ - - public StudyPostImage(String url) { - this.url = url; - } } diff --git a/src/main/java/com/example/spot/domain/study/StudyPost.java b/src/main/java/com/example/spot/domain/study/StudyPost.java index 57946a27..83da25b1 100644 --- a/src/main/java/com/example/spot/domain/study/StudyPost.java +++ b/src/main/java/com/example/spot/domain/study/StudyPost.java @@ -107,10 +107,6 @@ public void deleteLikedPost(StudyLikedPost likedPost) { likedPosts.remove(likedPost); } - public void updateImage(StudyPostImage studyPostImage) { - images.set(images.indexOf(studyPostImage), studyPostImage); - } - public void updateComment(StudyPostComment studyPostComment) { comments.set(comments.indexOf(studyPostComment), studyPostComment); } @@ -152,4 +148,8 @@ public void updatePost(StudyPostRequestDTO.PostDTO requestDTO) { member.updateStudyPost(this); study.updateStudyPost(this); } + + public void updateImage(StudyPostImage studyPostImage) { + images.set(images.indexOf(studyPostImage), studyPostImage); + } } From 5c553a6df3d4d8880a42c3ec5506d5823027572b Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Tue, 8 Apr 2025 15:15:31 +0900 Subject: [PATCH 2/5] [SPOT-259][MODIFY] Modify updateStudyPost --- .../StudyPostCommandServiceImpl.java | 58 ++++++++++--------- .../web/controller/StudyPostController.java | 6 -- .../request/StudyPostRequestDTO.java | 9 ++- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java b/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java index eb13ee2b..539d1146 100644 --- a/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java +++ b/src/main/java/com/example/spot/service/studypost/StudyPostCommandServiceImpl.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import java.time.LocalDateTime; import java.util.ArrayList; @@ -96,6 +97,8 @@ public StudyPostResDTO.PostPreviewDTO createPost(Long studyId, StudyPostRequestD .likeNum(0) .hitNum(0) .commentNum(0) + .member(member) + .study(study) .build(); // 공지면 announcedAt 설정 @@ -103,19 +106,19 @@ public StudyPostResDTO.PostPreviewDTO createPost(Long studyId, StudyPostRequestD studyPost.setAnnouncedAt(LocalDateTime.now()); } + studyPost = studyPostRepository.save(studyPost); member.addStudyPost(studyPost); study.addStudyPost(studyPost); - studyPost = studyPostRepository.save(studyPost); - if (postRequestDTO.getImages() != null && !postRequestDTO.getImages().isEmpty()) { - ImageResponse.ImageUploadResponse imageUploadResponse = s3ImageService.uploadImages(postRequestDTO.getImages()); - for (ImageResponse.Images imageDTO : imageUploadResponse.getImageUrls()) { - String imageUrl = imageDTO.getImageUrl(); - StudyPostImage studyPostImage = new StudyPostImage(imageUrl); - studyPost.addImage(studyPostImage); // image id가 저장되지 않음 - studyPostImage = studyPostImageRepository.save(studyPostImage); - studyPost.updateImage(studyPostImage); // image id 저장 - } + // 이미지가 있는 경우 이미지 저장 + if (postRequestDTO.getImage() != null) { + String imageUrl = s3ImageService.upload(postRequestDTO.getImage()); + StudyPostImage studyPostImage = StudyPostImage.builder() + .url(imageUrl) + .studyPost(studyPost) + .build(); + studyPostImage = studyPostImageRepository.save(studyPostImage); + studyPost.addImage(studyPostImage); } if (studyPost.getIsAnnouncement()){ @@ -154,9 +157,9 @@ public StudyPostResDTO.PostPreviewDTO updatePost(Long studyId, Long postId, Stud Long memberId = SecurityUtils.getCurrentUserId(); SecurityUtils.verifyUserId(memberId); - Member member = memberRepository.findById(memberId) + memberRepository.findById(memberId) .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) + studyRepository.findById(studyId) .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); StudyPost studyPost = studyPostRepository.findById(postId) .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_POST_NOT_FOUND)); @@ -174,25 +177,28 @@ public StudyPostResDTO.PostPreviewDTO updatePost(Long studyId, Long postId, Stud throw new StudyHandler(ErrorStatus._STUDY_POST_ANNOUNCEMENT_INVALID); } + // 스터디 게시글 이미지 업데이트 + updateStudyPostImage(postDTO, studyPost); + // 스터디 게시글 업데이트 studyPost.updatePost(postDTO); - studyPost = studyPostRepository.save(studyPost); - // 기존 게시글 이미지 삭제 - studyPostImageRepository.deleteAllByStudyPostId(postId); - - if (postDTO.getImages() != null && !postDTO.getImages().isEmpty()) { - ImageResponse.ImageUploadResponse imageUploadResponse = s3ImageService.uploadImages(postDTO.getImages()); - for (ImageResponse.Images imageDTO : imageUploadResponse.getImageUrls()) { - String imageUrl = imageDTO.getImageUrl(); - StudyPostImage studyPostImage = new StudyPostImage(imageUrl); - studyPost.addImage(studyPostImage); // image id가 저장되지 않음 - studyPostImage = studyPostImageRepository.save(studyPostImage); - studyPost.updateImage(studyPostImage); // image id 저장 + return StudyPostResDTO.PostPreviewDTO.toDTO(studyPost); + } + + private void updateStudyPostImage(StudyPostRequestDTO.PostDTO postDTO, StudyPost studyPost) { + List studyPostImages = studyPost.getImages(); + // 기존 이미지가 존재하는 경우 이미지 유지 + if (!StringUtils.hasText(postDTO.getExistingImage())) { + // 기존 이미지가 없고 새로운 이미지를 등록한 경우 이미지 url 변경 + if (postDTO.getImage() != null) { + String imageUrl = s3ImageService.upload(postDTO.getImage()); + studyPostImages.forEach(studyPostImage -> { + studyPostImage.setUrl(imageUrl); + studyPost.updateImage(studyPostImage); + }); } } - - return StudyPostResDTO.PostPreviewDTO.toDTO(studyPost); } /** diff --git a/src/main/java/com/example/spot/web/controller/StudyPostController.java b/src/main/java/com/example/spot/web/controller/StudyPostController.java index 4ed48aff..5e5ce8c6 100644 --- a/src/main/java/com/example/spot/web/controller/StudyPostController.java +++ b/src/main/java/com/example/spot/web/controller/StudyPostController.java @@ -54,9 +54,6 @@ public class StudyPostController { public ApiResponse createPost( @PathVariable @ExistStudy Long studyId, @ModelAttribute(name = "post") @Valid StudyPostRequestDTO.PostDTO postRequestDTO) { - if (postRequestDTO.getImages() == null) { - postRequestDTO.initImages(); - } StudyPostResDTO.PostPreviewDTO postPreviewDTO = studyPostCommandService.createPost(studyId, postRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_CREATED, postPreviewDTO); } @@ -74,9 +71,6 @@ public ApiResponse updatePost( @PathVariable @ExistStudyPost Long postId, @ModelAttribute(name= "post") @Valid StudyPostRequestDTO.PostDTO postDTO ) { - if (postDTO.getImages() == null) { - postDTO.initImages(); - } StudyPostResDTO.PostPreviewDTO postPreviewDTO = studyPostCommandService.updatePost(studyId, postId, postDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_UPDATED, postPreviewDTO); } diff --git a/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java b/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java index a3716640..c4837f2a 100644 --- a/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java +++ b/src/main/java/com/example/spot/web/dto/memberstudy/request/StudyPostRequestDTO.java @@ -38,12 +38,11 @@ public static class PostDTO { @Schema(description = "내용", example = "content") private String content; - @Schema(description = "이미지") - private List images; + @Schema(description = "신규 이미지") + private MultipartFile image; - public void initImages() { - this.images = new ArrayList<>(); - } + @Schema(description = "기존 이미지") + private String existingImage; } } From 9ceb6af22124f690601d1fd1559ae5399b05dd75 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Tue, 8 Apr 2025 15:16:34 +0900 Subject: [PATCH 3/5] [SPOT-259][TEST] Update StudyPostCommandServiceTest --- .../service/studypost/StudyPostCommandServiceTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/example/spot/service/studypost/StudyPostCommandServiceTest.java b/src/test/java/com/example/spot/service/studypost/StudyPostCommandServiceTest.java index 5577640c..3946e925 100644 --- a/src/test/java/com/example/spot/service/studypost/StudyPostCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/studypost/StudyPostCommandServiceTest.java @@ -13,6 +13,7 @@ import com.example.spot.domain.study.StudyPost; import com.example.spot.domain.study.StudyPostComment; import com.example.spot.repository.*; +import com.example.spot.service.s3.S3ImageService; import com.example.spot.web.dto.memberstudy.request.StudyPostCommentRequestDTO; import com.example.spot.web.dto.memberstudy.request.StudyPostRequestDTO; import com.example.spot.web.dto.memberstudy.response.StudyPostCommentResponseDTO; @@ -30,6 +31,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.multipart.MultipartFile; import java.util.ArrayList; import java.util.Collections; @@ -70,6 +72,9 @@ class StudyPostCommandServiceTest { @Mock private NotificationRepository notificationRepository; + @Mock + private S3ImageService s3ImageService; + @InjectMocks private StudyPostCommandServiceImpl studyPostCommandService; @@ -125,12 +130,15 @@ void setUp() { when(studyLikedPostRepository.existsByMemberIdAndStudyPostId(3L, 1L)) .thenReturn(true); - // Comment + // Comment when(studyPostCommentRepository.findAllByStudyPostId(1L)) .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); when(studyPostCommentRepository.findById(1L)).thenReturn(Optional.of(studyPost1Comment1)); when(studyPostCommentRepository.findById(2L)).thenReturn(Optional.of(studyPost1Comment2)); + // S3 + when(s3ImageService.upload(any(MultipartFile.class))).thenReturn("url"); + } /*-------------------------------------------------------- 게시글 작성 ------------------------------------------------------------------------*/ From 2630bfdcdce277408a9354772e158af9e7bd0db1 Mon Sep 17 00:00:00 2001 From: msk226 Date: Tue, 8 Apr 2025 22:40:18 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[SPOT-260][FEAT]=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=9C=A0=EB=AC=B4=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=97=94=EB=93=9C=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spot/web/controller/AuthController.java | 14 ++++++++++++++ .../spot/web/dto/member/MemberResponseDTO.java | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/main/java/com/example/spot/web/controller/AuthController.java b/src/main/java/com/example/spot/web/controller/AuthController.java index 45ea453c..a1677450 100644 --- a/src/main/java/com/example/spot/web/controller/AuthController.java +++ b/src/main/java/com/example/spot/web/controller/AuthController.java @@ -2,6 +2,7 @@ import com.example.spot.api.ApiResponse; import com.example.spot.api.code.status.SuccessStatus; +import com.example.spot.security.utils.SecurityUtils; import com.example.spot.web.dto.rsa.Rsa; import com.example.spot.service.auth.AuthService; import com.example.spot.validation.annotation.TextLength; @@ -268,6 +269,19 @@ public ApiResponse login( return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNED_IN, memberSignInDTO); } + @Tag(name = "회원 관리 API - 개발 완료", description = "회원 관리 API") + @Operation(summary = "[가입 유무 확인] 서비스 가입 유무 확인 API", + description = """ + ## [가입 유무 확인] 서비스 가입 유무 확인 API입니다. + 해당 유저가 가입 후 체크리스트를 작성 했는지 확인합니다. + * 가입 후 체크리스트를 작성한 경우 true, 작성하지 않은 경우 false를 반환합니다. + """) + @GetMapping("/check") + public ApiResponse checkIsSpotMember() { + MemberResponseDTO.CheckMemberDTO checkMemberDTO = authService.checkIsSpotMember(SecurityUtils.getCurrentUserId()); + return ApiResponse.onSuccess(SuccessStatus._MEMBER_SIGNUP_CHECK_COMPLETED, checkMemberDTO); + } + /* ----------------------------- 로그아웃 API ------------------------------------- */ @Tag(name = "회원 관리 API - 개발 중", description = "회원 관리 API") diff --git a/src/main/java/com/example/spot/web/dto/member/MemberResponseDTO.java b/src/main/java/com/example/spot/web/dto/member/MemberResponseDTO.java index 0aa7bc02..af481101 100644 --- a/src/main/java/com/example/spot/web/dto/member/MemberResponseDTO.java +++ b/src/main/java/com/example/spot/web/dto/member/MemberResponseDTO.java @@ -47,6 +47,18 @@ public static SocialLoginSignInDTO toDTO(Boolean isSpotMember, MemberSignInDTO s .build(); } } + @Getter + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + @Builder(access = AccessLevel.PRIVATE) + public static class CheckMemberDTO { + private final Boolean isSpotMember; + + public static CheckMemberDTO toDTO(Boolean isSpotMember) { + return CheckMemberDTO.builder() + .isSpotMember(isSpotMember) + .build(); + } + } @Getter @RequiredArgsConstructor From f1b628a5fc6928a2ccc8aab8f7f642ea2e18bd2e Mon Sep 17 00:00:00 2001 From: msk226 Date: Tue, 8 Apr 2025 22:40:25 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[SPOT-260][FEAT]=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=9C=A0=EB=AC=B4=20=EC=A1=B0=ED=9A=8C=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/spot/api/code/status/SuccessStatus.java | 1 + .../com/example/spot/service/auth/AuthService.java | 2 ++ .../example/spot/service/auth/AuthServiceImpl.java | 11 +++++++++++ 3 files changed, 14 insertions(+) diff --git a/src/main/java/com/example/spot/api/code/status/SuccessStatus.java b/src/main/java/com/example/spot/api/code/status/SuccessStatus.java index d294cea6..b68a066f 100644 --- a/src/main/java/com/example/spot/api/code/status/SuccessStatus.java +++ b/src/main/java/com/example/spot/api/code/status/SuccessStatus.java @@ -31,6 +31,7 @@ public enum SuccessStatus implements BaseCode { _MEMBER_LOGIN_ID_CHECK_COMPLETED(HttpStatus.OK, "MEMBER2012", "회원 아이디 사용 가능 여부 조회 완료"), _MEMBER_EMAIL_CHECK_COMPLETED(HttpStatus.OK, "MEMBER2013", "회원 이메일 사용 가능 여부 조회 완료"), _RSA_PUBLIC_KEY_FOUND(HttpStatus.OK, "MEMBER2014", "RSA Public Key 조회 완료"), + _MEMBER_SIGNUP_CHECK_COMPLETED(HttpStatus.OK, "MEMBER2015", "회원 가입 유무 조회 완료"), //스터디 게시글 관련 응답 _STUDY_POST_CREATED(HttpStatus.CREATED, "STUDYPOST3001", "스터디 게시글 작성 완료"), diff --git a/src/main/java/com/example/spot/service/auth/AuthService.java b/src/main/java/com/example/spot/service/auth/AuthService.java index 6182153b..24574d02 100644 --- a/src/main/java/com/example/spot/service/auth/AuthService.java +++ b/src/main/java/com/example/spot/service/auth/AuthService.java @@ -43,4 +43,6 @@ public interface AuthService { MemberResponseDTO.AvailabilityDTO checkLoginIdAvailability(String loginId); MemberResponseDTO.AvailabilityDTO checkEmailAvailability(String email); + + MemberResponseDTO.CheckMemberDTO checkIsSpotMember(Long loginId); } diff --git a/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java b/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java index aea01c17..bc21b495 100644 --- a/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java +++ b/src/main/java/com/example/spot/service/auth/AuthServiceImpl.java @@ -10,6 +10,7 @@ import com.example.spot.repository.MemberThemeRepository; import com.example.spot.repository.PreferredRegionRepository; import com.example.spot.repository.StudyReasonRepository; +import com.example.spot.web.dto.member.MemberResponseDTO.CheckMemberDTO; import com.example.spot.web.dto.rsa.Rsa; import com.example.spot.domain.auth.RefreshToken; import com.example.spot.domain.auth.VerificationCode; @@ -670,6 +671,16 @@ public MemberResponseDTO.AvailabilityDTO checkEmailAvailability(String email) { return MemberResponseDTO.AvailabilityDTO.toDTO(Boolean.TRUE, null); } + @Override + public CheckMemberDTO checkIsSpotMember(Long loginId) { + Member member = memberRepository.findById(loginId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + + boolean memberExistsByCheckList = isMemberExistsByCheckList(member); + + return CheckMemberDTO.toDTO(memberExistsByCheckList); + } + /** * 임시 비밀번호를 발급하는 메서드입니다. * 알파벳 대소문자, 숫자, 특수기호를 혼합하여 13자리 비밀번호를 생성합니다.