From d80687324a667c600a30ae2d8c1e8842d53a5599 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 16:11:26 +0900 Subject: [PATCH 1/8] [SPOT-286][REFACTOR] Rename aggregate to association --- .../spot/common/config/RegionDataLoader.java | 4 +- .../application/legacy/MemberServiceImpl.java | 46 +--------- .../impl/MemberPreferenceServiceImpl.java | 4 +- .../example/spot/member/domain/Member.java | 14 +-- .../domain/association/MemberTheme.java | 2 +- .../domain/association/PreferredRegion.java | 2 +- .../NotificationCommandServiceImpl.java | 2 +- .../spot/schedule/domain/Schedule.java | 2 +- .../{aggregate => association}/Quiz.java | 2 +- .../QuizSubmission.java | 2 +- .../domain/repository/QuizRepository.java | 2 +- .../repository/QuizSubmissionRepository.java | 2 +- .../com/example/spot/story/domain/Story.java | 8 +- .../application/StoryCommandService.java} | 4 +- .../application/StoryCommandServiceImpl.java} | 14 +-- .../application/StoryQueryService.java} | 4 +- .../application/StoryQueryServiceImpl.java} | 6 +- .../LikedStory.java | 2 +- .../LikedStoryComment.java | 2 +- .../StoryComment.java | 2 +- .../StoryImage.java | 2 +- .../StoryReport.java | 2 +- .../LikedStoryCommentRepository.java | 2 +- .../repository/LikedStoryRepository.java | 2 +- .../repository/StoryCommentRepository.java | 2 +- .../repository/StoryImageRepository.java | 2 +- .../repository/StoryReportRepository.java | 2 +- .../application/StudyCommandServiceImpl.java | 10 +-- ...ce.java => StudyMemberCommandService.java} | 2 +- ...ava => StudyMemberCommandServiceImpl.java} | 14 +-- ...vice.java => StudyMemberQueryService.java} | 2 +- ....java => StudyMemberQueryServiceImpl.java} | 10 +-- .../application/StudyQueryServiceImpl.java | 10 +-- .../com/example/spot/study/domain/Study.java | 6 +- .../study/domain/StudyRepositoryCustom.java | 6 +- .../domain/StudyRepositoryCustomImpl.java | 8 +- .../{aggregate => association}/Region.java | 2 +- .../StudyMember.java | 2 +- .../StudyRegion.java | 2 +- .../StudyTheme.java | 2 +- .../{aggregate => association}/Theme.java | 2 +- .../domain/repository/RegionRepository.java | 2 +- .../repository/StudyMemberRepository.java | 2 +- .../repository/StudyRegionRepository.java | 4 +- .../repository/StudyThemeRepository.java | 4 +- .../domain/repository/ThemeRepository.java | 2 +- .../controller/StudyMemberController.java | 86 +++++++++---------- .../controller/StudyPostController.java | 38 ++++---- .../dto/response/SearchResponseDTO.java | 8 +- .../dto/response/StudyImageResponseDTO.java | 2 +- .../response/StudyPostCommentResponseDTO.java | 4 +- .../dto/response/StudyPostResDTO.java | 2 +- .../dto/response/StudyQuizResponseDTO.java | 6 +- .../dto/response/StudyVoteResponseDTO.java | 4 +- .../com/example/spot/vote/domain/Vote.java | 2 +- .../VoteOption.java | 2 +- .../VoteParticipant.java | 2 +- .../repository/VoteOptionRepository.java | 2 +- .../repository/VoteParticipantRepository.java | 2 +- .../NotificationCommandServiceTest.java | 2 +- .../study/StudyCommandServiceTest.java | 10 +-- .../service/study/StudyQueryServiceTest.java | 10 +-- .../StudyMemberCommandServiceTest.java | 6 +- .../StudyMemberQueryServiceTest.java | 6 +- .../studypost/StoryCommandServiceTest.java | 12 +-- .../studypost/StoryQueryServiceTest.java | 12 +-- .../StudyAttendanceCommandServiceTest.java | 10 +-- .../StudyAttendanceQueryServiceTest.java | 10 +-- .../StudyScheduleCommandServiceTest.java | 6 +- .../StudyScheduleQueryServiceTest.java | 6 +- 70 files changed, 219 insertions(+), 263 deletions(-) rename src/main/java/com/example/spot/schedule/domain/{aggregate => association}/Quiz.java (97%) rename src/main/java/com/example/spot/schedule/domain/{aggregate => association}/QuizSubmission.java (94%) rename src/main/java/com/example/spot/{study/application/StudyPostCommandService.java => story/domain/application/StoryCommandService.java} (95%) rename src/main/java/com/example/spot/{study/application/StudyPostCommandServiceImpl.java => story/domain/application/StoryCommandServiceImpl.java} (98%) rename src/main/java/com/example/spot/{study/application/StudyPostQueryService.java => story/domain/application/StoryQueryService.java} (89%) rename src/main/java/com/example/spot/{study/application/StudyPostQueryServiceImpl.java => story/domain/application/StoryQueryServiceImpl.java} (98%) rename src/main/java/com/example/spot/story/domain/{aggregate => association}/LikedStory.java (95%) rename src/main/java/com/example/spot/story/domain/{aggregate => association}/LikedStoryComment.java (94%) rename src/main/java/com/example/spot/story/domain/{aggregate => association}/StoryComment.java (98%) rename src/main/java/com/example/spot/story/domain/{aggregate => association}/StoryImage.java (94%) rename src/main/java/com/example/spot/story/domain/{aggregate => association}/StoryReport.java (94%) rename src/main/java/com/example/spot/study/application/{MemberStudyCommandService.java => StudyMemberCommandService.java} (98%) rename src/main/java/com/example/spot/study/application/{MemberStudyCommandServiceImpl.java => StudyMemberCommandServiceImpl.java} (99%) rename src/main/java/com/example/spot/study/application/{MemberStudyQueryService.java => StudyMemberQueryService.java} (98%) rename src/main/java/com/example/spot/study/application/{MemberStudyQueryServiceImpl.java => StudyMemberQueryServiceImpl.java} (99%) rename src/main/java/com/example/spot/study/domain/{aggregate => association}/Region.java (96%) rename src/main/java/com/example/spot/study/domain/{aggregate => association}/StudyMember.java (97%) rename src/main/java/com/example/spot/study/domain/{aggregate => association}/StudyRegion.java (95%) rename src/main/java/com/example/spot/study/domain/{aggregate => association}/StudyTheme.java (95%) rename src/main/java/com/example/spot/study/domain/{aggregate => association}/Theme.java (97%) rename src/main/java/com/example/spot/vote/domain/{aggregate => association}/VoteOption.java (97%) rename src/main/java/com/example/spot/vote/domain/{aggregate => association}/VoteParticipant.java (95%) diff --git a/src/main/java/com/example/spot/common/config/RegionDataLoader.java b/src/main/java/com/example/spot/common/config/RegionDataLoader.java index 16abf971..6f7ca747 100644 --- a/src/main/java/com/example/spot/common/config/RegionDataLoader.java +++ b/src/main/java/com/example/spot/common/config/RegionDataLoader.java @@ -1,9 +1,9 @@ package com.example.spot.common.config; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Theme; import com.example.spot.study.domain.enums.ThemeType; import com.example.spot.study.domain.repository.RegionRepository; -import com.example.spot.study.domain.aggregate.Region; +import com.example.spot.study.domain.association.Region; import com.example.spot.study.domain.repository.ThemeRepository; import java.io.IOException; import java.io.InputStreamReader; diff --git a/src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java b/src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java index 62db6150..6e630924 100644 --- a/src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java +++ b/src/main/java/com/example/spot/member/application/legacy/MemberServiceImpl.java @@ -1,64 +1,20 @@ package com.example.spot.member.application.legacy; -import com.example.spot.common.api.code.status.ErrorStatus; -import com.example.spot.common.api.exception.GeneralException; -import com.example.spot.common.api.exception.handler.MemberHandler; -import com.example.spot.member.domain.association.StudyJoinReason; -import com.example.spot.member.domain.enums.Gender; -import com.example.spot.member.domain.enums.LoginType; -import com.example.spot.member.domain.enums.Reason; -import com.example.spot.member.domain.enums.Status; -import com.example.spot.study.domain.enums.ThemeType; -import com.example.spot.member.domain.association.StudyJoinReasonRepository; -import com.example.spot.common.security.utils.JwtTokenProvider; import com.example.spot.member.domain.Member; import com.example.spot.member.domain.MemberRepository; -import com.example.spot.member.presentation.dto.MemberRequestDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberReasonDTO; import com.example.spot.auth.domain.CustomUserDetails; -import com.example.spot.auth.domain.RefreshToken; -import com.example.spot.auth.domain.RefreshTokenRepository; -import com.example.spot.auth.infrastructure.kakao.KakaoOAuthClient; -import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberRegionDTO.RegionDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberSignInDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberStudyReasonDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberTestDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.SocialLoginSignInDTO; -import com.example.spot.auth.presentation.dto.kakao.KaKaoOAuthToken.KaKaoOAuthTokenDTO; -import com.example.spot.auth.presentation.dto.kakao.KaKaoUser; -import com.example.spot.auth.presentation.dto.token.TokenResponseDTO.TokenDTO; -import com.fasterxml.jackson.core.JsonProcessingException; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; + import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.Theme; -import com.example.spot.member.domain.association.MemberTheme; -import com.example.spot.member.domain.association.PreferredRegion; -import com.example.spot.member.domain.association.MemberThemeRepository; -import com.example.spot.member.domain.association.PreferredRegionRepository; -import com.example.spot.study.domain.repository.RegionRepository; -import com.example.spot.study.domain.repository.ThemeRepository; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberInfoListDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberRegionDTO; -import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberThemeDTO; -import com.example.spot.member.presentation.dto.MemberResponseDTO.MemberUpdateDTO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.UUID; - // TODO 추후 삭제 예정 -> 구글 로그인 관련 로직이 남아있음 diff --git a/src/main/java/com/example/spot/member/application/refactor/impl/MemberPreferenceServiceImpl.java b/src/main/java/com/example/spot/member/application/refactor/impl/MemberPreferenceServiceImpl.java index a762a188..e3e84aaa 100644 --- a/src/main/java/com/example/spot/member/application/refactor/impl/MemberPreferenceServiceImpl.java +++ b/src/main/java/com/example/spot/member/application/refactor/impl/MemberPreferenceServiceImpl.java @@ -20,8 +20,8 @@ import com.example.spot.member.domain.enums.Reason; import com.example.spot.member.presentation.dto.MemberRequestDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.Theme; import com.example.spot.study.domain.enums.ThemeType; import com.example.spot.study.domain.repository.RegionRepository; import com.example.spot.study.domain.repository.ThemeRepository; diff --git a/src/main/java/com/example/spot/member/domain/Member.java b/src/main/java/com/example/spot/member/domain/Member.java index 60896dc4..da9c3cba 100644 --- a/src/main/java/com/example/spot/member/domain/Member.java +++ b/src/main/java/com/example/spot/member/domain/Member.java @@ -8,26 +8,26 @@ import com.example.spot.report.domain.MemberReport; import com.example.spot.report.domain.PostReport; import com.example.spot.schedule.domain.Schedule; -import com.example.spot.schedule.domain.aggregate.Quiz; +import com.example.spot.schedule.domain.association.Quiz; import com.example.spot.story.domain.Story; -import com.example.spot.story.domain.aggregate.LikedStoryComment; -import com.example.spot.story.domain.aggregate.LikedStory; +import com.example.spot.story.domain.association.LikedStoryComment; +import com.example.spot.story.domain.association.LikedStory; import com.example.spot.member.domain.association.StudyJoinReason; import com.example.spot.common.entity.BaseEntity; import com.example.spot.member.domain.enums.Carrier; import com.example.spot.member.domain.enums.Gender; import com.example.spot.member.domain.enums.LoginType; import com.example.spot.member.domain.enums.Status; -import com.example.spot.schedule.domain.aggregate.QuizSubmission; +import com.example.spot.schedule.domain.association.QuizSubmission; import com.example.spot.post.domain.association.MemberScrap; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.member.domain.association.MemberTheme; import com.example.spot.todo.domain.ToDo; import com.example.spot.vote.domain.Vote; -import com.example.spot.vote.domain.aggregate.VoteParticipant; +import com.example.spot.vote.domain.association.VoteParticipant; import com.example.spot.member.domain.association.PreferredRegion; import com.example.spot.member.domain.association.PreferredStudy; -import com.example.spot.story.domain.aggregate.StoryComment; +import com.example.spot.story.domain.association.StoryComment; import com.example.spot.member.presentation.dto.MemberRequestDTO.MemberUpdateDTO; import jakarta.persistence.*; import java.util.ArrayList; diff --git a/src/main/java/com/example/spot/member/domain/association/MemberTheme.java b/src/main/java/com/example/spot/member/domain/association/MemberTheme.java index 20e439b6..b8696024 100644 --- a/src/main/java/com/example/spot/member/domain/association/MemberTheme.java +++ b/src/main/java/com/example/spot/member/domain/association/MemberTheme.java @@ -1,7 +1,7 @@ package com.example.spot.member.domain.association; import com.example.spot.member.domain.Member; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Theme; import com.example.spot.common.entity.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; diff --git a/src/main/java/com/example/spot/member/domain/association/PreferredRegion.java b/src/main/java/com/example/spot/member/domain/association/PreferredRegion.java index ccc8eee5..625a0553 100644 --- a/src/main/java/com/example/spot/member/domain/association/PreferredRegion.java +++ b/src/main/java/com/example/spot/member/domain/association/PreferredRegion.java @@ -1,6 +1,6 @@ package com.example.spot.member.domain.association; -import com.example.spot.study.domain.aggregate.Region; +import com.example.spot.study.domain.association.Region; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; import jakarta.persistence.*; diff --git a/src/main/java/com/example/spot/notification/application/notification/NotificationCommandServiceImpl.java b/src/main/java/com/example/spot/notification/application/notification/NotificationCommandServiceImpl.java index 3fa26680..b87c4c5e 100644 --- a/src/main/java/com/example/spot/notification/application/notification/NotificationCommandServiceImpl.java +++ b/src/main/java/com/example/spot/notification/application/notification/NotificationCommandServiceImpl.java @@ -3,7 +3,7 @@ import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.GeneralException; import com.example.spot.notification.domain.Notification; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.notification.domain.enums.NotifyType; import com.example.spot.study.domain.repository.StudyMemberRepository; diff --git a/src/main/java/com/example/spot/schedule/domain/Schedule.java b/src/main/java/com/example/spot/schedule/domain/Schedule.java index cb2fc039..f423422a 100644 --- a/src/main/java/com/example/spot/schedule/domain/Schedule.java +++ b/src/main/java/com/example/spot/schedule/domain/Schedule.java @@ -1,7 +1,7 @@ package com.example.spot.schedule.domain; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; -import com.example.spot.schedule.domain.aggregate.Quiz; +import com.example.spot.schedule.domain.association.Quiz; import com.example.spot.study.domain.Study; import com.example.spot.schedule.domain.enums.SchedulePeriod; import com.example.spot.study.presentation.dto.request.ScheduleRequestDTO; diff --git a/src/main/java/com/example/spot/schedule/domain/aggregate/Quiz.java b/src/main/java/com/example/spot/schedule/domain/association/Quiz.java similarity index 97% rename from src/main/java/com/example/spot/schedule/domain/aggregate/Quiz.java rename to src/main/java/com/example/spot/schedule/domain/association/Quiz.java index 11906e88..d909089d 100644 --- a/src/main/java/com/example/spot/schedule/domain/aggregate/Quiz.java +++ b/src/main/java/com/example/spot/schedule/domain/association/Quiz.java @@ -1,4 +1,4 @@ -package com.example.spot.schedule.domain.aggregate; +package com.example.spot.schedule.domain.association; import com.example.spot.member.domain.Member; diff --git a/src/main/java/com/example/spot/schedule/domain/aggregate/QuizSubmission.java b/src/main/java/com/example/spot/schedule/domain/association/QuizSubmission.java similarity index 94% rename from src/main/java/com/example/spot/schedule/domain/aggregate/QuizSubmission.java rename to src/main/java/com/example/spot/schedule/domain/association/QuizSubmission.java index 86c3b5ec..d495c014 100644 --- a/src/main/java/com/example/spot/schedule/domain/aggregate/QuizSubmission.java +++ b/src/main/java/com/example/spot/schedule/domain/association/QuizSubmission.java @@ -1,4 +1,4 @@ -package com.example.spot.schedule.domain.aggregate; +package com.example.spot.schedule.domain.association; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; diff --git a/src/main/java/com/example/spot/schedule/domain/repository/QuizRepository.java b/src/main/java/com/example/spot/schedule/domain/repository/QuizRepository.java index d1af47de..fcb3070a 100644 --- a/src/main/java/com/example/spot/schedule/domain/repository/QuizRepository.java +++ b/src/main/java/com/example/spot/schedule/domain/repository/QuizRepository.java @@ -1,6 +1,6 @@ package com.example.spot.schedule.domain.repository; -import com.example.spot.schedule.domain.aggregate.Quiz; +import com.example.spot.schedule.domain.association.Quiz; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/schedule/domain/repository/QuizSubmissionRepository.java b/src/main/java/com/example/spot/schedule/domain/repository/QuizSubmissionRepository.java index 775e8cad..a5860e2b 100644 --- a/src/main/java/com/example/spot/schedule/domain/repository/QuizSubmissionRepository.java +++ b/src/main/java/com/example/spot/schedule/domain/repository/QuizSubmissionRepository.java @@ -1,6 +1,6 @@ package com.example.spot.schedule.domain.repository; -import com.example.spot.schedule.domain.aggregate.QuizSubmission; +import com.example.spot.schedule.domain.association.QuizSubmission; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/story/domain/Story.java b/src/main/java/com/example/spot/story/domain/Story.java index 7301946a..2749538e 100644 --- a/src/main/java/com/example/spot/story/domain/Story.java +++ b/src/main/java/com/example/spot/story/domain/Story.java @@ -2,10 +2,10 @@ import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; -import com.example.spot.story.domain.aggregate.LikedStory; -import com.example.spot.story.domain.aggregate.StoryComment; -import com.example.spot.story.domain.aggregate.StoryImage; -import com.example.spot.story.domain.aggregate.StoryReport; +import com.example.spot.story.domain.association.LikedStory; +import com.example.spot.story.domain.association.StoryComment; +import com.example.spot.story.domain.association.StoryImage; +import com.example.spot.story.domain.association.StoryReport; import com.example.spot.story.domain.enums.StoryCategory; import com.example.spot.study.domain.Study; import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; diff --git a/src/main/java/com/example/spot/study/application/StudyPostCommandService.java b/src/main/java/com/example/spot/story/domain/application/StoryCommandService.java similarity index 95% rename from src/main/java/com/example/spot/study/application/StudyPostCommandService.java rename to src/main/java/com/example/spot/story/domain/application/StoryCommandService.java index ce696120..fba122c8 100644 --- a/src/main/java/com/example/spot/study/application/StudyPostCommandService.java +++ b/src/main/java/com/example/spot/story/domain/application/StoryCommandService.java @@ -1,11 +1,11 @@ -package com.example.spot.study.application; +package com.example.spot.story.domain.application; import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -public interface StudyPostCommandService { +public interface StoryCommandService { // 스터디 게시글 생성 StudyPostResDTO.PostPreviewDTO createPost(Long studyId, StudyPostRequestDTO.PostDTO postRequestDTO); diff --git a/src/main/java/com/example/spot/study/application/StudyPostCommandServiceImpl.java b/src/main/java/com/example/spot/story/domain/application/StoryCommandServiceImpl.java similarity index 98% rename from src/main/java/com/example/spot/study/application/StudyPostCommandServiceImpl.java rename to src/main/java/com/example/spot/story/domain/application/StoryCommandServiceImpl.java index a78184cb..9da95d7a 100644 --- a/src/main/java/com/example/spot/study/application/StudyPostCommandServiceImpl.java +++ b/src/main/java/com/example/spot/story/domain/application/StoryCommandServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.spot.study.application; +package com.example.spot.story.domain.application; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; @@ -7,16 +7,16 @@ import com.example.spot.notification.domain.Notification; import com.example.spot.story.domain.Story; import com.example.spot.story.domain.StoryRepository; -import com.example.spot.story.domain.aggregate.LikedStory; -import com.example.spot.story.domain.aggregate.LikedStoryComment; -import com.example.spot.story.domain.aggregate.StoryComment; -import com.example.spot.story.domain.aggregate.StoryImage; +import com.example.spot.story.domain.association.LikedStory; +import com.example.spot.story.domain.association.LikedStoryComment; +import com.example.spot.story.domain.association.StoryComment; +import com.example.spot.story.domain.association.StoryImage; import com.example.spot.story.domain.repository.LikedStoryCommentRepository; import com.example.spot.story.domain.repository.LikedStoryRepository; import com.example.spot.story.domain.repository.StoryCommentRepository; import com.example.spot.story.domain.repository.StoryImageRepository; import com.example.spot.story.domain.repository.StoryReportRepository; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.notification.domain.enums.NotifyType; import com.example.spot.study.domain.Study; @@ -43,7 +43,7 @@ @Service @Transactional @RequiredArgsConstructor -public class StudyPostCommandServiceImpl implements StudyPostCommandService { +public class StoryCommandServiceImpl implements StoryCommandService { @Value("${image.post.anonymous.profile}") private String defaultImage; diff --git a/src/main/java/com/example/spot/study/application/StudyPostQueryService.java b/src/main/java/com/example/spot/story/domain/application/StoryQueryService.java similarity index 89% rename from src/main/java/com/example/spot/study/application/StudyPostQueryService.java rename to src/main/java/com/example/spot/story/domain/application/StoryQueryService.java index 094cdd4c..82250aa3 100644 --- a/src/main/java/com/example/spot/study/application/StudyPostQueryService.java +++ b/src/main/java/com/example/spot/story/domain/application/StoryQueryService.java @@ -1,11 +1,11 @@ -package com.example.spot.study.application; +package com.example.spot.story.domain.application; import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResDTO; import org.springframework.data.domain.PageRequest; -public interface StudyPostQueryService { +public interface StoryQueryService { // 스터디 게시글 목록 불러오기 StudyPostResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long studyId, StoryCategoryQuery storyCategoryQuery); diff --git a/src/main/java/com/example/spot/study/application/StudyPostQueryServiceImpl.java b/src/main/java/com/example/spot/story/domain/application/StoryQueryServiceImpl.java similarity index 98% rename from src/main/java/com/example/spot/study/application/StudyPostQueryServiceImpl.java rename to src/main/java/com/example/spot/story/domain/application/StoryQueryServiceImpl.java index 9974b350..58781213 100644 --- a/src/main/java/com/example/spot/study/application/StudyPostQueryServiceImpl.java +++ b/src/main/java/com/example/spot/story/domain/application/StoryQueryServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.spot.study.application; +package com.example.spot.story.domain.application; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; @@ -9,7 +9,7 @@ import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.study.domain.Study; -import com.example.spot.story.domain.aggregate.StoryComment; +import com.example.spot.story.domain.association.StoryComment; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.story.domain.repository.LikedStoryRepository; @@ -31,7 +31,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class StudyPostQueryServiceImpl implements StudyPostQueryService { +public class StoryQueryServiceImpl implements StoryQueryService { private final StoryCommentRepository storyCommentRepository; private final LikedStoryRepository likedStoryRepository; diff --git a/src/main/java/com/example/spot/story/domain/aggregate/LikedStory.java b/src/main/java/com/example/spot/story/domain/association/LikedStory.java similarity index 95% rename from src/main/java/com/example/spot/story/domain/aggregate/LikedStory.java rename to src/main/java/com/example/spot/story/domain/association/LikedStory.java index 0737fdf9..9a93bdd4 100644 --- a/src/main/java/com/example/spot/story/domain/aggregate/LikedStory.java +++ b/src/main/java/com/example/spot/story/domain/association/LikedStory.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.aggregate; +package com.example.spot.story.domain.association; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; diff --git a/src/main/java/com/example/spot/story/domain/aggregate/LikedStoryComment.java b/src/main/java/com/example/spot/story/domain/association/LikedStoryComment.java similarity index 94% rename from src/main/java/com/example/spot/story/domain/aggregate/LikedStoryComment.java rename to src/main/java/com/example/spot/story/domain/association/LikedStoryComment.java index da2ebcd3..7bb475eb 100644 --- a/src/main/java/com/example/spot/story/domain/aggregate/LikedStoryComment.java +++ b/src/main/java/com/example/spot/story/domain/association/LikedStoryComment.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.aggregate; +package com.example.spot.story.domain.association; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; diff --git a/src/main/java/com/example/spot/story/domain/aggregate/StoryComment.java b/src/main/java/com/example/spot/story/domain/association/StoryComment.java similarity index 98% rename from src/main/java/com/example/spot/story/domain/aggregate/StoryComment.java rename to src/main/java/com/example/spot/story/domain/association/StoryComment.java index c8088eaf..41d5d48f 100644 --- a/src/main/java/com/example/spot/story/domain/aggregate/StoryComment.java +++ b/src/main/java/com/example/spot/story/domain/association/StoryComment.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.aggregate; +package com.example.spot.story.domain.association; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; diff --git a/src/main/java/com/example/spot/story/domain/aggregate/StoryImage.java b/src/main/java/com/example/spot/story/domain/association/StoryImage.java similarity index 94% rename from src/main/java/com/example/spot/story/domain/aggregate/StoryImage.java rename to src/main/java/com/example/spot/story/domain/association/StoryImage.java index 862c9bef..f9f16bc2 100644 --- a/src/main/java/com/example/spot/story/domain/aggregate/StoryImage.java +++ b/src/main/java/com/example/spot/story/domain/association/StoryImage.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.aggregate; +package com.example.spot.story.domain.association; import com.example.spot.common.entity.BaseEntity; import com.example.spot.story.domain.Story; diff --git a/src/main/java/com/example/spot/story/domain/aggregate/StoryReport.java b/src/main/java/com/example/spot/story/domain/association/StoryReport.java similarity index 94% rename from src/main/java/com/example/spot/story/domain/aggregate/StoryReport.java rename to src/main/java/com/example/spot/story/domain/association/StoryReport.java index ffdbc912..8d1aed8b 100644 --- a/src/main/java/com/example/spot/story/domain/aggregate/StoryReport.java +++ b/src/main/java/com/example/spot/story/domain/association/StoryReport.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.aggregate; +package com.example.spot.story.domain.association; import com.example.spot.story.domain.Story; import jakarta.persistence.*; diff --git a/src/main/java/com/example/spot/story/domain/repository/LikedStoryCommentRepository.java b/src/main/java/com/example/spot/story/domain/repository/LikedStoryCommentRepository.java index 8e90f078..b443066d 100644 --- a/src/main/java/com/example/spot/story/domain/repository/LikedStoryCommentRepository.java +++ b/src/main/java/com/example/spot/story/domain/repository/LikedStoryCommentRepository.java @@ -1,6 +1,6 @@ package com.example.spot.story.domain.repository; -import com.example.spot.story.domain.aggregate.LikedStoryComment; +import com.example.spot.story.domain.association.LikedStoryComment; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/story/domain/repository/LikedStoryRepository.java b/src/main/java/com/example/spot/story/domain/repository/LikedStoryRepository.java index 255948db..a9fd00d2 100644 --- a/src/main/java/com/example/spot/story/domain/repository/LikedStoryRepository.java +++ b/src/main/java/com/example/spot/story/domain/repository/LikedStoryRepository.java @@ -1,6 +1,6 @@ package com.example.spot.story.domain.repository; -import com.example.spot.story.domain.aggregate.LikedStory; +import com.example.spot.story.domain.association.LikedStory; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/story/domain/repository/StoryCommentRepository.java b/src/main/java/com/example/spot/story/domain/repository/StoryCommentRepository.java index 1ab6dfac..c084dbae 100644 --- a/src/main/java/com/example/spot/story/domain/repository/StoryCommentRepository.java +++ b/src/main/java/com/example/spot/story/domain/repository/StoryCommentRepository.java @@ -1,6 +1,6 @@ package com.example.spot.story.domain.repository; -import com.example.spot.story.domain.aggregate.StoryComment; +import com.example.spot.story.domain.association.StoryComment; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/story/domain/repository/StoryImageRepository.java b/src/main/java/com/example/spot/story/domain/repository/StoryImageRepository.java index 987d5d4c..f6f31687 100644 --- a/src/main/java/com/example/spot/story/domain/repository/StoryImageRepository.java +++ b/src/main/java/com/example/spot/story/domain/repository/StoryImageRepository.java @@ -1,6 +1,6 @@ package com.example.spot.story.domain.repository; -import com.example.spot.story.domain.aggregate.StoryImage; +import com.example.spot.story.domain.association.StoryImage; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/example/spot/story/domain/repository/StoryReportRepository.java b/src/main/java/com/example/spot/story/domain/repository/StoryReportRepository.java index 12c5dad0..905963cf 100644 --- a/src/main/java/com/example/spot/story/domain/repository/StoryReportRepository.java +++ b/src/main/java/com/example/spot/story/domain/repository/StoryReportRepository.java @@ -1,6 +1,6 @@ package com.example.spot.story.domain.repository; -import com.example.spot.story.domain.aggregate.StoryReport; +import com.example.spot.story.domain.association.StoryReport; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/study/application/StudyCommandServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyCommandServiceImpl.java index cc8d01f2..d7f8f2e0 100644 --- a/src/main/java/com/example/spot/study/application/StudyCommandServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyCommandServiceImpl.java @@ -1,19 +1,19 @@ package com.example.spot.study.application; -import com.example.spot.study.domain.aggregate.StudyRegion; +import com.example.spot.study.domain.association.StudyRegion; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.StudyMember; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.association.Theme; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Status; import com.example.spot.study.domain.enums.StudyLikeStatus; import com.example.spot.study.domain.enums.StudyState; import com.example.spot.member.domain.association.PreferredStudy; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyTheme; import com.example.spot.study.domain.Study; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; diff --git a/src/main/java/com/example/spot/study/application/MemberStudyCommandService.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java similarity index 98% rename from src/main/java/com/example/spot/study/application/MemberStudyCommandService.java rename to src/main/java/com/example/spot/study/application/StudyMemberCommandService.java index e7c5d9e1..9abc89b9 100644 --- a/src/main/java/com/example/spot/study/application/MemberStudyCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java @@ -20,7 +20,7 @@ import java.time.LocalDate; -public interface MemberStudyCommandService { +public interface StudyMemberCommandService { StudyWithdrawalResponseDTO.WithdrawalDTO withdrawFromStudy(Long studyId); StudyWithdrawalResponseDTO.WithdrawalDTO withdrawHostFromStudy(Long studyId, StudyHostWithdrawRequestDTO requestDTO); diff --git a/src/main/java/com/example/spot/study/application/MemberStudyCommandServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java similarity index 99% rename from src/main/java/com/example/spot/study/application/MemberStudyCommandServiceImpl.java rename to src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java index a62438dc..26e1033f 100644 --- a/src/main/java/com/example/spot/study/application/MemberStudyCommandServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java @@ -8,18 +8,18 @@ import com.example.spot.report.domain.MemberReport; import com.example.spot.notification.domain.Notification; import com.example.spot.schedule.domain.Schedule; -import com.example.spot.schedule.domain.aggregate.Quiz; -import com.example.spot.schedule.domain.aggregate.QuizSubmission; +import com.example.spot.schedule.domain.association.Quiz; +import com.example.spot.schedule.domain.association.QuizSubmission; import com.example.spot.schedule.domain.repository.QuizRepository; import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.story.domain.Story; -import com.example.spot.story.domain.aggregate.StoryReport; +import com.example.spot.story.domain.association.StoryReport; import com.example.spot.todo.domain.ToDo; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.notification.domain.enums.NotifyType; import com.example.spot.member.domain.enums.Status; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.Study; import com.example.spot.report.domain.MemberReportRepository; import com.example.spot.member.domain.MemberRepository; @@ -53,8 +53,8 @@ import java.util.Objects; import com.example.spot.vote.domain.Vote; -import com.example.spot.vote.domain.aggregate.VoteOption; -import com.example.spot.vote.domain.aggregate.VoteParticipant; +import com.example.spot.vote.domain.association.VoteOption; +import com.example.spot.vote.domain.association.VoteParticipant; import com.example.spot.vote.domain.repository.VoteOptionRepository; import com.example.spot.vote.domain.repository.VoteParticipantRepository; import com.example.spot.vote.domain.VoteRepository; @@ -70,7 +70,7 @@ @Service @RequiredArgsConstructor @Transactional -public class MemberStudyCommandServiceImpl implements MemberStudyCommandService { +public class StudyMemberCommandServiceImpl implements StudyMemberCommandService { @Value("${image.post.anonymous.profile}") private String defaultImage; diff --git a/src/main/java/com/example/spot/study/application/MemberStudyQueryService.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java similarity index 98% rename from src/main/java/com/example/spot/study/application/MemberStudyQueryService.java rename to src/main/java/com/example/spot/study/application/StudyMemberQueryService.java index fb9a19ba..8d2af24b 100644 --- a/src/main/java/com/example/spot/study/application/MemberStudyQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java @@ -16,7 +16,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -public interface MemberStudyQueryService { +public interface StudyMemberQueryService { ScheduleResponseDTO.MonthlyScheduleListDTO getMonthlySchedules(Long studyId, int year, int month); diff --git a/src/main/java/com/example/spot/study/application/MemberStudyQueryServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java similarity index 99% rename from src/main/java/com/example/spot/study/application/MemberStudyQueryServiceImpl.java rename to src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java index 3ae1e882..62aebc33 100644 --- a/src/main/java/com/example/spot/study/application/MemberStudyQueryServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java @@ -5,13 +5,13 @@ import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.schedule.domain.Schedule; -import com.example.spot.schedule.domain.aggregate.Quiz; -import com.example.spot.schedule.domain.aggregate.QuizSubmission; +import com.example.spot.schedule.domain.association.Quiz; +import com.example.spot.schedule.domain.association.QuizSubmission; import com.example.spot.schedule.domain.repository.QuizRepository; import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.story.domain.Story; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.schedule.domain.enums.SchedulePeriod; import com.example.spot.study.domain.Study; @@ -36,7 +36,7 @@ import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; import com.example.spot.vote.domain.Vote; -import com.example.spot.vote.domain.aggregate.VoteOption; +import com.example.spot.vote.domain.association.VoteOption; import com.example.spot.vote.domain.repository.VoteOptionRepository; import com.example.spot.vote.domain.repository.VoteParticipantRepository; import com.example.spot.vote.domain.VoteRepository; @@ -61,7 +61,7 @@ @Service @Transactional(readOnly = true) @RequiredArgsConstructor -public class MemberStudyQueryServiceImpl implements MemberStudyQueryService { +public class StudyMemberQueryServiceImpl implements StudyMemberQueryService { @Value("${image.post.anonymous.profile}") private String defaultImage; diff --git a/src/main/java/com/example/spot/study/application/StudyQueryServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyQueryServiceImpl.java index 5e89066a..072dbd73 100644 --- a/src/main/java/com/example/spot/study/application/StudyQueryServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyQueryServiceImpl.java @@ -5,9 +5,9 @@ import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.StudyMember; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.association.Theme; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.member.domain.enums.Status; @@ -17,8 +17,8 @@ import com.example.spot.member.domain.association.MemberTheme; import com.example.spot.member.domain.association.PreferredRegion; import com.example.spot.member.domain.association.PreferredStudy; -import com.example.spot.study.domain.aggregate.StudyRegion; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyRegion; +import com.example.spot.study.domain.association.StudyTheme; import com.example.spot.study.domain.Study; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; diff --git a/src/main/java/com/example/spot/study/domain/Study.java b/src/main/java/com/example/spot/study/domain/Study.java index 330c67c9..33717be2 100644 --- a/src/main/java/com/example/spot/study/domain/Study.java +++ b/src/main/java/com/example/spot/study/domain/Study.java @@ -6,9 +6,9 @@ import com.example.spot.member.domain.enums.Status; import com.example.spot.schedule.domain.Schedule; import com.example.spot.story.domain.Story; -import com.example.spot.study.domain.aggregate.StudyMember; -import com.example.spot.study.domain.aggregate.StudyRegion; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.association.StudyRegion; +import com.example.spot.study.domain.association.StudyTheme; import com.example.spot.todo.domain.ToDo; import com.example.spot.vote.domain.Vote; import com.example.spot.study.domain.enums.StudyState; diff --git a/src/main/java/com/example/spot/study/domain/StudyRepositoryCustom.java b/src/main/java/com/example/spot/study/domain/StudyRepositoryCustom.java index 9ee345ba..90923ca7 100644 --- a/src/main/java/com/example/spot/study/domain/StudyRepositoryCustom.java +++ b/src/main/java/com/example/spot/study/domain/StudyRepositoryCustom.java @@ -1,10 +1,10 @@ package com.example.spot.study.domain; import com.example.spot.member.domain.enums.Status; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudySortBy; -import com.example.spot.study.domain.aggregate.StudyRegion; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyRegion; +import com.example.spot.study.domain.association.StudyTheme; import java.util.List; import java.util.Map; diff --git a/src/main/java/com/example/spot/study/domain/StudyRepositoryCustomImpl.java b/src/main/java/com/example/spot/study/domain/StudyRepositoryCustomImpl.java index 47479eb0..982ce92e 100644 --- a/src/main/java/com/example/spot/study/domain/StudyRepositoryCustomImpl.java +++ b/src/main/java/com/example/spot/study/domain/StudyRepositoryCustomImpl.java @@ -3,15 +3,15 @@ import static com.example.spot.study.domain.QStudy.*; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.StudyRegion; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.StudyRegion; import com.example.spot.member.domain.enums.Gender; import com.example.spot.member.domain.enums.Status; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudySortBy; import com.example.spot.study.domain.enums.StudyState; import com.example.spot.study.domain.enums.ThemeType; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyTheme; import com.querydsl.core.BooleanBuilder; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; diff --git a/src/main/java/com/example/spot/study/domain/aggregate/Region.java b/src/main/java/com/example/spot/study/domain/association/Region.java similarity index 96% rename from src/main/java/com/example/spot/study/domain/aggregate/Region.java rename to src/main/java/com/example/spot/study/domain/association/Region.java index ca10c1be..8941f1f3 100644 --- a/src/main/java/com/example/spot/study/domain/aggregate/Region.java +++ b/src/main/java/com/example/spot/study/domain/association/Region.java @@ -1,4 +1,4 @@ -package com.example.spot.study.domain.aggregate; +package com.example.spot.study.domain.association; import com.example.spot.common.entity.BaseEntity; import com.example.spot.member.domain.association.PreferredRegion; diff --git a/src/main/java/com/example/spot/study/domain/aggregate/StudyMember.java b/src/main/java/com/example/spot/study/domain/association/StudyMember.java similarity index 97% rename from src/main/java/com/example/spot/study/domain/aggregate/StudyMember.java rename to src/main/java/com/example/spot/study/domain/association/StudyMember.java index 835d07ba..6adb8e38 100644 --- a/src/main/java/com/example/spot/study/domain/aggregate/StudyMember.java +++ b/src/main/java/com/example/spot/study/domain/association/StudyMember.java @@ -1,4 +1,4 @@ -package com.example.spot.study.domain.aggregate; +package com.example.spot.study.domain.association; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; diff --git a/src/main/java/com/example/spot/study/domain/aggregate/StudyRegion.java b/src/main/java/com/example/spot/study/domain/association/StudyRegion.java similarity index 95% rename from src/main/java/com/example/spot/study/domain/aggregate/StudyRegion.java rename to src/main/java/com/example/spot/study/domain/association/StudyRegion.java index 167f0c50..39ae9c8a 100644 --- a/src/main/java/com/example/spot/study/domain/aggregate/StudyRegion.java +++ b/src/main/java/com/example/spot/study/domain/association/StudyRegion.java @@ -1,4 +1,4 @@ -package com.example.spot.study.domain.aggregate; +package com.example.spot.study.domain.association; import com.example.spot.common.entity.BaseEntity; import com.example.spot.study.domain.Study; diff --git a/src/main/java/com/example/spot/study/domain/aggregate/StudyTheme.java b/src/main/java/com/example/spot/study/domain/association/StudyTheme.java similarity index 95% rename from src/main/java/com/example/spot/study/domain/aggregate/StudyTheme.java rename to src/main/java/com/example/spot/study/domain/association/StudyTheme.java index 2f91d062..c70f71c3 100644 --- a/src/main/java/com/example/spot/study/domain/aggregate/StudyTheme.java +++ b/src/main/java/com/example/spot/study/domain/association/StudyTheme.java @@ -1,4 +1,4 @@ -package com.example.spot.study.domain.aggregate; +package com.example.spot.study.domain.association; import com.example.spot.common.entity.BaseEntity; import com.example.spot.study.domain.Study; diff --git a/src/main/java/com/example/spot/study/domain/aggregate/Theme.java b/src/main/java/com/example/spot/study/domain/association/Theme.java similarity index 97% rename from src/main/java/com/example/spot/study/domain/aggregate/Theme.java rename to src/main/java/com/example/spot/study/domain/association/Theme.java index 4e295dc6..9ee98da8 100644 --- a/src/main/java/com/example/spot/study/domain/aggregate/Theme.java +++ b/src/main/java/com/example/spot/study/domain/association/Theme.java @@ -1,4 +1,4 @@ -package com.example.spot.study.domain.aggregate; +package com.example.spot.study.domain.association; import com.example.spot.common.entity.BaseEntity; import com.example.spot.study.domain.enums.ThemeType; diff --git a/src/main/java/com/example/spot/study/domain/repository/RegionRepository.java b/src/main/java/com/example/spot/study/domain/repository/RegionRepository.java index 80054ece..03be7a95 100644 --- a/src/main/java/com/example/spot/study/domain/repository/RegionRepository.java +++ b/src/main/java/com/example/spot/study/domain/repository/RegionRepository.java @@ -1,6 +1,6 @@ package com.example.spot.study.domain.repository; -import com.example.spot.study.domain.aggregate.Region; +import com.example.spot.study.domain.association.Region; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/study/domain/repository/StudyMemberRepository.java b/src/main/java/com/example/spot/study/domain/repository/StudyMemberRepository.java index d5f2b922..1ef69da4 100644 --- a/src/main/java/com/example/spot/study/domain/repository/StudyMemberRepository.java +++ b/src/main/java/com/example/spot/study/domain/repository/StudyMemberRepository.java @@ -1,6 +1,6 @@ package com.example.spot.study.domain.repository; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Status; diff --git a/src/main/java/com/example/spot/study/domain/repository/StudyRegionRepository.java b/src/main/java/com/example/spot/study/domain/repository/StudyRegionRepository.java index 82955bf8..10da169a 100644 --- a/src/main/java/com/example/spot/study/domain/repository/StudyRegionRepository.java +++ b/src/main/java/com/example/spot/study/domain/repository/StudyRegionRepository.java @@ -2,8 +2,8 @@ import java.util.List; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.StudyRegion; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.StudyRegion; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/study/domain/repository/StudyThemeRepository.java b/src/main/java/com/example/spot/study/domain/repository/StudyThemeRepository.java index 6f4088fe..bf7e7dbe 100644 --- a/src/main/java/com/example/spot/study/domain/repository/StudyThemeRepository.java +++ b/src/main/java/com/example/spot/study/domain/repository/StudyThemeRepository.java @@ -2,8 +2,8 @@ import java.util.List; -import com.example.spot.study.domain.aggregate.StudyTheme; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.StudyTheme; +import com.example.spot.study.domain.association.Theme; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/study/domain/repository/ThemeRepository.java b/src/main/java/com/example/spot/study/domain/repository/ThemeRepository.java index f5ef6c60..ad072298 100644 --- a/src/main/java/com/example/spot/study/domain/repository/ThemeRepository.java +++ b/src/main/java/com/example/spot/study/domain/repository/ThemeRepository.java @@ -1,6 +1,6 @@ package com.example.spot.study.domain.repository; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Theme; import com.example.spot.study.domain.enums.ThemeType; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/study/presentation/controller/StudyMemberController.java b/src/main/java/com/example/spot/study/presentation/controller/StudyMemberController.java index 50c976f3..013425e1 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/StudyMemberController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/StudyMemberController.java @@ -2,8 +2,8 @@ import com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; -import com.example.spot.study.application.MemberStudyCommandService; -import com.example.spot.study.application.MemberStudyQueryService; +import com.example.spot.study.application.StudyMemberCommandService; +import com.example.spot.study.application.StudyMemberQueryService; import com.example.spot.member.domain.validation.annotation.ExistMember; import com.example.spot.schedule.domain.validation.annotation.ExistSchedule; import com.example.spot.study.domain.validation.annotation.ExistStudy; @@ -54,8 +54,8 @@ @Validated public class StudyMemberController { - private final MemberStudyQueryService memberStudyQueryService; - private final MemberStudyCommandService memberStudyCommandService; + private final StudyMemberQueryService studyMemberQueryService; + private final StudyMemberCommandService studyMemberCommandService; /* ----------------------------- 진행중인 스터디 관련 API ------------------------------------- */ @@ -67,7 +67,7 @@ public class StudyMemberController { """) @DeleteMapping("/studies/{studyId}/withdrawal") public ApiResponse withdrawFromStudy(@PathVariable Long studyId) { - StudyWithdrawalResponseDTO.WithdrawalDTO withdrawalDTO = memberStudyCommandService.withdrawFromStudy(studyId); + StudyWithdrawalResponseDTO.WithdrawalDTO withdrawalDTO = studyMemberCommandService.withdrawFromStudy(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_DELETED, withdrawalDTO); } @@ -83,7 +83,7 @@ public ApiResponse withdrawHostFromStu @PathVariable Long studyId, @RequestBody StudyHostWithdrawRequestDTO requestDTO) { StudyWithdrawalResponseDTO.WithdrawalDTO withdrawalDTO = - memberStudyCommandService.withdrawHostFromStudy(studyId, requestDTO); + studyMemberCommandService.withdrawHostFromStudy(studyId, requestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_DELETED, withdrawalDTO); } @@ -99,7 +99,7 @@ public ApiResponse terminateStudy( @PathVariable @ExistStudy Long studyId, @RequestParam @TextLength(min=1, max=30) String performance ) { - StudyTerminationResponseDTO.TerminationDTO terminationDTO = memberStudyCommandService.terminateStudy(studyId, performance); + StudyTerminationResponseDTO.TerminationDTO terminationDTO = studyMemberCommandService.terminateStudy(studyId, performance); return ApiResponse.onSuccess(SuccessStatus._STUDY_TERMINATED, terminationDTO); } @@ -117,7 +117,7 @@ public ApiResponse terminateStudy( @Parameter(name = "studyId", description = "모집중인 스터디의 ID를 입력 받습니다.", required = true) public ApiResponse getIsApplied(@PathVariable @ExistStudy Long studyId) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_FOUND, - memberStudyQueryService.isApplied(studyId)); + studyMemberQueryService.isApplied(studyId)); } @Tag(name = "모집중인 스터디") @@ -129,7 +129,7 @@ public ApiResponse getIsApplied(@PathVariable @ExistStudy Lon @Parameter(name = "studyId", description = "모집중인 스터디의 ID를 입력 받습니다.", required = true) public ApiResponse getAllApplicants(@PathVariable @ExistStudy Long studyId) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_FOUND, - memberStudyQueryService.findStudyApplicants(studyId)); + studyMemberQueryService.findStudyApplicants(studyId)); } @Tag(name = "모집중인 스터디") @Operation(summary = "[모집중인 스터디] 스터디 신청 정보(이름, 자기소개) 불러오기", description = """ @@ -143,7 +143,7 @@ public ApiResponse getApplicantInfo( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistMember Long applicantId) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_FOUND, - memberStudyQueryService.findStudyApplication(studyId, applicantId)); + studyMemberQueryService.findStudyApplication(studyId, applicantId)); } @Tag(name = "모집중인 스터디") @Operation(summary = "[모집중인 스터디] 스터디 신청 처리하기", description = """ @@ -160,7 +160,7 @@ public ApiResponse rejectApplicant( @PathVariable @ExistMember Long applicantId, @RequestParam boolean isAccept) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_UPDATED, - memberStudyCommandService.acceptAndRejectStudyApply(applicantId, studyId, isAccept)); + studyMemberCommandService.acceptAndRejectStudyApply(applicantId, studyId, isAccept)); } @Tag(name = "테스트 용 API", description = "테스트 용 API") @@ -183,7 +183,7 @@ public ApiResponse rejectApplicantForTest( @PathVariable @ExistMember Long applicantId, @RequestParam boolean isAccept) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_UPDATED, - memberStudyCommandService.acceptAndRejectStudyApplyForTest(applicantId, studyId, isAccept)); + studyMemberCommandService.acceptAndRejectStudyApplyForTest(applicantId, studyId, isAccept)); } @@ -196,7 +196,7 @@ public ApiResponse rejectApplicantForTest( """) @GetMapping("/studies/{studyId}/announce") public ApiResponse getRecentAnnouncement(@PathVariable @ExistStudy Long studyId) { - StudyPostResponseDTO studyPostResponseDTO = memberStudyQueryService.findStudyAnnouncementPost(studyId); + StudyPostResponseDTO studyPostResponseDTO = studyMemberQueryService.findStudyAnnouncementPost(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, studyPostResponseDTO); } @Tag(name = "스터디 상세 정보") @@ -209,7 +209,7 @@ public ApiResponse getUpcomingSchedules( @PathVariable @ExistStudy Long studyId, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "1") int size){ - StudyScheduleResponseDTO studyScheduleResponseDTO = memberStudyQueryService.findStudySchedule(studyId, PageRequest.of(page, size)); + StudyScheduleResponseDTO studyScheduleResponseDTO = studyMemberQueryService.findStudySchedule(studyId, PageRequest.of(page, size)); return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, studyScheduleResponseDTO); } @Tag(name = "스터디 상세 정보") @@ -220,7 +220,7 @@ public ApiResponse getUpcomingSchedules( @GetMapping("/studies/{studyId}/members") public ApiResponse getStudyMembers( @PathVariable @ExistStudy Long studyId){ - StudyMemberResponseDTO studyMemberResponseDTO = memberStudyQueryService.findStudyMembers(studyId); + StudyMemberResponseDTO studyMemberResponseDTO = studyMemberQueryService.findStudyMembers(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_FOUND, studyMemberResponseDTO); } @@ -234,7 +234,7 @@ public ApiResponse getStudyMembers( public ApiResponse getStudyHost( @PathVariable @ExistStudy Long studyId) { - StudyMemberResDTO.StudyHostDTO studyHostDTO = memberStudyQueryService.getStudyHost(studyId); + StudyMemberResDTO.StudyHostDTO studyHostDTO = studyMemberQueryService.getStudyHost(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_HOST_FOUND, studyHostDTO); } @@ -253,7 +253,7 @@ public ApiResponse getMonthlySchedul @PathVariable @ExistStudy Long studyId, @RequestParam @IntSize(min = 1) Integer year, @RequestParam @IntSize(min = 1, max= 12) Integer month) { - ScheduleResponseDTO.MonthlyScheduleListDTO monthlyScheduleDTO = memberStudyQueryService.getMonthlySchedules(studyId, year, month); + ScheduleResponseDTO.MonthlyScheduleListDTO monthlyScheduleDTO = studyMemberQueryService.getMonthlySchedules(studyId, year, month); return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, monthlyScheduleDTO); } @@ -268,7 +268,7 @@ public ApiResponse getMonthlySchedul public ApiResponse getSchedule( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistSchedule Long scheduleId) { - ScheduleResponseDTO.MonthlyScheduleDTO scheduleDTO = memberStudyQueryService.getSchedule(studyId, scheduleId); + ScheduleResponseDTO.MonthlyScheduleDTO scheduleDTO = studyMemberQueryService.getSchedule(studyId, scheduleId); return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, scheduleDTO); } @@ -284,7 +284,7 @@ public ApiResponse getSchedule( public ApiResponse addSchedule( @PathVariable @ExistStudy Long studyId, @RequestBody @Valid ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO) { - ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = memberStudyCommandService.addSchedule(studyId, scheduleRequestDTO); + ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = studyMemberCommandService.addSchedule(studyId, scheduleRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_CREATED, scheduleResponseDTO); } @@ -302,7 +302,7 @@ public ApiResponse modSchedule( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistSchedule Long scheduleId, @RequestBody @Valid ScheduleRequestDTO.ScheduleDTO scheduleModDTO) { - ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = memberStudyCommandService.modSchedule(studyId, scheduleId, scheduleModDTO); + ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = studyMemberCommandService.modSchedule(studyId, scheduleId, scheduleModDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_UPDATED, scheduleResponseDTO); } @@ -319,7 +319,7 @@ public ApiResponse modSchedule( public ApiResponse createVote( @PathVariable @ExistStudy Long studyId, @RequestBody @Valid StudyVoteRequestDTO.VoteDTO voteDTO) { - StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = memberStudyCommandService.createVote(studyId, voteDTO); + StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = studyMemberCommandService.createVote(studyId, voteDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_CREATED, votePreviewDTO); } @@ -335,7 +335,7 @@ public ApiResponse vote( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistVote Long voteId, @RequestBody @Valid StudyVoteRequestDTO.VotedOptionDTO votedOptionDTO) { - StudyVoteResponseDTO.VotedOptionDTO votedOptionResDTO = memberStudyCommandService.vote(studyId, voteId, votedOptionDTO); + StudyVoteResponseDTO.VotedOptionDTO votedOptionResDTO = studyMemberCommandService.vote(studyId, voteId, votedOptionDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_PARTICIPATED, votedOptionResDTO); } @@ -351,7 +351,7 @@ public ApiResponse updateVote( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistVote Long voteId, @RequestBody @Valid StudyVoteRequestDTO.VoteUpdateDTO voteDTO) { - StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = memberStudyCommandService.updateVote(studyId, voteId, voteDTO); + StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = studyMemberCommandService.updateVote(studyId, voteId, voteDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_UPDATED, votePreviewDTO); } @@ -366,7 +366,7 @@ public ApiResponse updateVote( public ApiResponse deleteVote( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistVote Long voteId) { - StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = memberStudyCommandService.deleteVote(studyId, voteId); + StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = studyMemberCommandService.deleteVote(studyId, voteId); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_DELETED, votePreviewDTO); } @@ -379,7 +379,7 @@ public ApiResponse deleteVote( @GetMapping("/studies/{studyId}/votes") public ApiResponse getAllVotes( @PathVariable @ExistStudy Long studyId) { - StudyVoteResponseDTO.VoteListDTO voteListDTO = memberStudyQueryService.getAllVotes(studyId); + StudyVoteResponseDTO.VoteListDTO voteListDTO = studyMemberQueryService.getAllVotes(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, voteListDTO); } @@ -396,12 +396,12 @@ public ApiResponse getAllVotes( public ApiResponse getVote( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistVote Long voteId) { - Boolean isCompleted = memberStudyQueryService.getIsCompleted(voteId); + Boolean isCompleted = studyMemberQueryService.getIsCompleted(voteId); if (isCompleted) { - StudyVoteResponseDTO.CompletedVoteDTO completedVoteDTO = memberStudyQueryService.getVoteInCompletion(studyId, voteId); + StudyVoteResponseDTO.CompletedVoteDTO completedVoteDTO = studyMemberQueryService.getVoteInCompletion(studyId, voteId); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, completedVoteDTO); } else { - StudyVoteResponseDTO.VoteDTO voteDTO = memberStudyQueryService.getVoteInProgress(studyId, voteId); + StudyVoteResponseDTO.VoteDTO voteDTO = studyMemberQueryService.getVoteInProgress(studyId, voteId); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, voteDTO); } } @@ -417,7 +417,7 @@ public ApiResponse getVote( public ApiResponse getCompletedVoteDetail( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistVote Long voteId) { - StudyVoteResponseDTO.CompletedVoteDetailDTO completedVoteDetailDTO = memberStudyQueryService.getCompletedVoteDetail(studyId, voteId); + StudyVoteResponseDTO.CompletedVoteDetailDTO completedVoteDetailDTO = studyMemberQueryService.getCompletedVoteDetail(studyId, voteId); return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_DETAIL_STATUS_FOUND, completedVoteDetailDTO); } @@ -433,7 +433,7 @@ public ApiResponse getAllStudyImages( @PathVariable @ExistStudy Long studyId, @RequestParam @Min(0) Integer offset, @RequestParam @Min(1) Integer limit) { - StudyImageResponseDTO.ImageListDTO imageListDTO = memberStudyQueryService.getAllStudyImages(studyId, PageRequest.of(offset, limit)); + StudyImageResponseDTO.ImageListDTO imageListDTO = studyMemberQueryService.getAllStudyImages(studyId, PageRequest.of(offset, limit)); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_IMAGES_FOUND, imageListDTO); } @@ -452,7 +452,7 @@ public ApiResponse createAttendanceQuiz( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistSchedule Long scheduleId, @RequestBody @Valid StudyQuizRequestDTO.QuizDTO quizRequestDTO) { - StudyQuizResponseDTO.QuizDTO quizResponseDTO = memberStudyCommandService.createAttendanceQuiz(studyId, scheduleId, quizRequestDTO); + StudyQuizResponseDTO.QuizDTO quizResponseDTO = studyMemberCommandService.createAttendanceQuiz(studyId, scheduleId, quizRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_CREATED, quizResponseDTO); } @@ -469,7 +469,7 @@ public ApiResponse getAttendanceQuiz( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistSchedule Long scheduleId, @RequestParam LocalDate date) { - StudyQuizResponseDTO.QuizDTO quizDTO = memberStudyQueryService.getAttendanceQuiz(studyId, scheduleId, date); + StudyQuizResponseDTO.QuizDTO quizDTO = studyMemberQueryService.getAttendanceQuiz(studyId, scheduleId, date); return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_FOUND, quizDTO); } @@ -487,7 +487,7 @@ public ApiResponse attendantStudy( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistSchedule Long scheduleId, @RequestBody @Valid StudyQuizRequestDTO.AttendanceDTO attendanceRequestDTO) { - StudyQuizResponseDTO.AttendanceDTO attendanceResponseDTO = memberStudyCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO); + StudyQuizResponseDTO.AttendanceDTO attendanceResponseDTO = studyMemberCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO); if (attendanceResponseDTO.getIsCorrect()) { return ApiResponse.onSuccess(SuccessStatus._STUDY_ATTENDANCE_CREATED_CORRECT_ANSWER, attendanceResponseDTO); } else { @@ -509,7 +509,7 @@ public ApiResponse deleteAttendanceQuiz( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistSchedule Long scheduleId, @RequestParam LocalDate date) { - StudyQuizResponseDTO.QuizDTO quizDTO = memberStudyCommandService.deleteAttendanceQuiz(studyId, scheduleId, date); + StudyQuizResponseDTO.QuizDTO quizDTO = studyMemberCommandService.deleteAttendanceQuiz(studyId, scheduleId, date); return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_DELETED, quizDTO); } @@ -526,7 +526,7 @@ public ApiResponse getAllAttendances( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistSchedule Long scheduleId, @RequestParam LocalDate date) { - StudyQuizResponseDTO.AttendanceListDTO attendanceListDTO = memberStudyQueryService.getAllAttendances(studyId, scheduleId, date); + StudyQuizResponseDTO.AttendanceListDTO attendanceListDTO = studyMemberQueryService.getAllAttendances(studyId, scheduleId, date); return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_ATTENDANCES_FOUND, attendanceListDTO); } @@ -545,7 +545,7 @@ public ApiResponse getAllAttendances( public ApiResponse reportStudyMember( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistMember Long memberId, @RequestBody @Valid StudyMemberReportDTO studyMemberReportDTO) { - MemberResponseDTO.ReportedMemberDTO reportedMemberDTO = memberStudyCommandService.reportStudyMember(studyId, memberId, studyMemberReportDTO); + MemberResponseDTO.ReportedMemberDTO reportedMemberDTO = studyMemberCommandService.reportStudyMember(studyId, memberId, studyMemberReportDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_REPORTED, reportedMemberDTO); } @@ -560,7 +560,7 @@ public ApiResponse reportStudyMember( public ApiResponse reportStudyPost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = memberStudyCommandService.reportStudyPost(studyId, postId); + StudyPostResDTO.PostPreviewDTO postPreviewDTO = studyMemberCommandService.reportStudyPost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_REPORTED, postPreviewDTO); } @@ -576,7 +576,7 @@ public ApiResponse reportStudyPost( public ApiResponse createToDoList( @PathVariable @ExistStudy Long studyId, @RequestBody @Valid ToDoListRequestDTO.ToDoListCreateDTO request) { - ToDoListCreateResponseDTO toDoList = memberStudyCommandService.createToDoList(studyId, + ToDoListCreateResponseDTO toDoList = studyMemberCommandService.createToDoList(studyId, request); return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_CREATED, toDoList); } @@ -596,7 +596,7 @@ public ApiResponse updateToDoList( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistToDo Long toDoId, @RequestBody @Valid ToDoListRequestDTO.ToDoListCreateDTO request) { - ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = memberStudyCommandService.updateToDoList( + ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = studyMemberCommandService.updateToDoList( studyId, toDoId, request); return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_UPDATED, toDoListUpdateResponseDTO); } @@ -620,7 +620,7 @@ public ApiResponse updateToDoList( public ApiResponse checkToDoList( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistToDo Long toDoId) { - ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = memberStudyCommandService.checkToDoList( + ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = studyMemberCommandService.checkToDoList( studyId, toDoId); return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_UPDATED, toDoListUpdateResponseDTO); } @@ -638,7 +638,7 @@ public ApiResponse checkToDoList( public ApiResponse deleteToDoList( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistToDo Long toDoId) { - ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = memberStudyCommandService.deleteToDoList( + ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = studyMemberCommandService.deleteToDoList( studyId, toDoId); return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_DELETED, toDoListUpdateResponseDTO); } @@ -659,7 +659,7 @@ public ApiResponse getMyToDoList( @RequestParam @Min(0) Integer page, @RequestParam @Min(1) Integer size, @RequestParam LocalDate date) { - ToDoListSearchResponseDTO toDoList = memberStudyQueryService.getToDoList(studyId, date, + ToDoListSearchResponseDTO toDoList = studyMemberQueryService.getToDoList(studyId, date, PageRequest.of(page, size)); return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_FOUND, toDoList); } @@ -681,7 +681,7 @@ public ApiResponse getOtherToDoList( @RequestParam @Min(0) Integer page, @RequestParam @Min(1) Integer size, @RequestParam LocalDate date) { - ToDoListSearchResponseDTO toDoList = memberStudyQueryService.getMemberToDoList(studyId, + ToDoListSearchResponseDTO toDoList = studyMemberQueryService.getMemberToDoList(studyId, memberId, date, PageRequest.of(page, size)); return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_FOUND, toDoList); } diff --git a/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java b/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java index 418c9dfc..71eb7197 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java @@ -4,8 +4,8 @@ import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.common.application.s3.S3ImageService; -import com.example.spot.study.application.StudyPostCommandService; -import com.example.spot.study.application.StudyPostQueryService; +import com.example.spot.story.domain.application.StoryCommandService; +import com.example.spot.story.domain.application.StoryQueryService; import com.example.spot.study.domain.validation.annotation.ExistStudy; import com.example.spot.story.domain.validation.annotation.ExistStory; import com.example.spot.story.domain.validation.annotation.ExistStoryComment; @@ -31,8 +31,8 @@ @Validated public class StudyPostController { - private final StudyPostQueryService studyPostQueryService; - private final StudyPostCommandService studyPostCommandService; + private final StoryQueryService storyQueryService; + private final StoryCommandService storyCommandService; private final S3ImageService s3ImageService; /* ----------------------------- 스터디 게시글 관련 API ------------------------------------- */ @@ -47,7 +47,7 @@ public class StudyPostController { public ApiResponse createPost( @PathVariable @ExistStudy Long studyId, @ModelAttribute(name = "post") @Valid StudyPostRequestDTO.PostDTO postRequestDTO) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = studyPostCommandService.createPost(studyId, postRequestDTO); + StudyPostResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.createPost(studyId, postRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_CREATED, postPreviewDTO); } @@ -64,7 +64,7 @@ public ApiResponse updatePost( @PathVariable @ExistStory Long postId, @ModelAttribute(name= "post") @Valid StudyPostRequestDTO.PostDTO postDTO ) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = studyPostCommandService.updatePost(studyId, postId, postDTO); + StudyPostResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.updatePost(studyId, postId, postDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_UPDATED, postPreviewDTO); } @@ -80,7 +80,7 @@ public ApiResponse updatePost( public ApiResponse deletePost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = studyPostCommandService.deletePost(studyId, postId); + StudyPostResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.deletePost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_DELETED, postPreviewDTO); } @@ -100,7 +100,7 @@ public ApiResponse getAllPosts( @RequestParam(required = false) StoryCategoryQuery storyCategoryQuery, @RequestParam @Min(0) Integer offset, @RequestParam @Min(1) Integer limit) { - StudyPostResDTO.PostListDTO postListDTO = studyPostQueryService.getAllPosts(PageRequest.of(offset, limit), studyId, storyCategoryQuery); + StudyPostResDTO.PostListDTO postListDTO = storyQueryService.getAllPosts(PageRequest.of(offset, limit), studyId, storyCategoryQuery); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_LIST_FOUND, postListDTO); } @@ -116,7 +116,7 @@ public ApiResponse getPost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @RequestParam Boolean likeOrScrap) { - StudyPostResDTO.PostDetailDTO postDetailDTO = studyPostQueryService.getPost(studyId, postId, likeOrScrap); + StudyPostResDTO.PostDetailDTO postDetailDTO = storyQueryService.getPost(studyId, postId, likeOrScrap); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, postDetailDTO); } @@ -131,7 +131,7 @@ public ApiResponse getPost( public ApiResponse likePost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostLikeNumDTO postLikeNumDTO = studyPostCommandService.likePost(studyId, postId); + StudyPostResDTO.PostLikeNumDTO postLikeNumDTO = storyCommandService.likePost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_LIKED, postLikeNumDTO); } @@ -146,7 +146,7 @@ public ApiResponse likePost( public ApiResponse cancelPostLike( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostLikeNumDTO postLikeNumDTO = studyPostCommandService.cancelPostLike(studyId, postId); + StudyPostResDTO.PostLikeNumDTO postLikeNumDTO = storyCommandService.cancelPostLike(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_DISLIKED, postLikeNumDTO); } @@ -164,7 +164,7 @@ public ApiResponse createComment( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @RequestBody @Valid StudyPostCommentRequestDTO.CommentDTO commentRequestDTO) { - StudyPostCommentResponseDTO.CommentDTO commentResponseDTO = studyPostCommandService.createComment(studyId, postId, commentRequestDTO); + StudyPostCommentResponseDTO.CommentDTO commentResponseDTO = storyCommandService.createComment(studyId, postId, commentRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_CREATED, commentResponseDTO); } @@ -182,7 +182,7 @@ public ApiResponse createReply( @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId, @RequestBody @Valid StudyPostCommentRequestDTO.CommentDTO commentRequestDTO) { - StudyPostCommentResponseDTO.CommentDTO commentResponseDTO = studyPostCommandService.createReply(studyId, postId, commentId, commentRequestDTO); + StudyPostCommentResponseDTO.CommentDTO commentResponseDTO = storyCommandService.createReply(studyId, postId, commentId, commentRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_CREATED, commentResponseDTO); } @@ -199,7 +199,7 @@ public ApiResponse deleteComment( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentIdDTO commentPreviewDTO = studyPostCommandService.deleteComment(studyId, postId, commentId); + StudyPostCommentResponseDTO.CommentIdDTO commentPreviewDTO = storyCommandService.deleteComment(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_DELETED, commentPreviewDTO); } @@ -216,7 +216,7 @@ public ApiResponse likeComment( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = studyPostCommandService.likeComment(studyId, postId, commentId); + StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.likeComment(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_LIKED, commentPreviewDTO); } @@ -233,7 +233,7 @@ public ApiResponse dislikeComment @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = studyPostCommandService.dislikeComment(studyId, postId, commentId); + StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.dislikeComment(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_DISLIKED, commentPreviewDTO); } @@ -250,7 +250,7 @@ public ApiResponse cancelCommentL @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = studyPostCommandService.cancelCommentLike(studyId, postId, commentId); + StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.cancelCommentLike(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_LIKE_CANCELED, commentPreviewDTO); } @@ -267,7 +267,7 @@ public ApiResponse cancelCommentD @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = studyPostCommandService.cancelCommentDislike(studyId, postId, commentId); + StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.cancelCommentDislike(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_DISLIKE_CANCELED, commentPreviewDTO); } @@ -282,7 +282,7 @@ public ApiResponse cancelCommentD public ApiResponse getAllComments( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostCommentResponseDTO.CommentReplyListDTO commentReplyListDTO = studyPostQueryService.getAllComments(studyId, postId); + StudyPostCommentResponseDTO.CommentReplyListDTO commentReplyListDTO = storyQueryService.getAllComments(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_FOUND, commentReplyListDTO); } diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/SearchResponseDTO.java b/src/main/java/com/example/spot/study/presentation/dto/response/SearchResponseDTO.java index 4c638445..cb0e6689 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/SearchResponseDTO.java +++ b/src/main/java/com/example/spot/study/presentation/dto/response/SearchResponseDTO.java @@ -1,14 +1,14 @@ package com.example.spot.study.presentation.dto.response; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.StudyRegion; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.StudyRegion; +import com.example.spot.study.domain.association.Theme; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.study.domain.enums.StudyLikeStatus; import com.example.spot.study.domain.enums.StudyState; import com.example.spot.study.domain.enums.ThemeType; import com.example.spot.member.domain.association.PreferredStudy; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyTheme; import com.example.spot.study.domain.Study; import java.time.LocalDateTime; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyImageResponseDTO.java b/src/main/java/com/example/spot/study/presentation/dto/response/StudyImageResponseDTO.java index 15a0dcae..06453485 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyImageResponseDTO.java +++ b/src/main/java/com/example/spot/study/presentation/dto/response/StudyImageResponseDTO.java @@ -1,6 +1,6 @@ package com.example.spot.study.presentation.dto.response; -import com.example.spot.story.domain.aggregate.StoryImage; +import com.example.spot.story.domain.association.StoryImage; import lombok.AccessLevel; import lombok.Builder; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostCommentResponseDTO.java b/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostCommentResponseDTO.java index e7a8f5e6..ed012972 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostCommentResponseDTO.java +++ b/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostCommentResponseDTO.java @@ -1,8 +1,8 @@ package com.example.spot.study.presentation.dto.response; import com.example.spot.member.domain.Member; -import com.example.spot.story.domain.aggregate.LikedStoryComment; -import com.example.spot.story.domain.aggregate.StoryComment; +import com.example.spot.story.domain.association.LikedStoryComment; +import com.example.spot.story.domain.association.StoryComment; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResDTO.java b/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResDTO.java index 7d23ad32..6f5c0580 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResDTO.java +++ b/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResDTO.java @@ -3,7 +3,7 @@ import com.example.spot.member.domain.Member; import com.example.spot.story.domain.Story; import com.example.spot.story.domain.enums.StoryCategory; -import com.example.spot.story.domain.aggregate.StoryImage; +import com.example.spot.story.domain.association.StoryImage; import lombok.*; import java.time.LocalDateTime; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyQuizResponseDTO.java b/src/main/java/com/example/spot/study/presentation/dto/response/StudyQuizResponseDTO.java index 495a1934..c1172c91 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyQuizResponseDTO.java +++ b/src/main/java/com/example/spot/study/presentation/dto/response/StudyQuizResponseDTO.java @@ -1,8 +1,8 @@ package com.example.spot.study.presentation.dto.response; -import com.example.spot.schedule.domain.aggregate.Quiz; -import com.example.spot.schedule.domain.aggregate.QuizSubmission; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.schedule.domain.association.Quiz; +import com.example.spot.schedule.domain.association.QuizSubmission; +import com.example.spot.study.domain.association.StudyMember; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyVoteResponseDTO.java b/src/main/java/com/example/spot/study/presentation/dto/response/StudyVoteResponseDTO.java index 688c585d..5630f316 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyVoteResponseDTO.java +++ b/src/main/java/com/example/spot/study/presentation/dto/response/StudyVoteResponseDTO.java @@ -2,8 +2,8 @@ import com.example.spot.member.domain.Member; import com.example.spot.vote.domain.Vote; -import com.example.spot.vote.domain.aggregate.VoteParticipant; -import com.example.spot.vote.domain.aggregate.VoteOption; +import com.example.spot.vote.domain.association.VoteParticipant; +import com.example.spot.vote.domain.association.VoteOption; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/example/spot/vote/domain/Vote.java b/src/main/java/com/example/spot/vote/domain/Vote.java index 2a8fa0da..5f0598b1 100644 --- a/src/main/java/com/example/spot/vote/domain/Vote.java +++ b/src/main/java/com/example/spot/vote/domain/Vote.java @@ -3,7 +3,7 @@ import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; import com.example.spot.study.domain.Study; -import com.example.spot.vote.domain.aggregate.VoteOption; +import com.example.spot.vote.domain.association.VoteOption; import jakarta.persistence.*; import java.time.LocalDateTime; diff --git a/src/main/java/com/example/spot/vote/domain/aggregate/VoteOption.java b/src/main/java/com/example/spot/vote/domain/association/VoteOption.java similarity index 97% rename from src/main/java/com/example/spot/vote/domain/aggregate/VoteOption.java rename to src/main/java/com/example/spot/vote/domain/association/VoteOption.java index 16eac517..8f8b91af 100644 --- a/src/main/java/com/example/spot/vote/domain/aggregate/VoteOption.java +++ b/src/main/java/com/example/spot/vote/domain/association/VoteOption.java @@ -1,4 +1,4 @@ -package com.example.spot.vote.domain.aggregate; +package com.example.spot.vote.domain.association; import com.example.spot.common.entity.BaseEntity; import com.example.spot.vote.domain.Vote; import jakarta.persistence.CascadeType; diff --git a/src/main/java/com/example/spot/vote/domain/aggregate/VoteParticipant.java b/src/main/java/com/example/spot/vote/domain/association/VoteParticipant.java similarity index 95% rename from src/main/java/com/example/spot/vote/domain/aggregate/VoteParticipant.java rename to src/main/java/com/example/spot/vote/domain/association/VoteParticipant.java index 415f3f83..278004ce 100644 --- a/src/main/java/com/example/spot/vote/domain/aggregate/VoteParticipant.java +++ b/src/main/java/com/example/spot/vote/domain/association/VoteParticipant.java @@ -1,4 +1,4 @@ -package com.example.spot.vote.domain.aggregate; +package com.example.spot.vote.domain.association; import com.example.spot.member.domain.Member; import com.example.spot.common.entity.BaseEntity; diff --git a/src/main/java/com/example/spot/vote/domain/repository/VoteOptionRepository.java b/src/main/java/com/example/spot/vote/domain/repository/VoteOptionRepository.java index 7d01696d..b9eec6b0 100644 --- a/src/main/java/com/example/spot/vote/domain/repository/VoteOptionRepository.java +++ b/src/main/java/com/example/spot/vote/domain/repository/VoteOptionRepository.java @@ -1,6 +1,6 @@ package com.example.spot.vote.domain.repository; -import com.example.spot.vote.domain.aggregate.VoteOption; +import com.example.spot.vote.domain.association.VoteOption; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/vote/domain/repository/VoteParticipantRepository.java b/src/main/java/com/example/spot/vote/domain/repository/VoteParticipantRepository.java index fe204a16..d450cd27 100644 --- a/src/main/java/com/example/spot/vote/domain/repository/VoteParticipantRepository.java +++ b/src/main/java/com/example/spot/vote/domain/repository/VoteParticipantRepository.java @@ -1,6 +1,6 @@ package com.example.spot.vote.domain.repository; -import com.example.spot.vote.domain.aggregate.VoteParticipant; +import com.example.spot.vote.domain.association.VoteParticipant; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/test/java/com/example/spot/service/notification/NotificationCommandServiceTest.java b/src/test/java/com/example/spot/service/notification/NotificationCommandServiceTest.java index cff57317..e75cf032 100644 --- a/src/test/java/com/example/spot/service/notification/NotificationCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/notification/NotificationCommandServiceTest.java @@ -11,7 +11,7 @@ import com.example.spot.notification.domain.Notification; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.notification.domain.enums.NotifyType; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.Study; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.notification.domain.NotificationRepository; diff --git a/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java b/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java index df0280fc..90227c7a 100644 --- a/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/StudyCommandServiceTest.java @@ -4,17 +4,17 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; -import com.example.spot.study.domain.aggregate.StudyRegion; +import com.example.spot.study.domain.association.StudyRegion; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.StudyMember; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.association.Theme; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.study.domain.enums.StudyState; import com.example.spot.study.domain.enums.ThemeType; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyTheme; import com.example.spot.study.domain.Study; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; diff --git a/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java b/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java index 8c0f11d8..4eff1be3 100644 --- a/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/StudyQueryServiceTest.java @@ -9,13 +9,13 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; -import com.example.spot.study.domain.aggregate.StudyRegion; +import com.example.spot.study.domain.association.StudyRegion; import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; -import com.example.spot.study.domain.aggregate.Region; -import com.example.spot.study.domain.aggregate.StudyMember; -import com.example.spot.study.domain.aggregate.Theme; +import com.example.spot.study.domain.association.Region; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.association.Theme; import com.example.spot.member.domain.enums.Gender; import com.example.spot.member.domain.enums.Status; import com.example.spot.study.domain.enums.StudyApplicationStatus; @@ -26,7 +26,7 @@ import com.example.spot.member.domain.association.MemberTheme; import com.example.spot.member.domain.association.PreferredRegion; import com.example.spot.member.domain.association.PreferredStudy; -import com.example.spot.study.domain.aggregate.StudyTheme; +import com.example.spot.study.domain.association.StudyTheme; import com.example.spot.study.domain.Study; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java index ce3c08c8..ea096525 100644 --- a/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java @@ -9,7 +9,7 @@ import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.todo.domain.ToDo; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Status; @@ -18,7 +18,7 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.todo.domain.ToDoRepository; -import com.example.spot.study.application.MemberStudyCommandServiceImpl; +import com.example.spot.study.application.StudyMemberCommandServiceImpl; import com.example.spot.study.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; @@ -48,7 +48,7 @@ public class StudyMemberCommandServiceTest { @InjectMocks - private MemberStudyCommandServiceImpl memberStudyCommandService; + private StudyMemberCommandServiceImpl memberStudyCommandService; @Mock private StudyRepository studyRepository; diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java index fcfde847..68650fac 100644 --- a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java @@ -3,7 +3,7 @@ import com.example.spot.common.api.exception.GeneralException; import com.example.spot.member.domain.Member; import com.example.spot.story.domain.Story; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.schedule.domain.Schedule; import com.example.spot.todo.domain.ToDo; import com.example.spot.study.domain.enums.StudyApplicationStatus; @@ -15,7 +15,7 @@ import com.example.spot.story.domain.StoryRepository; import com.example.spot.todo.domain.ToDoRepository; import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.study.application.MemberStudyQueryServiceImpl; +import com.example.spot.study.application.StudyMemberQueryServiceImpl; import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; @@ -53,7 +53,7 @@ public class StudyMemberQueryServiceTest { @InjectMocks - private MemberStudyQueryServiceImpl memberStudyQueryService; + private StudyMemberQueryServiceImpl memberStudyQueryService; @Mock private StudyMemberRepository studyMemberRepository; diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java index 5c1d019c..ad5fe6b5 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java @@ -5,15 +5,15 @@ import com.example.spot.notification.domain.Notification; import com.example.spot.story.domain.Story; import com.example.spot.story.domain.StoryRepository; -import com.example.spot.story.domain.aggregate.LikedStory; -import com.example.spot.story.domain.aggregate.LikedStoryComment; -import com.example.spot.story.domain.aggregate.StoryComment; +import com.example.spot.story.domain.association.LikedStory; +import com.example.spot.story.domain.association.LikedStoryComment; +import com.example.spot.story.domain.association.StoryComment; import com.example.spot.story.domain.repository.LikedStoryCommentRepository; import com.example.spot.story.domain.repository.LikedStoryRepository; import com.example.spot.story.domain.repository.StoryCommentRepository; import com.example.spot.story.domain.repository.StoryImageRepository; import com.example.spot.story.domain.repository.StoryReportRepository; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.story.domain.enums.StoryCategory; @@ -22,7 +22,7 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.notification.domain.NotificationRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.StudyPostCommandServiceImpl; +import com.example.spot.story.domain.application.StoryCommandServiceImpl; import com.example.spot.common.application.s3.S3ImageService; import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; @@ -86,7 +86,7 @@ class StoryCommandServiceTest { private S3ImageService s3ImageService; @InjectMocks - private StudyPostCommandServiceImpl studyPostCommandService; + private StoryCommandServiceImpl studyPostCommandService; private static Study study; private static Member member1; diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java index 1101e204..5937e107 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java @@ -3,14 +3,14 @@ import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.story.domain.Story; -import com.example.spot.story.domain.aggregate.LikedStory; -import com.example.spot.story.domain.aggregate.LikedStoryComment; -import com.example.spot.story.domain.aggregate.StoryComment; +import com.example.spot.story.domain.association.LikedStory; +import com.example.spot.story.domain.association.LikedStoryComment; +import com.example.spot.story.domain.association.StoryComment; import com.example.spot.story.domain.enums.StoryCategory; import com.example.spot.story.domain.repository.LikedStoryRepository; import com.example.spot.story.domain.repository.StoryCommentRepository; import com.example.spot.story.domain.StoryRepository; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.story.domain.enums.StoryCategoryQuery; @@ -18,7 +18,7 @@ import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.StudyPostQueryServiceImpl; +import com.example.spot.story.domain.application.StoryQueryServiceImpl; import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResDTO; import org.junit.jupiter.api.BeforeEach; @@ -66,7 +66,7 @@ class StoryQueryServiceTest { private StoryCommentRepository storyCommentRepository; @InjectMocks - private StudyPostQueryServiceImpl studyPostQueryService; + private StoryQueryServiceImpl studyPostQueryService; private static PageRequest pageRequest; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java index 47b0ee99..9e5b400a 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java @@ -3,19 +3,19 @@ import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.schedule.domain.Schedule; -import com.example.spot.schedule.domain.aggregate.Quiz; -import com.example.spot.schedule.domain.aggregate.QuizSubmission; +import com.example.spot.schedule.domain.association.Quiz; +import com.example.spot.schedule.domain.association.QuizSubmission; import com.example.spot.schedule.domain.repository.QuizRepository; import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; import com.example.spot.schedule.domain.ScheduleRepository; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.study.domain.Study; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.MemberStudyCommandServiceImpl; +import com.example.spot.study.application.StudyMemberCommandServiceImpl; import com.example.spot.study.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; import org.junit.jupiter.api.BeforeEach; @@ -63,7 +63,7 @@ class StudyAttendanceCommandServiceTest { private QuizSubmissionRepository quizSubmissionRepository; @InjectMocks - private MemberStudyCommandServiceImpl memberStudyCommandService; + private StudyMemberCommandServiceImpl memberStudyCommandService; private static Study study; private static Member member1; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java index 7c90e95c..99835e35 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java @@ -3,19 +3,19 @@ import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.schedule.domain.Schedule; -import com.example.spot.schedule.domain.aggregate.Quiz; -import com.example.spot.schedule.domain.aggregate.QuizSubmission; +import com.example.spot.schedule.domain.association.Quiz; +import com.example.spot.schedule.domain.association.QuizSubmission; import com.example.spot.schedule.domain.repository.QuizRepository; import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; import com.example.spot.schedule.domain.ScheduleRepository; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.study.domain.Study; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.MemberStudyQueryServiceImpl; +import com.example.spot.study.application.StudyMemberQueryServiceImpl; import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -61,7 +61,7 @@ class StudyAttendanceQueryServiceTest { private QuizSubmissionRepository quizSubmissionRepository; @InjectMocks - private MemberStudyQueryServiceImpl memberStudyQueryService; + private StudyMemberQueryServiceImpl memberStudyQueryService; private static Study study; private static Member member1; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java index 9d855074..cf9ca63a 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java @@ -5,7 +5,7 @@ import com.example.spot.notification.domain.Notification; import com.example.spot.schedule.domain.Schedule; import com.example.spot.schedule.domain.enums.SchedulePeriod; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.study.domain.Study; @@ -14,7 +14,7 @@ import com.example.spot.notification.domain.NotificationRepository; import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.MemberStudyCommandServiceImpl; +import com.example.spot.study.application.StudyMemberCommandServiceImpl; import com.example.spot.study.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; import org.junit.jupiter.api.BeforeEach; @@ -65,7 +65,7 @@ class StudyScheduleCommandServiceTest { private NotificationRepository notificationRepository; @InjectMocks - private MemberStudyCommandServiceImpl memberStudyCommandService; + private StudyMemberCommandServiceImpl memberStudyCommandService; private static Study study1; private static Member member1; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java index 56478d32..6d0a1f79 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java @@ -6,13 +6,13 @@ import com.example.spot.schedule.domain.enums.SchedulePeriod; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; -import com.example.spot.study.domain.aggregate.StudyMember; +import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.Study; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.MemberStudyQueryServiceImpl; +import com.example.spot.study.application.StudyMemberQueryServiceImpl; import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -55,7 +55,7 @@ class StudyScheduleQueryServiceTest { private ScheduleRepository scheduleRepository; @InjectMocks - private MemberStudyQueryServiceImpl memberStudyQueryService; + private StudyMemberQueryServiceImpl memberStudyQueryService; private static Study study1; private static Study study2; From 30e2d6ba83fecbd1ad1e17567630516098549037 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 16:40:33 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[SPOT-286][REFACTOR]=20Schedule=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ScheduleCommandService.java | 26 ++ .../ScheduleCommandServiceImpl.java | 369 ++++++++++++++++++ .../application/ScheduleQueryService.java | 26 ++ .../application/ScheduleQueryServiceImpl.java | 314 +++++++++++++++ .../spot/schedule/domain/Schedule.java | 2 +- .../controller/ScheduleController.java | 209 ++++++++++ .../dto/request/ScheduleRequestDTO.java | 2 +- .../dto/request/StudyQuizRequestDTO.java | 2 +- .../dto/response/ScheduleResponseDTO.java | 2 +- .../dto/response/StudyQuizResponseDTO.java | 2 +- .../response/StudyScheduleResponseDTO.java | 2 +- .../application/StoryCommandService.java | 2 +- .../application/StoryCommandServiceImpl.java | 2 +- .../application/StoryQueryService.java | 2 +- .../application/StoryQueryServiceImpl.java | 2 +- .../StudyMemberCommandService.java | 8 +- .../StudyMemberCommandServiceImpl.java | 8 +- .../application/StudyMemberQueryService.java | 6 +- .../StudyMemberQueryServiceImpl.java | 8 +- ...roller.java => MemberStudyController.java} | 191 +-------- .../controller/StudyPostController.java | 4 +- .../StudyMemberQueryServiceTest.java | 2 +- .../studypost/StoryCommandServiceTest.java | 2 +- .../studypost/StoryQueryServiceTest.java | 2 +- .../StudyAttendanceCommandServiceTest.java | 4 +- .../StudyAttendanceQueryServiceTest.java | 2 +- .../StudyScheduleCommandServiceTest.java | 4 +- .../StudyScheduleQueryServiceTest.java | 2 +- 28 files changed, 987 insertions(+), 220 deletions(-) create mode 100644 src/main/java/com/example/spot/schedule/application/ScheduleCommandService.java create mode 100644 src/main/java/com/example/spot/schedule/application/ScheduleCommandServiceImpl.java create mode 100644 src/main/java/com/example/spot/schedule/application/ScheduleQueryService.java create mode 100644 src/main/java/com/example/spot/schedule/application/ScheduleQueryServiceImpl.java create mode 100644 src/main/java/com/example/spot/schedule/presentation/controller/ScheduleController.java rename src/main/java/com/example/spot/{study => schedule}/presentation/dto/request/ScheduleRequestDTO.java (92%) rename src/main/java/com/example/spot/{study => schedule}/presentation/dto/request/StudyQuizRequestDTO.java (92%) rename src/main/java/com/example/spot/{study => schedule}/presentation/dto/response/ScheduleResponseDTO.java (98%) rename src/main/java/com/example/spot/{study => schedule}/presentation/dto/response/StudyQuizResponseDTO.java (98%) rename src/main/java/com/example/spot/{study => schedule}/presentation/dto/response/StudyScheduleResponseDTO.java (95%) rename src/main/java/com/example/spot/story/{domain => application}/application/StoryCommandService.java (97%) rename src/main/java/com/example/spot/story/{domain => application}/application/StoryCommandServiceImpl.java (99%) rename src/main/java/com/example/spot/story/{domain => application}/application/StoryQueryService.java (93%) rename src/main/java/com/example/spot/story/{domain => application}/application/StoryQueryServiceImpl.java (99%) rename src/main/java/com/example/spot/study/presentation/controller/{StudyMemberController.java => MemberStudyController.java} (72%) diff --git a/src/main/java/com/example/spot/schedule/application/ScheduleCommandService.java b/src/main/java/com/example/spot/schedule/application/ScheduleCommandService.java new file mode 100644 index 00000000..a23fa0b2 --- /dev/null +++ b/src/main/java/com/example/spot/schedule/application/ScheduleCommandService.java @@ -0,0 +1,26 @@ +package com.example.spot.schedule.application; + +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; + +import java.time.LocalDate; + +public interface ScheduleCommandService { + + // 일정 생성 + ScheduleResponseDTO.ScheduleDTO addSchedule(Long studyId, ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO); + + // 일정 수정 + ScheduleResponseDTO.ScheduleDTO modSchedule(Long studyId, Long scheduleId, ScheduleRequestDTO.ScheduleDTO scheduleModDTO); + + // 스터디 퀴즈 생성 + StudyQuizResponseDTO.QuizDTO createAttendanceQuiz(Long studyId, Long scheduleId, StudyQuizRequestDTO.QuizDTO quizRequestDTO); + + // 스터디 출석 + StudyQuizResponseDTO.AttendanceDTO attendantStudy(Long studyId, Long scheduleId, StudyQuizRequestDTO.AttendanceDTO attendanceRequestDTO); + + // 스터디 퀴즈 삭제 + StudyQuizResponseDTO.QuizDTO deleteAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date); +} diff --git a/src/main/java/com/example/spot/schedule/application/ScheduleCommandServiceImpl.java b/src/main/java/com/example/spot/schedule/application/ScheduleCommandServiceImpl.java new file mode 100644 index 00000000..e0313710 --- /dev/null +++ b/src/main/java/com/example/spot/schedule/application/ScheduleCommandServiceImpl.java @@ -0,0 +1,369 @@ +package com.example.spot.schedule.application; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.handler.MemberHandler; +import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.common.security.utils.SecurityUtils; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.MemberRepository; +import com.example.spot.notification.domain.Notification; +import com.example.spot.notification.domain.NotificationRepository; +import com.example.spot.notification.domain.enums.NotifyType; +import com.example.spot.schedule.domain.Schedule; +import com.example.spot.schedule.domain.ScheduleRepository; +import com.example.spot.schedule.domain.association.Quiz; +import com.example.spot.schedule.domain.association.QuizSubmission; +import com.example.spot.schedule.domain.repository.QuizRepository; +import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.StudyRepository; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional +public class ScheduleCommandServiceImpl implements ScheduleCommandService { + + private final MemberRepository memberRepository; + private final StudyRepository studyRepository; + private final StudyMemberRepository studyMemberRepository; + private final NotificationRepository notificationRepository; + private final ScheduleRepository scheduleRepository; + private final QuizRepository quizRepository; + private final QuizSubmissionRepository quizSubmissionRepository; + + /* ----------------------------- 스터디 일정 관련 API ------------------------------------- */ + + /** + * 스터디 일정을 추가하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param scheduleRequestDTO 생성할 일정의 제목, 위치, 시작 일시, 종료 일시, 종일 진행 여부, 반복 여부를 입력 받습니다. + * @return 스터디 아이디와 생성된 일정의 아이디, 제목을 반환합니다. + */ + @Override + public ScheduleResponseDTO.ScheduleDTO addSchedule(Long studyId, ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + //=== Feature ===// + + // Period 기반 시작일 종료일 제한 + checkStartAndFinishDate(scheduleRequestDTO); + + Schedule schedule = Schedule.builder() + .study(study) + .member(member) + .title(scheduleRequestDTO.getTitle()) + .location(scheduleRequestDTO.getLocation()) + .startedAt(scheduleRequestDTO.getStartedAt()) + .finishedAt(scheduleRequestDTO.getFinishedAt()) + .isAllDay(scheduleRequestDTO.getIsAllDay()) + .schedulePeriod(scheduleRequestDTO.getSchedulePeriod()) + .build(); + + // 알림 생성 + + // 스터디에 참여중인 회원들에게 알림 전송 위해 회원 조회 + List members = studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED).stream() + .map(StudyMember::getMember) + .toList(); + + if (members.isEmpty()) + throw new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND); + + members.forEach(studyMember -> { + Notification notification = Notification.builder() + .member(studyMember) + .study(study) + .notifierName(member.getName()) // 일정 생성자 이름 + .type(NotifyType.SCHEDULE_UPDATE) + .isChecked(Boolean.FALSE) + .build(); + notificationRepository.save(notification); + }); + + scheduleRepository.save(schedule); + study.addSchedule(schedule); + member.addSchedule(schedule); + + return ScheduleResponseDTO.ScheduleDTO.toDTO(schedule); + } + + /** + * 스터디 일정을 변경하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param scheduleId 변경할 일정의 아이디를 입력 받습니다. + * @param scheduleModDTO 변경된 일정의 제목, 위치, 시작 일시, 종료 일시, 종일 진행 여부, 반복 여부를 입력 받습니다. + * @return 스터디 아이디와 변경된 일정의 아이디, 제목을 반환합니다. + */ + @Override + public ScheduleResponseDTO.ScheduleDTO modSchedule(Long studyId, Long scheduleId, ScheduleRequestDTO.ScheduleDTO scheduleModDTO) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 로그인한 회원이 일정 생성자인지 확인 + scheduleRepository.findByIdAndMemberId(scheduleId, memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._SCHEDULE_MOD_INVALID)); + + // 해당 스터디의 일정인지 확인 + scheduleRepository.findByIdAndStudyId(scheduleId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); + + //=== Feature ===// + + // Period 기반 시작일 종료일 제한 + checkStartAndFinishDate(scheduleModDTO); + + schedule.modSchedule(scheduleModDTO); + schedule = scheduleRepository.save(schedule); + + study.updateSchedule(schedule); + member.updateSchedule(schedule); + + return ScheduleResponseDTO.ScheduleDTO.toDTO(schedule); + } + + private static void checkStartAndFinishDate(ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO) { + LocalDate startDate = scheduleRequestDTO.getStartedAt().toLocalDate(); + LocalDate finishDate = scheduleRequestDTO.getFinishedAt().toLocalDate(); + System.out.println(startDate); + System.out.println(finishDate); + switch (scheduleRequestDTO.getSchedulePeriod()) { + case DAILY : + // 시작일과 종료일이 일치해야 함 + if (finishDate.equals(startDate.plusDays(1)) || + finishDate.isAfter(startDate.plusDays(1))) { + throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); + } + case WEEKLY : + // 시작일과 종료일이 일주일 이상 차이나지 않아야 함 + if (finishDate.equals(startDate.plusWeeks(1)) || + finishDate.isAfter(startDate.plusWeeks(1))) { + throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); + } + case BIWEEKLY : + // 시작일과 종료일이 2주 이상 차이나지 않아야 함 + if (finishDate.equals(startDate.plusWeeks(2)) || + finishDate.isAfter(startDate.plusWeeks(2))) { + throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); + } + case MONTHLY : + // 시작일과 종료일이 한 달 이상 차이나지 않아야 함 + if (finishDate.equals(startDate.plusMonths(1)) || + finishDate.isAfter(startDate.plusMonths(1))) { + throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); + } + } + } + + + /* ----------------------------- 스터디 출석 관련 API ------------------------------------- */ + + /** + * 출석 퀴즈를 생성하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param scheduleId 타겟 일정의 아이디를 입력 받습니다. + * @param quizRequestDTO 출석 퀴즈에 담길 질문과 정답을 입력 받습니다. + * @return 생성된 퀴즈의 아이디와 질문이 반환됩니다. + */ + @Override + public StudyQuizResponseDTO.QuizDTO createAttendanceQuiz(Long studyId, Long scheduleId, StudyQuizRequestDTO.QuizDTO quizRequestDTO) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 해당 스터디에서 생성된 일정인지 확인 + if (!schedule.getStudy().equals(study)) { + throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND); + } + + // 로그인한 회원이 스터디장인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, Boolean.TRUE) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_QUIZ_CREATION_INVALID)); + + // 요청한 날짜에 이미 출석 퀴즈가 생성되었는지 확인 + LocalDateTime startOfDay = quizRequestDTO.getCreatedAt().withHour(0).withMinute(0).withSecond(0).withNano(0); + LocalDateTime endOfDay = quizRequestDTO.getCreatedAt().withHour(23).withMinute(59).withSecond(59).withNano(999_999_000); + List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); + if (!todayQuizzes.isEmpty()) { + throw new StudyHandler(ErrorStatus._STUDY_QUIZ_ALREADY_EXIST); + } + + //=== Feature ===// + Quiz quiz = Quiz.builder() + .schedule(schedule) + .member(member) + .question(quizRequestDTO.getQuestion()) + .answer(quizRequestDTO.getAnswer()) + .createdAt(quizRequestDTO.getCreatedAt()) + .build(); + + quiz = quizRepository.save(quiz); + schedule.addQuiz(quiz); + member.addQuiz(quiz); + + return StudyQuizResponseDTO.QuizDTO.toDTO(quiz); + } + + /** + * 출석 체크에 사용되는 메서드입니다. + * 메서드 내에서 퀴즈의 제한 시간과 시도 횟수를 확인하며, 조건을 충족하는 경우 회원 출석 정보를 저장합니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param scheduleId 출석을 체크할 일정을 입력 받습니다. + * @param attendanceRequestDTO 퀴즈에 대한 회원의 답변을 입력 받습니다. + * @return 회원 아이디, 퀴즈 아이디, 출석 아이디, 정답 여부, 시도 횟수, 출석 정보 생성 시각을 반환합니다. + */ + @Override + public StudyQuizResponseDTO.AttendanceDTO attendantStudy(Long studyId, Long scheduleId, StudyQuizRequestDTO.AttendanceDTO attendanceRequestDTO) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 요청한 날짜에 생성된 출석 퀴즈 조회 + LocalDateTime startOfDay = attendanceRequestDTO.getDateTime().withHour(0).withMinute(0).withSecond(0).withNano(0); + LocalDateTime endOfDay = attendanceRequestDTO.getDateTime().withHour(23).withMinute(59).withSecond(59).withNano(999_999_000); + List quizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); + if (quizzes.isEmpty()) { + throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); + } + Quiz quiz = quizzes.get(0); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(member.getId(), study.getId(), StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 퀴즈 제한시간 확인 + if (attendanceRequestDTO.getDateTime().isAfter(quiz.getCreatedAt().plusMinutes(5))) { + throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_VALID); + } + + // 이미 출석이 완료되었거나 시도 횟수를 초과하였는지 확인 + List attendanceList = quizSubmissionRepository.findByQuizIdAndMemberId(quiz.getId(), member.getId()); + int try_num = 0; + for (QuizSubmission attendance : attendanceList) { + if (attendance.getIsCorrect()) + throw new StudyHandler(ErrorStatus._STUDY_ATTENDANCE_ALREADY_EXIST); + else + try_num++; + } + if (try_num >= 3) { + throw new StudyHandler(ErrorStatus._STUDY_ATTENDANCE_ATTEMPT_LIMIT_EXCEEDED); + } + + //=== Feature ===// + Boolean isCorrect; + if (attendanceRequestDTO.getAnswer().equals(quiz.getAnswer())) { + isCorrect = Boolean.TRUE; + } else { + isCorrect = Boolean.FALSE; + } + + QuizSubmission quizSubmission = new QuizSubmission(isCorrect); + member.addMemberAttendance(quizSubmission); + quiz.addMemberAttendance(quizSubmission); + quizSubmission = quizSubmissionRepository.save(quizSubmission); + + return StudyQuizResponseDTO.AttendanceDTO.toDTO(quizSubmission, try_num+1); + } + + /** + * 출석 퀴즈를 삭제하는 메서드입니다. + * + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param scheduleId 스터디 일정의 아이디를 입력 받습니다. + * @param date 출석 퀴즈가 생성된 날짜를 입력 받습니다. + * @return 삭제된 퀴즈의 아이디와 질문을 반환합니다. + */ + @Override + public StudyQuizResponseDTO.QuizDTO deleteAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 요청한 날짜에 생성된 출석 퀴즈 조회 + LocalDateTime startOfDay = date.atStartOfDay(); + LocalDateTime endOfDay = date.atStartOfDay().plusDays(1); + List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); + if (todayQuizzes.isEmpty()) { + throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); + } + Quiz quiz = todayQuizzes.get(0); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(member.getId(), study.getId(), StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 로그인한 회원이 스터디장인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, Boolean.TRUE) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_QUIZ_DELETION_INVALID)); + + //=== Feature ===// + quizSubmissionRepository.findByQuizId(quiz.getId()) + .forEach(memberAttendance -> { + quiz.deleteMemberAttendance(memberAttendance); + quizSubmissionRepository.delete(memberAttendance); + }); + quizRepository.delete(quiz); + + return StudyQuizResponseDTO.QuizDTO.toDTO(quiz); + } + + +} diff --git a/src/main/java/com/example/spot/schedule/application/ScheduleQueryService.java b/src/main/java/com/example/spot/schedule/application/ScheduleQueryService.java new file mode 100644 index 00000000..696c1276 --- /dev/null +++ b/src/main/java/com/example/spot/schedule/application/ScheduleQueryService.java @@ -0,0 +1,26 @@ +package com.example.spot.schedule.application; + +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; +import org.springframework.data.domain.Pageable; + +import java.time.LocalDate; + +public interface ScheduleQueryService { + + // 특정 연월 일정 조회하기 + ScheduleResponseDTO.MonthlyScheduleListDTO getMonthlySchedules(Long studyId, int year, int month); + + // 일정 단건 조회하기 + ScheduleResponseDTO.MonthlyScheduleDTO getSchedule(Long studyId, Long scheduleId); + + // 다가오는 모임 일정 조회하기 + StudyScheduleResponseDTO findStudySchedule(Long studyId, Pageable pageable); + + // 금일 회원 출석 여부 조회하기 + StudyQuizResponseDTO.AttendanceListDTO getAllAttendances(Long studyId, Long scheduleId, LocalDate date); + + // 스터디 출석퀴즈 조회하기 + StudyQuizResponseDTO.QuizDTO getAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date); +} diff --git a/src/main/java/com/example/spot/schedule/application/ScheduleQueryServiceImpl.java b/src/main/java/com/example/spot/schedule/application/ScheduleQueryServiceImpl.java new file mode 100644 index 00000000..069aecb0 --- /dev/null +++ b/src/main/java/com/example/spot/schedule/application/ScheduleQueryServiceImpl.java @@ -0,0 +1,314 @@ +package com.example.spot.schedule.application; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.GeneralException; +import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.common.security.utils.SecurityUtils; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.MemberRepository; +import com.example.spot.schedule.domain.Schedule; +import com.example.spot.schedule.domain.ScheduleRepository; +import com.example.spot.schedule.domain.association.Quiz; +import com.example.spot.schedule.domain.association.QuizSubmission; +import com.example.spot.schedule.domain.enums.SchedulePeriod; +import com.example.spot.schedule.domain.repository.QuizRepository; +import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.StudyRepository; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.ArrayList; +import java.util.List; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ScheduleQueryServiceImpl implements ScheduleQueryService { + + private final MemberRepository memberRepository; + private final StudyRepository studyRepository; + private final StudyMemberRepository studyMemberRepository; + private final ScheduleRepository scheduleRepository; + private final QuizRepository quizRepository; + private final QuizSubmissionRepository quizSubmissionRepository; + + /* ----------------------------- 스터디 일정 관련 API ------------------------------------- */ + + /** + * 특정 연/월의 일정을 불러오는 메서드입니다. + * @param studyId 일정을 불러올 스터디의 아이디를 입력 받습니다. + * @param year 일정을 불러올 기준 연도를 입력 받습니다. + * @param month 일정을 불러올 달을 입력 받습니다. + * @return 스터디 아이디와 해당 스터디의 월별 일정 목록을 반환합니다. + */ + @Override + public ScheduleResponseDTO.MonthlyScheduleListDTO getMonthlySchedules(Long studyId, int year, int month) { + + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + boolean isStudyMember; + if (studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED)) { + isStudyMember = true; + } else { + isStudyMember = false; + } + + List monthlyScheduleDTOS = new ArrayList<>(); + + study.getSchedules().forEach(schedule -> { + if (schedule.getSchedulePeriod().equals(SchedulePeriod.NONE)) { + addSchedule(schedule, year, month, monthlyScheduleDTOS, isStudyMember); + } else { + addPeriodSchedules(schedule, year, month, monthlyScheduleDTOS, isStudyMember); + } + }); + + return ScheduleResponseDTO.MonthlyScheduleListDTO.toDTO(study, monthlyScheduleDTOS); + } + + /** + * 하나의 일정에 대한 상세 정보를 불러오는 메서드입니다. + * @param studyId 일정을 불러올 스터디의 아이디를 입력 받습니다. + * @param scheduleId 상세 정보를 물러올 일정의 아이디를 입력 받습니다. + * @return 일정 아이디, 제목, 위치, 시작 일시, 종료 일시, 매일 진행 여부, 주기를 반환합니다. + */ + @Override + public ScheduleResponseDTO.MonthlyScheduleDTO getSchedule(Long studyId, Long scheduleId) { + + // Exception + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + boolean isStudyMember; + if (studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED)) { + isStudyMember = true; + } else { + isStudyMember = false; + } + + // 해당 스터디의 일정인지 확인 + scheduleRepository.findByIdAndStudyId(scheduleId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); + + return ScheduleResponseDTO.MonthlyScheduleDTO.toDTO(schedule, isStudyMember); + } + + /** + * 로그인한 회원이 참여하는 특정 스터디의 다가오는 모임 목록을 페이징 조회 합니다. + * @param studyId 스터디 ID + * @param pageable 페이징 정보 + * @return 다가오는 모임 목록을 반환합니다. + * @throws GeneralException 스터디 일정이 존재하지 않는 경우 + * @throws GeneralException 스터디 멤버가 아닌 경우 + */ + @Override + public StudyScheduleResponseDTO findStudySchedule(Long studyId, Pageable pageable) { + + // 로그인한 회원이 해당 스터디 회원인지 확인 + if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) + throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_SCHEDULE); + + // 스터디 일정 조회 + List schedules = scheduleRepository.findAllByStudyId(studyId, pageable); + + // 스터디 일정이 존재하지 않는 경우 + if (schedules.isEmpty()) + throw new GeneralException(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND); + + // DTO로 변환하여 반환 + List scheduleDTOS = schedules.stream().map(schedule -> StudyScheduleResponseDTO.StudyScheduleDTO.builder() + .title(schedule.getTitle()) + .location(schedule.getLocation()) + .startedAt(schedule.getStartedAt()) + .finishedAt(schedule.getFinishedAt()) + .build()).toList(); + + // 페이징 처리 + return new StudyScheduleResponseDTO(new PageImpl<>(scheduleDTOS, pageable, schedules.size()), scheduleDTOS, schedules.size()); + } + + /** + * 회원이 스터디 구성원인지 확인합니다. + * @param memberId 확인 하려는 회원 ID + * @param studyId 확인 하려는 스터디 ID + * @return 스터디 참여 여부를 반환합니다. + */ + private boolean isMember(Long memberId, Long studyId) { + return studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED).isPresent(); + } + + /** + * 월별 일정 리스트에 주기가 정해져 있지 않은 일정을 추가하기 위한 메서드입니다. + * 일정의 시작일이 기준 연월과 일치하는 경우 월별 일정 리스트에 추가합니다. + * getMonthlySchedules API에서 호출되는 내부 메서드입니다. + * @param schedule 리스트에 추가할 일정 정보를 입력 받습니다. + * @param year 기준 연도를 입력 받습니다. + * @param month 기준 월을 입력 받습니다. + * @param monthlyScheduleDTOS 일정을 추가할 월별 일정 리스트를 입력 받습니다. + * @param isStudyMember 스터디 회원 여부를 입력 받습니다. + */ + private void addSchedule(Schedule schedule, int year, int month, List monthlyScheduleDTOS, boolean isStudyMember) { + if (schedule.getStartedAt().getYear() == year && schedule.getStartedAt().getMonthValue() == month) { + monthlyScheduleDTOS.add(ScheduleResponseDTO.MonthlyScheduleDTO.toDTO(schedule, isStudyMember)); + } + } + + /** + * 월별 일정 리스트에 반복적인 일정을 추가하기 위한 메서드입니다. + * 일정의 시작일이 기준 연월 내에 있는 경우에만 일정을 추가하며, 주기에 따라 하나의 일정이라도 여러 번 추가될 수 있습니다. + * 예를 들어 기준 연월이 2024년 8월이고, 2024년 8월 2일부터 시작되는 WEEKLY 일정이 있다고 가정 + * 1. 이 일정은 기준 연월 내에서 2024년 8월 2일, 8월 9일, 8월 16일, 8월 23일, 8월 30일에 시행 + * 2. 따라서 monthlyScheduleDTOS에 추가되는 일정은 총 5개 + * @param schedule 리스트에 추가할 일정 정보를 입력 받습니다. + * @param year 기준 연도를 입력 받습니다. + * @param month 기준 월을 입력 받습니다. + * @param monthlyScheduleDTOS 일정을 추가할 월별 일정 리스트를 입력 받습니다. + * @param isStudyMember 스터디 회원 여부를 입력 받습니다. + */ + private void addPeriodSchedules(Schedule schedule, int year, int month, List monthlyScheduleDTOS, boolean isStudyMember) { + + LocalDateTime startedAt = schedule.getStartedAt(); + LocalDateTime finishedAt = schedule.getFinishedAt(); + + YearMonth yearMonth = YearMonth.of(year, month); // 탐색 연월 + LocalDateTime endOfMonth = yearMonth.atEndOfMonth().atTime(23, 59, 59); // 탐색 연월의 마지막 날 + + // 일정 시작일이 탐색 연월 내에 있는 경우만 반복 + while (startedAt.isBefore(endOfMonth)) { + // 업데이트된 일정 시작일의 month가 탐색 month와 일치하면 추가 + if (startedAt.getMonthValue() == month) { + monthlyScheduleDTOS.add(ScheduleResponseDTO.MonthlyScheduleDTO.toDTOWithDate(schedule, startedAt, finishedAt, isStudyMember)); + } + + if (schedule.getSchedulePeriod().equals(SchedulePeriod.DAILY)) { + startedAt = startedAt.plusDays(1); + finishedAt = finishedAt.plusDays(1); + } else if (schedule.getSchedulePeriod().equals(SchedulePeriod.WEEKLY)) { + startedAt = startedAt.plusWeeks(1); + finishedAt = finishedAt.plusWeeks(1); + } else if (schedule.getSchedulePeriod().equals(SchedulePeriod.BIWEEKLY)) { + startedAt = startedAt.plusWeeks(2); + finishedAt = finishedAt.plusWeeks(2); + } else if (schedule.getSchedulePeriod().equals(SchedulePeriod.MONTHLY)) { + startedAt = startedAt.plusMonths(1); + finishedAt = finishedAt.plusMonths(1); + } + } + } + + /* ----------------------------- 스터디 출석 관련 API ------------------------------------- */ + + /** + * 특정 일자에 대한 모든 스터디 회원의 출석 정보를 불러옵니다. + * @param studyId 출석 정보를 불러올 스터디의 아이디를 입력 받습니다. + * @param scheduleId 스터디 일정의 아이디를 입력 받습니다. + * @param date 출석 정보를 불러올 날짜를 입력 받습니다. + * @return 모든 스터디 회원에 대한 정보와 출석 여부를 담은 리스트를 반환합니다. + */ + @Override + public StudyQuizResponseDTO.AttendanceListDTO getAllAttendances(Long studyId, Long scheduleId, LocalDate date) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 요청한 날짜에 생성된 출석 퀴즈 조회 + LocalDateTime startOfDay = date.atStartOfDay(); + LocalDateTime endOfDay = date.atStartOfDay().plusDays(1); + List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); + if (todayQuizzes.isEmpty()) { + throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); + } + Quiz quiz = todayQuizzes.get(0); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + //=== Feature ===// + List studyMembers = studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED).stream() + .map(memberStudy -> { + List attendanceList = quizSubmissionRepository.findByQuizIdAndMemberId(quiz.getId(), memberStudy.getMember().getId()); + for (QuizSubmission attendance : attendanceList) { + // MemberAttendance에 퀴즈에 대한 정답이 저장되어 있으면 금일 출석 성공 + if (attendance.getIsCorrect()) + return StudyQuizResponseDTO.StudyMemberDTO.toDTO(memberStudy, Boolean.TRUE); + } + // 퀴즈를 풀지 않았거나 MemberAttendance에 오답만 저장되어 있으면 금일 출석 실패 + return StudyQuizResponseDTO.StudyMemberDTO.toDTO(memberStudy, Boolean.FALSE); + }) + .toList(); + + return StudyQuizResponseDTO.AttendanceListDTO.toDTO(quiz, studyMembers); + + } + + @Override + public StudyQuizResponseDTO.QuizDTO getAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date) { + + // Authorization + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Schedule schedule = scheduleRepository.findById(scheduleId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); + + // 해당 스터디에서 생성된 일정인지 확인 + if (!schedule.getStudy().equals(study)) { + throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND); + } + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 해당 날짜에 생성된 스터디 퀴즈 조회 + LocalDateTime startOfDay = date.atStartOfDay(); + LocalDateTime endOfDay = date.atStartOfDay().plusDays(1); + List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); + if (todayQuizzes.isEmpty()) { + throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); + } + Quiz quiz = todayQuizzes.get(0); + + return StudyQuizResponseDTO.QuizDTO.toDTO(quiz); + } + + +} diff --git a/src/main/java/com/example/spot/schedule/domain/Schedule.java b/src/main/java/com/example/spot/schedule/domain/Schedule.java index f423422a..ff8e0914 100644 --- a/src/main/java/com/example/spot/schedule/domain/Schedule.java +++ b/src/main/java/com/example/spot/schedule/domain/Schedule.java @@ -4,7 +4,7 @@ import com.example.spot.schedule.domain.association.Quiz; import com.example.spot.study.domain.Study; import com.example.spot.schedule.domain.enums.SchedulePeriod; -import com.example.spot.study.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import jakarta.persistence.*; import java.time.LocalDateTime; diff --git a/src/main/java/com/example/spot/schedule/presentation/controller/ScheduleController.java b/src/main/java/com/example/spot/schedule/presentation/controller/ScheduleController.java new file mode 100644 index 00000000..17dd1e67 --- /dev/null +++ b/src/main/java/com/example/spot/schedule/presentation/controller/ScheduleController.java @@ -0,0 +1,209 @@ +package com.example.spot.schedule.presentation.controller; + +import com.example.spot.common.api.ApiResponse; +import com.example.spot.common.api.code.status.SuccessStatus; +import com.example.spot.common.presentation.validator.IntSize; +import com.example.spot.schedule.application.ScheduleCommandService; +import com.example.spot.schedule.application.ScheduleQueryService; +import com.example.spot.schedule.domain.validation.annotation.ExistSchedule; +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; +import com.example.spot.study.domain.validation.annotation.ExistStudy; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/spot") +@Validated +public class ScheduleController { + + private final ScheduleQueryService scheduleQueryService; + private final ScheduleCommandService scheduleCommandService; + + /* ----------------------------- 스터디 일정 관련 API ------------------------------------- */ + + @Tag(name = "스터디 일정") + @Operation(summary = "[스터디 일정] 월별 일정 불러오기", description = """ + ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 클릭, 로그인한 회원이 참여하는 특정 스터디의 일정을 월 단위로 불러옵니다. + 처음 캘린더를 클릭하면 오늘 날짜가 포함된 연/월에 해당하는 일정 목록이 schedule에서 반환됩니다. + 캘린더를 넘기면 해당 연/월에 해당하는 일정 목록이 schedule에서 반환됩니다. + """) + @Parameter(name = "studyId", description = "일정을 불러올 스터디의 id를 입력합니다.", required = true) + @GetMapping("/studies/{studyId}/schedules") + public ApiResponse getMonthlySchedules( + @PathVariable @ExistStudy Long studyId, + @RequestParam @IntSize(min = 1) Integer year, + @RequestParam @IntSize(min = 1, max = 12) Integer month) { + ScheduleResponseDTO.MonthlyScheduleListDTO monthlyScheduleDTO = scheduleQueryService.getMonthlySchedules(studyId, year, month); + return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, monthlyScheduleDTO); + } + + @Tag(name = "스터디 일정") + @Operation(summary = "[스터디 일정] 상세 일정 불러오기", description = """ + ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 > 일정 클릭, 로그인한 회원이 참여하는 특정 스터디의 상세 일정을 불러옵니다. + 스터디의 일정 정보를 상세하게 불러옵니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "scheduleId", description = "불러올 스터디 일정의 id를 입력합니다.", required = true) + @GetMapping("/studies/{studyId}/schedules/{scheduleId}") + public ApiResponse getSchedule( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistSchedule Long scheduleId) { + ScheduleResponseDTO.MonthlyScheduleDTO scheduleDTO = scheduleQueryService.getSchedule(studyId, scheduleId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, scheduleDTO); + } + + @Tag(name = "스터디 일정") + @Operation(summary = "[스터디 일정] 일정 추가하기", description = """ + ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 > 추가 버튼 클릭, 로그인한 회원이 운영하는 특정 스터디에 일정을 추가합니다. + 로그인한 회원이 owner인 경우 schedule에 새로운 일정을 등록합니다. + + period에는 [NONE, DAILY, WEEKLY, BIWEEKLY, MONTHLY] 중 하나를 입력해야 합니다. + """) + @Parameter(name = "studyId", description = "일정을 추가할 스터디의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/schedules") + public ApiResponse addSchedule( + @PathVariable @ExistStudy Long studyId, + @RequestBody @Valid ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO) { + ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = scheduleCommandService.addSchedule(studyId, scheduleRequestDTO); + return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_CREATED, scheduleResponseDTO); + } + + @Tag(name = "스터디 일정") + @Operation(summary = "[스터디 일정] 일정 변경하기", description = """ + ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 > 일정 클릭, 로그인한 회원이 특정 스터디에 등록한 일정을 수정합니다. + 로그인한 회원이 owner인 경우 schedule에 등록한 일정을 수정할 수 있습니다. + + period에는 [NONE, DAILY, WEEKLY, BIWEEKLY, MONTHLY] 중 하나를 입력해야 합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "scheduleId", description = "변경할 일정의 id를 입력합니다.", required = true) + @PatchMapping("/studies/{studyId}/schedules/{scheduleId}") + public ApiResponse modSchedule( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistSchedule Long scheduleId, + @RequestBody @Valid ScheduleRequestDTO.ScheduleDTO scheduleModDTO) { + ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = scheduleCommandService.modSchedule(studyId, scheduleId, scheduleModDTO); + return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_UPDATED, scheduleResponseDTO); + } + + @Tag(name = "스터디 일정") + @Operation(summary = "[스터디 일정] 다가오는 모임 목록 조회하기", description = """ + ## [스터디 일정] 내 스터디 > 스터디 클릭, 로그인한 회원이 참여하는 특정 스터디의 다가오는 모임 목록을 페이징 조회 합니다. + 현재 시점 이후에 진행되는 모임 일정의 목록을 schedule에서 반환합니다. + """) + @GetMapping("/studies/{studyId}/upcoming-schedules") + public ApiResponse getUpcomingSchedules( + @PathVariable @ExistStudy Long studyId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "1") int size){ + StudyScheduleResponseDTO studyScheduleResponseDTO = scheduleQueryService.findStudySchedule(studyId, PageRequest.of(page, size)); + return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, studyScheduleResponseDTO); + } + + + /* ----------------------------- 스터디 출석체크 관련 API ------------------------------------- */ + + @Tag(name = "스터디 출석체크") + @Operation(summary = "[스터디 출석체크] 출석 퀴즈 생성하기", description = """ + ## [스터디 출석체크] 내 스터디 > 스터디 > 캘린더 > 출석체크 > 퀴즈 만들기 클릭, 로그인한 회원이 운영하는 스터디에 퀴즈를 생성합니다. + * 로그인한 회원이 스터디장인 경우 quiz에 새로운 퀴즈를 생성합니다. + * createdAt에는 출석 퀴즈를 생성할 날짜를 입력합니다. + """) + @Parameter(name = "studyId", description = "출석 퀴즈를 생성할 스터디의 id를 입력합니다.", required = true) + @Parameter(name = "scheduleId", description = "출석 퀴즈를 생성할 일정의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/schedules/{scheduleId}/quiz") + public ApiResponse createAttendanceQuiz( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistSchedule Long scheduleId, + @RequestBody @Valid StudyQuizRequestDTO.QuizDTO quizRequestDTO) { + StudyQuizResponseDTO.QuizDTO quizResponseDTO = scheduleCommandService.createAttendanceQuiz(studyId, scheduleId, quizRequestDTO); + return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_CREATED, quizResponseDTO); + } + + @Tag(name = "스터디 출석체크") + @Operation(summary = "[스터디 출석체크] 출석 퀴즈 불러오기", description = """ + ## [스터디 출석체크] 내 스터디 > 스터디 > 캘린더 > 출석체크, 로그인한 회원이 참여하는 스터디의 퀴즈를 불러옵니다. + * 날짜에 해당하는 퀴즈의 아이디와 질문이 반환됩니다. + * date에는 출석 퀴즈를 불러올 날짜를 입력합니다. + """) + @Parameter(name = "studyId", description = "출석 퀴즈를 불러올 스터디의 id를 입력합니다.", required = true) + @Parameter(name = "scheduleId", description = "출석 퀴즈를 불러올 일정의 id를 입력합니다.", required = true) + @GetMapping("/studies/{studyId}/schedules/{scheduleId}/quiz") + public ApiResponse getAttendanceQuiz( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistSchedule Long scheduleId, + @RequestParam LocalDate date) { + StudyQuizResponseDTO.QuizDTO quizDTO = scheduleQueryService.getAttendanceQuiz(studyId, scheduleId, date); + return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_FOUND, quizDTO); + } + + + @Tag(name = "스터디 출석체크") + @Operation(summary = "[스터디 출석체크] 출석 체크하기", description = """ + ## [스터디 출석체크] 내 스터디 > 스터디 > 캘린더 > 이미지 클릭, 로그인한 회원이 참여하는 스터디에서 오늘의 퀴즈를 풀어 출석을 체크합니다. + * 특정 시점의 quiz에 대해 member_attendance 튜플을 추가합니다. + * dateTime에는 출석을 체크할 날짜와 시간을 입력합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "scheduleId", description = "일정의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/schedules/{scheduleId}/attendance") + public ApiResponse attendantStudy( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistSchedule Long scheduleId, + @RequestBody @Valid StudyQuizRequestDTO.AttendanceDTO attendanceRequestDTO) { + StudyQuizResponseDTO.AttendanceDTO attendanceResponseDTO = scheduleCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO); + if (attendanceResponseDTO.getIsCorrect()) { + return ApiResponse.onSuccess(SuccessStatus._STUDY_ATTENDANCE_CREATED_CORRECT_ANSWER, attendanceResponseDTO); + } else { + return ApiResponse.onSuccess(SuccessStatus._STUDY_ATTENDANCE_CREATED_WRONG_ANSWER, attendanceResponseDTO); + } + } + + @Tag(name = "스터디 출석체크") + @Operation(summary = "[스터디 출석체크] 출석 퀴즈 삭제하기", description = """ + ## [스터디 출석체크] 기한이 지난 출석 퀴즈를 삭제합니다. (화면 X) + * PathVariable을 통해 전달받은 정보를 바탕으로 출석 퀴즈를 삭제합니다. + * 출석 퀴즈 정보와 함께 퀴즈에 대한 MemberAttendance(회원 출석) 목록도 함께 삭제됩니다. + * date에는 출석 퀴즈를 삭제할 날짜를 입력합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "scheduleId", description = "일정의 id를 입력합니다.", required = true) + @DeleteMapping("/studies/{studyId}/schedules/{scheduleId}/quiz") + public ApiResponse deleteAttendanceQuiz( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistSchedule Long scheduleId, + @RequestParam LocalDate date) { + StudyQuizResponseDTO.QuizDTO quizDTO = scheduleCommandService.deleteAttendanceQuiz(studyId, scheduleId, date); + return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_DELETED, quizDTO); + } + + @Tag(name = "스터디 출석체크") + @Operation(summary = "[스터디 출석체크] 회원 출석부 불러오기", description = """ + ## [스터디 출석체크] 지정된 날짜의 모든 스터디 회원의 출석 여부를 불러옵니다. + * 출석체크 화면에 표시되는 스터디 회원 정보(프로필 사진, 이름, 출석 여부, 스터디장 여부) 목록를 반환합니다. + * date에는 출석 정보를 확인할 날짜를 입력합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "scheduleId", description = "출석을 확인할 일정의 id를 입력합니다.", required = true) + @GetMapping("/studies/{studyId}/schedules/{scheduleId}/attendance") + public ApiResponse getAllAttendances( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistSchedule Long scheduleId, + @RequestParam LocalDate date) { + StudyQuizResponseDTO.AttendanceListDTO attendanceListDTO = scheduleQueryService.getAllAttendances(studyId, scheduleId, date); + return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_ATTENDANCES_FOUND, attendanceListDTO); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/study/presentation/dto/request/ScheduleRequestDTO.java b/src/main/java/com/example/spot/schedule/presentation/dto/request/ScheduleRequestDTO.java similarity index 92% rename from src/main/java/com/example/spot/study/presentation/dto/request/ScheduleRequestDTO.java rename to src/main/java/com/example/spot/schedule/presentation/dto/request/ScheduleRequestDTO.java index f6404ed5..03e3e8ac 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/request/ScheduleRequestDTO.java +++ b/src/main/java/com/example/spot/schedule/presentation/dto/request/ScheduleRequestDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.request; +package com.example.spot.schedule.presentation.dto.request; import com.example.spot.schedule.domain.enums.SchedulePeriod; import com.example.spot.common.presentation.validator.TextLength; diff --git a/src/main/java/com/example/spot/study/presentation/dto/request/StudyQuizRequestDTO.java b/src/main/java/com/example/spot/schedule/presentation/dto/request/StudyQuizRequestDTO.java similarity index 92% rename from src/main/java/com/example/spot/study/presentation/dto/request/StudyQuizRequestDTO.java rename to src/main/java/com/example/spot/schedule/presentation/dto/request/StudyQuizRequestDTO.java index 3ed655e4..907ed5e6 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/request/StudyQuizRequestDTO.java +++ b/src/main/java/com/example/spot/schedule/presentation/dto/request/StudyQuizRequestDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.request; +package com.example.spot.schedule.presentation.dto.request; import com.example.spot.common.presentation.validator.TextLength; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/ScheduleResponseDTO.java b/src/main/java/com/example/spot/schedule/presentation/dto/response/ScheduleResponseDTO.java similarity index 98% rename from src/main/java/com/example/spot/study/presentation/dto/response/ScheduleResponseDTO.java rename to src/main/java/com/example/spot/schedule/presentation/dto/response/ScheduleResponseDTO.java index d74f0018..2b56c54e 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/ScheduleResponseDTO.java +++ b/src/main/java/com/example/spot/schedule/presentation/dto/response/ScheduleResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.schedule.presentation.dto.response; import com.example.spot.schedule.domain.Schedule; import com.example.spot.schedule.domain.enums.SchedulePeriod; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyQuizResponseDTO.java b/src/main/java/com/example/spot/schedule/presentation/dto/response/StudyQuizResponseDTO.java similarity index 98% rename from src/main/java/com/example/spot/study/presentation/dto/response/StudyQuizResponseDTO.java rename to src/main/java/com/example/spot/schedule/presentation/dto/response/StudyQuizResponseDTO.java index c1172c91..98e88fb7 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyQuizResponseDTO.java +++ b/src/main/java/com/example/spot/schedule/presentation/dto/response/StudyQuizResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.schedule.presentation.dto.response; import com.example.spot.schedule.domain.association.Quiz; import com.example.spot.schedule.domain.association.QuizSubmission; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyScheduleResponseDTO.java b/src/main/java/com/example/spot/schedule/presentation/dto/response/StudyScheduleResponseDTO.java similarity index 95% rename from src/main/java/com/example/spot/study/presentation/dto/response/StudyScheduleResponseDTO.java rename to src/main/java/com/example/spot/schedule/presentation/dto/response/StudyScheduleResponseDTO.java index bc66ab6b..fdd0fa7f 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyScheduleResponseDTO.java +++ b/src/main/java/com/example/spot/schedule/presentation/dto/response/StudyScheduleResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.schedule.presentation.dto.response; import java.time.LocalDateTime; diff --git a/src/main/java/com/example/spot/story/domain/application/StoryCommandService.java b/src/main/java/com/example/spot/story/application/application/StoryCommandService.java similarity index 97% rename from src/main/java/com/example/spot/story/domain/application/StoryCommandService.java rename to src/main/java/com/example/spot/story/application/application/StoryCommandService.java index fba122c8..aec0b9d3 100644 --- a/src/main/java/com/example/spot/story/domain/application/StoryCommandService.java +++ b/src/main/java/com/example/spot/story/application/application/StoryCommandService.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.application; +package com.example.spot.story.application.application; import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; diff --git a/src/main/java/com/example/spot/story/domain/application/StoryCommandServiceImpl.java b/src/main/java/com/example/spot/story/application/application/StoryCommandServiceImpl.java similarity index 99% rename from src/main/java/com/example/spot/story/domain/application/StoryCommandServiceImpl.java rename to src/main/java/com/example/spot/story/application/application/StoryCommandServiceImpl.java index 9da95d7a..8c6c2972 100644 --- a/src/main/java/com/example/spot/story/domain/application/StoryCommandServiceImpl.java +++ b/src/main/java/com/example/spot/story/application/application/StoryCommandServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.application; +package com.example.spot.story.application.application; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; diff --git a/src/main/java/com/example/spot/story/domain/application/StoryQueryService.java b/src/main/java/com/example/spot/story/application/application/StoryQueryService.java similarity index 93% rename from src/main/java/com/example/spot/story/domain/application/StoryQueryService.java rename to src/main/java/com/example/spot/story/application/application/StoryQueryService.java index 82250aa3..91d852b9 100644 --- a/src/main/java/com/example/spot/story/domain/application/StoryQueryService.java +++ b/src/main/java/com/example/spot/story/application/application/StoryQueryService.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.application; +package com.example.spot.story.application.application; import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; diff --git a/src/main/java/com/example/spot/story/domain/application/StoryQueryServiceImpl.java b/src/main/java/com/example/spot/story/application/application/StoryQueryServiceImpl.java similarity index 99% rename from src/main/java/com/example/spot/story/domain/application/StoryQueryServiceImpl.java rename to src/main/java/com/example/spot/story/application/application/StoryQueryServiceImpl.java index 58781213..dd33e450 100644 --- a/src/main/java/com/example/spot/story/domain/application/StoryQueryServiceImpl.java +++ b/src/main/java/com/example/spot/story/application/application/StoryQueryServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.application; +package com.example.spot.story.application.application; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java index 9abc89b9..6aa7724c 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java @@ -1,14 +1,14 @@ package com.example.spot.study.application; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.study.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.study.presentation.dto.request.StudyQuizRequestDTO; +import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; -import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java index 26e1033f..062c1f9f 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java @@ -29,14 +29,14 @@ import com.example.spot.story.domain.StoryRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.todo.domain.ToDoRepository; -import com.example.spot.study.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.study.presentation.dto.request.StudyQuizRequestDTO; +import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; -import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java index 8d2af24b..17459fa4 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java @@ -1,14 +1,14 @@ package com.example.spot.study.application; import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO; -import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; -import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java index 62aebc33..9d683272 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java @@ -21,14 +21,14 @@ import com.example.spot.story.domain.StoryRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.todo.domain.ToDoRepository; -import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; -import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; @@ -44,7 +44,7 @@ import com.example.spot.common.api.exception.GeneralException; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplyMemberDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyMemberDTO; -import com.example.spot.study.presentation.dto.response.StudyScheduleResponseDTO.StudyScheduleDTO; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO.StudyScheduleDTO; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; diff --git a/src/main/java/com/example/spot/study/presentation/controller/StudyMemberController.java b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java similarity index 72% rename from src/main/java/com/example/spot/study/presentation/controller/StudyMemberController.java rename to src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java index 013425e1..7ec1242e 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/StudyMemberController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java @@ -12,16 +12,16 @@ import com.example.spot.vote.domain.validation.annotation.ExistVote; import com.example.spot.common.presentation.validator.IntSize; import com.example.spot.common.presentation.validator.TextLength; -import com.example.spot.study.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.study.presentation.dto.request.StudyQuizRequestDTO; +import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; -import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; @@ -34,7 +34,6 @@ import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; -import com.example.spot.study.presentation.dto.response.StudyScheduleResponseDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -52,7 +51,7 @@ @RequiredArgsConstructor @RequestMapping("/spot") @Validated -public class StudyMemberController { +public class MemberStudyController { private final StudyMemberQueryService studyMemberQueryService; private final StudyMemberCommandService studyMemberCommandService; @@ -166,7 +165,7 @@ public ApiResponse rejectApplicant( @Tag(name = "테스트 용 API", description = "테스트 용 API") @Operation(summary = "!테스트 용! [모집중인 스터디] 스터디 신청 처리하기", description = """ ## [모집중인 스터디] 빠른 API 적용를 위해 스터디 신청 처리를 즉시 수행합니다. - 위 API와 동일한 기능을 수행하지만, 실제 알림이 발송되지 않습니다. + 위 API와 동일한 기능을 수행하지만, 실제 알림이 발송되지 않습니다. 또한 스터디 신청 승인 시, 회원의 상태가 AWAITING_SELF_APPROVAL가 아닌 APPROVED로 변경됩니다. 즉, 바로 스터디 참여가 완료됩니다. 스터디 회원 조회 시, 바로 승인된 회원으로 조회됩니다. @@ -199,19 +198,7 @@ public ApiResponse getRecentAnnouncement(@PathVariable @Ex StudyPostResponseDTO studyPostResponseDTO = studyMemberQueryService.findStudyAnnouncementPost(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, studyPostResponseDTO); } - @Tag(name = "스터디 상세 정보") - @Operation(summary = "[스터디 상세 정보] 다가오는 모임 목록 불러오기", description = """ - ## [스터디 상세 정보] 내 스터디 > 스터디 클릭, 로그인한 회원이 참여하는 특정 스터디의 다가오는 모임 목록을 페이징 조회 합니다. - 현재 시점 이후에 진행되는 모임 일정의 목록을 schedule에서 반환합니다. - """) - @GetMapping("/studies/{studyId}/upcoming-schedules") - public ApiResponse getUpcomingSchedules( - @PathVariable @ExistStudy Long studyId, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "1") int size){ - StudyScheduleResponseDTO studyScheduleResponseDTO = studyMemberQueryService.findStudySchedule(studyId, PageRequest.of(page, size)); - return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, studyScheduleResponseDTO); - } + @Tag(name = "스터디 상세 정보") @Operation(summary = "[스터디 상세 정보] 스터디에 참여하는 회원 목록 불러오기", description = """ ## [스터디 상세 정보] 로그인한 회원이 참여하는 특정 스터디의 회원 목록을 전체 조회 합니다. @@ -238,75 +225,6 @@ public ApiResponse getStudyHost( return ApiResponse.onSuccess(SuccessStatus._STUDY_HOST_FOUND, studyHostDTO); } - -/* ----------------------------- 스터디 일정 관련 API ------------------------------------- */ - - @Tag(name = "스터디 일정") - @Operation(summary = "[스터디 일정] 월별 일정 불러오기", description = """ - ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 클릭, 로그인한 회원이 참여하는 특정 스터디의 일정을 월 단위로 불러옵니다. - 처음 캘린더를 클릭하면 오늘 날짜가 포함된 연/월에 해당하는 일정 목록이 schedule에서 반환됩니다. - 캘린더를 넘기면 해당 연/월에 해당하는 일정 목록이 schedule에서 반환됩니다. - """) - @Parameter(name = "studyId", description = "일정을 불러올 스터디의 id를 입력합니다.", required = true) - @GetMapping("/studies/{studyId}/schedules") - public ApiResponse getMonthlySchedules( - @PathVariable @ExistStudy Long studyId, - @RequestParam @IntSize(min = 1) Integer year, - @RequestParam @IntSize(min = 1, max= 12) Integer month) { - ScheduleResponseDTO.MonthlyScheduleListDTO monthlyScheduleDTO = studyMemberQueryService.getMonthlySchedules(studyId, year, month); - return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, monthlyScheduleDTO); - } - - @Tag(name = "스터디 일정") - @Operation(summary = "[스터디 일정] 상세 일정 불러오기", description = """ - ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 > 일정 클릭, 로그인한 회원이 참여하는 특정 스터디의 상세 일정을 불러옵니다. - 스터디의 일정 정보를 상세하게 불러옵니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "scheduleId", description = "불러올 스터디 일정의 id를 입력합니다.", required = true) - @GetMapping("/studies/{studyId}/schedules/{scheduleId}") - public ApiResponse getSchedule( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistSchedule Long scheduleId) { - ScheduleResponseDTO.MonthlyScheduleDTO scheduleDTO = studyMemberQueryService.getSchedule(studyId, scheduleId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_FOUND, scheduleDTO); - } - - @Tag(name = "스터디 일정") - @Operation(summary = "[스터디 일정] 일정 추가하기", description = """ - ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 > 추가 버튼 클릭, 로그인한 회원이 운영하는 특정 스터디에 일정을 추가합니다. - 로그인한 회원이 owner인 경우 schedule에 새로운 일정을 등록합니다. - - period에는 [NONE, DAILY, WEEKLY, BIWEEKLY, MONTHLY] 중 하나를 입력해야 합니다. - """) - @Parameter(name = "studyId", description = "일정을 추가할 스터디의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/schedules") - public ApiResponse addSchedule( - @PathVariable @ExistStudy Long studyId, - @RequestBody @Valid ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO) { - ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = studyMemberCommandService.addSchedule(studyId, scheduleRequestDTO); - return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_CREATED, scheduleResponseDTO); - } - - @Tag(name = "스터디 일정") - @Operation(summary = "[스터디 일정] 일정 변경하기", description = """ - ## [스터디 일정] 내 스터디 > 스터디 > 캘린더 > 일정 클릭, 로그인한 회원이 특정 스터디에 등록한 일정을 수정합니다. - 로그인한 회원이 owner인 경우 schedule에 등록한 일정을 수정할 수 있습니다. - - period에는 [NONE, DAILY, WEEKLY, BIWEEKLY, MONTHLY] 중 하나를 입력해야 합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "scheduleId", description = "변경할 일정의 id를 입력합니다.", required = true) - @PatchMapping("/studies/{studyId}/schedules/{scheduleId}") - public ApiResponse modSchedule( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistSchedule Long scheduleId, - @RequestBody @Valid ScheduleRequestDTO.ScheduleDTO scheduleModDTO) { - ScheduleResponseDTO.ScheduleDTO scheduleResponseDTO = studyMemberCommandService.modSchedule(studyId, scheduleId, scheduleModDTO); - return ApiResponse.onSuccess(SuccessStatus._STUDY_SCHEDULE_UPDATED, scheduleResponseDTO); - } - - /* ----------------------------- 스터디 투표 관련 API ------------------------------------- */ @Tag(name = "스터디 투표") @@ -437,101 +355,6 @@ public ApiResponse getAllStudyImages( return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_IMAGES_FOUND, imageListDTO); } -/* ----------------------------- 스터디 출석체크 관련 API ------------------------------------- */ - - @Tag(name = "스터디 출석체크") - @Operation(summary = "[스터디 출석체크] 출석 퀴즈 생성하기", description = """ - ## [스터디 출석체크] 내 스터디 > 스터디 > 캘린더 > 출석체크 > 퀴즈 만들기 클릭, 로그인한 회원이 운영하는 스터디에 퀴즈를 생성합니다. - * 로그인한 회원이 스터디장인 경우 quiz에 새로운 퀴즈를 생성합니다. - * createdAt에는 출석 퀴즈를 생성할 날짜를 입력합니다. - """) - @Parameter(name = "studyId", description = "출석 퀴즈를 생성할 스터디의 id를 입력합니다.", required = true) - @Parameter(name = "scheduleId", description = "출석 퀴즈를 생성할 일정의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/schedules/{scheduleId}/quiz") - public ApiResponse createAttendanceQuiz( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistSchedule Long scheduleId, - @RequestBody @Valid StudyQuizRequestDTO.QuizDTO quizRequestDTO) { - StudyQuizResponseDTO.QuizDTO quizResponseDTO = studyMemberCommandService.createAttendanceQuiz(studyId, scheduleId, quizRequestDTO); - return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_CREATED, quizResponseDTO); - } - - @Tag(name = "스터디 출석체크") - @Operation(summary = "[스터디 출석체크] 출석 퀴즈 불러오기", description = """ - ## [스터디 출석체크] 내 스터디 > 스터디 > 캘린더 > 출석체크, 로그인한 회원이 참여하는 스터디의 퀴즈를 불러옵니다. - * 날짜에 해당하는 퀴즈의 아이디와 질문이 반환됩니다. - * date에는 출석 퀴즈를 불러올 날짜를 입력합니다. - """) - @Parameter(name = "studyId", description = "출석 퀴즈를 불러올 스터디의 id를 입력합니다.", required = true) - @Parameter(name = "scheduleId", description = "출석 퀴즈를 불러올 일정의 id를 입력합니다.", required = true) - @GetMapping("/studies/{studyId}/schedules/{scheduleId}/quiz") - public ApiResponse getAttendanceQuiz( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistSchedule Long scheduleId, - @RequestParam LocalDate date) { - StudyQuizResponseDTO.QuizDTO quizDTO = studyMemberQueryService.getAttendanceQuiz(studyId, scheduleId, date); - return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_FOUND, quizDTO); - } - - - @Tag(name = "스터디 출석체크") - @Operation(summary = "[스터디 출석체크] 출석 체크하기", description = """ - ## [스터디 출석체크] 내 스터디 > 스터디 > 캘린더 > 이미지 클릭, 로그인한 회원이 참여하는 스터디에서 오늘의 퀴즈를 풀어 출석을 체크합니다. - * 특정 시점의 quiz에 대해 member_attendance 튜플을 추가합니다. - * dateTime에는 출석을 체크할 날짜와 시간을 입력합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "scheduleId", description = "일정의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/schedules/{scheduleId}/attendance") - public ApiResponse attendantStudy( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistSchedule Long scheduleId, - @RequestBody @Valid StudyQuizRequestDTO.AttendanceDTO attendanceRequestDTO) { - StudyQuizResponseDTO.AttendanceDTO attendanceResponseDTO = studyMemberCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO); - if (attendanceResponseDTO.getIsCorrect()) { - return ApiResponse.onSuccess(SuccessStatus._STUDY_ATTENDANCE_CREATED_CORRECT_ANSWER, attendanceResponseDTO); - } else { - return ApiResponse.onSuccess(SuccessStatus._STUDY_ATTENDANCE_CREATED_WRONG_ANSWER, attendanceResponseDTO); - } - } - - @Tag(name = "스터디 출석체크") - @Operation(summary = "[스터디 출석체크] 출석 퀴즈 삭제하기", description = """ - ## [스터디 출석체크] 기한이 지난 출석 퀴즈를 삭제합니다. (화면 X) - * PathVariable을 통해 전달받은 정보를 바탕으로 출석 퀴즈를 삭제합니다. - * 출석 퀴즈 정보와 함께 퀴즈에 대한 MemberAttendance(회원 출석) 목록도 함께 삭제됩니다. - * date에는 출석 퀴즈를 삭제할 날짜를 입력합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "scheduleId", description = "일정의 id를 입력합니다.", required = true) - @DeleteMapping("/studies/{studyId}/schedules/{scheduleId}/quiz") - public ApiResponse deleteAttendanceQuiz( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistSchedule Long scheduleId, - @RequestParam LocalDate date) { - StudyQuizResponseDTO.QuizDTO quizDTO = studyMemberCommandService.deleteAttendanceQuiz(studyId, scheduleId, date); - return ApiResponse.onSuccess(SuccessStatus._STUDY_QUIZ_DELETED, quizDTO); - } - - @Tag(name = "스터디 출석체크") - @Operation(summary = "[스터디 출석체크] 회원 출석부 불러오기", description = """ - ## [스터디 출석체크] 지정된 날짜의 모든 스터디 회원의 출석 여부를 불러옵니다. - * 출석체크 화면에 표시되는 스터디 회원 정보(프로필 사진, 이름, 출석 여부, 스터디장 여부) 목록를 반환합니다. - * date에는 출석 정보를 확인할 날짜를 입력합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "scheduleId", description = "출석을 확인할 일정의 id를 입력합니다.", required = true) - @GetMapping("/studies/{studyId}/schedules/{scheduleId}/attendance") - public ApiResponse getAllAttendances( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistSchedule Long scheduleId, - @RequestParam LocalDate date) { - StudyQuizResponseDTO.AttendanceListDTO attendanceListDTO = studyMemberQueryService.getAllAttendances(studyId, scheduleId, date); - return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_ATTENDANCES_FOUND, attendanceListDTO); - } - - - /* ----------------------------- 스터디 회원 신고 관련 API ------------------------------------- */ @Tag(name = "스터디 신고") diff --git a/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java b/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java index 71eb7197..b5d2390d 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java @@ -4,8 +4,8 @@ import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.common.application.s3.S3ImageService; -import com.example.spot.story.domain.application.StoryCommandService; -import com.example.spot.story.domain.application.StoryQueryService; +import com.example.spot.story.application.application.StoryCommandService; +import com.example.spot.story.application.application.StoryQueryService; import com.example.spot.study.domain.validation.annotation.ExistStudy; import com.example.spot.story.domain.validation.annotation.ExistStory; import com.example.spot.story.domain.validation.annotation.ExistStoryComment; diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java index 68650fac..0278ae05 100644 --- a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java @@ -21,7 +21,7 @@ import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplyMemberDTO; import com.example.spot.study.presentation.dto.response.StudyPostResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import java.time.LocalDate; import java.util.Collections; import java.util.List; diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java index ad5fe6b5..a33f27e3 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java @@ -22,7 +22,7 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.notification.domain.NotificationRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.story.domain.application.StoryCommandServiceImpl; +import com.example.spot.story.application.application.StoryCommandServiceImpl; import com.example.spot.common.application.s3.S3ImageService; import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java index 5937e107..3ee3f23d 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java @@ -18,7 +18,7 @@ import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.story.domain.application.StoryQueryServiceImpl; +import com.example.spot.story.application.application.StoryQueryServiceImpl; import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; import com.example.spot.study.presentation.dto.response.StudyPostResDTO; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java index 9e5b400a..8b29935d 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java @@ -16,8 +16,8 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.study.application.StudyMemberCommandServiceImpl; -import com.example.spot.study.presentation.dto.request.StudyQuizRequestDTO; -import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java index 99835e35..86a77e74 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java @@ -16,7 +16,7 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.study.application.StudyMemberQueryServiceImpl; -import com.example.spot.study.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java index cf9ca63a..d5eeca24 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java @@ -15,8 +15,8 @@ import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.study.application.StudyMemberCommandServiceImpl; -import com.example.spot.study.presentation.dto.request.ScheduleRequestDTO; -import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java index 6d0a1f79..fa3cdb47 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java @@ -13,7 +13,7 @@ import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.study.application.StudyMemberQueryServiceImpl; -import com.example.spot.study.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From e2fc1f3bb833283ae36dee34c6df08646bb6edfd Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 16:49:27 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[SPOT-286][REFACTOR]=20Story=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/StoryCommandService.java | 46 +++ .../StoryCommandServiceImpl.java | 62 ++-- .../story/application/StoryQueryService.java | 18 + .../StoryQueryServiceImpl.java | 22 +- .../application/StoryCommandService.java | 46 --- .../application/StoryQueryService.java | 18 - .../com/example/spot/story/domain/Story.java | 4 +- .../web/controller/StoryController.java} | 84 ++--- .../dto/request/StoryCommentRequestDTO.java} | 4 +- .../web/dto/request/StoryRequestDTO.java} | 4 +- .../response/StoryCommentResponseDTO.java} | 4 +- .../web/dto/response/StoryResDTO.java} | 4 +- .../web/dto/response/StoryResponseDTO.java} | 4 +- .../StudyMemberCommandService.java | 25 +- .../StudyMemberCommandServiceImpl.java | 336 +----------------- .../application/StudyMemberQueryService.java | 4 +- .../StudyMemberQueryServiceImpl.java | 6 +- .../validation/annotation}/ExistRegion.java | 4 +- .../validator}/ExistRegionValidator.java | 4 +- .../controller/MemberStudyController.java | 20 +- .../StudyMemberQueryServiceTest.java | 4 +- .../studypost/StoryCommandServiceTest.java | 60 ++-- .../studypost/StoryQueryServiceTest.java | 18 +- 23 files changed, 222 insertions(+), 579 deletions(-) create mode 100644 src/main/java/com/example/spot/story/application/StoryCommandService.java rename src/main/java/com/example/spot/story/application/{application => }/StoryCommandServiceImpl.java (92%) create mode 100644 src/main/java/com/example/spot/story/application/StoryQueryService.java rename src/main/java/com/example/spot/story/application/{application => }/StoryQueryServiceImpl.java (90%) delete mode 100644 src/main/java/com/example/spot/story/application/application/StoryCommandService.java delete mode 100644 src/main/java/com/example/spot/story/application/application/StoryQueryService.java rename src/main/java/com/example/spot/{study/presentation/controller/StudyPostController.java => story/web/controller/StoryController.java} (80%) rename src/main/java/com/example/spot/{study/presentation/dto/request/StudyPostCommentRequestDTO.java => story/web/dto/request/StoryCommentRequestDTO.java} (80%) rename src/main/java/com/example/spot/{study/presentation/dto/request/StudyPostRequestDTO.java => story/web/dto/request/StoryRequestDTO.java} (92%) rename src/main/java/com/example/spot/{study/presentation/dto/response/StudyPostCommentResponseDTO.java => story/web/dto/response/StoryCommentResponseDTO.java} (98%) rename src/main/java/com/example/spot/{study/presentation/dto/response/StudyPostResDTO.java => story/web/dto/response/StoryResDTO.java} (98%) rename src/main/java/com/example/spot/{study/presentation/dto/response/StudyPostResponseDTO.java => story/web/dto/response/StoryResponseDTO.java} (70%) rename src/main/java/com/example/spot/{common/presentation/validator => study/domain/validation/annotation}/ExistRegion.java (82%) rename src/main/java/com/example/spot/{common/infrastructure => study/domain/validation/validator}/ExistRegionValidator.java (91%) diff --git a/src/main/java/com/example/spot/story/application/StoryCommandService.java b/src/main/java/com/example/spot/story/application/StoryCommandService.java new file mode 100644 index 00000000..9ab27cdb --- /dev/null +++ b/src/main/java/com/example/spot/story/application/StoryCommandService.java @@ -0,0 +1,46 @@ +package com.example.spot.story.application; + +import com.example.spot.story.web.dto.request.StoryCommentRequestDTO; +import com.example.spot.story.web.dto.request.StoryRequestDTO; +import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; + +public interface StoryCommandService { + + // 스터디 게시글 생성 + StoryResDTO.PostPreviewDTO createPost(Long studyId, StoryRequestDTO.PostDTO postRequestDTO); + + // 스터디 게시글 편집 + StoryResDTO.PostPreviewDTO updatePost(Long studyId, Long postId, StoryRequestDTO.PostDTO postDTO); + + // 스터디 게시글 삭제 + StoryResDTO.PostPreviewDTO deletePost(Long studyId, Long postId); + + // 스터디 게시글 좋아요 + StoryResDTO.PostLikeNumDTO likePost(Long studyId, Long postId); + + // 스터디 게시글 좋아요 취소 + StoryResDTO.PostLikeNumDTO cancelPostLike(Long studyId, Long postId); + + // 스터디 게시글 댓글 생성 + StoryCommentResponseDTO.CommentDTO createComment(Long studyId, Long postId, StoryCommentRequestDTO.CommentDTO commentRequestDTO); + + // 스터디 게시글 답글 생성 + StoryCommentResponseDTO.CommentDTO createReply(Long studyId, Long postId, Long commentId, StoryCommentRequestDTO.CommentDTO commentRequestDTO); + + // 스터디 게시글 댓글 삭제 (댓/답글 구분 X) + StoryCommentResponseDTO.CommentIdDTO deleteComment(Long studyId, Long postId, Long commentId); + + // 스터디 게시글 댓글 좋아요 + StoryCommentResponseDTO.CommentPreviewDTO likeComment(Long studyId, Long postId, Long commentId); + + // 스터디 게시글 댓글 싫어요 + StoryCommentResponseDTO.CommentPreviewDTO dislikeComment(Long studyId, Long postId, Long commentId); + + // 스터디 게시글 댓글 좋아요 취소 + StoryCommentResponseDTO.CommentPreviewDTO cancelCommentLike(Long studyId, Long postId, Long commentId); + + // 스터디 게시글 댓글 싫어요 취소 + StoryCommentResponseDTO.CommentPreviewDTO cancelCommentDislike(Long studyId, Long postId, Long commentId); + +} diff --git a/src/main/java/com/example/spot/story/application/application/StoryCommandServiceImpl.java b/src/main/java/com/example/spot/story/application/StoryCommandServiceImpl.java similarity index 92% rename from src/main/java/com/example/spot/story/application/application/StoryCommandServiceImpl.java rename to src/main/java/com/example/spot/story/application/StoryCommandServiceImpl.java index 8c6c2972..d754e532 100644 --- a/src/main/java/com/example/spot/story/application/application/StoryCommandServiceImpl.java +++ b/src/main/java/com/example/spot/story/application/StoryCommandServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.spot.story.application.application; +package com.example.spot.story.application; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; @@ -26,10 +26,10 @@ import com.example.spot.study.domain.StudyRepository; import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.common.application.s3.S3ImageService; -import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; -import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; -import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; +import com.example.spot.story.web.dto.request.StoryCommentRequestDTO; +import com.example.spot.story.web.dto.request.StoryRequestDTO; +import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -72,7 +72,7 @@ public class StoryCommandServiceImpl implements StoryCommandService { * @return 작성된 스터디 게시글의 Preview(게시글 아이디, 제목)를 반환합니다. */ @Override - public StudyPostResDTO.PostPreviewDTO createPost(Long studyId, StudyPostRequestDTO.PostDTO postRequestDTO) { + public StoryResDTO.PostPreviewDTO createPost(Long studyId, StoryRequestDTO.PostDTO postRequestDTO) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -157,11 +157,11 @@ public StudyPostResDTO.PostPreviewDTO createPost(Long studyId, StudyPostRequestD member.updateStudyPost(story); study.updateStudyPost(story); - return StudyPostResDTO.PostPreviewDTO.toDTO(story); + return StoryResDTO.PostPreviewDTO.toDTO(story); } @Override - public StudyPostResDTO.PostPreviewDTO updatePost(Long studyId, Long postId, StudyPostRequestDTO.PostDTO postDTO) { + public StoryResDTO.PostPreviewDTO updatePost(Long studyId, Long postId, StoryRequestDTO.PostDTO postDTO) { Long memberId = SecurityUtils.getCurrentUserId(); SecurityUtils.verifyUserId(memberId); @@ -192,10 +192,10 @@ public StudyPostResDTO.PostPreviewDTO updatePost(Long studyId, Long postId, Stud // 스터디 게시글 업데이트 story.updatePost(postDTO); - return StudyPostResDTO.PostPreviewDTO.toDTO(story); + return StoryResDTO.PostPreviewDTO.toDTO(story); } - private void updateStudyPostImage(StudyPostRequestDTO.PostDTO postDTO, Story story) { + private void updateStudyPostImage(StoryRequestDTO.PostDTO postDTO, Story story) { List storyImages = story.getImages(); // 기존 이미지가 존재하는 경우 이미지 유지 if (!StringUtils.hasText(postDTO.getExistingImage())) { @@ -217,7 +217,7 @@ private void updateStudyPostImage(StudyPostRequestDTO.PostDTO postDTO, Story sto * @return 삭제된 스터디 게시글의 Preview(게시글 아이디, 제목)를 반환합니다. */ @Override - public StudyPostResDTO.PostPreviewDTO deletePost(Long studyId, Long postId) { + public StoryResDTO.PostPreviewDTO deletePost(Long studyId, Long postId) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -261,7 +261,7 @@ public StudyPostResDTO.PostPreviewDTO deletePost(Long studyId, Long postId) { throw new StudyHandler(ErrorStatus._STUDY_POST_DELETION_INVALID); } - return StudyPostResDTO.PostPreviewDTO.toDTO(story); + return StoryResDTO.PostPreviewDTO.toDTO(story); } /** @@ -272,7 +272,7 @@ public StudyPostResDTO.PostPreviewDTO deletePost(Long studyId, Long postId) { * @return 게시글의 Preview(게시글 아이디, 제목)와 함께 좋아요 개수가 반환됩니다. */ @Override - public StudyPostResDTO.PostLikeNumDTO likePost(Long studyId, Long postId) { + public StoryResDTO.PostLikeNumDTO likePost(Long studyId, Long postId) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -311,7 +311,7 @@ public StudyPostResDTO.PostLikeNumDTO likePost(Long studyId, Long postId) { story.plusLikeNum(); story = storyRepository.save(story); - return StudyPostResDTO.PostLikeNumDTO.toDTO(story); + return StoryResDTO.PostLikeNumDTO.toDTO(story); } /** @@ -322,7 +322,7 @@ public StudyPostResDTO.PostLikeNumDTO likePost(Long studyId, Long postId) { * @return 게시글의 Preview(게시글 아이디, 제목)와 함께 좋아요 개수가 반환됩니다. */ @Override - public StudyPostResDTO.PostLikeNumDTO cancelPostLike(Long studyId, Long postId) { + public StoryResDTO.PostLikeNumDTO cancelPostLike(Long studyId, Long postId) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -353,7 +353,7 @@ public StudyPostResDTO.PostLikeNumDTO cancelPostLike(Long studyId, Long postId) likedStoryRepository.delete(likedStory); storyRepository.save(story); - return StudyPostResDTO.PostLikeNumDTO.toDTO(story); + return StoryResDTO.PostLikeNumDTO.toDTO(story); } /* ----------------------------- 스터디 게시글 댓글 관련 API ------------------------------------- */ @@ -366,7 +366,7 @@ public StudyPostResDTO.PostLikeNumDTO cancelPostLike(Long studyId, Long postId) * @return 댓글 아이디와 작성자, 내용, 좋아요와 싫어요 개수를 함께 반환합니다. */ @Override - public StudyPostCommentResponseDTO.CommentDTO createComment(Long studyId, Long postId, StudyPostCommentRequestDTO.CommentDTO commentRequestDTO) { + public StoryCommentResponseDTO.CommentDTO createComment(Long studyId, Long postId, StoryCommentRequestDTO.CommentDTO commentRequestDTO) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -410,7 +410,7 @@ public StudyPostCommentResponseDTO.CommentDTO createComment(Long studyId, Long p story.addComment(storyComment); member.addComment(storyComment); - return StudyPostCommentResponseDTO.CommentDTO.toDTO(storyComment, "익명"+anonymousNum, defaultImage); + return StoryCommentResponseDTO.CommentDTO.toDTO(storyComment, "익명"+anonymousNum, defaultImage); } /** @@ -421,7 +421,7 @@ public StudyPostCommentResponseDTO.CommentDTO createComment(Long studyId, Long p * @return 댓글 아이디와 작성자, 내용, 좋아요와 싫어요 개수를 함께 반환합니다. */ @Override - public StudyPostCommentResponseDTO.CommentDTO createReply(Long studyId, Long postId, Long commentId, StudyPostCommentRequestDTO.CommentDTO commentRequestDTO) { + public StoryCommentResponseDTO.CommentDTO createReply(Long studyId, Long postId, Long commentId, StoryCommentRequestDTO.CommentDTO commentRequestDTO) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -470,7 +470,7 @@ public StudyPostCommentResponseDTO.CommentDTO createReply(Long studyId, Long pos member.addComment(storyComment); parentComment.addChildrenComment(storyComment); - return StudyPostCommentResponseDTO.CommentDTO.toDTO(storyComment, "익명"+anonymousNum, defaultImage); + return StoryCommentResponseDTO.CommentDTO.toDTO(storyComment, "익명"+anonymousNum, defaultImage); } /** @@ -482,7 +482,7 @@ public StudyPostCommentResponseDTO.CommentDTO createReply(Long studyId, Long pos * @return 댓글 작성자의 익명 번호를 반환합니다. * 회원이 이미 타겟 스터디 게시글에 익명으로 댓글을 작성한 이력이 있는 경우 해당 번호를 반환합니다. */ - private Integer getAnonymousNum(Long postId, StudyPostCommentRequestDTO.CommentDTO commentRequestDTO, Member member) { + private Integer getAnonymousNum(Long postId, StoryCommentRequestDTO.CommentDTO commentRequestDTO, Member member) { Integer anonymousNum = null; List storyComments = storyCommentRepository.findAllByStoryId(postId); @@ -520,7 +520,7 @@ private Integer getAnonymousNum(Long postId, StudyPostCommentRequestDTO.CommentD * @return 삭제한 댓글의 아이디를 반환합니다. */ @Override - public StudyPostCommentResponseDTO.CommentIdDTO deleteComment(Long studyId, Long postId, Long commentId) { + public StoryCommentResponseDTO.CommentIdDTO deleteComment(Long studyId, Long postId, Long commentId) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -558,7 +558,7 @@ public StudyPostCommentResponseDTO.CommentIdDTO deleteComment(Long studyId, Long member.updateComment(storyComment); storyCommentRepository.save(storyComment); - return new StudyPostCommentResponseDTO.CommentIdDTO(commentId); + return new StoryCommentResponseDTO.CommentIdDTO(commentId); } /** @@ -570,9 +570,9 @@ public StudyPostCommentResponseDTO.CommentIdDTO deleteComment(Long studyId, Long * @return 댓글 아이디와 타겟 댓글의 좋아요 수와 싫어요 수가 반환됩니다. */ @Override - public StudyPostCommentResponseDTO.CommentPreviewDTO likeComment(Long studyId, Long postId, Long commentId) { + public StoryCommentResponseDTO.CommentPreviewDTO likeComment(Long studyId, Long postId, Long commentId) { StoryComment storyComment = saveStudyPostComment(studyId, postId, commentId, Boolean.TRUE); - return StudyPostCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); + return StoryCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); } /** @@ -584,9 +584,9 @@ public StudyPostCommentResponseDTO.CommentPreviewDTO likeComment(Long studyId, L * @return 댓글 아이디와 타겟 댓글의 좋아요 수와 싫어요 수가 반환됩니다. */ @Override - public StudyPostCommentResponseDTO.CommentPreviewDTO dislikeComment(Long studyId, Long postId, Long commentId) { + public StoryCommentResponseDTO.CommentPreviewDTO dislikeComment(Long studyId, Long postId, Long commentId) { StoryComment storyComment = saveStudyPostComment(studyId, postId, commentId, Boolean.FALSE); - return StudyPostCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); + return StoryCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); } /** @@ -658,7 +658,7 @@ private StoryComment saveStudyPostComment(Long studyId, Long postId, Long commen * @return 댓글 아이디와 타겟 댓글의 좋아요 수와 싫어요 수가 반환됩니다. */ @Override - public StudyPostCommentResponseDTO.CommentPreviewDTO cancelCommentLike(Long studyId, Long postId, Long commentId) { + public StoryCommentResponseDTO.CommentPreviewDTO cancelCommentLike(Long studyId, Long postId, Long commentId) { Long memberId = SecurityUtils.getCurrentUserId(); SecurityUtils.verifyUserId(memberId); @@ -667,7 +667,7 @@ public StudyPostCommentResponseDTO.CommentPreviewDTO cancelCommentLike(Long stud .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_LIKED_COMMENT_NOT_FOUND)); StoryComment storyComment = deleteStudyLikedComment(studyId, postId, commentId, memberId, likedStoryComment); - return StudyPostCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); + return StoryCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); } /** @@ -679,7 +679,7 @@ public StudyPostCommentResponseDTO.CommentPreviewDTO cancelCommentLike(Long stud * @return 댓글 아이디와 타겟 댓글의 좋아요 수와 싫어요 수가 반환됩니다. */ @Override - public StudyPostCommentResponseDTO.CommentPreviewDTO cancelCommentDislike(Long studyId, Long postId, Long commentId) { + public StoryCommentResponseDTO.CommentPreviewDTO cancelCommentDislike(Long studyId, Long postId, Long commentId) { Long memberId = SecurityUtils.getCurrentUserId(); SecurityUtils.verifyUserId(memberId); @@ -688,7 +688,7 @@ public StudyPostCommentResponseDTO.CommentPreviewDTO cancelCommentDislike(Long s .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_DISLIKED_COMMENT_NOT_FOUND)); StoryComment storyComment = deleteStudyLikedComment(studyId, postId, commentId, memberId, likedStoryComment); - return StudyPostCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); + return StoryCommentResponseDTO.CommentPreviewDTO.toDTO(storyComment); } /** diff --git a/src/main/java/com/example/spot/story/application/StoryQueryService.java b/src/main/java/com/example/spot/story/application/StoryQueryService.java new file mode 100644 index 00000000..2369ffc7 --- /dev/null +++ b/src/main/java/com/example/spot/story/application/StoryQueryService.java @@ -0,0 +1,18 @@ +package com.example.spot.story.application; + +import com.example.spot.story.domain.enums.StoryCategoryQuery; +import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; +import org.springframework.data.domain.PageRequest; + +public interface StoryQueryService { + + // 스터디 게시글 목록 불러오기 + StoryResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long studyId, StoryCategoryQuery storyCategoryQuery); + + // 스터디 게시글 불러오기 + StoryResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean likeOrScrap); + + // 스터디 게시글 댓글 목록 불러오기 + StoryCommentResponseDTO.CommentReplyListDTO getAllComments(Long studyId, Long postId); +} diff --git a/src/main/java/com/example/spot/story/application/application/StoryQueryServiceImpl.java b/src/main/java/com/example/spot/story/application/StoryQueryServiceImpl.java similarity index 90% rename from src/main/java/com/example/spot/story/application/application/StoryQueryServiceImpl.java rename to src/main/java/com/example/spot/story/application/StoryQueryServiceImpl.java index dd33e450..d5839068 100644 --- a/src/main/java/com/example/spot/story/application/application/StoryQueryServiceImpl.java +++ b/src/main/java/com/example/spot/story/application/StoryQueryServiceImpl.java @@ -1,4 +1,4 @@ -package com.example.spot.story.application.application; +package com.example.spot.story.application; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.common.api.exception.handler.MemberHandler; @@ -17,8 +17,8 @@ import com.example.spot.story.domain.StoryRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; +import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; @@ -56,7 +56,7 @@ public class StoryQueryServiceImpl implements StoryQueryService { * 2. themeQuery가 null인 경우 필터링 없이 게시글 목록을 반환합니다. */ @Override - public StudyPostResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long studyId, StoryCategoryQuery storyCategoryQuery) { + public StoryResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long studyId, StoryCategoryQuery storyCategoryQuery) { Long memberId = SecurityUtils.getCurrentUserId(); SecurityUtils.verifyUserId(memberId); @@ -89,14 +89,14 @@ else if (storyCategoryQuery.equals(StoryCategoryQuery.ANNOUNCEMENT)) { totalPosts = storyRepository.countByStudyIdAndStoryCategory(studyId, storyCategory); } - return StudyPostResDTO.PostListDTO.builder() + return StoryResDTO.PostListDTO.builder() .studyId(studyId) .posts(stories.stream() .map(studyPost -> { if (likedStoryRepository.existsByMemberIdAndStoryId(memberId, studyPost.getId())) { - return StudyPostResDTO.PostDTO.toDTO(studyPost, true); + return StoryResDTO.PostDTO.toDTO(studyPost, true); } else { - return StudyPostResDTO.PostDTO.toDTO(studyPost, false); + return StoryResDTO.PostDTO.toDTO(studyPost, false); } }) .toList()) @@ -116,7 +116,7 @@ else if (storyCategoryQuery.equals(StoryCategoryQuery.ANNOUNCEMENT)) { */ @Override @Transactional(readOnly = false) - public StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean likeOrScrap) { + public StoryResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean likeOrScrap) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -151,7 +151,7 @@ public StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean Integer commentNum = storyCommentRepository.findAllByStoryId(postId).size(); boolean isLiked = likedStoryRepository.existsByMemberIdAndStoryId(memberId, story.getId()); boolean isWriter = story.getMember().getId().equals(memberId); - return StudyPostResDTO.PostDetailDTO.toDTO(story, commentNum, isLiked, isWriter); + return StoryResDTO.PostDetailDTO.toDTO(story, commentNum, isLiked, isWriter); } /* ----------------------------- 스터디 게시글 댓글 관련 API ------------------------------------- */ @@ -163,7 +163,7 @@ public StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean * @return 스터디 게시글에 작성된 댓글의 목록을 반환합니다. 하나의 댓글에는 해당 댓글에 대한 답글 목록이 포함되어 있습니다. */ @Override - public StudyPostCommentResponseDTO.CommentReplyListDTO getAllComments(Long studyId, Long postId) { + public StoryCommentResponseDTO.CommentReplyListDTO getAllComments(Long studyId, Long postId) { //=== Exception ===// Long memberId = SecurityUtils.getCurrentUserId(); @@ -190,7 +190,7 @@ public StudyPostCommentResponseDTO.CommentReplyListDTO getAllComments(Long study .sorted(Comparator.comparing(StoryComment::getCreatedAt)) .toList(); - return StudyPostCommentResponseDTO.CommentReplyListDTO.toDTO(story.getId(), storyComments, member, defaultImage); + return StoryCommentResponseDTO.CommentReplyListDTO.toDTO(story.getId(), storyComments, member, defaultImage); } } diff --git a/src/main/java/com/example/spot/story/application/application/StoryCommandService.java b/src/main/java/com/example/spot/story/application/application/StoryCommandService.java deleted file mode 100644 index aec0b9d3..00000000 --- a/src/main/java/com/example/spot/story/application/application/StoryCommandService.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.example.spot.story.application.application; - -import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; -import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; -import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; - -public interface StoryCommandService { - - // 스터디 게시글 생성 - StudyPostResDTO.PostPreviewDTO createPost(Long studyId, StudyPostRequestDTO.PostDTO postRequestDTO); - - // 스터디 게시글 편집 - StudyPostResDTO.PostPreviewDTO updatePost(Long studyId, Long postId, StudyPostRequestDTO.PostDTO postDTO); - - // 스터디 게시글 삭제 - StudyPostResDTO.PostPreviewDTO deletePost(Long studyId, Long postId); - - // 스터디 게시글 좋아요 - StudyPostResDTO.PostLikeNumDTO likePost(Long studyId, Long postId); - - // 스터디 게시글 좋아요 취소 - StudyPostResDTO.PostLikeNumDTO cancelPostLike(Long studyId, Long postId); - - // 스터디 게시글 댓글 생성 - StudyPostCommentResponseDTO.CommentDTO createComment(Long studyId, Long postId, StudyPostCommentRequestDTO.CommentDTO commentRequestDTO); - - // 스터디 게시글 답글 생성 - StudyPostCommentResponseDTO.CommentDTO createReply(Long studyId, Long postId, Long commentId, StudyPostCommentRequestDTO.CommentDTO commentRequestDTO); - - // 스터디 게시글 댓글 삭제 (댓/답글 구분 X) - StudyPostCommentResponseDTO.CommentIdDTO deleteComment(Long studyId, Long postId, Long commentId); - - // 스터디 게시글 댓글 좋아요 - StudyPostCommentResponseDTO.CommentPreviewDTO likeComment(Long studyId, Long postId, Long commentId); - - // 스터디 게시글 댓글 싫어요 - StudyPostCommentResponseDTO.CommentPreviewDTO dislikeComment(Long studyId, Long postId, Long commentId); - - // 스터디 게시글 댓글 좋아요 취소 - StudyPostCommentResponseDTO.CommentPreviewDTO cancelCommentLike(Long studyId, Long postId, Long commentId); - - // 스터디 게시글 댓글 싫어요 취소 - StudyPostCommentResponseDTO.CommentPreviewDTO cancelCommentDislike(Long studyId, Long postId, Long commentId); - -} diff --git a/src/main/java/com/example/spot/story/application/application/StoryQueryService.java b/src/main/java/com/example/spot/story/application/application/StoryQueryService.java deleted file mode 100644 index 91d852b9..00000000 --- a/src/main/java/com/example/spot/story/application/application/StoryQueryService.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.spot.story.application.application; - -import com.example.spot.story.domain.enums.StoryCategoryQuery; -import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -import org.springframework.data.domain.PageRequest; - -public interface StoryQueryService { - - // 스터디 게시글 목록 불러오기 - StudyPostResDTO.PostListDTO getAllPosts(PageRequest pageRequest, Long studyId, StoryCategoryQuery storyCategoryQuery); - - // 스터디 게시글 불러오기 - StudyPostResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean likeOrScrap); - - // 스터디 게시글 댓글 목록 불러오기 - StudyPostCommentResponseDTO.CommentReplyListDTO getAllComments(Long studyId, Long postId); -} diff --git a/src/main/java/com/example/spot/story/domain/Story.java b/src/main/java/com/example/spot/story/domain/Story.java index 2749538e..c5d199b4 100644 --- a/src/main/java/com/example/spot/story/domain/Story.java +++ b/src/main/java/com/example/spot/story/domain/Story.java @@ -8,7 +8,7 @@ import com.example.spot.story.domain.association.StoryReport; import com.example.spot.story.domain.enums.StoryCategory; import com.example.spot.study.domain.Study; -import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; +import com.example.spot.story.web.dto.request.StoryRequestDTO; import jakarta.persistence.*; import java.time.LocalDateTime; @@ -136,7 +136,7 @@ public void addStudyPostReport(StoryReport storyReport) { storyReports.add(storyReport); } - public void updatePost(StudyPostRequestDTO.PostDTO requestDTO) { + public void updatePost(StoryRequestDTO.PostDTO requestDTO) { isAnnouncement = requestDTO.getIsAnnouncement(); storyCategory = requestDTO.getStoryCategory(); title = requestDTO.getTitle(); diff --git a/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java b/src/main/java/com/example/spot/story/web/controller/StoryController.java similarity index 80% rename from src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java rename to src/main/java/com/example/spot/story/web/controller/StoryController.java index b5d2390d..eaa5e167 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/StudyPostController.java +++ b/src/main/java/com/example/spot/story/web/controller/StoryController.java @@ -1,18 +1,18 @@ -package com.example.spot.study.presentation.controller; +package com.example.spot.story.web.controller; import com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.common.application.s3.S3ImageService; -import com.example.spot.story.application.application.StoryCommandService; -import com.example.spot.story.application.application.StoryQueryService; +import com.example.spot.story.application.StoryCommandService; +import com.example.spot.story.application.StoryQueryService; import com.example.spot.study.domain.validation.annotation.ExistStudy; import com.example.spot.story.domain.validation.annotation.ExistStory; import com.example.spot.story.domain.validation.annotation.ExistStoryComment; -import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; -import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; -import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; +import com.example.spot.story.web.dto.request.StoryCommentRequestDTO; +import com.example.spot.story.web.dto.request.StoryRequestDTO; +import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -29,7 +29,7 @@ @RequiredArgsConstructor @RequestMapping("/spot") @Validated -public class StudyPostController { +public class StoryController { private final StoryQueryService storyQueryService; private final StoryCommandService storyCommandService; @@ -44,10 +44,10 @@ public class StudyPostController { """) @Parameter(name = "studyId", description = "게시글을 작성할 스터디의 id를 입력합니다.", required = true) @PostMapping(value = "/studies/{studyId}/posts", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ApiResponse createPost( + public ApiResponse createPost( @PathVariable @ExistStudy Long studyId, - @ModelAttribute(name = "post") @Valid StudyPostRequestDTO.PostDTO postRequestDTO) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.createPost(studyId, postRequestDTO); + @ModelAttribute(name = "post") @Valid StoryRequestDTO.PostDTO postRequestDTO) { + StoryResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.createPost(studyId, postRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_CREATED, postPreviewDTO); } @@ -59,12 +59,12 @@ public ApiResponse createPost( @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "편집할 스터디 게시글의 id를 입력합니다.", required = true) @PatchMapping(value = "/studies/{studyId}/posts/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ApiResponse updatePost( + public ApiResponse updatePost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, - @ModelAttribute(name= "post") @Valid StudyPostRequestDTO.PostDTO postDTO + @ModelAttribute(name= "post") @Valid StoryRequestDTO.PostDTO postDTO ) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.updatePost(studyId, postId, postDTO); + StoryResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.updatePost(studyId, postId, postDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_UPDATED, postPreviewDTO); } @@ -77,10 +77,10 @@ public ApiResponse updatePost( @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "삭제할 스터디 게시글의 id를 입력합니다.", required = true) @DeleteMapping("/studies/{studyId}/posts/{postId}") - public ApiResponse deletePost( + public ApiResponse deletePost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.deletePost(studyId, postId); + StoryResDTO.PostPreviewDTO postPreviewDTO = storyCommandService.deletePost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_DELETED, postPreviewDTO); } @@ -95,12 +95,12 @@ public ApiResponse deletePost( """) @Parameter(name = "studyId", description = "게시글 목록을 불러올 스터디의 id를 입력합니다.", required = true) @GetMapping("/studies/{studyId}/posts") - public ApiResponse getAllPosts( + public ApiResponse getAllPosts( @PathVariable @ExistStudy Long studyId, @RequestParam(required = false) StoryCategoryQuery storyCategoryQuery, @RequestParam @Min(0) Integer offset, @RequestParam @Min(1) Integer limit) { - StudyPostResDTO.PostListDTO postListDTO = storyQueryService.getAllPosts(PageRequest.of(offset, limit), studyId, storyCategoryQuery); + StoryResDTO.PostListDTO postListDTO = storyQueryService.getAllPosts(PageRequest.of(offset, limit), studyId, storyCategoryQuery); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_LIST_FOUND, postListDTO); } @@ -112,11 +112,11 @@ public ApiResponse getAllPosts( @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "불러올 스터디 게시글의 id를 입력합니다.", required = true) @GetMapping("/studies/{studyId}/posts/{postId}") - public ApiResponse getPost( + public ApiResponse getPost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @RequestParam Boolean likeOrScrap) { - StudyPostResDTO.PostDetailDTO postDetailDTO = storyQueryService.getPost(studyId, postId, likeOrScrap); + StoryResDTO.PostDetailDTO postDetailDTO = storyQueryService.getPost(studyId, postId, likeOrScrap); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, postDetailDTO); } @@ -128,10 +128,10 @@ public ApiResponse getPost( @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "좋아요를 누를 스터디 게시글의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/posts/{postId}/likes") - public ApiResponse likePost( + public ApiResponse likePost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostLikeNumDTO postLikeNumDTO = storyCommandService.likePost(studyId, postId); + StoryResDTO.PostLikeNumDTO postLikeNumDTO = storyCommandService.likePost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_LIKED, postLikeNumDTO); } @@ -143,10 +143,10 @@ public ApiResponse likePost( @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "좋아요를 취소할 스터디 게시글의 id를 입력합니다.", required = true) @DeleteMapping("/studies/{studyId}/posts/{postId}/likes") - public ApiResponse cancelPostLike( + public ApiResponse cancelPostLike( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostLikeNumDTO postLikeNumDTO = storyCommandService.cancelPostLike(studyId, postId); + StoryResDTO.PostLikeNumDTO postLikeNumDTO = storyCommandService.cancelPostLike(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_DISLIKED, postLikeNumDTO); } @@ -160,11 +160,11 @@ public ApiResponse cancelPostLike( @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "댓글을 작성할 스터디 게시글의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/posts/{postId}/comments") - public ApiResponse createComment( + public ApiResponse createComment( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, - @RequestBody @Valid StudyPostCommentRequestDTO.CommentDTO commentRequestDTO) { - StudyPostCommentResponseDTO.CommentDTO commentResponseDTO = storyCommandService.createComment(studyId, postId, commentRequestDTO); + @RequestBody @Valid StoryCommentRequestDTO.CommentDTO commentRequestDTO) { + StoryCommentResponseDTO.CommentDTO commentResponseDTO = storyCommandService.createComment(studyId, postId, commentRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_CREATED, commentResponseDTO); } @@ -177,12 +177,12 @@ public ApiResponse createComment( @Parameter(name = "postId", description = "스터디 게시글의 id를 입력합니다.", required = true) @Parameter(name = "commentId", description = "답글을 작성할 댓글의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/posts/{postId}/comments/{commentId}/replies") - public ApiResponse createReply( + public ApiResponse createReply( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId, - @RequestBody @Valid StudyPostCommentRequestDTO.CommentDTO commentRequestDTO) { - StudyPostCommentResponseDTO.CommentDTO commentResponseDTO = storyCommandService.createReply(studyId, postId, commentId, commentRequestDTO); + @RequestBody @Valid StoryCommentRequestDTO.CommentDTO commentRequestDTO) { + StoryCommentResponseDTO.CommentDTO commentResponseDTO = storyCommandService.createReply(studyId, postId, commentId, commentRequestDTO); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_CREATED, commentResponseDTO); } @@ -195,11 +195,11 @@ public ApiResponse createReply( @Parameter(name = "postId", description = "스터디 게시글의 id를 입력합니다.", required = true) @Parameter(name = "commentId", description = "삭제할 댓글의 id를 입력합니다.", required = true) @PatchMapping("/studies/{studyId}/posts/{postId}/comments/{commentId}") - public ApiResponse deleteComment( + public ApiResponse deleteComment( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentIdDTO commentPreviewDTO = storyCommandService.deleteComment(studyId, postId, commentId); + StoryCommentResponseDTO.CommentIdDTO commentPreviewDTO = storyCommandService.deleteComment(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_DELETED, commentPreviewDTO); } @@ -212,11 +212,11 @@ public ApiResponse deleteComment( @Parameter(name = "postId", description = "스터디 게시글의 id를 입력합니다.", required = true) @Parameter(name = "commentId", description = "좋아요를 누를 댓글의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/posts/{postId}/comments/{commentId}/likes") - public ApiResponse likeComment( + public ApiResponse likeComment( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.likeComment(studyId, postId, commentId); + StoryCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.likeComment(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_LIKED, commentPreviewDTO); } @@ -229,11 +229,11 @@ public ApiResponse likeComment( @Parameter(name = "postId", description = "스터디 게시글의 id를 입력합니다.", required = true) @Parameter(name = "commentId", description = "싫어요를 누를 댓글의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/posts/{postId}/comments/{commentId}/dislikes") - public ApiResponse dislikeComment( + public ApiResponse dislikeComment( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.dislikeComment(studyId, postId, commentId); + StoryCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.dislikeComment(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_DISLIKED, commentPreviewDTO); } @@ -246,11 +246,11 @@ public ApiResponse dislikeComment @Parameter(name = "postId", description = "스터디 게시글의 id를 입력합니다.", required = true) @Parameter(name = "commentId", description = "좋아요를 취소할 댓글의 id를 입력합니다.", required = true) @DeleteMapping("/studies/{studyId}/posts/{postId}/comments/{commentId}/likes") - public ApiResponse cancelCommentLike( + public ApiResponse cancelCommentLike( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.cancelCommentLike(studyId, postId, commentId); + StoryCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.cancelCommentLike(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_LIKE_CANCELED, commentPreviewDTO); } @@ -263,11 +263,11 @@ public ApiResponse cancelCommentL @Parameter(name = "postId", description = "스터디 게시글의 id를 입력합니다.", required = true) @Parameter(name = "commentId", description = "싫어요를 취소할 댓글의 id를 입력합니다.", required = true) @DeleteMapping("/studies/{studyId}/posts/{postId}/comments/{commentId}/dislikes") - public ApiResponse cancelCommentDislike( + public ApiResponse cancelCommentDislike( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId, @PathVariable @ExistStoryComment Long commentId) { - StudyPostCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.cancelCommentDislike(studyId, postId, commentId); + StoryCommentResponseDTO.CommentPreviewDTO commentPreviewDTO = storyCommandService.cancelCommentDislike(studyId, postId, commentId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_DISLIKE_CANCELED, commentPreviewDTO); } @@ -279,10 +279,10 @@ public ApiResponse cancelCommentD @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "댓글을 불러올 스터디 게시글의 id를 입력합니다.", required = true) @GetMapping("/studies/{studyId}/posts/{postId}/comments") - public ApiResponse getAllComments( + public ApiResponse getAllComments( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostCommentResponseDTO.CommentReplyListDTO commentReplyListDTO = storyQueryService.getAllComments(studyId, postId); + StoryCommentResponseDTO.CommentReplyListDTO commentReplyListDTO = storyQueryService.getAllComments(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_FOUND, commentReplyListDTO); } diff --git a/src/main/java/com/example/spot/study/presentation/dto/request/StudyPostCommentRequestDTO.java b/src/main/java/com/example/spot/story/web/dto/request/StoryCommentRequestDTO.java similarity index 80% rename from src/main/java/com/example/spot/study/presentation/dto/request/StudyPostCommentRequestDTO.java rename to src/main/java/com/example/spot/story/web/dto/request/StoryCommentRequestDTO.java index 1aee8ab2..71d95c57 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/request/StudyPostCommentRequestDTO.java +++ b/src/main/java/com/example/spot/story/web/dto/request/StoryCommentRequestDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.request; +package com.example.spot.story.web.dto.request; import com.example.spot.common.presentation.validator.TextLength; import lombok.AllArgsConstructor; @@ -7,7 +7,7 @@ import lombok.NoArgsConstructor; @Getter -public class StudyPostCommentRequestDTO { +public class StoryCommentRequestDTO { @Getter @Builder diff --git a/src/main/java/com/example/spot/study/presentation/dto/request/StudyPostRequestDTO.java b/src/main/java/com/example/spot/story/web/dto/request/StoryRequestDTO.java similarity index 92% rename from src/main/java/com/example/spot/study/presentation/dto/request/StudyPostRequestDTO.java rename to src/main/java/com/example/spot/story/web/dto/request/StoryRequestDTO.java index 557334fa..419ec847 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/request/StudyPostRequestDTO.java +++ b/src/main/java/com/example/spot/story/web/dto/request/StoryRequestDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.request; +package com.example.spot.story.web.dto.request; import com.example.spot.story.domain.enums.StoryCategory; import com.example.spot.common.presentation.validator.TextLength; @@ -8,7 +8,7 @@ import org.springframework.web.multipart.MultipartFile; @Getter -public class StudyPostRequestDTO { +public class StoryRequestDTO { @Getter @Setter diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostCommentResponseDTO.java b/src/main/java/com/example/spot/story/web/dto/response/StoryCommentResponseDTO.java similarity index 98% rename from src/main/java/com/example/spot/study/presentation/dto/response/StudyPostCommentResponseDTO.java rename to src/main/java/com/example/spot/story/web/dto/response/StoryCommentResponseDTO.java index ed012972..c8c7b645 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostCommentResponseDTO.java +++ b/src/main/java/com/example/spot/story/web/dto/response/StoryCommentResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.story.web.dto.response; import com.example.spot.member.domain.Member; import com.example.spot.story.domain.association.LikedStoryComment; @@ -12,7 +12,7 @@ import java.util.List; @Getter -public class StudyPostCommentResponseDTO { +public class StoryCommentResponseDTO { @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResDTO.java b/src/main/java/com/example/spot/story/web/dto/response/StoryResDTO.java similarity index 98% rename from src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResDTO.java rename to src/main/java/com/example/spot/story/web/dto/response/StoryResDTO.java index 6f5c0580..2acca7c3 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResDTO.java +++ b/src/main/java/com/example/spot/story/web/dto/response/StoryResDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.story.web.dto.response; import com.example.spot.member.domain.Member; import com.example.spot.story.domain.Story; @@ -10,7 +10,7 @@ import java.util.List; @Getter -public class StudyPostResDTO { +public class StoryResDTO { @Getter @RequiredArgsConstructor(access = AccessLevel.PRIVATE) diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResponseDTO.java b/src/main/java/com/example/spot/story/web/dto/response/StoryResponseDTO.java similarity index 70% rename from src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResponseDTO.java rename to src/main/java/com/example/spot/story/web/dto/response/StoryResponseDTO.java index a2ad28f2..a083cf47 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyPostResponseDTO.java +++ b/src/main/java/com/example/spot/story/web/dto/response/StoryResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.story.web.dto.response; import lombok.AllArgsConstructor; @@ -10,7 +10,7 @@ @Getter @NoArgsConstructor @AllArgsConstructor -public class StudyPostResponseDTO { +public class StoryResponseDTO { private String title; private String content; } diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java index 6aa7724c..266281a9 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java @@ -1,14 +1,10 @@ package com.example.spot.study.application; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; -import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; @@ -18,8 +14,6 @@ import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; import jakarta.validation.Valid; -import java.time.LocalDate; - public interface StudyMemberCommandService { StudyWithdrawalResponseDTO.WithdrawalDTO withdrawFromStudy(Long studyId); @@ -32,21 +26,6 @@ public interface StudyMemberCommandService { StudyApplyResponseDTO acceptAndRejectStudyApplyForTest(Long memberId, Long studyId, boolean isAccept); - // 일정 생성 - ScheduleResponseDTO.ScheduleDTO addSchedule(Long studyId, ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO); - - // 일정 수정 - ScheduleResponseDTO.ScheduleDTO modSchedule(Long studyId, Long scheduleId, ScheduleRequestDTO.ScheduleDTO scheduleModDTO); - - // 스터디 퀴즈 생성 - StudyQuizResponseDTO.QuizDTO createAttendanceQuiz(Long studyId, Long scheduleId, StudyQuizRequestDTO.QuizDTO quizRequestDTO); - - // 스터디 출석 - StudyQuizResponseDTO.AttendanceDTO attendantStudy(Long studyId, Long scheduleId, StudyQuizRequestDTO.AttendanceDTO attendanceRequestDTO); - - // 스터디 퀴즈 삭제 - StudyQuizResponseDTO.QuizDTO deleteAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date); - // 스터디 투표 생성 StudyVoteResponseDTO.VotePreviewDTO createVote(Long studyId, StudyVoteRequestDTO.VoteDTO voteDTO); @@ -63,7 +42,7 @@ public interface StudyMemberCommandService { MemberResponseDTO.ReportedMemberDTO reportStudyMember(Long studyId, Long memberId, @Valid StudyMemberReportDTO studyMemberReportDTO); // 스터디 게시글 신고 - StudyPostResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId); + StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId); // 투두 리스트 생성 ToDoListCreateResponseDTO createToDoList(Long studyId, ToDoListRequestDTO.ToDoListCreateDTO toDoListCreateDTO); diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java index 062c1f9f..8efcb496 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java @@ -7,9 +7,6 @@ import com.example.spot.member.domain.Member; import com.example.spot.report.domain.MemberReport; import com.example.spot.notification.domain.Notification; -import com.example.spot.schedule.domain.Schedule; -import com.example.spot.schedule.domain.association.Quiz; -import com.example.spot.schedule.domain.association.QuizSubmission; import com.example.spot.schedule.domain.repository.QuizRepository; import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; import com.example.spot.schedule.domain.ScheduleRepository; @@ -29,14 +26,10 @@ import com.example.spot.story.domain.StoryRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.todo.domain.ToDoRepository; -import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; -import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; @@ -49,7 +42,6 @@ import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO.WithdrawalDTO; import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; -import java.time.LocalDate; import java.util.Objects; import com.example.spot.vote.domain.Vote; @@ -63,7 +55,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; import java.util.List; @@ -310,327 +301,6 @@ public StudyApplyResponseDTO acceptAndRejectStudyApplyForTest(Long memberId, Lon .build(); } - /* ----------------------------- 스터디 일정 관련 API ------------------------------------- */ - - /** - * 스터디 일정을 추가하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param scheduleRequestDTO 생성할 일정의 제목, 위치, 시작 일시, 종료 일시, 종일 진행 여부, 반복 여부를 입력 받습니다. - * @return 스터디 아이디와 생성된 일정의 아이디, 제목을 반환합니다. - */ - @Override - public ScheduleResponseDTO.ScheduleDTO addSchedule(Long studyId, ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - //=== Feature ===// - - // Period 기반 시작일 종료일 제한 - checkStartAndFinishDate(scheduleRequestDTO); - - Schedule schedule = Schedule.builder() - .study(study) - .member(member) - .title(scheduleRequestDTO.getTitle()) - .location(scheduleRequestDTO.getLocation()) - .startedAt(scheduleRequestDTO.getStartedAt()) - .finishedAt(scheduleRequestDTO.getFinishedAt()) - .isAllDay(scheduleRequestDTO.getIsAllDay()) - .schedulePeriod(scheduleRequestDTO.getSchedulePeriod()) - .build(); - - // 알림 생성 - - // 스터디에 참여중인 회원들에게 알림 전송 위해 회원 조회 - List members = studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED).stream() - .map(StudyMember::getMember) - .toList(); - - if (members.isEmpty()) - throw new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND); - - members.forEach(studyMember -> { - Notification notification = Notification.builder() - .member(studyMember) - .study(study) - .notifierName(member.getName()) // 일정 생성자 이름 - .type(NotifyType.SCHEDULE_UPDATE) - .isChecked(Boolean.FALSE) - .build(); - notificationRepository.save(notification); - }); - - scheduleRepository.save(schedule); - study.addSchedule(schedule); - member.addSchedule(schedule); - - return ScheduleResponseDTO.ScheduleDTO.toDTO(schedule); - } - - /** - * 스터디 일정을 변경하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param scheduleId 변경할 일정의 아이디를 입력 받습니다. - * @param scheduleModDTO 변경된 일정의 제목, 위치, 시작 일시, 종료 일시, 종일 진행 여부, 반복 여부를 입력 받습니다. - * @return 스터디 아이디와 변경된 일정의 아이디, 제목을 반환합니다. - */ - @Override - public ScheduleResponseDTO.ScheduleDTO modSchedule(Long studyId, Long scheduleId, ScheduleRequestDTO.ScheduleDTO scheduleModDTO) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 로그인한 회원이 일정 생성자인지 확인 - scheduleRepository.findByIdAndMemberId(scheduleId, memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._SCHEDULE_MOD_INVALID)); - - // 해당 스터디의 일정인지 확인 - scheduleRepository.findByIdAndStudyId(scheduleId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); - - //=== Feature ===// - - // Period 기반 시작일 종료일 제한 - checkStartAndFinishDate(scheduleModDTO); - - schedule.modSchedule(scheduleModDTO); - schedule = scheduleRepository.save(schedule); - - study.updateSchedule(schedule); - member.updateSchedule(schedule); - - return ScheduleResponseDTO.ScheduleDTO.toDTO(schedule); - } - - private static void checkStartAndFinishDate(ScheduleRequestDTO.ScheduleDTO scheduleRequestDTO) { - LocalDate startDate = scheduleRequestDTO.getStartedAt().toLocalDate(); - LocalDate finishDate = scheduleRequestDTO.getFinishedAt().toLocalDate(); - System.out.println(startDate); - System.out.println(finishDate); - switch (scheduleRequestDTO.getSchedulePeriod()) { - case DAILY : - // 시작일과 종료일이 일치해야 함 - if (finishDate.equals(startDate.plusDays(1)) || - finishDate.isAfter(startDate.plusDays(1))) { - throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); - } - case WEEKLY : - // 시작일과 종료일이 일주일 이상 차이나지 않아야 함 - if (finishDate.equals(startDate.plusWeeks(1)) || - finishDate.isAfter(startDate.plusWeeks(1))) { - throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); - } - case BIWEEKLY : - // 시작일과 종료일이 2주 이상 차이나지 않아야 함 - if (finishDate.equals(startDate.plusWeeks(2)) || - finishDate.isAfter(startDate.plusWeeks(2))) { - throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); - } - case MONTHLY : - // 시작일과 종료일이 한 달 이상 차이나지 않아야 함 - if (finishDate.equals(startDate.plusMonths(1)) || - finishDate.isAfter(startDate.plusMonths(1))) { - throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_WRONG_FORMAT); - } - } - } - - - /* ----------------------------- 스터디 출석 관련 API ------------------------------------- */ - - /** - * 출석 퀴즈를 생성하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param scheduleId 타겟 일정의 아이디를 입력 받습니다. - * @param quizRequestDTO 출석 퀴즈에 담길 질문과 정답을 입력 받습니다. - * @return 생성된 퀴즈의 아이디와 질문이 반환됩니다. - */ - @Override - public StudyQuizResponseDTO.QuizDTO createAttendanceQuiz(Long studyId, Long scheduleId, StudyQuizRequestDTO.QuizDTO quizRequestDTO) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 해당 스터디에서 생성된 일정인지 확인 - if (!schedule.getStudy().equals(study)) { - throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND); - } - - // 로그인한 회원이 스터디장인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, Boolean.TRUE) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_QUIZ_CREATION_INVALID)); - - // 요청한 날짜에 이미 출석 퀴즈가 생성되었는지 확인 - LocalDateTime startOfDay = quizRequestDTO.getCreatedAt().withHour(0).withMinute(0).withSecond(0).withNano(0); - LocalDateTime endOfDay = quizRequestDTO.getCreatedAt().withHour(23).withMinute(59).withSecond(59).withNano(999_999_000); - List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); - if (!todayQuizzes.isEmpty()) { - throw new StudyHandler(ErrorStatus._STUDY_QUIZ_ALREADY_EXIST); - } - - //=== Feature ===// - Quiz quiz = Quiz.builder() - .schedule(schedule) - .member(member) - .question(quizRequestDTO.getQuestion()) - .answer(quizRequestDTO.getAnswer()) - .createdAt(quizRequestDTO.getCreatedAt()) - .build(); - - quiz = quizRepository.save(quiz); - schedule.addQuiz(quiz); - member.addQuiz(quiz); - - return StudyQuizResponseDTO.QuizDTO.toDTO(quiz); - } - - /** - * 출석 체크에 사용되는 메서드입니다. - * 메서드 내에서 퀴즈의 제한 시간과 시도 횟수를 확인하며, 조건을 충족하는 경우 회원 출석 정보를 저장합니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param scheduleId 출석을 체크할 일정을 입력 받습니다. - * @param attendanceRequestDTO 퀴즈에 대한 회원의 답변을 입력 받습니다. - * @return 회원 아이디, 퀴즈 아이디, 출석 아이디, 정답 여부, 시도 횟수, 출석 정보 생성 시각을 반환합니다. - */ - @Override - public StudyQuizResponseDTO.AttendanceDTO attendantStudy(Long studyId, Long scheduleId, StudyQuizRequestDTO.AttendanceDTO attendanceRequestDTO) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 요청한 날짜에 생성된 출석 퀴즈 조회 - LocalDateTime startOfDay = attendanceRequestDTO.getDateTime().withHour(0).withMinute(0).withSecond(0).withNano(0); - LocalDateTime endOfDay = attendanceRequestDTO.getDateTime().withHour(23).withMinute(59).withSecond(59).withNano(999_999_000); - List quizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); - if (quizzes.isEmpty()) { - throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); - } - Quiz quiz = quizzes.get(0); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(member.getId(), study.getId(), StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 퀴즈 제한시간 확인 - if (attendanceRequestDTO.getDateTime().isAfter(quiz.getCreatedAt().plusMinutes(5))) { - throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_VALID); - } - - // 이미 출석이 완료되었거나 시도 횟수를 초과하였는지 확인 - List attendanceList = quizSubmissionRepository.findByQuizIdAndMemberId(quiz.getId(), member.getId()); - int try_num = 0; - for (QuizSubmission attendance : attendanceList) { - if (attendance.getIsCorrect()) - throw new StudyHandler(ErrorStatus._STUDY_ATTENDANCE_ALREADY_EXIST); - else - try_num++; - } - if (try_num >= 3) { - throw new StudyHandler(ErrorStatus._STUDY_ATTENDANCE_ATTEMPT_LIMIT_EXCEEDED); - } - - //=== Feature ===// - Boolean isCorrect; - if (attendanceRequestDTO.getAnswer().equals(quiz.getAnswer())) { - isCorrect = Boolean.TRUE; - } else { - isCorrect = Boolean.FALSE; - } - - QuizSubmission quizSubmission = new QuizSubmission(isCorrect); - member.addMemberAttendance(quizSubmission); - quiz.addMemberAttendance(quizSubmission); - quizSubmission = quizSubmissionRepository.save(quizSubmission); - - return StudyQuizResponseDTO.AttendanceDTO.toDTO(quizSubmission, try_num+1); - } - - /** - * 출석 퀴즈를 삭제하는 메서드입니다. - * - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param scheduleId 스터디 일정의 아이디를 입력 받습니다. - * @param date 출석 퀴즈가 생성된 날짜를 입력 받습니다. - * @return 삭제된 퀴즈의 아이디와 질문을 반환합니다. - */ - @Override - public StudyQuizResponseDTO.QuizDTO deleteAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 요청한 날짜에 생성된 출석 퀴즈 조회 - LocalDateTime startOfDay = date.atStartOfDay(); - LocalDateTime endOfDay = date.atStartOfDay().plusDays(1); - List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); - if (todayQuizzes.isEmpty()) { - throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); - } - Quiz quiz = todayQuizzes.get(0); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(member.getId(), study.getId(), StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 로그인한 회원이 스터디장인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, Boolean.TRUE) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_QUIZ_DELETION_INVALID)); - - //=== Feature ===// - quizSubmissionRepository.findByQuizId(quiz.getId()) - .forEach(memberAttendance -> { - quiz.deleteMemberAttendance(memberAttendance); - quizSubmissionRepository.delete(memberAttendance); - }); - quizRepository.delete(quiz); - - return StudyQuizResponseDTO.QuizDTO.toDTO(quiz); - } - - /* ----------------------------- 스터디 투표 관련 API ------------------------------------- */ /** @@ -942,7 +612,7 @@ public MemberResponseDTO.ReportedMemberDTO reportStudyMember(Long studyId, Long * @return 신고를 당한 스터디 게시글의 아이디와 제목을 반환합니다. */ @Override - public StudyPostResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId) { + public StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId) { //=== Exception ===// Long reporterId = SecurityUtils.getCurrentUserId(); @@ -971,7 +641,7 @@ public StudyPostResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId) storyReport = storyReportRepository.save(storyReport); story.addStudyPostReport(storyReport); - return StudyPostResDTO.PostPreviewDTO.toDTO(story); + return StoryResDTO.PostPreviewDTO.toDTO(story); } /* ----------------------------- 스터디 To-Do List 관련 API ------------------------------------- */ diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java index 17459fa4..2d78ac8c 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java @@ -7,7 +7,7 @@ import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResponseDTO; +import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; @@ -23,7 +23,7 @@ public interface StudyMemberQueryService { ScheduleResponseDTO.MonthlyScheduleDTO getSchedule(Long studyId, Long scheduleId); // 스터디 공지 게시글 불러오기 - StudyPostResponseDTO findStudyAnnouncementPost(Long studyId); + StoryResponseDTO findStudyAnnouncementPost(Long studyId); // 스터디 다가오는 모임 일정 불러오기 StudyScheduleResponseDTO findStudySchedule(Long studyId, Pageable pageable); diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java index 9d683272..d0d2003b 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java @@ -27,7 +27,7 @@ import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResponseDTO; +import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import com.example.spot.common.security.utils.SecurityUtils; @@ -87,7 +87,7 @@ public class StudyMemberQueryServiceImpl implements StudyMemberQueryService { * @throws GeneralException 스터디 멤버가 아닌 경우 */ @Override - public StudyPostResponseDTO findStudyAnnouncementPost(Long studyId) { + public StoryResponseDTO findStudyAnnouncementPost(Long studyId) { // 로그인한 회원이 해당 스터디 회원인지 확인 if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) @@ -98,7 +98,7 @@ public StudyPostResponseDTO findStudyAnnouncementPost(Long studyId) { studyId, true).orElseThrow(() -> new GeneralException(ErrorStatus._STUDY_POST_NOT_FOUND)); // DTO로 변환하여 반환 - return StudyPostResponseDTO.builder() + return StoryResponseDTO.builder() .title(story.getTitle()) .content(story.getContent()).build(); } diff --git a/src/main/java/com/example/spot/common/presentation/validator/ExistRegion.java b/src/main/java/com/example/spot/study/domain/validation/annotation/ExistRegion.java similarity index 82% rename from src/main/java/com/example/spot/common/presentation/validator/ExistRegion.java rename to src/main/java/com/example/spot/study/domain/validation/annotation/ExistRegion.java index 1d85b8af..a9911863 100644 --- a/src/main/java/com/example/spot/common/presentation/validator/ExistRegion.java +++ b/src/main/java/com/example/spot/study/domain/validation/annotation/ExistRegion.java @@ -1,6 +1,6 @@ -package com.example.spot.common.presentation.validator; +package com.example.spot.study.domain.validation.annotation; -import com.example.spot.common.infrastructure.ExistRegionValidator; +import com.example.spot.study.domain.validation.validator.ExistRegionValidator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/src/main/java/com/example/spot/common/infrastructure/ExistRegionValidator.java b/src/main/java/com/example/spot/study/domain/validation/validator/ExistRegionValidator.java similarity index 91% rename from src/main/java/com/example/spot/common/infrastructure/ExistRegionValidator.java rename to src/main/java/com/example/spot/study/domain/validation/validator/ExistRegionValidator.java index b4acbb5a..4c6410e3 100644 --- a/src/main/java/com/example/spot/common/infrastructure/ExistRegionValidator.java +++ b/src/main/java/com/example/spot/study/domain/validation/validator/ExistRegionValidator.java @@ -1,8 +1,8 @@ -package com.example.spot.common.infrastructure; +package com.example.spot.study.domain.validation.validator; import com.example.spot.common.api.code.status.ErrorStatus; import com.example.spot.study.domain.repository.RegionRepository; -import com.example.spot.common.presentation.validator.ExistRegion; +import com.example.spot.study.domain.validation.annotation.ExistRegion; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; diff --git a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java index 7ec1242e..b818279e 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java @@ -5,28 +5,22 @@ import com.example.spot.study.application.StudyMemberCommandService; import com.example.spot.study.application.StudyMemberQueryService; import com.example.spot.member.domain.validation.annotation.ExistMember; -import com.example.spot.schedule.domain.validation.annotation.ExistSchedule; import com.example.spot.study.domain.validation.annotation.ExistStudy; import com.example.spot.story.domain.validation.annotation.ExistStory; import com.example.spot.todo.domain.validation.annotation.ExistToDo; import com.example.spot.vote.domain.validation.annotation.ExistVote; -import com.example.spot.common.presentation.validator.IntSize; import com.example.spot.common.presentation.validator.TextLength; -import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; -import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; -import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResponseDTO; +import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; import com.example.spot.study.presentation.dto.request.ToDoListRequestDTO; import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; @@ -194,9 +188,9 @@ public ApiResponse rejectApplicantForTest( study_post의 announced_at이 가장 최근인 공지 1개가 반환됩니다. """) @GetMapping("/studies/{studyId}/announce") - public ApiResponse getRecentAnnouncement(@PathVariable @ExistStudy Long studyId) { - StudyPostResponseDTO studyPostResponseDTO = studyMemberQueryService.findStudyAnnouncementPost(studyId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, studyPostResponseDTO); + public ApiResponse getRecentAnnouncement(@PathVariable @ExistStudy Long studyId) { + StoryResponseDTO storyResponseDTO = studyMemberQueryService.findStudyAnnouncementPost(studyId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, storyResponseDTO); } @Tag(name = "스터디 상세 정보") @@ -380,10 +374,10 @@ public ApiResponse reportStudyMember( @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "신고할 스터디 게시글의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/posts/{postId}/reports") - public ApiResponse reportStudyPost( + public ApiResponse reportStudyPost( @PathVariable @ExistStudy Long studyId, @PathVariable @ExistStory Long postId) { - StudyPostResDTO.PostPreviewDTO postPreviewDTO = studyMemberCommandService.reportStudyPost(studyId, postId); + StoryResDTO.PostPreviewDTO postPreviewDTO = studyMemberCommandService.reportStudyPost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_REPORTED, postPreviewDTO); } diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java index 0278ae05..0b05a989 100644 --- a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java @@ -20,7 +20,7 @@ import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplyMemberDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResponseDTO; +import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import java.time.LocalDate; import java.util.Collections; @@ -136,7 +136,7 @@ void setup(){ Optional.ofNullable(studyMember)); // when - StudyPostResponseDTO responseDTO = memberStudyQueryService.findStudyAnnouncementPost(studyId); + StoryResponseDTO responseDTO = memberStudyQueryService.findStudyAnnouncementPost(studyId); // then assertEquals(title,responseDTO.getTitle()); diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java index a33f27e3..4dbce5f0 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java @@ -22,12 +22,12 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.notification.domain.NotificationRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.story.application.application.StoryCommandServiceImpl; +import com.example.spot.story.application.StoryCommandServiceImpl; import com.example.spot.common.application.s3.S3ImageService; -import com.example.spot.study.presentation.dto.request.StudyPostCommentRequestDTO; -import com.example.spot.study.presentation.dto.request.StudyPostRequestDTO; -import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; +import com.example.spot.story.web.dto.request.StoryCommentRequestDTO; +import com.example.spot.story.web.dto.request.StoryRequestDTO; +import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -161,7 +161,7 @@ void createPost_Announcement_Success() { Long memberId = 3L; Long studyId = 1L; - StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + StoryRequestDTO.PostDTO postPreviewDTO = StoryRequestDTO.PostDTO.builder() .isAnnouncement(true) .storyCategory(StoryCategory.INFO_SHARING) .title("공지") @@ -178,7 +178,7 @@ void createPost_Announcement_Success() { .thenReturn(List.of(member1Study, ownerStudy)); // when - StudyPostResDTO.PostPreviewDTO result = studyPostCommandService.createPost(studyId, postPreviewDTO); + StoryResDTO.PostPreviewDTO result = studyPostCommandService.createPost(studyId, postPreviewDTO); // then assertNotNull(result); @@ -194,7 +194,7 @@ void createPost_Common_Success() { Long memberId = 1L; Long studyId = 1L; - StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + StoryRequestDTO.PostDTO postPreviewDTO = StoryRequestDTO.PostDTO.builder() .isAnnouncement(false) .storyCategory(StoryCategory.FREE_TALK) .title("잡담") @@ -211,7 +211,7 @@ void createPost_Common_Success() { .thenReturn(List.of(member1Study, ownerStudy)); // when - StudyPostResDTO.PostPreviewDTO result = studyPostCommandService.createPost(studyId, postPreviewDTO); + StoryResDTO.PostPreviewDTO result = studyPostCommandService.createPost(studyId, postPreviewDTO); // then assertNotNull(result); @@ -227,7 +227,7 @@ void createPost_NotStudyMember_Fail() { Long memberId = 2L; Long studyId = 1L; - StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + StoryRequestDTO.PostDTO postPreviewDTO = StoryRequestDTO.PostDTO.builder() .isAnnouncement(true) .storyCategory(StoryCategory.INFO_SHARING) .title("공지") @@ -255,7 +255,7 @@ void createPost_MemberAnnounced_Fail() { Long memberId = 2L; Long studyId = 1L; - StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + StoryRequestDTO.PostDTO postPreviewDTO = StoryRequestDTO.PostDTO.builder() .isAnnouncement(true) .storyCategory(StoryCategory.INFO_SHARING) .title("공지") @@ -283,7 +283,7 @@ void createPost_TitleOverflow_Fail() { Long memberId = 1L; Long studyId = 1L; - StudyPostRequestDTO.PostDTO postPreviewDTO = StudyPostRequestDTO.PostDTO.builder() + StoryRequestDTO.PostDTO postPreviewDTO = StoryRequestDTO.PostDTO.builder() .isAnnouncement(true) .storyCategory(StoryCategory.INFO_SHARING) .title("50자가 넘어가는 제목 " @@ -327,7 +327,7 @@ void deletePost_Success() { .thenReturn(Optional.of(story1)); // when - StudyPostResDTO.PostPreviewDTO result = studyPostCommandService.deletePost(studyId, postId); + StoryResDTO.PostPreviewDTO result = studyPostCommandService.deletePost(studyId, postId); // then assertNotNull(result); @@ -398,7 +398,7 @@ void likePost_Success() { when(storyRepository.save(any(Story.class))).thenReturn(story1); // when - StudyPostResDTO.PostLikeNumDTO result = studyPostCommandService.likePost(studyId, postId); + StoryResDTO.PostLikeNumDTO result = studyPostCommandService.likePost(studyId, postId); // then assertNotNull(result); @@ -472,7 +472,7 @@ void cancelPostLike_Success() { when(storyRepository.save(any(Story.class))).thenReturn(story1); // when - StudyPostResDTO.PostLikeNumDTO result = studyPostCommandService.cancelPostLike(studyId, postId); + StoryResDTO.PostLikeNumDTO result = studyPostCommandService.cancelPostLike(studyId, postId); // then assertNotNull(result); @@ -537,7 +537,7 @@ void createComment_Anonymous_Success() { getAuthentication(memberId); - StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + StoryCommentRequestDTO.CommentDTO commentDTO = StoryCommentRequestDTO.CommentDTO.builder() .content("댓글") .isAnonymous(true) .build(); @@ -550,7 +550,7 @@ void createComment_Anonymous_Success() { when(storyCommentRepository.findAllByMemberIdAndStoryId(memberId, postId)).thenReturn(List.of()); // when - StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService.createComment(studyId, postId, commentDTO); + StoryCommentResponseDTO.CommentDTO result = studyPostCommandService.createComment(studyId, postId, commentDTO); // then assertNotNull(result); @@ -570,7 +570,7 @@ void createComment_Name_Success() { getAuthentication(memberId); - StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + StoryCommentRequestDTO.CommentDTO commentDTO = StoryCommentRequestDTO.CommentDTO.builder() .content("댓글") .isAnonymous(false) .build(); @@ -583,7 +583,7 @@ void createComment_Name_Success() { when(storyCommentRepository.findAllByMemberIdAndStoryId(memberId, postId)).thenReturn(List.of()); // when - StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService.createComment(studyId, postId, commentDTO); + StoryCommentResponseDTO.CommentDTO result = studyPostCommandService.createComment(studyId, postId, commentDTO); // then assertNotNull(result); @@ -603,7 +603,7 @@ void createComment_NotStudyMember_Fail() { getAuthentication(memberId); - StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + StoryCommentRequestDTO.CommentDTO commentDTO = StoryCommentRequestDTO.CommentDTO.builder() .content("댓글") .isAnonymous(false) .build(); @@ -635,7 +635,7 @@ void createReply_Anonymous_Success() { getAuthentication(memberId); - StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + StoryCommentRequestDTO.CommentDTO commentDTO = StoryCommentRequestDTO.CommentDTO.builder() .content("답글") .isAnonymous(true) .build(); @@ -650,7 +650,7 @@ void createReply_Anonymous_Success() { .thenReturn(List.of()); // when - StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService + StoryCommentResponseDTO.CommentDTO result = studyPostCommandService .createReply(studyId, postId, commentId, commentDTO); //then @@ -672,7 +672,7 @@ void createReply_Name_Success() { getAuthentication(memberId); - StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + StoryCommentRequestDTO.CommentDTO commentDTO = StoryCommentRequestDTO.CommentDTO.builder() .content("답글") .isAnonymous(false) .build(); @@ -687,7 +687,7 @@ void createReply_Name_Success() { .thenReturn(List.of()); // when - StudyPostCommentResponseDTO.CommentDTO result = studyPostCommandService + StoryCommentResponseDTO.CommentDTO result = studyPostCommandService .createReply(studyId, postId, commentId, commentDTO); //then @@ -709,7 +709,7 @@ void createReply_NotStudyMember_Fail() { getAuthentication(memberId); - StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + StoryCommentRequestDTO.CommentDTO commentDTO = StoryCommentRequestDTO.CommentDTO.builder() .content("답글") .isAnonymous(false) .build(); @@ -738,7 +738,7 @@ void createReply_ParentCommentNotExist_Fail() { getAuthentication(memberId); - StudyPostCommentRequestDTO.CommentDTO commentDTO = StudyPostCommentRequestDTO.CommentDTO.builder() + StoryCommentRequestDTO.CommentDTO commentDTO = StoryCommentRequestDTO.CommentDTO.builder() .content("답글") .isAnonymous(false) .build(); @@ -785,7 +785,7 @@ void likeComment_Success() { when(likedStoryCommentRepository.save(any(LikedStoryComment.class))).thenReturn(likedStoryComment); // when - StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService.likeComment(studyId, postId, commentId); + StoryCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService.likeComment(studyId, postId, commentId); // then assertNotNull(result); @@ -860,7 +860,7 @@ void dislikeComment_Success() { when(likedStoryCommentRepository.save(any(LikedStoryComment.class))).thenReturn(likedStoryComment); // when - StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService.dislikeComment(studyId, postId, commentId); + StoryCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService.dislikeComment(studyId, postId, commentId); // then assertNotNull(result); @@ -933,7 +933,7 @@ void cancelCommentLike_Success() { when(storyCommentRepository.save(any(StoryComment.class))).thenReturn(studyPost1Comment2); // when - StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService + StoryCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService .cancelCommentLike(studyId, postId, commentId); // then @@ -1002,7 +1002,7 @@ void cancelCommentDislike() { when(storyCommentRepository.save(any(StoryComment.class))).thenReturn(studyPost1Comment2); // when - StudyPostCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService + StoryCommentResponseDTO.CommentPreviewDTO result = studyPostCommandService .cancelCommentDislike(studyId, postId, commentId); // then diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java index 3ee3f23d..ebfa07d0 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java @@ -18,9 +18,9 @@ import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.story.application.application.StoryQueryServiceImpl; -import com.example.spot.study.presentation.dto.response.StudyPostCommentResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyPostResDTO; +import com.example.spot.story.application.StoryQueryServiceImpl; +import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -149,7 +149,7 @@ void getAllPosts_All_Success() { .thenReturn(List.of(story1, story3)); // when - StudyPostResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, null); + StoryResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, null); // then assertNotNull(result); @@ -178,7 +178,7 @@ void getAllPosts_Theme_Success() { .thenReturn(List.of(story1, story3)); // when - StudyPostResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, StoryCategoryQuery.FREE_TALK); + StoryResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, StoryCategoryQuery.FREE_TALK); // then assertNotNull(result); @@ -207,7 +207,7 @@ void getAllPosts_Announcements_Success() { .thenReturn(List.of(story1, story3)); // when - StudyPostResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, StoryCategoryQuery.ANNOUNCEMENT); + StoryResDTO.PostListDTO result = studyPostQueryService.getAllPosts(pageRequest, studyId, StoryCategoryQuery.ANNOUNCEMENT); // then assertNotNull(result); @@ -286,7 +286,7 @@ void getPost_Common_Success() { .thenReturn(false); // when - StudyPostResDTO.PostDetailDTO result = studyPostQueryService.getPost(studyId, postId, false); + StoryResDTO.PostDetailDTO result = studyPostQueryService.getPost(studyId, postId, false); // then assertNotNull(result); @@ -319,7 +319,7 @@ void getPost_LikeOrScrap_Success() { .thenReturn(false); // when - StudyPostResDTO.PostDetailDTO result = studyPostQueryService.getPost(studyId, postId, true); + StoryResDTO.PostDetailDTO result = studyPostQueryService.getPost(studyId, postId, true); // then assertNotNull(result); @@ -394,7 +394,7 @@ void getAllComments_Success() { .thenReturn(List.of(studyPost1Comment1, studyPost1Comment2)); // when - StudyPostCommentResponseDTO.CommentReplyListDTO result = studyPostQueryService.getAllComments(studyId, postId); + StoryCommentResponseDTO.CommentReplyListDTO result = studyPostQueryService.getAllComments(studyId, postId); // then assertNotNull(result); From bfb1f1d7d2cb2a475d0afba5e7f77ae4b826f159 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 17:00:13 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[SPOT-286][REFACTOR]=20ToDo=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StudyMemberCommandService.java | 6 +- .../StudyMemberCommandServiceImpl.java | 6 +- .../application/StudyMemberQueryService.java | 2 +- .../StudyMemberQueryServiceImpl.java | 4 +- .../controller/MemberStudyController.java | 339 ++++++------------ .../todo/application/ToDoCommandService.java | 20 ++ .../application/ToDoCommandServiceImpl.java | 255 +++++++++++++ .../todo/application/ToDoQueryService.java | 16 + .../application/ToDoQueryServiceImpl.java | 134 +++++++ .../controller/ToDoController.java | 156 ++++++++ .../dto/request/ToDoListRequestDTO.java | 2 +- .../dto/response/ToDoListResponseDTO.java | 2 +- ...eTest.java => ToDoCommandServiceTest.java} | 8 +- ...iceTest.java => ToDoQueryServiceTest.java} | 4 +- 14 files changed, 707 insertions(+), 247 deletions(-) create mode 100644 src/main/java/com/example/spot/todo/application/ToDoCommandService.java create mode 100644 src/main/java/com/example/spot/todo/application/ToDoCommandServiceImpl.java create mode 100644 src/main/java/com/example/spot/todo/application/ToDoQueryService.java create mode 100644 src/main/java/com/example/spot/todo/application/ToDoQueryServiceImpl.java create mode 100644 src/main/java/com/example/spot/todo/presentation/controller/ToDoController.java rename src/main/java/com/example/spot/{study => todo}/presentation/dto/request/ToDoListRequestDTO.java (87%) rename src/main/java/com/example/spot/{study => todo}/presentation/dto/response/ToDoListResponseDTO.java (96%) rename src/test/java/com/example/spot/service/study/studymember/{StudyMemberCommandServiceTest.java => ToDoCommandServiceTest.java} (97%) rename src/test/java/com/example/spot/service/study/studymember/{StudyMemberQueryServiceTest.java => ToDoQueryServiceTest.java} (99%) diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java index 266281a9..f2541cca 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java @@ -8,9 +8,9 @@ import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; -import com.example.spot.study.presentation.dto.request.ToDoListRequestDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; import jakarta.validation.Valid; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java index 8efcb496..2f25e803 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java @@ -36,9 +36,9 @@ import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.common.application.s3.S3ImageService; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.study.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO.WithdrawalDTO; import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java index 2d78ac8c..91a8da6c 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java @@ -1,6 +1,6 @@ package com.example.spot.study.application; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO; import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java index d0d2003b..11457ffd 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java @@ -31,8 +31,8 @@ import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO.ToDoListDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO.ToDoListDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; import com.example.spot.vote.domain.Vote; diff --git a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java index b818279e..5cffa454 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java @@ -22,10 +22,10 @@ import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.study.presentation.dto.request.ToDoListRequestDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; @@ -50,14 +50,14 @@ public class MemberStudyController { private final StudyMemberQueryService studyMemberQueryService; private final StudyMemberCommandService studyMemberCommandService; -/* ----------------------------- 진행중인 스터디 관련 API ------------------------------------- */ + /* ----------------------------- 진행중인 스터디 관련 API ------------------------------------- */ @Tag(name = "진행중인 스터디") @Operation(summary = "[진행중인 스터디] 스터디 탈퇴하기", description = """ - ## [진행중인 스터디] 마이페이지 > 진행중 > 진행중인 스터디의 메뉴 클릭, 로그인한 회원이 현재 진행중인 스터디에서 탈퇴합니다. - 로그인한 회원이 참여하는 특정 스터디에 대해 member_study 튜플을 삭제합니다. - """) + ## [진행중인 스터디] 마이페이지 > 진행중 > 진행중인 스터디의 메뉴 클릭, 로그인한 회원이 현재 진행중인 스터디에서 탈퇴합니다. + 로그인한 회원이 참여하는 특정 스터디에 대해 member_study 튜플을 삭제합니다. + """) @DeleteMapping("/studies/{studyId}/withdrawal") public ApiResponse withdrawFromStudy(@PathVariable Long studyId) { StudyWithdrawalResponseDTO.WithdrawalDTO withdrawalDTO = studyMemberCommandService.withdrawFromStudy(studyId); @@ -67,10 +67,10 @@ public ApiResponse withdrawFromStudy(@ @Tag(name = "진행중인 스터디") @Operation(summary = "[진행중인 스터디] 스터디 호스트 탈퇴", description = """ - ## [진행중인 스터디] 특정 스터디의 호스트가 해당 스터디에서 탈퇴합니다. - 탈퇴 시, 호스트 권한이 회수되며 스터디에서 제외됩니다. - 요청 시, 새로운 호스트의 아이디와 임명 사유를 입력해야 합니다. - """) + ## [진행중인 스터디] 특정 스터디의 호스트가 해당 스터디에서 탈퇴합니다. + 탈퇴 시, 호스트 권한이 회수되며 스터디에서 제외됩니다. + 요청 시, 새로운 호스트의 아이디와 임명 사유를 입력해야 합니다. + """) @DeleteMapping("/studies/{studyId}/hosts/withdrawal") public ApiResponse withdrawHostFromStudy( @PathVariable Long studyId, @@ -83,110 +83,112 @@ public ApiResponse withdrawHostFromStu @Tag(name = "진행중인 스터디") @Operation(summary = "[진행중인 스터디] 스터디 끝내기", description = """ - ## [진행중인 스터디] 마이페이지 > 진행중 > 진행중인 스터디의 메뉴 클릭, 로그인한 회원이 운영중인 스터디를 끝냅니다. - * 로그인한 회원이 운영하는 특정 스터디에 대해 study status OFF로 전환합니다. - * 스터디 성과를 입력받아 DB에 저장합니다. - """) + ## [진행중인 스터디] 마이페이지 > 진행중 > 진행중인 스터디의 메뉴 클릭, 로그인한 회원이 운영중인 스터디를 끝냅니다. + * 로그인한 회원이 운영하는 특정 스터디에 대해 study status OFF로 전환합니다. + * 스터디 성과를 입력받아 DB에 저장합니다. + """) @PatchMapping("/studies/{studyId}/termination") public ApiResponse terminateStudy( @PathVariable @ExistStudy Long studyId, - @RequestParam @TextLength(min=1, max=30) String performance + @RequestParam @TextLength(min = 1, max = 30) String performance ) { StudyTerminationResponseDTO.TerminationDTO terminationDTO = studyMemberCommandService.terminateStudy(studyId, performance); return ApiResponse.onSuccess(SuccessStatus._STUDY_TERMINATED, terminationDTO); } -/* ----------------------------- 모집중인 스터디 관련 API ------------------------------------- */ + /* ----------------------------- 모집중인 스터디 관련 API ------------------------------------- */ @Tag(name = "모집중인 스터디") @Operation(summary = "[모집중인 스터디] 스터디 별 신청 여부 조회하기", description = """ - ## [모집중인 스터디] 로그인한 회원이 모집중인 스터디에 대해 신청 여부를 조회합니다. - 로그인한 회원이 참여하는 특정 스터디에 대해 member_study의 application_status가 APPLIED인지 확인합니다. - 반환 값은 boolean으로, 신청 여부를 나타냅니다. - true: 신청한 상태, false: 신청하지 않은 상태 - """) + ## [모집중인 스터디] 로그인한 회원이 모집중인 스터디에 대해 신청 여부를 조회합니다. + 로그인한 회원이 참여하는 특정 스터디에 대해 member_study의 application_status가 APPLIED인지 확인합니다. + 반환 값은 boolean으로, 신청 여부를 나타냅니다. + true: 신청한 상태, false: 신청하지 않은 상태 + """) @GetMapping("/studies/{studyId}/is-applied") @Parameter(name = "studyId", description = "모집중인 스터디의 ID를 입력 받습니다.", required = true) public ApiResponse getIsApplied(@PathVariable @ExistStudy Long studyId) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_FOUND, - studyMemberQueryService.isApplied(studyId)); + studyMemberQueryService.isApplied(studyId)); } @Tag(name = "모집중인 스터디") @Operation(summary = "[모집중인 스터디] 스터디별 신청 회원 목록 불러오기", description = """ - ## [모집중인 스터디] 마이페이지 > 모집중 > 스터디 클릭, 로그인한 회원이 모집중인 스터디에 신청한 회원 목록을 불러옵니다. - 로그인한 회원이 모집중인 특정 스터디에 대해 member_study의 application_status가 APPLIED인 회원 목록이 반환됩니다. - """) + ## [모집중인 스터디] 마이페이지 > 모집중 > 스터디 클릭, 로그인한 회원이 모집중인 스터디에 신청한 회원 목록을 불러옵니다. + 로그인한 회원이 모집중인 특정 스터디에 대해 member_study의 application_status가 APPLIED인 회원 목록이 반환됩니다. + """) @GetMapping("/studies/{studyId}/applicants") @Parameter(name = "studyId", description = "모집중인 스터디의 ID를 입력 받습니다.", required = true) public ApiResponse getAllApplicants(@PathVariable @ExistStudy Long studyId) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_FOUND, - studyMemberQueryService.findStudyApplicants(studyId)); + studyMemberQueryService.findStudyApplicants(studyId)); } + @Tag(name = "모집중인 스터디") @Operation(summary = "[모집중인 스터디] 스터디 신청 정보(이름, 자기소개) 불러오기", description = """ - ## [모집중인 스터디] 마이페이지 > 모집중 > 스터디 > 신청 회원 클릭, 로그인한 회원이 모집중인 스터디에 신청한 회원의 정보를 불러옵니다. - 로그인한 회원이 모집중인 특정 스터디에 신청한 회원의 정보(member.name & member_study.introduction)가 반환됩니다. - """) + ## [모집중인 스터디] 마이페이지 > 모집중 > 스터디 > 신청 회원 클릭, 로그인한 회원이 모집중인 스터디에 신청한 회원의 정보를 불러옵니다. + 로그인한 회원이 모집중인 특정 스터디에 신청한 회원의 정보(member.name & member_study.introduction)가 반환됩니다. + """) @GetMapping("/studies/{studyId}/applicants/{applicantId}") @Parameter(name = "studyId", description = "모집중인 스터디의 ID를 입력 받습니다.", required = true) @Parameter(name = "applicantId", description = "신청자의 ID를 입력 받습니다.", required = true) public ApiResponse getApplicantInfo( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistMember Long applicantId) { + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistMember Long applicantId) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_FOUND, - studyMemberQueryService.findStudyApplication(studyId, applicantId)); + studyMemberQueryService.findStudyApplication(studyId, applicantId)); } + @Tag(name = "모집중인 스터디") @Operation(summary = "[모집중인 스터디] 스터디 신청 처리하기", description = """ - ## [모집중인 스터디] 마이페이지 > 모집중 > 스터디 > 신청 회원 > 거절 클릭, 로그인한 회원이 모집중인 스터디에 신청한 회원을 처리합니다. - isAccept가 true인 경우 member_study에서 application_status를 AWAITING_SELF_APPROVAL 수정합니다. -> 참가 희망하는 회원이 알림을 통해 스스로 승인 해야 스터디 참여가 완료됩니다. - isAccept가 false인 경우 member_study에서 application_status를 REJECTED로 수정합니다. - 스터디 신청 처리 결과를 응답으로 반환합니다. - """) + ## [모집중인 스터디] 마이페이지 > 모집중 > 스터디 > 신청 회원 > 거절 클릭, 로그인한 회원이 모집중인 스터디에 신청한 회원을 처리합니다. + isAccept가 true인 경우 member_study에서 application_status를 AWAITING_SELF_APPROVAL 수정합니다. -> 참가 희망하는 회원이 알림을 통해 스스로 승인 해야 스터디 참여가 완료됩니다. + isAccept가 false인 경우 member_study에서 application_status를 REJECTED로 수정합니다. + 스터디 신청 처리 결과를 응답으로 반환합니다. + """) @PostMapping("/studies/{studyId}/applicants/{applicantId}") @Parameter(name = "studyId", description = "모집중인 스터디의 ID를 입력 받습니다.", required = true) @Parameter(name = "applicantId", description = "신청자의 ID를 입력 받습니다.", required = true) public ApiResponse rejectApplicant( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistMember Long applicantId, - @RequestParam boolean isAccept) { + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistMember Long applicantId, + @RequestParam boolean isAccept) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_UPDATED, - studyMemberCommandService.acceptAndRejectStudyApply(applicantId, studyId, isAccept)); + studyMemberCommandService.acceptAndRejectStudyApply(applicantId, studyId, isAccept)); } @Tag(name = "테스트 용 API", description = "테스트 용 API") @Operation(summary = "!테스트 용! [모집중인 스터디] 스터디 신청 처리하기", description = """ - ## [모집중인 스터디] 빠른 API 적용를 위해 스터디 신청 처리를 즉시 수행합니다. - 위 API와 동일한 기능을 수행하지만, 실제 알림이 발송되지 않습니다. - 또한 스터디 신청 승인 시, 회원의 상태가 AWAITING_SELF_APPROVAL가 아닌 APPROVED로 변경됩니다. - - 즉, 바로 스터디 참여가 완료됩니다. 스터디 회원 조회 시, 바로 승인된 회원으로 조회됩니다. - - isAccept가 true인 경우 member_study에서 application_status를 APPROVED로 수정합니다. - isAccept가 false인 경우 member_study에서 application_status를 REJECTED로 수정합니다. - 스터디 신청 처리 결과를 응답으로 반환합니다. - """) + ## [모집중인 스터디] 빠른 API 적용를 위해 스터디 신청 처리를 즉시 수행합니다. + 위 API와 동일한 기능을 수행하지만, 실제 알림이 발송되지 않습니다. + 또한 스터디 신청 승인 시, 회원의 상태가 AWAITING_SELF_APPROVAL가 아닌 APPROVED로 변경됩니다. + + 즉, 바로 스터디 참여가 완료됩니다. 스터디 회원 조회 시, 바로 승인된 회원으로 조회됩니다. + + isAccept가 true인 경우 member_study에서 application_status를 APPROVED로 수정합니다. + isAccept가 false인 경우 member_study에서 application_status를 REJECTED로 수정합니다. + 스터디 신청 처리 결과를 응답으로 반환합니다. + """) @PostMapping("/studies/{studyId}/applicants/{applicantId}/test") @Parameter(name = "studyId", description = "모집중인 스터디의 ID를 입력 받습니다.", required = true) @Parameter(name = "applicantId", description = "신청자의 ID를 입력 받습니다.", required = true) public ApiResponse rejectApplicantForTest( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistMember Long applicantId, - @RequestParam boolean isAccept) { + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistMember Long applicantId, + @RequestParam boolean isAccept) { return ApiResponse.onSuccess(SuccessStatus._STUDY_APPLICANT_UPDATED, - studyMemberCommandService.acceptAndRejectStudyApplyForTest(applicantId, studyId, isAccept)); + studyMemberCommandService.acceptAndRejectStudyApplyForTest(applicantId, studyId, isAccept)); } -/* ----------------------------- 스터디 상세 정보 관련 API ------------------------------------- */ + /* ----------------------------- 스터디 상세 정보 관련 API ------------------------------------- */ @Tag(name = "스터디 상세 정보") @Operation(summary = "[스터디 상세 정보] 스터디 최근 공지 1개 불러오기", description = """ - ## [스터디 상세 정보] 내 스터디 > 스터디 클릭, 로그인한 회원이 참여하는 특정 스터디의 최근 공지 1개를 불러옵니다. - study_post의 announced_at이 가장 최근인 공지 1개가 반환됩니다. - """) + ## [스터디 상세 정보] 내 스터디 > 스터디 클릭, 로그인한 회원이 참여하는 특정 스터디의 최근 공지 1개를 불러옵니다. + study_post의 announced_at이 가장 최근인 공지 1개가 반환됩니다. + """) @GetMapping("/studies/{studyId}/announce") public ApiResponse getRecentAnnouncement(@PathVariable @ExistStudy Long studyId) { StoryResponseDTO storyResponseDTO = studyMemberQueryService.findStudyAnnouncementPost(studyId); @@ -195,37 +197,36 @@ public ApiResponse getRecentAnnouncement(@PathVariable @ExistS @Tag(name = "스터디 상세 정보") @Operation(summary = "[스터디 상세 정보] 스터디에 참여하는 회원 목록 불러오기", description = """ - ## [스터디 상세 정보] 로그인한 회원이 참여하는 특정 스터디의 회원 목록을 전체 조회 합니다. - member_study에서 application_status=APPROVED인 회원의 목록(이름, 프로필 사진 포함)이 반환됩니다. - """) + ## [스터디 상세 정보] 로그인한 회원이 참여하는 특정 스터디의 회원 목록을 전체 조회 합니다. + member_study에서 application_status=APPROVED인 회원의 목록(이름, 프로필 사진 포함)이 반환됩니다. + """) @GetMapping("/studies/{studyId}/members") public ApiResponse getStudyMembers( - @PathVariable @ExistStudy Long studyId){ + @PathVariable @ExistStudy Long studyId) { StudyMemberResponseDTO studyMemberResponseDTO = studyMemberQueryService.findStudyMembers(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_FOUND, studyMemberResponseDTO); } @Tag(name = "스터디 상세 정보") @Operation(summary = "[스터디 상세 정보] 스터디 호스트 정보 불러오기", description = """ - ## [스터디 상세 정보] 로그인한 회원이 참여하는 특정 스터디의 호스트 정보를 조회합니다. - * isOwned : 로그인한 회원이 호스트인지 true or false로 반환 - * host : 호스트의 id와 nickname 반환 - """) + ## [스터디 상세 정보] 로그인한 회원이 참여하는 특정 스터디의 호스트 정보를 조회합니다. + * isOwned : 로그인한 회원이 호스트인지 true or false로 반환 + * host : 호스트의 id와 nickname 반환 + """) @GetMapping("/studies/{studyId}/host") public ApiResponse getStudyHost( - @PathVariable @ExistStudy Long studyId) - { + @PathVariable @ExistStudy Long studyId) { StudyMemberResDTO.StudyHostDTO studyHostDTO = studyMemberQueryService.getStudyHost(studyId); return ApiResponse.onSuccess(SuccessStatus._STUDY_HOST_FOUND, studyHostDTO); } -/* ----------------------------- 스터디 투표 관련 API ------------------------------------- */ + /* ----------------------------- 스터디 투표 관련 API ------------------------------------- */ @Tag(name = "스터디 투표") @Operation(summary = "[스터디 투표] 투표 생성하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 작성 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 새로운 투표를 등록합니다. - 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. - """) + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 작성 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 새로운 투표를 등록합니다. + 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. + """) @Parameter(name = "studyId", description = "투표를 생성할 스터디의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/votes") public ApiResponse createVote( @@ -237,9 +238,9 @@ public ApiResponse createVote( @Tag(name = "스터디 투표") @Operation(summary = "[스터디 투표] 투표하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 스터디에서 특정 항목에 투표합니다. - member_vote에 투표 정보를 저장합니다. - """) + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 스터디에서 특정 항목에 투표합니다. + member_vote에 투표 정보를 저장합니다. + """) @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "voteId", description = "참여할 스터디 투표의 id를 입력합니다.") @PostMapping("/studies/{studyId}/votes/{voteId}/options") @@ -253,9 +254,9 @@ public ApiResponse vote( @Tag(name = "스터디 투표") @Operation(summary = "[스터디 투표] 투표 편집하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 편집하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표 정보를 수정합니다. - 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. - """) + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 편집하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표 정보를 수정합니다. + 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. + """) @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "voteId", description = "편집할 스터디 투표의 id를 입력합니다.") @PatchMapping("/studies/{studyId}/votes/{voteId}") @@ -269,9 +270,9 @@ public ApiResponse updateVote( @Tag(name = "스터디 투표") @Operation(summary = "[스터디 투표] 투표 삭제하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 삭제하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표를 삭제합니다. - 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. - """) + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 삭제하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표를 삭제합니다. + 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. + """) @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "voteId", description = "삭제할 스터디 투표의 id를 입력합니다.") @DeleteMapping("/studies/{studyId}/votes/{voteId}") @@ -284,9 +285,9 @@ public ApiResponse deleteVote( @Tag(name = "스터디 투표") @Operation(summary = "[스터디 투표] 투표 목록 불러오기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표 목록을 불러옵니다. - 진행 중(finished_at 이전)인 투표 목록과 마감(finished_at 이후)된 투표 목록을 구분하여 반환합니다. - """) + ## [스터디 투표] 내 스터디 > 스터디 > 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표 목록을 불러옵니다. + 진행 중(finished_at 이전)인 투표 목록과 마감(finished_at 이후)된 투표 목록을 구분하여 반환합니다. + """) @Parameter(name = "studyId", description = "투표 목록을 불러올 스터디의 id를 입력합니다.", required = true) @GetMapping("/studies/{studyId}/votes") public ApiResponse getAllVotes( @@ -297,11 +298,11 @@ public ApiResponse getAllVotes( @Tag(name = "스터디 투표") @Operation(summary = "[스터디 투표] 투표 불러오기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. - 진행중인 투표 : 진행중인 투표에 대한 항목 및 기본 정보가 반환됩니다. - 마감된 투표 : 마감된 투표에 대한 항목과 투표 인원수가 반환됩니다. - (진행중인 투표인지 마감된 투표인지에 따라 Response DTO가 서로 다릅니다.) - """) + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. + 진행중인 투표 : 진행중인 투표에 대한 항목 및 기본 정보가 반환됩니다. + 마감된 투표 : 마감된 투표에 대한 항목과 투표 인원수가 반환됩니다. + (진행중인 투표인지 마감된 투표인지에 따라 Response DTO가 서로 다릅니다.) + """) @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "voteId", description = "불러올 스터디 투표의 id를 입력합니다.") @GetMapping("/studies/{studyId}/votes/{voteId}") @@ -320,9 +321,9 @@ public ApiResponse getVote( @Tag(name = "스터디 투표") @Operation(summary = "[스터디 투표] 마감된 투표 현황 불러오기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 마감된 투표 > n명 참여 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. - 마감된 투표에 대하여 항목별 투표 회원 목록을 반환합니다. - """) + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 마감된 투표 > n명 참여 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. + 마감된 투표에 대하여 항목별 투표 회원 목록을 반환합니다. + """) @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "voteId", description = "마감된 스터디 투표의 id를 입력합니다.") @GetMapping("/studies/{studyId}/votes/{voteId}/details") @@ -333,12 +334,12 @@ public ApiResponse getCompletedVote return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_DETAIL_STATUS_FOUND, completedVoteDetailDTO); } -/* ----------------------------- 스터디 갤러리 관련 API ------------------------------------- */ + /* ----------------------------- 스터디 갤러리 관련 API ------------------------------------- */ @Tag(name = "스터디 이미지") @Operation(summary = "[스터디 갤러리] 스터디 이미지 목록 불러오기", description = """ - ## [스터디 갤러리] 내 스터디 > 스터디 > 갤러리 클릭, 로그인한 회원이 참여하는 스터디의 이미지 목록을 불러옵니다. - study_post에 존재하는 모든 게시글의 이미지를 최신순으로 반환합니다. - """) + ## [스터디 갤러리] 내 스터디 > 스터디 > 갤러리 클릭, 로그인한 회원이 참여하는 스터디의 이미지 목록을 불러옵니다. + study_post에 존재하는 모든 게시글의 이미지를 최신순으로 반환합니다. + """) @Parameter(name = "studyId", description = "이미지 목록을 불러올 스터디의 id를 입력합니다.", required = true) @GetMapping("/studies/{studyId}/images") public ApiResponse getAllStudyImages( @@ -349,13 +350,13 @@ public ApiResponse getAllStudyImages( return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_IMAGES_FOUND, imageListDTO); } -/* ----------------------------- 스터디 회원 신고 관련 API ------------------------------------- */ + /* ----------------------------- 스터디 회원 신고 관련 API ------------------------------------- */ @Tag(name = "스터디 신고") @Operation(summary = "[스터디 신고] 스터디원 신고하기", description = """ - ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 다른 회원을 신고합니다. - 신고당한 회원의 id와 이름이 반환됩니다. - """) + ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 다른 회원을 신고합니다. + 신고당한 회원의 id와 이름이 반환됩니다. + """) @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "memberId", description = "신고할 스터디원의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/members/{memberId}/reports") @@ -368,9 +369,9 @@ public ApiResponse reportStudyMember( @Tag(name = "스터디 신고") @Operation(summary = "[스터디 신고] 스터디 게시글 신고하기", description = """ - ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 게시글을 신고합니다. - 신고당한 스터디 게시글의 id와 제목이 반환됩니다. - """) + ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 게시글을 신고합니다. + 신고당한 스터디 게시글의 id와 제목이 반환됩니다. + """) @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) @Parameter(name = "postId", description = "신고할 스터디 게시글의 id를 입력합니다.", required = true) @PostMapping("/studies/{studyId}/posts/{postId}/reports") @@ -380,127 +381,5 @@ public ApiResponse reportStudyPost( StoryResDTO.PostPreviewDTO postPreviewDTO = studyMemberCommandService.reportStudyPost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_REPORTED, postPreviewDTO); } - - /* ----------------------------- To-do list 관련 API ------------------------------------- */ - - @Tag(name = "To-Do List") - @Operation(summary = "[To-Do List] To-Do List 생성", description = """ - ## [To-Do List] 로그인한 회원이 참여하는 스터디에 To-Do List를 생성합니다. - To-Do List의 id와 제목, 생성 시간이 반환됩니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/to-do") - public ApiResponse createToDoList( - @PathVariable @ExistStudy Long studyId, - @RequestBody @Valid ToDoListRequestDTO.ToDoListCreateDTO request) { - ToDoListCreateResponseDTO toDoList = studyMemberCommandService.createToDoList(studyId, - request); - return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_CREATED, toDoList); - } - - @Tag(name = "To-Do List") - @Operation(summary = "[To-Do List] To-Do List 내용 수정", description = """ - ## [To-Do List] To-Do List에 작성한 할 일의 내용을 수정 합니다. - 변경 하지 않을 값은 아예 입력하지 않아야 합니다. - ex) date만 변경할 경우, content는 입력하지 않습니다. -> "date": "2022-12-31" 만 입력 - - To-Do List의 id와 수정된 할 일의 내용, 수정 시간이 반환됩니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "toDoId", description = "상태를 변경할 To-Do List의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/to-do/{toDoId}/update") - public ApiResponse updateToDoList( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistToDo Long toDoId, - @RequestBody @Valid ToDoListRequestDTO.ToDoListCreateDTO request) { - ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = studyMemberCommandService.updateToDoList( - studyId, toDoId, request); - return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_UPDATED, toDoListUpdateResponseDTO); - } - - - - @Tag(name = "To-Do List") - @Operation(summary = "[To-Do List] To-Do List 체크 처리", description = """ - ## [To-Do List] To-Do List에 작성한 할 일의 체크 상태를 변경 합니다. - - 체크 표시 되어 있는 경우, 해당 API를 재호출 하면 체크가 해제됩니다. - - To-Do List의 id와 체크한 할 일의 id, 체크 여부가 반환됩니다. - - 본인이 작성한 To-Do List만 체크할 수 있습니다. - 체크 여부가 true 인 경우, 할 일이 완료 되었음을 의미합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "toDoId", description = "상태를 변경할 To-Do List의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/to-do/{toDoId}/check") - public ApiResponse checkToDoList( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistToDo Long toDoId) { - ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = studyMemberCommandService.checkToDoList( - studyId, toDoId); - return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_UPDATED, toDoListUpdateResponseDTO); - } - - @Tag(name = "To-Do List") - @Operation(summary = "[To-Do List] To-Do List 삭제", description = """ - ## [To-do list] 로그인한 회원이 참여하는 스터디에 To-Do List를 삭제합니다. - - To-Do List 완료 처리와는 다른 개념으로, To-Do List를 삭제합니다. - To-Do List의 id와 상태 업데이트 시간이 반환됩니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "toDoId", description = "삭제할 To-Do List의 id를 입력합니다.", required = true) - @DeleteMapping("/studies/{studyId}/to-do/{toDoId}") - public ApiResponse deleteToDoList( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistToDo Long toDoId) { - ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = studyMemberCommandService.deleteToDoList( - studyId, toDoId); - return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_DELETED, toDoListUpdateResponseDTO); - } - - @Tag(name = "To-Do List") - @Operation(summary = "[To-Do List] 내 To-Do List 조회", description = """ - ## [To-Do List] 특정 스터디에 저장된 내 To-Do List를 날짜 별로 페이징 조회합니다. - 조회하고 싶은 날짜를 입력 받아, 해당 날짜의 할 일 목록, 체크 여부가 반환됩니다. - - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "page", description = "조회할 페이지 번호를 입력 받습니다. 페이지 번호는 0부터 시작합니다.", required = true) - @Parameter(name = "size", description = "조회할 페이지 크기를 입력 받습니다. 페이지 크기는 1 이상의 정수 입니다. ", required = true) - @Parameter(name = "date", description = "조회할 날짜를 입력 받습니다. 날짜는 yyyy-MM-dd 형식으로 입력 받습니다.", required = true) - @GetMapping("/studies/{studyId}/to-do/my") - public ApiResponse getMyToDoList( - @PathVariable @ExistStudy Long studyId, - @RequestParam @Min(0) Integer page, - @RequestParam @Min(1) Integer size, - @RequestParam LocalDate date) { - ToDoListSearchResponseDTO toDoList = studyMemberQueryService.getToDoList(studyId, date, - PageRequest.of(page, size)); - return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_FOUND, toDoList); - } - - @Tag(name = "To-Do List") - @Operation(summary = "[To-Do List] 다른 스터디 원 To-Do List 조회", description = """ - ## [To-Do List] 특정 스터디에 저장된 다른 스터디원의 To-Do List를 날짜 별로 페이징 조회합니다. - 조회하고 싶은 날짜를 입력 받아, 해당 날짜의 할 일 목록, 체크 여부가 반환됩니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "memberId", description = "To-do list를 조회할 회원의 id를 입력합니다.", required = true) - @Parameter(name = "page", description = "조회할 페이지 번호를 입력 받습니다. 페이지 번호는 0부터 시작합니다.", required = true) - @Parameter(name = "size", description = "조회할 페이지 크기를 입력 받습니다. 페이지 크기는 1 이상의 정수 입니다. ", required = true) - @Parameter(name = "date", description = "조회할 날짜를 입력 받습니다. 날짜는 yyyy-MM-dd 형식으로 입력 받습니다.", required = true) - @GetMapping("/studies/{studyId}/to-do/members/{memberId}") - public ApiResponse getOtherToDoList( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistMember Long memberId, - @RequestParam @Min(0) Integer page, - @RequestParam @Min(1) Integer size, - @RequestParam LocalDate date) { - ToDoListSearchResponseDTO toDoList = studyMemberQueryService.getMemberToDoList(studyId, - memberId, date, PageRequest.of(page, size)); - return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_FOUND, toDoList); - } - -} + +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/todo/application/ToDoCommandService.java b/src/main/java/com/example/spot/todo/application/ToDoCommandService.java new file mode 100644 index 00000000..d8237d0f --- /dev/null +++ b/src/main/java/com/example/spot/todo/application/ToDoCommandService.java @@ -0,0 +1,20 @@ +package com.example.spot.todo.application; + +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; + +public interface ToDoCommandService { + + // 투두 리스트 생성 + ToDoListCreateResponseDTO createToDoList(Long studyId, ToDoListRequestDTO.ToDoListCreateDTO toDoListCreateDTO); + + // 투두 리스트 체크 + ToDoListResponseDTO.ToDoListUpdateResponseDTO checkToDoList(Long studyId, Long toDoListId); + + // 투두 리스트 수정 + ToDoListResponseDTO.ToDoListUpdateResponseDTO updateToDoList(Long studyId, Long toDoListId, ToDoListRequestDTO.ToDoListCreateDTO toDoListCreateDTO); + + // 투두 리스트 삭제 + ToDoListResponseDTO.ToDoListUpdateResponseDTO deleteToDoList(Long studyId, Long toDoListId); +} diff --git a/src/main/java/com/example/spot/todo/application/ToDoCommandServiceImpl.java b/src/main/java/com/example/spot/todo/application/ToDoCommandServiceImpl.java new file mode 100644 index 00000000..59e0859c --- /dev/null +++ b/src/main/java/com/example/spot/todo/application/ToDoCommandServiceImpl.java @@ -0,0 +1,255 @@ +package com.example.spot.todo.application; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.handler.MemberHandler; +import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.common.security.utils.SecurityUtils; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.MemberRepository; +import com.example.spot.notification.domain.Notification; +import com.example.spot.notification.domain.NotificationRepository; +import com.example.spot.notification.domain.enums.NotifyType; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.StudyRepository; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.todo.domain.ToDo; +import com.example.spot.todo.domain.ToDoRepository; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Objects; + + +@Service +@RequiredArgsConstructor +@Transactional +public class ToDoCommandServiceImpl implements ToDoCommandService { + + private final MemberRepository memberRepository; + private final StudyRepository studyRepository; + private final StudyMemberRepository studyMemberRepository; + private final ToDoRepository toDoRepository; + private final NotificationRepository notificationRepository; + + + /** + * To-Do List를 생성합니다. + * @param studyId 생성할 To-Do List가 속한 스터디 ID + * @param toDoListCreateDTO 생성할 To-Do List 정보 + * @return 생성된 To-Do List 정보 + * @throws StudyHandler 스터디를 찾을 수 없을 때 + * @throws StudyHandler To-Do List를 생성하는 회원이 스터디 회원이 아닐 때 + * @throws StudyHandler 해당 회원을 찾을 수 없을 때 + */ + @Override + public ToDoListCreateResponseDTO createToDoList(Long studyId, + ToDoListCreateDTO toDoListCreateDTO) { + + // 스터디 조회 + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // To-Do List를 생성하는 회원 ID 조회 + Long currentUserId = SecurityUtils.getCurrentUserId(); + + // To-Do List를 생성하는 회원이 스터디 회원인지 확인 + if (!isMember(currentUserId, studyId)) + throw new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND); + + // 회원 조회 + Member member = memberRepository.findById(currentUserId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + + // To-Do List 생성 + ToDo toDo = ToDo.builder() + .study(study) + .member(member) + .date(toDoListCreateDTO.getDate()) + .isDone(false) + .content(toDoListCreateDTO.getContent()) + .build(); + + // To-Do List 저장 + toDo.setToDoList(); + toDoRepository.save(toDo); + + // To-Do List 생성 DTO 반환 + return ToDoListCreateResponseDTO.builder() + .id(toDo.getId()) + .content(toDo.getContent()) + .createdAt(toDo.getCreatedAt()) + .build(); + } + + // studyId가 필요할까? + + /** + * To-Do List에 작성한 할 일의 체크 상태를 변경 합니다. 체크 상태를 변경 하면 해당 스터디에 참여하고 있는 모든 회원에게 알림이 전송됩니다. + * @param studyId 스터디 ID + * @param toDoListId 변경할 To-Do List ID + * @return To-Do List 변경 여부와 변경 시간 + * @throws StudyHandler To-Do List를 찾을 수 없을 때 + * @throws StudyHandler To-Do List가 스터디에 속하지 않을 때 + * @throws StudyHandler To-Do List를 변경하는 회원이 스터디 회원이 아닐 때 + * @throws StudyHandler 알림 생성 할 스터디 회원을 찾을 수 없을 때 + */ + @Override + public ToDoListUpdateResponseDTO checkToDoList(Long studyId, Long toDoListId) { + + // To-Do List 조회 + ToDo toDo = toDoRepository.findById(toDoListId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_TODO_NOT_FOUND)); + + // To-Do List가 속한 스터디가 아니면 예외 처리 + if (!Objects.equals(toDo.getStudy().getId(), studyId)) + throw new StudyHandler(ErrorStatus._STUDY_TODO_IS_NOT_BELONG_TO_STUDY); + + // To-Do List를 변경하는 회원이 스터디 회원이 아니면 예외 처리 + Long currentUserId = SecurityUtils.getCurrentUserId(); + if (!toDo.getMember().getId().equals(currentUserId)) + throw new StudyHandler(ErrorStatus._STUDY_TODO_NOT_AUTHORIZED); + + // To-Do List 체크 상태 변경 + toDo.check(); + + // 스터디 회원의 To-Do List 중 하나가 완료 되면, 해당 스터디의 모든 회원에게 알림 전송 + if (toDo.isDone()){ + List members = studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED).stream() + .map(StudyMember::getMember) + .toList(); + + // 알림을 생성할 회원이 없으면 알림 생성하지 않음 + if (members.isEmpty()){ + return ToDoListUpdateResponseDTO.builder() + .id(toDo.getId()) + .isDone(toDo.isDone()) + .updatedAt(toDo.getUpdatedAt()) + .build(); + } + + // 알림 생성 + members.forEach(studyMember -> { + Notification notification = Notification.builder() + .member(studyMember) + .notifierName(toDo.getMember().getName()) // To-Do 완료한 회원 이름 + .study(toDo.getStudy()) + .type(NotifyType.TO_DO_UPDATE) + .isChecked(Boolean.FALSE) + .build(); + notificationRepository.save(notification); + }); + } + + // To-Do List 저장 + toDoRepository.save(toDo); + + // To-Do List 변경 DTO 반환 + return ToDoListUpdateResponseDTO.builder() + .id(toDo.getId()) + .isDone(toDo.isDone()) + .updatedAt(toDo.getUpdatedAt()) + .build(); + } + + + /** + * To-Do List 내용을 수정합니다. + * @param studyId 수정할 To-Do List가 속한 스터디 ID + * @param toDoListId 수정할 To-Do List ID + * @param toDoListCreateDTO 수정할 To-Do List 정보 + * @return 수정된 To-Do List 정보 + * @throws StudyHandler To-Do List를 찾을 수 없을 때 + * @throws StudyHandler To-Do List가 스터디에 속하지 않을 때 + * @throws StudyHandler To-Do List를 수정하는 회원이 스터디 회원이 아닐 때 + */ + @Override + public ToDoListUpdateResponseDTO updateToDoList(Long studyId, Long toDoListId, + ToDoListCreateDTO toDoListCreateDTO) { + + // To-Do List 조회 + ToDo toDo = toDoRepository.findById(toDoListId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_TODO_NOT_FOUND)); + + // To-Do List가 속한 스터디가 아니면 예외 처리 + if (!Objects.equals(toDo.getStudy().getId(), studyId)) + throw new StudyHandler(ErrorStatus._STUDY_TODO_IS_NOT_BELONG_TO_STUDY); + + // To-Do List를 수정하는 회원이 스터디 회원이 아니면 예외 처리 + Long currentUserId = SecurityUtils.getCurrentUserId(); + if (!toDo.getMember().getId().equals(currentUserId)) + throw new StudyHandler(ErrorStatus._STUDY_TODO_NOT_AUTHORIZED); + + // To-Do List 수정 + toDo.update(toDoListCreateDTO.getContent(), toDoListCreateDTO.getDate()); + + // To-Do List 저장 + toDoRepository.save(toDo); + + // To-Do List 변경 DTO 반환 + return ToDoListUpdateResponseDTO.builder() + .id(toDo.getId()) + .isDone(toDo.isDone()) + .updatedAt(toDo.getUpdatedAt()) + .build(); + } + + /** + * To-Do List를 삭제합니다. + * @param studyId 삭제할 To-Do List가 속한 스터디 ID + * @param toDoListId 삭제할 To-Do List ID + * @return 삭제된 To-Do List 정보 + * @throws StudyHandler To-Do List를 찾을 수 없을 때 + * @throws StudyHandler To-Do List가 스터디에 속하지 않을 때 + * @throws StudyHandler To-Do List를 삭제하는 회원이 스터디 회원이 아닐 때 + */ + @Override + public ToDoListUpdateResponseDTO deleteToDoList(Long studyId, Long toDoListId) { + + // 로그인 중인 회원 ID 조회 + Long currentUserId = SecurityUtils.getCurrentUserId(); + + // To-Do List를 삭제하는 회원이 스터디 회원인지 확인 + if (!isMember(currentUserId, studyId)) + throw new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND); + + // To-Do List 조회 + ToDo toDo = toDoRepository.findById(toDoListId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_TODO_NOT_FOUND)); + + // To-Do List가 속한 스터디가 아니면 예외 처리 + if (!Objects.equals(toDo.getStudy().getId(), studyId)) + throw new StudyHandler(ErrorStatus._STUDY_TODO_IS_NOT_BELONG_TO_STUDY); + + // To-Do List를 삭제하는 회원의 ID와 To-Do List를 생성한 회원의 ID가 다르면 예외 처리 + if (!toDo.getMember().getId().equals(currentUserId)) + throw new StudyHandler(ErrorStatus._STUDY_TODO_NOT_AUTHORIZED); + + // To-Do List 삭제 + toDoRepository.deleteById(toDoListId); + + // To-Do List 삭제 DTO 반환 + return ToDoListUpdateResponseDTO.builder() + .id(toDo.getId()) + .isDone(toDo.isDone()) + .updatedAt(toDo.getUpdatedAt()) + .build(); + } + + /** + * 회원이 스터디 구성원인지 확인합니다. + * @param memberId 확인 하려는 회원 ID + * @param studyId 확인 하려는 스터디 ID + * @return 스터디 참여 여부를 반환합니다. + */ + private boolean isMember(Long memberId, Long studyId) { + return studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED).isPresent(); + } + +} diff --git a/src/main/java/com/example/spot/todo/application/ToDoQueryService.java b/src/main/java/com/example/spot/todo/application/ToDoQueryService.java new file mode 100644 index 00000000..4415cb79 --- /dev/null +++ b/src/main/java/com/example/spot/todo/application/ToDoQueryService.java @@ -0,0 +1,16 @@ +package com.example.spot.todo.application; + +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO; +import org.springframework.data.domain.PageRequest; + +import java.time.LocalDate; + +public interface ToDoQueryService { + + // 내 투두 리스트 조회 + ToDoListResponseDTO.ToDoListSearchResponseDTO getToDoList(Long studyId, LocalDate date, PageRequest pageRequest); + + // 스터디원 투두 리스트 조회 + ToDoListResponseDTO.ToDoListSearchResponseDTO getMemberToDoList(Long studyId, Long memberId, LocalDate date, PageRequest pageRequest); + +} diff --git a/src/main/java/com/example/spot/todo/application/ToDoQueryServiceImpl.java b/src/main/java/com/example/spot/todo/application/ToDoQueryServiceImpl.java new file mode 100644 index 00000000..b39451b9 --- /dev/null +++ b/src/main/java/com/example/spot/todo/application/ToDoQueryServiceImpl.java @@ -0,0 +1,134 @@ +package com.example.spot.todo.application; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.GeneralException; +import com.example.spot.common.security.utils.SecurityUtils; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.todo.domain.ToDo; +import com.example.spot.todo.domain.ToDoRepository; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO.ToDoListDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.List; + + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ToDoQueryServiceImpl implements ToDoQueryService { + + private final StudyMemberRepository studyMemberRepository; + private final ToDoRepository toDoRepository; + + + /** + * 특정 스터디에 저장된 내 To-Do List를 날짜 별로 페이징 조회합니다. + * @param studyId 스터디 ID + * @param date 조회하려는 날짜 + * @param pageRequest 페이징 정보 + * @return To-Do List 목록을 반환합니다. + * @throws GeneralException 스터디 멤버가 아닌 경우 + * @throws GeneralException 스터디 할 일이 존재하지 않는 경우 + */ + @Override + public ToDoListSearchResponseDTO getToDoList(Long studyId, LocalDate date, PageRequest pageRequest) { + // 로그인 중인 회원 ID 조회 + Long memberId = SecurityUtils.getCurrentUserId(); + + // 로그인한 회원이 스터디 회원인지 확인 + if (!isMember(memberId, studyId)) + throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_TODO_LIST); + + // 페이징 처리 + List toDos = toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc( + studyId, memberId, date, pageRequest); + + // 스터디 투 두 리스트가 존재하지 않는 경우 + if (toDos.isEmpty()) + throw new GeneralException(ErrorStatus._STUDY_TODO_NOT_FOUND); + + // 투 두 리스트 갯수 조회 + long totalElements = toDoRepository.countByStudyIdAndMemberIdAndDate(studyId, memberId, date); + + // DTO로 변환 + List toDoListDTOS = getToDoListDTOS(toDos); + + return new ToDoListSearchResponseDTO( + new PageImpl<>(toDoListDTOS, pageRequest, totalElements), toDoListDTOS, totalElements); + } + + /** + * 특정 스터디에 저장된 다른 스터디원의 To-Do List를 날짜 별로 페이징 조회합니다. + * @param studyId 스터디 ID + * @param memberId 조회하려는 회원 ID + * @param date 조회하려는 날짜 + * @param pageRequest 페이징 정보 + * @return To-Do List 목록을 반환합니다. + * @throws GeneralException 스터디 멤버가 아닌 경우 + * @throws GeneralException 조회하려는 회원이 스터디 멤버가 아닌 경우 + * @throws GeneralException 스터디 할 일이 존재하지 않는 경우 + */ + @Override + public ToDoListSearchResponseDTO getMemberToDoList(Long studyId, Long memberId, LocalDate date, + PageRequest pageRequest) { + + // 로그인 중인 회원이 스터디 회원인지 확인 + if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) + throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_TODO_LIST); + + // 조회하려는 회원이 스터디 회원인지 확인 + if (!isMember(memberId, studyId)) + throw new GeneralException(ErrorStatus._TODO_LIST_MEMBER_NOT_FOUND); + + // 조회하려는 회원의 투 두 리스트 조회 + List toDos = toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc( + studyId, memberId, date, pageRequest); + + // 투 두 리스트가 존재하지 않는 경우 + if (toDos.isEmpty()) + throw new GeneralException(ErrorStatus._STUDY_TODO_NOT_FOUND); + + // 투 두 리스트 갯수 조회 + long totalElements = toDoRepository.countByStudyIdAndMemberIdAndDate(studyId, memberId, date); + + // DTO로 변환 + List toDoListDTOS = getToDoListDTOS(toDos); + + return new ToDoListSearchResponseDTO( + new PageImpl<>(toDoListDTOS, pageRequest, totalElements), toDoListDTOS, totalElements); + } + + /** + * 투 두 리스트를 DTO로 변환합니다. + * @param toDos 투 두 리스트 + * @return 투 두 리스트 DTO 목록을 반환합니다. + */ + private static List getToDoListDTOS(List toDos) { + List toDoListDTOS = toDos.stream() + .map(toDoList -> ToDoListDTO.builder() + .id(toDoList.getId()) + .content(toDoList.getContent()) + .date(toDoList.getDate()) + .isDone(toDoList.isDone()) + .build()) + .toList(); + return toDoListDTOS; + } + + /** + * 회원이 스터디 구성원인지 확인합니다. + * @param memberId 확인 하려는 회원 ID + * @param studyId 확인 하려는 스터디 ID + * @return 스터디 참여 여부를 반환합니다. + */ + private boolean isMember(Long memberId, Long studyId) { + return studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED).isPresent(); + } +} diff --git a/src/main/java/com/example/spot/todo/presentation/controller/ToDoController.java b/src/main/java/com/example/spot/todo/presentation/controller/ToDoController.java new file mode 100644 index 00000000..67a46b43 --- /dev/null +++ b/src/main/java/com/example/spot/todo/presentation/controller/ToDoController.java @@ -0,0 +1,156 @@ +package com.example.spot.todo.presentation.controller; + +import com.example.spot.common.api.ApiResponse; +import com.example.spot.common.api.code.status.SuccessStatus; +import com.example.spot.member.domain.validation.annotation.ExistMember; +import com.example.spot.study.domain.validation.annotation.ExistStudy; +import com.example.spot.todo.application.ToDoCommandService; +import com.example.spot.todo.application.ToDoQueryService; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; +import com.example.spot.todo.domain.validation.annotation.ExistToDo; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/spot") +@Validated +public class ToDoController { + + private final ToDoQueryService toDoQueryService; + private final ToDoCommandService toDoCommandService; + + + @Tag(name = "To-Do List") + @Operation(summary = "[To-Do List] To-Do List 생성", description = """ + ## [To-Do List] 로그인한 회원이 참여하는 스터디에 To-Do List를 생성합니다. + To-Do List의 id와 제목, 생성 시간이 반환됩니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/to-do") + public ApiResponse createToDoList( + @PathVariable @ExistStudy Long studyId, + @RequestBody @Valid ToDoListRequestDTO.ToDoListCreateDTO request) { + ToDoListCreateResponseDTO toDoList = toDoCommandService.createToDoList(studyId, + request); + return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_CREATED, toDoList); + } + + @Tag(name = "To-Do List") + @Operation(summary = "[To-Do List] To-Do List 내용 수정", description = """ + ## [To-Do List] To-Do List에 작성한 할 일의 내용을 수정 합니다. + 변경 하지 않을 값은 아예 입력하지 않아야 합니다. + ex) date만 변경할 경우, content는 입력하지 않습니다. -> "date": "2022-12-31" 만 입력 + + To-Do List의 id와 수정된 할 일의 내용, 수정 시간이 반환됩니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "toDoId", description = "상태를 변경할 To-Do List의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/to-do/{toDoId}/update") + public ApiResponse updateToDoList( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistToDo Long toDoId, + @RequestBody @Valid ToDoListRequestDTO.ToDoListCreateDTO request) { + ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = toDoCommandService.updateToDoList( + studyId, toDoId, request); + return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_UPDATED, toDoListUpdateResponseDTO); + } + + + + @Tag(name = "To-Do List") + @Operation(summary = "[To-Do List] To-Do List 체크 처리", description = """ + ## [To-Do List] To-Do List에 작성한 할 일의 체크 상태를 변경 합니다. + + 체크 표시 되어 있는 경우, 해당 API를 재호출 하면 체크가 해제됩니다. + + To-Do List의 id와 체크한 할 일의 id, 체크 여부가 반환됩니다. + + 본인이 작성한 To-Do List만 체크할 수 있습니다. + 체크 여부가 true 인 경우, 할 일이 완료 되었음을 의미합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "toDoId", description = "상태를 변경할 To-Do List의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/to-do/{toDoId}/check") + public ApiResponse checkToDoList( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistToDo Long toDoId) { + ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = toDoCommandService.checkToDoList( + studyId, toDoId); + return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_UPDATED, toDoListUpdateResponseDTO); + } + + @Tag(name = "To-Do List") + @Operation(summary = "[To-Do List] To-Do List 삭제", description = """ + ## [To-do list] 로그인한 회원이 참여하는 스터디에 To-Do List를 삭제합니다. + + To-Do List 완료 처리와는 다른 개념으로, To-Do List를 삭제합니다. + To-Do List의 id와 상태 업데이트 시간이 반환됩니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "toDoId", description = "삭제할 To-Do List의 id를 입력합니다.", required = true) + @DeleteMapping("/studies/{studyId}/to-do/{toDoId}") + public ApiResponse deleteToDoList( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistToDo Long toDoId) { + ToDoListUpdateResponseDTO toDoListUpdateResponseDTO = toDoCommandService.deleteToDoList( + studyId, toDoId); + return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_DELETED, toDoListUpdateResponseDTO); + } + + @Tag(name = "To-Do List") + @Operation(summary = "[To-Do List] 내 To-Do List 조회", description = """ + ## [To-Do List] 특정 스터디에 저장된 내 To-Do List를 날짜 별로 페이징 조회합니다. + 조회하고 싶은 날짜를 입력 받아, 해당 날짜의 할 일 목록, 체크 여부가 반환됩니다. + + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "page", description = "조회할 페이지 번호를 입력 받습니다. 페이지 번호는 0부터 시작합니다.", required = true) + @Parameter(name = "size", description = "조회할 페이지 크기를 입력 받습니다. 페이지 크기는 1 이상의 정수 입니다. ", required = true) + @Parameter(name = "date", description = "조회할 날짜를 입력 받습니다. 날짜는 yyyy-MM-dd 형식으로 입력 받습니다.", required = true) + @GetMapping("/studies/{studyId}/to-do/my") + public ApiResponse getMyToDoList( + @PathVariable @ExistStudy Long studyId, + @RequestParam @Min(0) Integer page, + @RequestParam @Min(1) Integer size, + @RequestParam LocalDate date) { + ToDoListSearchResponseDTO toDoList = toDoQueryService.getToDoList(studyId, date, + PageRequest.of(page, size)); + return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_FOUND, toDoList); + } + + @Tag(name = "To-Do List") + @Operation(summary = "[To-Do List] 다른 스터디 원 To-Do List 조회", description = """ + ## [To-Do List] 특정 스터디에 저장된 다른 스터디원의 To-Do List를 날짜 별로 페이징 조회합니다. + 조회하고 싶은 날짜를 입력 받아, 해당 날짜의 할 일 목록, 체크 여부가 반환됩니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "memberId", description = "To-do list를 조회할 회원의 id를 입력합니다.", required = true) + @Parameter(name = "page", description = "조회할 페이지 번호를 입력 받습니다. 페이지 번호는 0부터 시작합니다.", required = true) + @Parameter(name = "size", description = "조회할 페이지 크기를 입력 받습니다. 페이지 크기는 1 이상의 정수 입니다. ", required = true) + @Parameter(name = "date", description = "조회할 날짜를 입력 받습니다. 날짜는 yyyy-MM-dd 형식으로 입력 받습니다.", required = true) + @GetMapping("/studies/{studyId}/to-do/members/{memberId}") + public ApiResponse getOtherToDoList( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistMember Long memberId, + @RequestParam @Min(0) Integer page, + @RequestParam @Min(1) Integer size, + @RequestParam LocalDate date) { + ToDoListSearchResponseDTO toDoList = toDoQueryService.getMemberToDoList(studyId, + memberId, date, PageRequest.of(page, size)); + return ApiResponse.onSuccess(SuccessStatus._TO_DO_LIST_FOUND, toDoList); + } + +} diff --git a/src/main/java/com/example/spot/study/presentation/dto/request/ToDoListRequestDTO.java b/src/main/java/com/example/spot/todo/presentation/dto/request/ToDoListRequestDTO.java similarity index 87% rename from src/main/java/com/example/spot/study/presentation/dto/request/ToDoListRequestDTO.java rename to src/main/java/com/example/spot/todo/presentation/dto/request/ToDoListRequestDTO.java index a04d558a..f6f00aaf 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/request/ToDoListRequestDTO.java +++ b/src/main/java/com/example/spot/todo/presentation/dto/request/ToDoListRequestDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.request; +package com.example.spot.todo.presentation.dto.request; import java.time.LocalDate; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/ToDoListResponseDTO.java b/src/main/java/com/example/spot/todo/presentation/dto/response/ToDoListResponseDTO.java similarity index 96% rename from src/main/java/com/example/spot/study/presentation/dto/response/ToDoListResponseDTO.java rename to src/main/java/com/example/spot/todo/presentation/dto/response/ToDoListResponseDTO.java index 2c96365c..4bcbf7bc 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/ToDoListResponseDTO.java +++ b/src/main/java/com/example/spot/todo/presentation/dto/response/ToDoListResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.todo.presentation.dto.response; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/ToDoCommandServiceTest.java similarity index 97% rename from src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java rename to src/test/java/com/example/spot/service/study/studymember/ToDoCommandServiceTest.java index ea096525..6ac0f348 100644 --- a/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studymember/ToDoCommandServiceTest.java @@ -19,9 +19,9 @@ import com.example.spot.study.domain.StudyRepository; import com.example.spot.todo.domain.ToDoRepository; import com.example.spot.study.application.StudyMemberCommandServiceImpl; -import com.example.spot.study.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; import java.time.LocalDate; import java.util.Collections; import java.util.Optional; @@ -45,7 +45,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -public class StudyMemberCommandServiceTest { +public class ToDoCommandServiceTest { @InjectMocks private StudyMemberCommandServiceImpl memberStudyCommandService; diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/ToDoQueryServiceTest.java similarity index 99% rename from src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java rename to src/test/java/com/example/spot/service/study/studymember/ToDoQueryServiceTest.java index 0b05a989..e9a30bee 100644 --- a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studymember/ToDoQueryServiceTest.java @@ -16,7 +16,7 @@ import com.example.spot.todo.domain.ToDoRepository; import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.study.application.StudyMemberQueryServiceImpl; -import com.example.spot.study.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplyMemberDTO; @@ -50,7 +50,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -public class StudyMemberQueryServiceTest { +public class ToDoQueryServiceTest { @InjectMocks private StudyMemberQueryServiceImpl memberStudyQueryService; From dd13f45151ffb041a426da4fe1a07274ef134a37 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 17:16:57 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[SPOT-286][REFACTOR]=20Vote=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StudyMemberCommandService.java | 27 +- .../StudyMemberCommandServiceImpl.java | 490 +-------------- .../application/StudyMemberQueryService.java | 36 +- .../StudyMemberQueryServiceImpl.java | 568 ------------------ .../controller/MemberStudyController.java | 125 +--- .../vote/application/VoteCommandService.java | 31 + .../application/VoteCommandServiceImpl.java | 276 +++++++++ .../vote/application/VoteQueryService.java | 34 ++ .../application/VoteQueryServiceImpl.java | 209 +++++++ .../controller/VoteController.java | 141 +++++ .../dto/request/StudyVoteRequestDTO.java | 2 +- .../dto/response/StudyVoteResponseDTO.java | 2 +- 12 files changed, 701 insertions(+), 1240 deletions(-) create mode 100644 src/main/java/com/example/spot/vote/application/VoteCommandService.java create mode 100644 src/main/java/com/example/spot/vote/application/VoteCommandServiceImpl.java create mode 100644 src/main/java/com/example/spot/vote/application/VoteQueryService.java create mode 100644 src/main/java/com/example/spot/vote/application/VoteQueryServiceImpl.java create mode 100644 src/main/java/com/example/spot/vote/presentation/controller/VoteController.java rename src/main/java/com/example/spot/{study => vote}/presentation/dto/request/StudyVoteRequestDTO.java (95%) rename src/main/java/com/example/spot/{study => vote}/presentation/dto/response/StudyVoteResponseDTO.java (99%) diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java index f2541cca..52ff0f00 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java @@ -3,10 +3,10 @@ import com.example.spot.member.presentation.dto.MemberResponseDTO; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; +import com.example.spot.vote.presentation.dto.request.StudyVoteRequestDTO; import com.example.spot.story.web.dto.response.StoryResDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO; @@ -26,33 +26,10 @@ public interface StudyMemberCommandService { StudyApplyResponseDTO acceptAndRejectStudyApplyForTest(Long memberId, Long studyId, boolean isAccept); - // 스터디 투표 생성 - StudyVoteResponseDTO.VotePreviewDTO createVote(Long studyId, StudyVoteRequestDTO.VoteDTO voteDTO); - - // 스터디 투표 참여 - StudyVoteResponseDTO.VotedOptionDTO vote(Long studyId, Long voteId, StudyVoteRequestDTO.VotedOptionDTO votedOptionDTO); - - // 스터디 투표 수정 - StudyVoteResponseDTO.VotePreviewDTO updateVote(Long studyId, Long voteId, StudyVoteRequestDTO.VoteUpdateDTO voteDTO); - - // 스터디 투표 삭제 - StudyVoteResponseDTO.VotePreviewDTO deleteVote(Long studyId, Long voteId); - // 스터디 회원 신고 MemberResponseDTO.ReportedMemberDTO reportStudyMember(Long studyId, Long memberId, @Valid StudyMemberReportDTO studyMemberReportDTO); // 스터디 게시글 신고 StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId); - // 투두 리스트 생성 - ToDoListCreateResponseDTO createToDoList(Long studyId, ToDoListRequestDTO.ToDoListCreateDTO toDoListCreateDTO); - - // 투두 리스트 체크 - ToDoListResponseDTO.ToDoListUpdateResponseDTO checkToDoList(Long studyId, Long toDoListId); - - // 투두 리스트 수정 - ToDoListResponseDTO.ToDoListUpdateResponseDTO updateToDoList(Long studyId, Long toDoListId, ToDoListRequestDTO.ToDoListCreateDTO toDoListCreateDTO); - - // 투두 리스트 삭제 - ToDoListResponseDTO.ToDoListUpdateResponseDTO deleteToDoList(Long studyId, Long toDoListId); } diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java index 2f25e803..1fbaf3be 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java @@ -7,12 +7,8 @@ import com.example.spot.member.domain.Member; import com.example.spot.report.domain.MemberReport; import com.example.spot.notification.domain.Notification; -import com.example.spot.schedule.domain.repository.QuizRepository; -import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; -import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.story.domain.Story; import com.example.spot.story.domain.association.StoryReport; -import com.example.spot.todo.domain.ToDo; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.notification.domain.enums.NotifyType; import com.example.spot.member.domain.enums.Status; @@ -25,62 +21,35 @@ import com.example.spot.story.domain.repository.StoryReportRepository; import com.example.spot.story.domain.StoryRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.todo.domain.ToDoRepository; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; import com.example.spot.story.web.dto.response.StoryResDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.common.application.s3.S3ImageService; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO.WithdrawalDTO; import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; -import java.util.Objects; - -import com.example.spot.vote.domain.Vote; -import com.example.spot.vote.domain.association.VoteOption; -import com.example.spot.vote.domain.association.VoteParticipant; -import com.example.spot.vote.domain.repository.VoteOptionRepository; -import com.example.spot.vote.domain.repository.VoteParticipantRepository; -import com.example.spot.vote.domain.VoteRepository; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @RequiredArgsConstructor @Transactional public class StudyMemberCommandServiceImpl implements StudyMemberCommandService { - @Value("${image.post.anonymous.profile}") - private String defaultImage; - private final MemberRepository memberRepository; + private final StudyMemberRepository studyMemberRepository; private final StudyRepository studyRepository; - private final ScheduleRepository scheduleRepository; - private final QuizRepository quizRepository; - private final VoteRepository voteRepository; - private final VoteOptionRepository voteOptionRepository; + private final StoryRepository storyRepository; + private final NotificationRepository notificationRepository; private final MemberReportRepository memberReportRepository; private final StoryReportRepository storyReportRepository; - private final StudyMemberRepository studyMemberRepository; - private final QuizSubmissionRepository quizSubmissionRepository; - private final StoryRepository storyRepository; - private final VoteParticipantRepository voteParticipantRepository; - private final ToDoRepository toDoRepository; - private final NotificationRepository notificationRepository; // S3 Service private final S3ImageService s3ImageService; @@ -301,243 +270,6 @@ public StudyApplyResponseDTO acceptAndRejectStudyApplyForTest(Long memberId, Lon .build(); } -/* ----------------------------- 스터디 투표 관련 API ------------------------------------- */ - - /** - * 스터디 투표를 생성하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param voteDTO 생성할 투표의 제목, 항목 목록, 중복 선택 가능 여부, 종료 일시를 입력 받습니다. - * @return 생성된 투표의 아이디와 제목을 반환합니다. - */ - @Override - public StudyVoteResponseDTO.VotePreviewDTO createVote(Long studyId, StudyVoteRequestDTO.VoteDTO voteDTO) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member loginMember = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - //=== Feature ===// - Vote vote = Vote.builder() - .study(study) - .member(loginMember) - .title(voteDTO.getTitle()) - .isMultipleChoice(voteDTO.getIsMultipleChoice()) - .finishedAt(voteDTO.getFinishedAt()) - .build(); - - // Vote 저장 - vote = voteRepository.save(vote); - // Option 저장 - vote = createOption(vote, voteDTO); - // 연관관계 매핑 - loginMember.addVote(vote); - study.addVote(vote); - - return StudyVoteResponseDTO.VotePreviewDTO.toDTO(vote); - } - - /** - * 스터디 투표의 항목을 생성하는 메서드입니다. - * createVote 메서드 내부에서 사용되는 메서드입니다. - * @param vote 항목을 생성할 타겟 투표를 입력 받습니다. - * @param voteDTO 생성할 투표의 제목, 항목 목록, 중복 선택 가능 여부, 종료 일시를 입력 받습니다. - * @return 투표 객체를 반환합니다. - */ - private Vote createOption(Vote vote, StudyVoteRequestDTO.VoteDTO voteDTO) { - voteDTO.getOptions() - .forEach(stringOption -> { - VoteOption voteOption = VoteOption.builder() - .vote(vote) - .content(stringOption) - .build(); - voteOption = voteOptionRepository.save(voteOption); - vote.addOption(voteOption); - }); - return voteRepository.save(vote); - } - - /** - * 특정 항목에 투표하기 위한 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param voteId 타겟 투표의 아이디를 입력 받습니다. - * @param votedOptionDTO 회원이 투표한 항목의 아이디 목록을 입력 받습니다. - * @return 투표 아이디, 회원 아이디, 투표한 항목 목록을 반환합니다. - */ - @Override - public StudyVoteResponseDTO.VotedOptionDTO vote(Long studyId, Long voteId, StudyVoteRequestDTO.VotedOptionDTO votedOptionDTO) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member loginMember = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - voteRepository.findByIdAndStudyId(voteId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 중복 선택이 허용되지 않는 투표는 여러 개의 option을 선택할 수 없음 - if (!vote.getIsMultipleChoice() && votedOptionDTO.getOptionIdList().size() > 1) { - throw new StudyHandler(ErrorStatus._STUDY_VOTE_MULTIPLE_CHOICE_NOT_VALID); - } - - // 한 번 참여한 투표는 다시 참여할 수 없음 - voteOptionRepository.findAllByVoteId(voteId) - .forEach(option -> { - if (voteParticipantRepository.existsByMemberIdAndVoteOptionId(loginMember.getId(), option.getId())) { - throw new StudyHandler(ErrorStatus._STUDY_VOTE_RE_PARTICIPATION_INVALID); - } - }); - - //=== Feature ===// - List voteParticipants = votedOptionDTO.getOptionIdList().stream() - .map(optionId -> { - VoteOption votedVoteOption = voteOptionRepository.findById(optionId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_OPTION_NOT_FOUND)); - voteOptionRepository.findByIdAndVoteId(optionId, voteId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_OPTION_NOT_FOUND)); - - VoteParticipant voteParticipant = VoteParticipant.builder() - .member(loginMember) - .voteOption(votedVoteOption) - .build(); - - voteParticipant = voteParticipantRepository.save(voteParticipant); - loginMember.addMemberVote(voteParticipant); - votedVoteOption.addMemberVote(voteParticipant); - - return voteParticipant; - }) - .toList(); - - return StudyVoteResponseDTO.VotedOptionDTO.toDTO(vote, loginMember, voteParticipants); - } - - /** - * 투표를 편집하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param voteId 편집할 투표의 아이디를 입력 받습니다. - * @param voteDTO 편집된 투표의 제목, 항목 목록, 복수 선택 가능 여부, 종료 일시를 입력 받습니다. - * @return 편집된 투표의 아이디와 제목을 반환합니다. - */ - @Override - public StudyVoteResponseDTO.VotePreviewDTO updateVote(Long studyId, Long voteId, StudyVoteRequestDTO.VoteUpdateDTO voteDTO) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member loginMember = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 로그인한 회원이 투표 생성자인지 확인 - if (!loginMember.equals(vote.getMember())) { - throw new StudyHandler(ErrorStatus._STUDY_VOTE_CREATOR_NOT_AUTHORIZED); - } - - // 한 명이라도 투표에 참여했으면 투표 편집 불가 - voteOptionRepository.findAllByVoteId(voteId) - .forEach(option -> { - if (voteParticipantRepository.existsByVoteOptionId(option.getId())) { - throw new StudyHandler(ErrorStatus._STUDY_VOTE_IS_IN_PROGRESS); - } - }); - - //=== Feature ===// - for (StudyVoteRequestDTO.OptionDTO optionDTO : voteDTO.getOptions()) { - VoteOption voteOption = voteOptionRepository.findById(optionDTO.getOptionId()) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_OPTION_NOT_FOUND)); - voteOption.setContent(optionDTO.getContent()); - voteOption = voteOptionRepository.save(voteOption); - vote.updateOption(voteOption); - } - - vote.updateVote(voteDTO.getTitle(), voteDTO.getIsMultipleChoice(), voteDTO.getFinishedAt()); - vote = voteRepository.save(vote); - loginMember.updateVote(vote); - study.updateVote(vote); - - return StudyVoteResponseDTO.VotePreviewDTO.toDTO(vote); - } - - /** - * 투표를 삭제하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param voteId 삭제할 투표의 아이디를 입력 받습니다. - * @return 삭제된 투표의 아이디와 제목을 반환합니다. - */ - @Override - public StudyVoteResponseDTO.VotePreviewDTO deleteVote(Long studyId, Long voteId) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member loginMember = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - voteRepository.findByIdAndStudyId(voteId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 로그인한 회원이 투표 생성자인지 확인 - if (!loginMember.equals(vote.getMember())) { - throw new StudyHandler(ErrorStatus._STUDY_VOTE_CREATOR_NOT_AUTHORIZED); - } - - //=== Feature ===// - deleteOptions(voteId); - loginMember.deleteVote(vote); - study.deleteVote(vote); - voteRepository.delete(vote); - - return StudyVoteResponseDTO.VotePreviewDTO.toDTO(vote); - } - - /** - * 모든 투표 항목을 삭제하는 메서드입니다. - * deleteVote 메서드 내부에서 호출되는 메서드입니다. - * @param voteId 항목을 삭제할 타겟 투표의 아이디를 입력 받습니다. - */ - private void deleteOptions(Long voteId) { - List voteOptions = voteOptionRepository.findAllByVoteId(voteId); - voteOptions.forEach(option -> { - option.deleteAllMemberVotes(); - voteParticipantRepository.deleteAll(voteParticipantRepository.findAllByVoteOptionId(option.getId())); - voteOptionRepository.delete(option); - }); - } - /** * 회원이 스터디 장인지 확인합니다. * @param memberId 확인 하려는 회원 ID @@ -548,16 +280,6 @@ private boolean isOwner(Long memberId, Long studyId) { return studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, true).isPresent(); } - /** - * 회원이 스터디 구성원인지 확인합니다. - * @param memberId 확인 하려는 회원 ID - * @param studyId 확인 하려는 스터디 ID - * @return 스터디 참여 여부를 반환합니다. - */ - private boolean isMember(Long memberId, Long studyId) { - return studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED).isPresent(); - } - /** * 스터디원을 신고하고 신고 내역을 저장하는 메서드입니다. * @param studyId 타겟 스터디의 아이디를 입력 받습니다. @@ -643,210 +365,4 @@ public StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId) { return StoryResDTO.PostPreviewDTO.toDTO(story); } - -/* ----------------------------- 스터디 To-Do List 관련 API ------------------------------------- */ - - /** - * To-Do List를 생성합니다. - * @param studyId 생성할 To-Do List가 속한 스터디 ID - * @param toDoListCreateDTO 생성할 To-Do List 정보 - * @return 생성된 To-Do List 정보 - * @throws StudyHandler 스터디를 찾을 수 없을 때 - * @throws StudyHandler To-Do List를 생성하는 회원이 스터디 회원이 아닐 때 - * @throws StudyHandler 해당 회원을 찾을 수 없을 때 - */ - @Override - public ToDoListCreateResponseDTO createToDoList(Long studyId, - ToDoListCreateDTO toDoListCreateDTO) { - - // 스터디 조회 - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // To-Do List를 생성하는 회원 ID 조회 - Long currentUserId = SecurityUtils.getCurrentUserId(); - - // To-Do List를 생성하는 회원이 스터디 회원인지 확인 - if (!isMember(currentUserId, studyId)) - throw new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND); - - // 회원 조회 - Member member = memberRepository.findById(currentUserId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - // To-Do List 생성 - ToDo toDo = ToDo.builder() - .study(study) - .member(member) - .date(toDoListCreateDTO.getDate()) - .isDone(false) - .content(toDoListCreateDTO.getContent()) - .build(); - - // To-Do List 저장 - toDo.setToDoList(); - toDoRepository.save(toDo); - - // To-Do List 생성 DTO 반환 - return ToDoListCreateResponseDTO.builder() - .id(toDo.getId()) - .content(toDo.getContent()) - .createdAt(toDo.getCreatedAt()) - .build(); - } - - // studyId가 필요할까? - - /** - * To-Do List에 작성한 할 일의 체크 상태를 변경 합니다. 체크 상태를 변경 하면 해당 스터디에 참여하고 있는 모든 회원에게 알림이 전송됩니다. - * @param studyId 스터디 ID - * @param toDoListId 변경할 To-Do List ID - * @return To-Do List 변경 여부와 변경 시간 - * @throws StudyHandler To-Do List를 찾을 수 없을 때 - * @throws StudyHandler To-Do List가 스터디에 속하지 않을 때 - * @throws StudyHandler To-Do List를 변경하는 회원이 스터디 회원이 아닐 때 - * @throws StudyHandler 알림 생성 할 스터디 회원을 찾을 수 없을 때 - */ - @Override - public ToDoListUpdateResponseDTO checkToDoList(Long studyId, Long toDoListId) { - - // To-Do List 조회 - ToDo toDo = toDoRepository.findById(toDoListId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_TODO_NOT_FOUND)); - - // To-Do List가 속한 스터디가 아니면 예외 처리 - if (!Objects.equals(toDo.getStudy().getId(), studyId)) - throw new StudyHandler(ErrorStatus._STUDY_TODO_IS_NOT_BELONG_TO_STUDY); - - // To-Do List를 변경하는 회원이 스터디 회원이 아니면 예외 처리 - Long currentUserId = SecurityUtils.getCurrentUserId(); - if (!toDo.getMember().getId().equals(currentUserId)) - throw new StudyHandler(ErrorStatus._STUDY_TODO_NOT_AUTHORIZED); - - // To-Do List 체크 상태 변경 - toDo.check(); - - // 스터디 회원의 To-Do List 중 하나가 완료 되면, 해당 스터디의 모든 회원에게 알림 전송 - if (toDo.isDone()){ - List members = studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED).stream() - .map(StudyMember::getMember) - .toList(); - - // 알림을 생성할 회원이 없으면 알림 생성하지 않음 - if (members.isEmpty()){ - return ToDoListUpdateResponseDTO.builder() - .id(toDo.getId()) - .isDone(toDo.isDone()) - .updatedAt(toDo.getUpdatedAt()) - .build(); - } - - // 알림 생성 - members.forEach(studyMember -> { - Notification notification = Notification.builder() - .member(studyMember) - .notifierName(toDo.getMember().getName()) // To-Do 완료한 회원 이름 - .study(toDo.getStudy()) - .type(NotifyType.TO_DO_UPDATE) - .isChecked(Boolean.FALSE) - .build(); - notificationRepository.save(notification); - }); - } - - // To-Do List 저장 - toDoRepository.save(toDo); - - // To-Do List 변경 DTO 반환 - return ToDoListUpdateResponseDTO.builder() - .id(toDo.getId()) - .isDone(toDo.isDone()) - .updatedAt(toDo.getUpdatedAt()) - .build(); - } - - - /** - * To-Do List 내용을 수정합니다. - * @param studyId 수정할 To-Do List가 속한 스터디 ID - * @param toDoListId 수정할 To-Do List ID - * @param toDoListCreateDTO 수정할 To-Do List 정보 - * @return 수정된 To-Do List 정보 - * @throws StudyHandler To-Do List를 찾을 수 없을 때 - * @throws StudyHandler To-Do List가 스터디에 속하지 않을 때 - * @throws StudyHandler To-Do List를 수정하는 회원이 스터디 회원이 아닐 때 - */ - @Override - public ToDoListUpdateResponseDTO updateToDoList(Long studyId, Long toDoListId, - ToDoListCreateDTO toDoListCreateDTO) { - - // To-Do List 조회 - ToDo toDo = toDoRepository.findById(toDoListId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_TODO_NOT_FOUND)); - - // To-Do List가 속한 스터디가 아니면 예외 처리 - if (!Objects.equals(toDo.getStudy().getId(), studyId)) - throw new StudyHandler(ErrorStatus._STUDY_TODO_IS_NOT_BELONG_TO_STUDY); - - // To-Do List를 수정하는 회원이 스터디 회원이 아니면 예외 처리 - Long currentUserId = SecurityUtils.getCurrentUserId(); - if (!toDo.getMember().getId().equals(currentUserId)) - throw new StudyHandler(ErrorStatus._STUDY_TODO_NOT_AUTHORIZED); - - // To-Do List 수정 - toDo.update(toDoListCreateDTO.getContent(), toDoListCreateDTO.getDate()); - - // To-Do List 저장 - toDoRepository.save(toDo); - - // To-Do List 변경 DTO 반환 - return ToDoListUpdateResponseDTO.builder() - .id(toDo.getId()) - .isDone(toDo.isDone()) - .updatedAt(toDo.getUpdatedAt()) - .build(); - } - - /** - * To-Do List를 삭제합니다. - * @param studyId 삭제할 To-Do List가 속한 스터디 ID - * @param toDoListId 삭제할 To-Do List ID - * @return 삭제된 To-Do List 정보 - * @throws StudyHandler To-Do List를 찾을 수 없을 때 - * @throws StudyHandler To-Do List가 스터디에 속하지 않을 때 - * @throws StudyHandler To-Do List를 삭제하는 회원이 스터디 회원이 아닐 때 - */ - @Override - public ToDoListUpdateResponseDTO deleteToDoList(Long studyId, Long toDoListId) { - - // 로그인 중인 회원 ID 조회 - Long currentUserId = SecurityUtils.getCurrentUserId(); - - // To-Do List를 삭제하는 회원이 스터디 회원인지 확인 - if (!isMember(currentUserId, studyId)) - throw new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND); - - // To-Do List 조회 - ToDo toDo = toDoRepository.findById(toDoListId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_TODO_NOT_FOUND)); - - // To-Do List가 속한 스터디가 아니면 예외 처리 - if (!Objects.equals(toDo.getStudy().getId(), studyId)) - throw new StudyHandler(ErrorStatus._STUDY_TODO_IS_NOT_BELONG_TO_STUDY); - - // To-Do List를 삭제하는 회원의 ID와 To-Do List를 생성한 회원의 ID가 다르면 예외 처리 - if (!toDo.getMember().getId().equals(currentUserId)) - throw new StudyHandler(ErrorStatus._STUDY_TODO_NOT_AUTHORIZED); - - // To-Do List 삭제 - toDoRepository.deleteById(toDoListId); - - // To-Do List 삭제 DTO 반환 - return ToDoListUpdateResponseDTO.builder() - .id(toDo.getId()) - .isDone(toDo.isDone()) - .updatedAt(toDo.getUpdatedAt()) - .build(); - } - } diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java index 91a8da6c..66c2ba9e 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java @@ -5,7 +5,7 @@ import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; @@ -18,16 +18,9 @@ public interface StudyMemberQueryService { - ScheduleResponseDTO.MonthlyScheduleListDTO getMonthlySchedules(Long studyId, int year, int month); - - ScheduleResponseDTO.MonthlyScheduleDTO getSchedule(Long studyId, Long scheduleId); - // 스터디 공지 게시글 불러오기 StoryResponseDTO findStudyAnnouncementPost(Long studyId); - // 스터디 다가오는 모임 일정 불러오기 - StudyScheduleResponseDTO findStudySchedule(Long studyId, Pageable pageable); - // 참여하는 회원 목록 불러오기 StudyMemberResponseDTO findStudyMembers(Long studyId); @@ -43,34 +36,7 @@ public interface StudyMemberQueryService { // 스터디 신청 여부 확인 StudyApplicantDTO isApplied(Long studyId); - // 금일 회원 출석 여부 불러오기 - StudyQuizResponseDTO.AttendanceListDTO getAllAttendances(Long studyId, Long scheduleId, LocalDate date); - - // 스터디 출석퀴즈 조회 - StudyQuizResponseDTO.QuizDTO getAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date); - - // 스터디 투표 목록 조회 - StudyVoteResponseDTO.VoteListDTO getAllVotes(Long studyId); - - // 스터디 투표 마감 여부 조회 - Boolean getIsCompleted(Long voteId); - - // 스터디 투표(진행중) 조회 - StudyVoteResponseDTO.VoteDTO getVoteInProgress(Long studyId, Long voteId); - - // 스터디 투표(마감) 조회 - StudyVoteResponseDTO.CompletedVoteDTO getVoteInCompletion(Long studyId, Long voteId); - - // 스터디 투표 현황 조회 - StudyVoteResponseDTO.CompletedVoteDetailDTO getCompletedVoteDetail(Long studyId, Long voteId); - // 스터디 이미지 목록 조회 StudyImageResponseDTO.ImageListDTO getAllStudyImages(Long studyId, PageRequest pageRequest); - // 내 투두 리스트 조회 - ToDoListResponseDTO.ToDoListSearchResponseDTO getToDoList(Long studyId, LocalDate date, PageRequest pageRequest); - - // 스터디 원 투두 리스트 조회 - ToDoListResponseDTO.ToDoListSearchResponseDTO getMemberToDoList(Long studyId, Long memberId, LocalDate date, PageRequest pageRequest); - } diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java index 11457ffd..e61f194a 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java @@ -4,56 +4,29 @@ import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; -import com.example.spot.schedule.domain.Schedule; -import com.example.spot.schedule.domain.association.Quiz; -import com.example.spot.schedule.domain.association.QuizSubmission; -import com.example.spot.schedule.domain.repository.QuizRepository; -import com.example.spot.schedule.domain.repository.QuizSubmissionRepository; -import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.story.domain.Story; import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; -import com.example.spot.schedule.domain.enums.SchedulePeriod; -import com.example.spot.study.domain.Study; -import com.example.spot.todo.domain.ToDo; import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.story.domain.StoryRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.todo.domain.ToDoRepository; -import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; -import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.story.web.dto.response.StoryResponseDTO; -import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO.ToDoListDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; -import com.example.spot.vote.domain.Vote; -import com.example.spot.vote.domain.association.VoteOption; -import com.example.spot.vote.domain.repository.VoteOptionRepository; -import com.example.spot.vote.domain.repository.VoteParticipantRepository; -import com.example.spot.vote.domain.VoteRepository; import lombok.RequiredArgsConstructor; import com.example.spot.common.api.exception.GeneralException; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplyMemberDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyMemberDTO; -import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO.StudyScheduleDTO; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.*; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -63,20 +36,10 @@ @RequiredArgsConstructor public class StudyMemberQueryServiceImpl implements StudyMemberQueryService { - @Value("${image.post.anonymous.profile}") - private String defaultImage; - private final MemberRepository memberRepository; private final StudyRepository studyRepository; private final StoryRepository storyRepository; - private final ScheduleRepository scheduleRepository; private final StudyMemberRepository studyMemberRepository; - private final QuizSubmissionRepository quizSubmissionRepository; - private final QuizRepository quizRepository; - private final VoteRepository voteRepository; - private final VoteOptionRepository voteOptionRepository; - private final VoteParticipantRepository voteParticipantRepository; - private final ToDoRepository toDoRepository; /** @@ -103,40 +66,6 @@ public StoryResponseDTO findStudyAnnouncementPost(Long studyId) { .content(story.getContent()).build(); } - /** - * 로그인한 회원이 참여하는 특정 스터디의 다가오는 모임 목록을 페이징 조회 합니다. - * @param studyId 스터디 ID - * @param pageable 페이징 정보 - * @return 다가오는 모임 목록을 반환합니다. - * @throws GeneralException 스터디 일정이 존재하지 않는 경우 - * @throws GeneralException 스터디 멤버가 아닌 경우 - */ - @Override - public StudyScheduleResponseDTO findStudySchedule(Long studyId, Pageable pageable) { - - // 로그인한 회원이 해당 스터디 회원인지 확인 - if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) - throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_SCHEDULE); - - // 스터디 일정 조회 - List schedules = scheduleRepository.findAllByStudyId(studyId, pageable); - - // 스터디 일정이 존재하지 않는 경우 - if (schedules.isEmpty()) - throw new GeneralException(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND); - - // DTO로 변환하여 반환 - List scheduleDTOS = schedules.stream().map(schedule -> StudyScheduleDTO.builder() - .title(schedule.getTitle()) - .location(schedule.getLocation()) - .startedAt(schedule.getStartedAt()) - .finishedAt(schedule.getFinishedAt()) - .build()).toList(); - - // 페이징 처리 - return new StudyScheduleResponseDTO(new PageImpl<>(scheduleDTOS, pageable, schedules.size()), scheduleDTOS, schedules.size()); - } - /** * 특정 스터디의 회원 목록을 전체 조회 합니다. 가입된 스터디가 아니더라도 회원 목록을 조회할 수 있습니다. * @param studyId 스터디 ID @@ -146,9 +75,6 @@ public StudyScheduleResponseDTO findStudySchedule(Long studyId, Pageable pageabl */ @Override public StudyMemberResponseDTO findStudyMembers(Long studyId) { -// // 스터디에 가입하지 않은 사람도 회원 목록은 볼 수 있어야 함. -// if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) -// throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_MEMBERS); // 스터디 멤버 조회 List memberStudies = studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED); @@ -277,403 +203,6 @@ public StudyApplicantDTO isApplied(Long studyId) { } - /* ----------------------------- 스터디 출석 관련 API ------------------------------------- */ - - /** - * 금일 모든 스터디 회원의 출석 정보를 불러옵니다. - * @param studyId 출석 정보를 불러올 스터디의 아이디를 입력 받습니다. - * @param scheduleId 스터디 일정의 아이디를 입력 받습니다. - * @param date - * @return 모든 스터디 회원에 대한 정보와 출석 여부를 담은 리스트를 반환합니다. - */ - @Override - public StudyQuizResponseDTO.AttendanceListDTO getAllAttendances(Long studyId, Long scheduleId, LocalDate date) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 요청한 날짜에 생성된 출석 퀴즈 조회 - LocalDateTime startOfDay = date.atStartOfDay(); - LocalDateTime endOfDay = date.atStartOfDay().plusDays(1); - List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); - if (todayQuizzes.isEmpty()) { - throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); - } - Quiz quiz = todayQuizzes.get(0); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - //=== Feature ===// - List studyMembers = studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED).stream() - .map(memberStudy -> { - List attendanceList = quizSubmissionRepository.findByQuizIdAndMemberId(quiz.getId(), memberStudy.getMember().getId()); - for (QuizSubmission attendance : attendanceList) { - // MemberAttendance에 퀴즈에 대한 정답이 저장되어 있으면 금일 출석 성공 - if (attendance.getIsCorrect()) - return StudyQuizResponseDTO.StudyMemberDTO.toDTO(memberStudy, Boolean.TRUE); - } - // 퀴즈를 풀지 않았거나 MemberAttendance에 오답만 저장되어 있으면 금일 출석 실패 - return StudyQuizResponseDTO.StudyMemberDTO.toDTO(memberStudy, Boolean.FALSE); - }) - .toList(); - - return StudyQuizResponseDTO.AttendanceListDTO.toDTO(quiz, studyMembers); - - } - - @Override - public StudyQuizResponseDTO.QuizDTO getAttendanceQuiz(Long studyId, Long scheduleId, LocalDate date) { - - // Authorization - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); - - // 해당 스터디에서 생성된 일정인지 확인 - if (!schedule.getStudy().equals(study)) { - throw new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND); - } - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 해당 날짜에 생성된 스터디 퀴즈 조회 - LocalDateTime startOfDay = date.atStartOfDay(); - LocalDateTime endOfDay = date.atStartOfDay().plusDays(1); - List todayQuizzes = quizRepository.findAllByScheduleIdAndCreatedAtBetween(scheduleId, startOfDay, endOfDay); - if (todayQuizzes.isEmpty()) { - throw new StudyHandler(ErrorStatus._STUDY_QUIZ_NOT_FOUND); - } - Quiz quiz = todayQuizzes.get(0); - - return StudyQuizResponseDTO.QuizDTO.toDTO(quiz); - } - - /* ----------------------------- 스터디 일정 관련 API ------------------------------------- */ - - /** - * 특정 연/월의 일정을 불러오는 메서드입니다. - * @param studyId 일정을 불러올 스터디의 아이디를 입력 받습니다. - * @param year 일정을 불러올 기준 연도를 입력 받습니다. - * @param month 일정을 불러올 달을 입력 받습니다. - * @return 스터디 아이디와 해당 스터디의 월별 일정 목록을 반환합니다. - */ - @Override - public ScheduleResponseDTO.MonthlyScheduleListDTO getMonthlySchedules(Long studyId, int year, int month) { - - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - boolean isStudyMember; - if (studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED)) { - isStudyMember = true; - } else { - isStudyMember = false; - } - - List monthlyScheduleDTOS = new ArrayList<>(); - - study.getSchedules().forEach(schedule -> { - if (schedule.getSchedulePeriod().equals(SchedulePeriod.NONE)) { - addSchedule(schedule, year, month, monthlyScheduleDTOS, isStudyMember); - } else { - addPeriodSchedules(schedule, year, month, monthlyScheduleDTOS, isStudyMember); - } - }); - - return ScheduleResponseDTO.MonthlyScheduleListDTO.toDTO(study, monthlyScheduleDTOS); - } - - /** - * 하나의 일정에 대한 상세 정보를 불러오는 메서드입니다. - * @param studyId 일정을 불러올 스터디의 아이디를 입력 받습니다. - * @param scheduleId 상세 정보를 물러올 일정의 아이디를 입력 받습니다. - * @return 일정 아이디, 제목, 위치, 시작 일시, 종료 일시, 매일 진행 여부, 주기를 반환합니다. - */ - @Override - public ScheduleResponseDTO.MonthlyScheduleDTO getSchedule(Long studyId, Long scheduleId) { - - // Exception - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Schedule schedule = scheduleRepository.findById(scheduleId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - boolean isStudyMember; - if (studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED)) { - isStudyMember = true; - } else { - isStudyMember = false; - } - - // 해당 스터디의 일정인지 확인 - scheduleRepository.findByIdAndStudyId(scheduleId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_SCHEDULE_NOT_FOUND)); - - return ScheduleResponseDTO.MonthlyScheduleDTO.toDTO(schedule, isStudyMember); - } - - /** - * 월별 일정 리스트에 주기가 정해져 있지 않은 일정을 추가하기 위한 메서드입니다. - * 일정의 시작일이 기준 연월과 일치하는 경우 월별 일정 리스트에 추가합니다. - * getMonthlySchedules API에서 호출되는 내부 메서드입니다. - * @param schedule 리스트에 추가할 일정 정보를 입력 받습니다. - * @param year 기준 연도를 입력 받습니다. - * @param month 기준 월을 입력 받습니다. - * @param monthlyScheduleDTOS 일정을 추가할 월별 일정 리스트를 입력 받습니다. - * @param isStudyMember 스터디 회원 여부를 입력 받습니다. - */ - private void addSchedule(Schedule schedule, int year, int month, List monthlyScheduleDTOS, boolean isStudyMember) { - if (schedule.getStartedAt().getYear() == year && schedule.getStartedAt().getMonthValue() == month) { - monthlyScheduleDTOS.add(ScheduleResponseDTO.MonthlyScheduleDTO.toDTO(schedule, isStudyMember)); - } - } - - /** - * 월별 일정 리스트에 반복적인 일정을 추가하기 위한 메서드입니다. - * 일정의 시작일이 기준 연월 내에 있는 경우에만 일정을 추가하며, 주기에 따라 하나의 일정이라도 여러 번 추가될 수 있습니다. - * 예를 들어 기준 연월이 2024년 8월이고, 2024년 8월 2일부터 시작되는 WEEKLY 일정이 있다고 가정 - * 1. 이 일정은 기준 연월 내에서 2024년 8월 2일, 8월 9일, 8월 16일, 8월 23일, 8월 30일에 시행 - * 2. 따라서 monthlyScheduleDTOS에 추가되는 일정은 총 5개 - * @param schedule 리스트에 추가할 일정 정보를 입력 받습니다. - * @param year 기준 연도를 입력 받습니다. - * @param month 기준 월을 입력 받습니다. - * @param monthlyScheduleDTOS 일정을 추가할 월별 일정 리스트를 입력 받습니다. - * @param isStudyMember 스터디 회원 여부를 입력 받습니다. - */ - private void addPeriodSchedules(Schedule schedule, int year, int month, List monthlyScheduleDTOS, boolean isStudyMember) { - - LocalDateTime startedAt = schedule.getStartedAt(); - LocalDateTime finishedAt = schedule.getFinishedAt(); - - YearMonth yearMonth = YearMonth.of(year, month); // 탐색 연월 - LocalDateTime endOfMonth = yearMonth.atEndOfMonth().atTime(23, 59, 59); // 탐색 연월의 마지막 날 - - // 일정 시작일이 탐색 연월 내에 있는 경우만 반복 - while (startedAt.isBefore(endOfMonth)) { - // 업데이트된 일정 시작일의 month가 탐색 month와 일치하면 추가 - if (startedAt.getMonthValue() == month) { - monthlyScheduleDTOS.add(ScheduleResponseDTO.MonthlyScheduleDTO.toDTOWithDate(schedule, startedAt, finishedAt, isStudyMember)); - } - - if (schedule.getSchedulePeriod().equals(SchedulePeriod.DAILY)) { - startedAt = startedAt.plusDays(1); - finishedAt = finishedAt.plusDays(1); - } else if (schedule.getSchedulePeriod().equals(SchedulePeriod.WEEKLY)) { - startedAt = startedAt.plusWeeks(1); - finishedAt = finishedAt.plusWeeks(1); - } else if (schedule.getSchedulePeriod().equals(SchedulePeriod.BIWEEKLY)) { - startedAt = startedAt.plusWeeks(2); - finishedAt = finishedAt.plusWeeks(2); - } else if (schedule.getSchedulePeriod().equals(SchedulePeriod.MONTHLY)) { - startedAt = startedAt.plusMonths(1); - finishedAt = finishedAt.plusMonths(1); - } - } - } - -/* ----------------------------- 스터디 투표 관련 API ------------------------------------- */ - - /** - * 스터디에 생성된 모든 투표 목록을 불러옵니다. - * @param studyId 투표 목록을 불러올 타겟 스터디의 아이디를 입력 받습니다. - * @return 스터디 아이디와 해당 스터디에서 진행중인 투표 목록, 마감된 투표 목록을 반환합니다. - */ - @Override - public StudyVoteResponseDTO.VoteListDTO getAllVotes(Long studyId) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - //=== Feature ===// - - // 진행중인 투표 목록 - List votesInProgress = voteRepository.findAllByStudyIdAndFinishedAtAfter(studyId, LocalDateTime.now()).stream() - .map(vote -> { - boolean isParticipated = isParticipated(vote, member); - return StudyVoteResponseDTO.VoteInfoDTO.toDTO(vote, isParticipated); - }) - .toList(); - - // 마감된 투표 목록 - List votesInCompletion = voteRepository.findAllByStudyIdAndFinishedAtBefore(studyId, LocalDateTime.now()).stream() - .map(vote -> { - boolean isParticipated = isParticipated(vote, member); - return StudyVoteResponseDTO.VoteInfoDTO.toDTO(vote, isParticipated); - }) - .toList(); - - return StudyVoteResponseDTO.VoteListDTO.toDTO(studyId, votesInProgress, votesInCompletion); - } - - /** - * 스터디 회원의 투표 참여 여부를 확인하는 메서드입니다. - * getAllVotes에서 사용되는 내부 메서드입니다. - * @param vote 스터디에서 생성한 투표의 아이디를 입력 받습니다. - * @param loginMember 로그인한 회원의 정보를 입력 받습니다. - * @return 투표 참여 여부를 true or false로 반환합니다. - */ - private boolean isParticipated(Vote vote, Member loginMember) { - // 투표 참여 여부 확인 - boolean isParticipated = false; - for (VoteOption voteOption : vote.getVoteOptions()) { - if (voteParticipantRepository.existsByMemberIdAndVoteOptionId(loginMember.getId(), voteOption.getId())) { - isParticipated = true; - } - } - return isParticipated; - } - - /** - * 입력 받은 스터디 투표가 종료되었는지 확인하는 메서드입니다. - * (클라이언트에서 투표 불러오기 API를 호출할 때 스터디 종료 여부에 따라 Response DTO가 바뀌어야 하기 때문에 필요한 메서드입니다) - * @param voteId 스터디에서 생성한 투표의 아이디를 입력 받습니다. - * @return 투표 종료 여부를 true or false로 반환합니다. - */ - @Override - public Boolean getIsCompleted(Long voteId) { - return voteRepository.existsByIdAndFinishedAtBefore(voteId, LocalDateTime.now()); - } - - /** - * 종료된 투표의 정보를 불러오는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param voteId 스터디에서 생성한 투표의 아이디를 입력 받습니다. - * @return 종료된 투표의 아이디, 생성자, 제목, 항목별 투표 인원수, 전체 참여자 수, 종료 일시를 반환합니다. - */ - @Override - public StudyVoteResponseDTO.CompletedVoteDTO getVoteInCompletion(Long studyId, Long voteId) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 해당 스터디의 투표인지 확인 - voteRepository.findByIdAndStudyId(voteId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - //=== Feature ===// - return StudyVoteResponseDTO.CompletedVoteDTO.toDTO(vote); - - } - - /** - * 진행중인 투표의 정보를 불러오는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param voteId 스터디에서 생성한 투표의 아이디를 입력 받습니다. - * @return 진행중인 투표의 아이디, 생성자, 제목, 항목 리스트, 복수 선택 가능 여부, 종료 일시, 로그인한 회원의 참여 여부를 반환합니다. - */ - @Override - public StudyVoteResponseDTO.VoteDTO getVoteInProgress(Long studyId, Long voteId) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 해당 스터디의 투표인지 확인 - voteRepository.findByIdAndStudyId(voteId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - //=== Feature ===// - return StudyVoteResponseDTO.VoteDTO.toDTO(vote, member); - } - - /** - * 마감된 투표에 대해 항목별 투표 현황을 불러오는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param voteId 마감된 스터디 투표의 아이디를 입력 받습니다. - * @return 마감된 투표의 아이디와 제목, 항목별 투표 회원 목록을 반환합니다. - */ - @Override - public StudyVoteResponseDTO.CompletedVoteDetailDTO getCompletedVoteDetail(Long studyId, Long voteId) { - - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - - memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Vote vote = voteRepository.findById(voteId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 해당 스터디의 투표인지 확인 - voteRepository.findByIdAndStudyId(voteId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 마감된 투표인지 확인 - if (!voteRepository.existsByIdAndFinishedAtBefore(voteId, LocalDateTime.now())) { - throw new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_COMPLETED); - } - - //=== Feature ===// - return StudyVoteResponseDTO.CompletedVoteDetailDTO.toDTO(vote); - } - /** * 회원이 스터디 장인지 확인합니다. * @param memberId 확인 하려는 회원 ID @@ -727,101 +256,4 @@ public StudyImageResponseDTO.ImageListDTO getAllStudyImages(Long studyId, PageRe return StudyImageResponseDTO.ImageListDTO.toDTO(studyId, images); } - -/* ----------------------------- To-do list 관련 API ------------------------------------- */ - - /** - * 특정 스터디에 저장된 내 To-Do List를 날짜 별로 페이징 조회합니다. - * @param studyId 스터디 ID - * @param date 조회하려는 날짜 - * @param pageRequest 페이징 정보 - * @return To-Do List 목록을 반환합니다. - * @throws GeneralException 스터디 멤버가 아닌 경우 - * @throws GeneralException 스터디 할 일이 존재하지 않는 경우 - */ - @Override - public ToDoListSearchResponseDTO getToDoList(Long studyId, LocalDate date, PageRequest pageRequest) { - // 로그인 중인 회원 ID 조회 - Long memberId = SecurityUtils.getCurrentUserId(); - - // 로그인한 회원이 스터디 회원인지 확인 - if (!isMember(memberId, studyId)) - throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_TODO_LIST); - - // 페이징 처리 - List toDos = toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc( - studyId, memberId, date, pageRequest); - - // 스터디 투 두 리스트가 존재하지 않는 경우 - if (toDos.isEmpty()) - throw new GeneralException(ErrorStatus._STUDY_TODO_NOT_FOUND); - - // 투 두 리스트 갯수 조회 - long totalElements = toDoRepository.countByStudyIdAndMemberIdAndDate(studyId, memberId, date); - - // DTO로 변환 - List toDoListDTOS = getToDoListDTOS(toDos); - - return new ToDoListSearchResponseDTO( - new PageImpl<>(toDoListDTOS, pageRequest, totalElements), toDoListDTOS, totalElements); - } - - /** - * 특정 스터디에 저장된 다른 스터디원의 To-Do List를 날짜 별로 페이징 조회합니다. - * @param studyId 스터디 ID - * @param memberId 조회하려는 회원 ID - * @param date 조회하려는 날짜 - * @param pageRequest 페이징 정보 - * @return To-Do List 목록을 반환합니다. - * @throws GeneralException 스터디 멤버가 아닌 경우 - * @throws GeneralException 조회하려는 회원이 스터디 멤버가 아닌 경우 - * @throws GeneralException 스터디 할 일이 존재하지 않는 경우 - */ - @Override - public ToDoListSearchResponseDTO getMemberToDoList(Long studyId, Long memberId, LocalDate date, - PageRequest pageRequest) { - - // 로그인 중인 회원이 스터디 회원인지 확인 - if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) - throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_TODO_LIST); - - // 조회하려는 회원이 스터디 회원인지 확인 - if (!isMember(memberId, studyId)) - throw new GeneralException(ErrorStatus._TODO_LIST_MEMBER_NOT_FOUND); - - // 조회하려는 회원의 투 두 리스트 조회 - List toDos = toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc( - studyId, memberId, date, pageRequest); - - // 투 두 리스트가 존재하지 않는 경우 - if (toDos.isEmpty()) - throw new GeneralException(ErrorStatus._STUDY_TODO_NOT_FOUND); - - // 투 두 리스트 갯수 조회 - long totalElements = toDoRepository.countByStudyIdAndMemberIdAndDate(studyId, memberId, date); - - // DTO로 변환 - List toDoListDTOS = getToDoListDTOS(toDos); - - return new ToDoListSearchResponseDTO( - new PageImpl<>(toDoListDTOS, pageRequest, totalElements), toDoListDTOS, totalElements); - } - - /** - * 투 두 리스트를 DTO로 변환합니다. - * @param toDos 투 두 리스트 - * @return 투 두 리스트 DTO 목록을 반환합니다. - */ - private static List getToDoListDTOS(List toDos) { - List toDoListDTOS = toDos.stream() - .map(toDoList -> ToDoListDTO.builder() - .id(toDoList.getId()) - .content(toDoList.getContent()) - .date(toDoList.getDate()) - .isDone(toDoList.isDone()) - .build()) - .toList(); - return toDoListDTOS; - } - } diff --git a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java index 5cffa454..5b2d7501 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java @@ -7,25 +7,20 @@ import com.example.spot.member.domain.validation.annotation.ExistMember; import com.example.spot.study.domain.validation.annotation.ExistStudy; import com.example.spot.story.domain.validation.annotation.ExistStory; -import com.example.spot.todo.domain.validation.annotation.ExistToDo; import com.example.spot.vote.domain.validation.annotation.ExistVote; import com.example.spot.common.presentation.validator.TextLength; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.study.presentation.dto.request.StudyVoteRequestDTO; +import com.example.spot.vote.presentation.dto.request.StudyVoteRequestDTO; import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; import com.example.spot.story.web.dto.response.StoryResDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyVoteResponseDTO; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.member.presentation.dto.MemberResponseDTO; -import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; @@ -34,7 +29,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; -import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; @@ -220,120 +214,6 @@ public ApiResponse getStudyHost( return ApiResponse.onSuccess(SuccessStatus._STUDY_HOST_FOUND, studyHostDTO); } - /* ----------------------------- 스터디 투표 관련 API ------------------------------------- */ - - @Tag(name = "스터디 투표") - @Operation(summary = "[스터디 투표] 투표 생성하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 작성 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 새로운 투표를 등록합니다. - 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. - """) - @Parameter(name = "studyId", description = "투표를 생성할 스터디의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/votes") - public ApiResponse createVote( - @PathVariable @ExistStudy Long studyId, - @RequestBody @Valid StudyVoteRequestDTO.VoteDTO voteDTO) { - StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = studyMemberCommandService.createVote(studyId, voteDTO); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_CREATED, votePreviewDTO); - } - - @Tag(name = "스터디 투표") - @Operation(summary = "[스터디 투표] 투표하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 스터디에서 특정 항목에 투표합니다. - member_vote에 투표 정보를 저장합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "voteId", description = "참여할 스터디 투표의 id를 입력합니다.") - @PostMapping("/studies/{studyId}/votes/{voteId}/options") - public ApiResponse vote( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistVote Long voteId, - @RequestBody @Valid StudyVoteRequestDTO.VotedOptionDTO votedOptionDTO) { - StudyVoteResponseDTO.VotedOptionDTO votedOptionResDTO = studyMemberCommandService.vote(studyId, voteId, votedOptionDTO); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_PARTICIPATED, votedOptionResDTO); - } - - @Tag(name = "스터디 투표") - @Operation(summary = "[스터디 투표] 투표 편집하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 편집하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표 정보를 수정합니다. - 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "voteId", description = "편집할 스터디 투표의 id를 입력합니다.") - @PatchMapping("/studies/{studyId}/votes/{voteId}") - public ApiResponse updateVote( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistVote Long voteId, - @RequestBody @Valid StudyVoteRequestDTO.VoteUpdateDTO voteDTO) { - StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = studyMemberCommandService.updateVote(studyId, voteId, voteDTO); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_UPDATED, votePreviewDTO); - } - - @Tag(name = "스터디 투표") - @Operation(summary = "[스터디 투표] 투표 삭제하기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 삭제하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표를 삭제합니다. - 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "voteId", description = "삭제할 스터디 투표의 id를 입력합니다.") - @DeleteMapping("/studies/{studyId}/votes/{voteId}") - public ApiResponse deleteVote( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistVote Long voteId) { - StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = studyMemberCommandService.deleteVote(studyId, voteId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_DELETED, votePreviewDTO); - } - - @Tag(name = "스터디 투표") - @Operation(summary = "[스터디 투표] 투표 목록 불러오기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표 목록을 불러옵니다. - 진행 중(finished_at 이전)인 투표 목록과 마감(finished_at 이후)된 투표 목록을 구분하여 반환합니다. - """) - @Parameter(name = "studyId", description = "투표 목록을 불러올 스터디의 id를 입력합니다.", required = true) - @GetMapping("/studies/{studyId}/votes") - public ApiResponse getAllVotes( - @PathVariable @ExistStudy Long studyId) { - StudyVoteResponseDTO.VoteListDTO voteListDTO = studyMemberQueryService.getAllVotes(studyId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, voteListDTO); - } - - @Tag(name = "스터디 투표") - @Operation(summary = "[스터디 투표] 투표 불러오기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. - 진행중인 투표 : 진행중인 투표에 대한 항목 및 기본 정보가 반환됩니다. - 마감된 투표 : 마감된 투표에 대한 항목과 투표 인원수가 반환됩니다. - (진행중인 투표인지 마감된 투표인지에 따라 Response DTO가 서로 다릅니다.) - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "voteId", description = "불러올 스터디 투표의 id를 입력합니다.") - @GetMapping("/studies/{studyId}/votes/{voteId}") - public ApiResponse getVote( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistVote Long voteId) { - Boolean isCompleted = studyMemberQueryService.getIsCompleted(voteId); - if (isCompleted) { - StudyVoteResponseDTO.CompletedVoteDTO completedVoteDTO = studyMemberQueryService.getVoteInCompletion(studyId, voteId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, completedVoteDTO); - } else { - StudyVoteResponseDTO.VoteDTO voteDTO = studyMemberQueryService.getVoteInProgress(studyId, voteId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, voteDTO); - } - } - - @Tag(name = "스터디 투표") - @Operation(summary = "[스터디 투표] 마감된 투표 현황 불러오기", description = """ - ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 마감된 투표 > n명 참여 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. - 마감된 투표에 대하여 항목별 투표 회원 목록을 반환합니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "voteId", description = "마감된 스터디 투표의 id를 입력합니다.") - @GetMapping("/studies/{studyId}/votes/{voteId}/details") - public ApiResponse getCompletedVoteDetail( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistVote Long voteId) { - StudyVoteResponseDTO.CompletedVoteDetailDTO completedVoteDetailDTO = studyMemberQueryService.getCompletedVoteDetail(studyId, voteId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_DETAIL_STATUS_FOUND, completedVoteDetailDTO); - } - /* ----------------------------- 스터디 갤러리 관련 API ------------------------------------- */ @Tag(name = "스터디 이미지") @Operation(summary = "[스터디 갤러리] 스터디 이미지 목록 불러오기", description = """ @@ -381,5 +261,4 @@ public ApiResponse reportStudyPost( StoryResDTO.PostPreviewDTO postPreviewDTO = studyMemberCommandService.reportStudyPost(studyId, postId); return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_REPORTED, postPreviewDTO); } - } \ No newline at end of file diff --git a/src/main/java/com/example/spot/vote/application/VoteCommandService.java b/src/main/java/com/example/spot/vote/application/VoteCommandService.java new file mode 100644 index 00000000..5435b3dc --- /dev/null +++ b/src/main/java/com/example/spot/vote/application/VoteCommandService.java @@ -0,0 +1,31 @@ +package com.example.spot.vote.application; + +import com.example.spot.member.presentation.dto.MemberResponseDTO; +import com.example.spot.story.web.dto.response.StoryResDTO; +import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; +import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; +import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; +import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; +import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; +import com.example.spot.vote.presentation.dto.request.StudyVoteRequestDTO; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; +import jakarta.validation.Valid; + +public interface VoteCommandService { + + // 스터디 투표 생성 + StudyVoteResponseDTO.VotePreviewDTO createVote(Long studyId, StudyVoteRequestDTO.VoteDTO voteDTO); + + // 스터디 투표 참여 + StudyVoteResponseDTO.VotedOptionDTO vote(Long studyId, Long voteId, StudyVoteRequestDTO.VotedOptionDTO votedOptionDTO); + + // 스터디 투표 수정 + StudyVoteResponseDTO.VotePreviewDTO updateVote(Long studyId, Long voteId, StudyVoteRequestDTO.VoteUpdateDTO voteDTO); + + // 스터디 투표 삭제 + StudyVoteResponseDTO.VotePreviewDTO deleteVote(Long studyId, Long voteId); + +} diff --git a/src/main/java/com/example/spot/vote/application/VoteCommandServiceImpl.java b/src/main/java/com/example/spot/vote/application/VoteCommandServiceImpl.java new file mode 100644 index 00000000..6066f836 --- /dev/null +++ b/src/main/java/com/example/spot/vote/application/VoteCommandServiceImpl.java @@ -0,0 +1,276 @@ +package com.example.spot.vote.application; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.handler.MemberHandler; +import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.common.security.utils.SecurityUtils; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.MemberRepository; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.StudyRepository; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.vote.domain.Vote; +import com.example.spot.vote.domain.VoteRepository; +import com.example.spot.vote.domain.association.VoteOption; +import com.example.spot.vote.domain.association.VoteParticipant; +import com.example.spot.vote.domain.repository.VoteOptionRepository; +import com.example.spot.vote.domain.repository.VoteParticipantRepository; +import com.example.spot.vote.presentation.dto.request.StudyVoteRequestDTO; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + + +@Service +@RequiredArgsConstructor +@Transactional +public class VoteCommandServiceImpl implements VoteCommandService { + + private final MemberRepository memberRepository; + private final StudyRepository studyRepository; + private final VoteRepository voteRepository; + private final VoteOptionRepository voteOptionRepository; + private final StudyMemberRepository studyMemberRepository; + private final VoteParticipantRepository voteParticipantRepository; + + + /** + * 스터디 투표를 생성하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param voteDTO 생성할 투표의 제목, 항목 목록, 중복 선택 가능 여부, 종료 일시를 입력 받습니다. + * @return 생성된 투표의 아이디와 제목을 반환합니다. + */ + @Override + public StudyVoteResponseDTO.VotePreviewDTO createVote(Long studyId, StudyVoteRequestDTO.VoteDTO voteDTO) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member loginMember = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + //=== Feature ===// + Vote vote = Vote.builder() + .study(study) + .member(loginMember) + .title(voteDTO.getTitle()) + .isMultipleChoice(voteDTO.getIsMultipleChoice()) + .finishedAt(voteDTO.getFinishedAt()) + .build(); + + // Vote 저장 + vote = voteRepository.save(vote); + // Option 저장 + vote = createOption(vote, voteDTO); + // 연관관계 매핑 + loginMember.addVote(vote); + study.addVote(vote); + + return StudyVoteResponseDTO.VotePreviewDTO.toDTO(vote); + } + + /** + * 스터디 투표의 항목을 생성하는 메서드입니다. + * createVote 메서드 내부에서 사용되는 메서드입니다. + * @param vote 항목을 생성할 타겟 투표를 입력 받습니다. + * @param voteDTO 생성할 투표의 제목, 항목 목록, 중복 선택 가능 여부, 종료 일시를 입력 받습니다. + * @return 투표 객체를 반환합니다. + */ + private Vote createOption(Vote vote, StudyVoteRequestDTO.VoteDTO voteDTO) { + voteDTO.getOptions() + .forEach(stringOption -> { + VoteOption voteOption = VoteOption.builder() + .vote(vote) + .content(stringOption) + .build(); + voteOption = voteOptionRepository.save(voteOption); + vote.addOption(voteOption); + }); + return voteRepository.save(vote); + } + + /** + * 특정 항목에 투표하기 위한 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param voteId 타겟 투표의 아이디를 입력 받습니다. + * @param votedOptionDTO 회원이 투표한 항목의 아이디 목록을 입력 받습니다. + * @return 투표 아이디, 회원 아이디, 투표한 항목 목록을 반환합니다. + */ + @Override + public StudyVoteResponseDTO.VotedOptionDTO vote(Long studyId, Long voteId, StudyVoteRequestDTO.VotedOptionDTO votedOptionDTO) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member loginMember = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Vote vote = voteRepository.findById(voteId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + voteRepository.findByIdAndStudyId(voteId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 중복 선택이 허용되지 않는 투표는 여러 개의 option을 선택할 수 없음 + if (!vote.getIsMultipleChoice() && votedOptionDTO.getOptionIdList().size() > 1) { + throw new StudyHandler(ErrorStatus._STUDY_VOTE_MULTIPLE_CHOICE_NOT_VALID); + } + + // 한 번 참여한 투표는 다시 참여할 수 없음 + voteOptionRepository.findAllByVoteId(voteId) + .forEach(option -> { + if (voteParticipantRepository.existsByMemberIdAndVoteOptionId(loginMember.getId(), option.getId())) { + throw new StudyHandler(ErrorStatus._STUDY_VOTE_RE_PARTICIPATION_INVALID); + } + }); + + //=== Feature ===// + List voteParticipants = votedOptionDTO.getOptionIdList().stream() + .map(optionId -> { + VoteOption votedVoteOption = voteOptionRepository.findById(optionId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_OPTION_NOT_FOUND)); + voteOptionRepository.findByIdAndVoteId(optionId, voteId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_OPTION_NOT_FOUND)); + + VoteParticipant voteParticipant = VoteParticipant.builder() + .member(loginMember) + .voteOption(votedVoteOption) + .build(); + + voteParticipant = voteParticipantRepository.save(voteParticipant); + loginMember.addMemberVote(voteParticipant); + votedVoteOption.addMemberVote(voteParticipant); + + return voteParticipant; + }) + .toList(); + + return StudyVoteResponseDTO.VotedOptionDTO.toDTO(vote, loginMember, voteParticipants); + } + + /** + * 투표를 편집하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param voteId 편집할 투표의 아이디를 입력 받습니다. + * @param voteDTO 편집된 투표의 제목, 항목 목록, 복수 선택 가능 여부, 종료 일시를 입력 받습니다. + * @return 편집된 투표의 아이디와 제목을 반환합니다. + */ + @Override + public StudyVoteResponseDTO.VotePreviewDTO updateVote(Long studyId, Long voteId, StudyVoteRequestDTO.VoteUpdateDTO voteDTO) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member loginMember = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Vote vote = voteRepository.findById(voteId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 로그인한 회원이 투표 생성자인지 확인 + if (!loginMember.equals(vote.getMember())) { + throw new StudyHandler(ErrorStatus._STUDY_VOTE_CREATOR_NOT_AUTHORIZED); + } + + // 한 명이라도 투표에 참여했으면 투표 편집 불가 + voteOptionRepository.findAllByVoteId(voteId) + .forEach(option -> { + if (voteParticipantRepository.existsByVoteOptionId(option.getId())) { + throw new StudyHandler(ErrorStatus._STUDY_VOTE_IS_IN_PROGRESS); + } + }); + + //=== Feature ===// + for (StudyVoteRequestDTO.OptionDTO optionDTO : voteDTO.getOptions()) { + VoteOption voteOption = voteOptionRepository.findById(optionDTO.getOptionId()) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_OPTION_NOT_FOUND)); + voteOption.setContent(optionDTO.getContent()); + voteOption = voteOptionRepository.save(voteOption); + vote.updateOption(voteOption); + } + + vote.updateVote(voteDTO.getTitle(), voteDTO.getIsMultipleChoice(), voteDTO.getFinishedAt()); + vote = voteRepository.save(vote); + loginMember.updateVote(vote); + study.updateVote(vote); + + return StudyVoteResponseDTO.VotePreviewDTO.toDTO(vote); + } + + /** + * 투표를 삭제하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param voteId 삭제할 투표의 아이디를 입력 받습니다. + * @return 삭제된 투표의 아이디와 제목을 반환합니다. + */ + @Override + public StudyVoteResponseDTO.VotePreviewDTO deleteVote(Long studyId, Long voteId) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member loginMember = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Vote vote = voteRepository.findById(voteId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + voteRepository.findByIdAndStudyId(voteId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 로그인한 회원이 투표 생성자인지 확인 + if (!loginMember.equals(vote.getMember())) { + throw new StudyHandler(ErrorStatus._STUDY_VOTE_CREATOR_NOT_AUTHORIZED); + } + + //=== Feature ===// + deleteOptions(voteId); + loginMember.deleteVote(vote); + study.deleteVote(vote); + voteRepository.delete(vote); + + return StudyVoteResponseDTO.VotePreviewDTO.toDTO(vote); + } + + /** + * 모든 투표 항목을 삭제하는 메서드입니다. + * deleteVote 메서드 내부에서 호출되는 메서드입니다. + * @param voteId 항목을 삭제할 타겟 투표의 아이디를 입력 받습니다. + */ + private void deleteOptions(Long voteId) { + List voteOptions = voteOptionRepository.findAllByVoteId(voteId); + voteOptions.forEach(option -> { + option.deleteAllMemberVotes(); + voteParticipantRepository.deleteAll(voteParticipantRepository.findAllByVoteOptionId(option.getId())); + voteOptionRepository.delete(option); + }); + } + +} diff --git a/src/main/java/com/example/spot/vote/application/VoteQueryService.java b/src/main/java/com/example/spot/vote/application/VoteQueryService.java new file mode 100644 index 00000000..9c828dee --- /dev/null +++ b/src/main/java/com/example/spot/vote/application/VoteQueryService.java @@ -0,0 +1,34 @@ +package com.example.spot.vote.application; + +import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; +import com.example.spot.story.web.dto.response.StoryResponseDTO; +import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; +import com.example.spot.study.presentation.dto.response.StudyMemberResDTO; +import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; +import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import java.time.LocalDate; + +public interface VoteQueryService { + + // 스터디 투표 목록 조회 + StudyVoteResponseDTO.VoteListDTO getAllVotes(Long studyId); + + // 스터디 투표 마감 여부 조회 + Boolean getIsCompleted(Long voteId); + + // 스터디 투표(진행중) 조회 + StudyVoteResponseDTO.VoteDTO getVoteInProgress(Long studyId, Long voteId); + + // 스터디 투표(마감) 조회 + StudyVoteResponseDTO.CompletedVoteDTO getVoteInCompletion(Long studyId, Long voteId); + + // 스터디 투표 현황 조회 + StudyVoteResponseDTO.CompletedVoteDetailDTO getCompletedVoteDetail(Long studyId, Long voteId); +} diff --git a/src/main/java/com/example/spot/vote/application/VoteQueryServiceImpl.java b/src/main/java/com/example/spot/vote/application/VoteQueryServiceImpl.java new file mode 100644 index 00000000..e771f715 --- /dev/null +++ b/src/main/java/com/example/spot/vote/application/VoteQueryServiceImpl.java @@ -0,0 +1,209 @@ +package com.example.spot.vote.application; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.common.security.utils.SecurityUtils; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.MemberRepository; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.StudyRepository; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.vote.domain.Vote; +import com.example.spot.vote.domain.VoteRepository; +import com.example.spot.vote.domain.association.VoteOption; +import com.example.spot.vote.domain.repository.VoteParticipantRepository; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class VoteQueryServiceImpl implements VoteQueryService { + + private final MemberRepository memberRepository; + private final StudyRepository studyRepository; + private final StudyMemberRepository studyMemberRepository; + private final VoteRepository voteRepository; + private final VoteParticipantRepository voteParticipantRepository; + + + /** + * 스터디에 생성된 모든 투표 목록을 불러옵니다. + * @param studyId 투표 목록을 불러올 타겟 스터디의 아이디를 입력 받습니다. + * @return 스터디 아이디와 해당 스터디에서 진행중인 투표 목록, 마감된 투표 목록을 반환합니다. + */ + @Override + public StudyVoteResponseDTO.VoteListDTO getAllVotes(Long studyId) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + //=== Feature ===// + + // 진행중인 투표 목록 + List votesInProgress = voteRepository.findAllByStudyIdAndFinishedAtAfter(studyId, LocalDateTime.now()).stream() + .map(vote -> { + boolean isParticipated = isParticipated(vote, member); + return StudyVoteResponseDTO.VoteInfoDTO.toDTO(vote, isParticipated); + }) + .toList(); + + // 마감된 투표 목록 + List votesInCompletion = voteRepository.findAllByStudyIdAndFinishedAtBefore(studyId, LocalDateTime.now()).stream() + .map(vote -> { + boolean isParticipated = isParticipated(vote, member); + return StudyVoteResponseDTO.VoteInfoDTO.toDTO(vote, isParticipated); + }) + .toList(); + + return StudyVoteResponseDTO.VoteListDTO.toDTO(studyId, votesInProgress, votesInCompletion); + } + + /** + * 스터디 회원의 투표 참여 여부를 확인하는 메서드입니다. + * getAllVotes에서 사용되는 내부 메서드입니다. + * @param vote 스터디에서 생성한 투표의 아이디를 입력 받습니다. + * @param loginMember 로그인한 회원의 정보를 입력 받습니다. + * @return 투표 참여 여부를 true or false로 반환합니다. + */ + private boolean isParticipated(Vote vote, Member loginMember) { + // 투표 참여 여부 확인 + boolean isParticipated = false; + for (VoteOption voteOption : vote.getVoteOptions()) { + if (voteParticipantRepository.existsByMemberIdAndVoteOptionId(loginMember.getId(), voteOption.getId())) { + isParticipated = true; + } + } + return isParticipated; + } + + /** + * 입력 받은 스터디 투표가 종료되었는지 확인하는 메서드입니다. + * (클라이언트에서 투표 불러오기 API를 호출할 때 스터디 종료 여부에 따라 Response DTO가 바뀌어야 하기 때문에 필요한 메서드입니다) + * @param voteId 스터디에서 생성한 투표의 아이디를 입력 받습니다. + * @return 투표 종료 여부를 true or false로 반환합니다. + */ + @Override + public Boolean getIsCompleted(Long voteId) { + return voteRepository.existsByIdAndFinishedAtBefore(voteId, LocalDateTime.now()); + } + + /** + * 종료된 투표의 정보를 불러오는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param voteId 스터디에서 생성한 투표의 아이디를 입력 받습니다. + * @return 종료된 투표의 아이디, 생성자, 제목, 항목별 투표 인원수, 전체 참여자 수, 종료 일시를 반환합니다. + */ + @Override + public StudyVoteResponseDTO.CompletedVoteDTO getVoteInCompletion(Long studyId, Long voteId) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Vote vote = voteRepository.findById(voteId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 해당 스터디의 투표인지 확인 + voteRepository.findByIdAndStudyId(voteId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + //=== Feature ===// + return StudyVoteResponseDTO.CompletedVoteDTO.toDTO(vote); + + } + + /** + * 진행중인 투표의 정보를 불러오는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param voteId 스터디에서 생성한 투표의 아이디를 입력 받습니다. + * @return 진행중인 투표의 아이디, 생성자, 제목, 항목 리스트, 복수 선택 가능 여부, 종료 일시, 로그인한 회원의 참여 여부를 반환합니다. + */ + @Override + public StudyVoteResponseDTO.VoteDTO getVoteInProgress(Long studyId, Long voteId) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Vote vote = voteRepository.findById(voteId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 해당 스터디의 투표인지 확인 + voteRepository.findByIdAndStudyId(voteId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + //=== Feature ===// + return StudyVoteResponseDTO.VoteDTO.toDTO(vote, member); + } + + /** + * 마감된 투표에 대해 항목별 투표 현황을 불러오는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param voteId 마감된 스터디 투표의 아이디를 입력 받습니다. + * @return 마감된 투표의 아이디와 제목, 항목별 투표 회원 목록을 반환합니다. + */ + @Override + public StudyVoteResponseDTO.CompletedVoteDetailDTO getCompletedVoteDetail(Long studyId, Long voteId) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + + memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Vote vote = voteRepository.findById(voteId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 해당 스터디의 투표인지 확인 + voteRepository.findByIdAndStudyId(voteId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 마감된 투표인지 확인 + if (!voteRepository.existsByIdAndFinishedAtBefore(voteId, LocalDateTime.now())) { + throw new StudyHandler(ErrorStatus._STUDY_VOTE_NOT_COMPLETED); + } + + //=== Feature ===// + return StudyVoteResponseDTO.CompletedVoteDetailDTO.toDTO(vote); + } +} diff --git a/src/main/java/com/example/spot/vote/presentation/controller/VoteController.java b/src/main/java/com/example/spot/vote/presentation/controller/VoteController.java new file mode 100644 index 00000000..548c1857 --- /dev/null +++ b/src/main/java/com/example/spot/vote/presentation/controller/VoteController.java @@ -0,0 +1,141 @@ +package com.example.spot.vote.presentation.controller; + +import com.example.spot.common.api.ApiResponse; +import com.example.spot.common.api.code.status.SuccessStatus; +import com.example.spot.study.domain.validation.annotation.ExistStudy; +import com.example.spot.vote.application.VoteCommandService; +import com.example.spot.vote.application.VoteQueryService; +import com.example.spot.vote.domain.validation.annotation.ExistVote; +import com.example.spot.vote.presentation.dto.request.StudyVoteRequestDTO; +import com.example.spot.vote.presentation.dto.response.StudyVoteResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/spot") +@Validated +public class VoteController { + + private final VoteQueryService voteQueryService; + private final VoteCommandService voteCommandService; + + + @Tag(name = "스터디 투표") + @Operation(summary = "[스터디 투표] 투표 생성하기", description = """ + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 작성 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 새로운 투표를 등록합니다. + 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. + """) + @Parameter(name = "studyId", description = "투표를 생성할 스터디의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/votes") + public ApiResponse createVote( + @PathVariable @ExistStudy Long studyId, + @RequestBody @Valid StudyVoteRequestDTO.VoteDTO voteDTO) { + StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = voteCommandService.createVote(studyId, voteDTO); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_CREATED, votePreviewDTO); + } + + @Tag(name = "스터디 투표") + @Operation(summary = "[스터디 투표] 투표하기", description = """ + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 스터디에서 특정 항목에 투표합니다. + member_vote에 투표 정보를 저장합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "voteId", description = "참여할 스터디 투표의 id를 입력합니다.") + @PostMapping("/studies/{studyId}/votes/{voteId}/options") + public ApiResponse vote( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistVote Long voteId, + @RequestBody @Valid StudyVoteRequestDTO.VotedOptionDTO votedOptionDTO) { + StudyVoteResponseDTO.VotedOptionDTO votedOptionResDTO = voteCommandService.vote(studyId, voteId, votedOptionDTO); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_PARTICIPATED, votedOptionResDTO); + } + + @Tag(name = "스터디 투표") + @Operation(summary = "[스터디 투표] 투표 편집하기", description = """ + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 편집하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표 정보를 수정합니다. + 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "voteId", description = "편집할 스터디 투표의 id를 입력합니다.") + @PatchMapping("/studies/{studyId}/votes/{voteId}") + public ApiResponse updateVote( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistVote Long voteId, + @RequestBody @Valid StudyVoteRequestDTO.VoteUpdateDTO voteDTO) { + StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = voteCommandService.updateVote(studyId, voteId, voteDTO); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_UPDATED, votePreviewDTO); + } + + @Tag(name = "스터디 투표") + @Operation(summary = "[스터디 투표] 투표 삭제하기", description = """ + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 삭제하기 버튼 클릭, 로그인한 회원이 참여하는 특정 스터디에서 투표를 삭제합니다. + 스터디에 참여하는 회원이 생성한 투표를 vote에 저장합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "voteId", description = "삭제할 스터디 투표의 id를 입력합니다.") + @DeleteMapping("/studies/{studyId}/votes/{voteId}") + public ApiResponse deleteVote( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistVote Long voteId) { + StudyVoteResponseDTO.VotePreviewDTO votePreviewDTO = voteCommandService.deleteVote(studyId, voteId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_DELETED, votePreviewDTO); + } + + @Tag(name = "스터디 투표") + @Operation(summary = "[스터디 투표] 투표 목록 불러오기", description = """ + ## [스터디 투표] 내 스터디 > 스터디 > 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표 목록을 불러옵니다. + 진행 중(finished_at 이전)인 투표 목록과 마감(finished_at 이후)된 투표 목록을 구분하여 반환합니다. + """) + @Parameter(name = "studyId", description = "투표 목록을 불러올 스터디의 id를 입력합니다.", required = true) + @GetMapping("/studies/{studyId}/votes") + public ApiResponse getAllVotes( + @PathVariable @ExistStudy Long studyId) { + StudyVoteResponseDTO.VoteListDTO voteListDTO = voteQueryService.getAllVotes(studyId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, voteListDTO); + } + + @Tag(name = "스터디 투표") + @Operation(summary = "[스터디 투표] 투표 불러오기", description = """ + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 특정 투표 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. + 진행중인 투표 : 진행중인 투표에 대한 항목 및 기본 정보가 반환됩니다. + 마감된 투표 : 마감된 투표에 대한 항목과 투표 인원수가 반환됩니다. + (진행중인 투표인지 마감된 투표인지에 따라 Response DTO가 서로 다릅니다.) + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "voteId", description = "불러올 스터디 투표의 id를 입력합니다.") + @GetMapping("/studies/{studyId}/votes/{voteId}") + public ApiResponse getVote( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistVote Long voteId) { + Boolean isCompleted = voteQueryService.getIsCompleted(voteId); + if (isCompleted) { + StudyVoteResponseDTO.CompletedVoteDTO completedVoteDTO = voteQueryService.getVoteInCompletion(studyId, voteId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, completedVoteDTO); + } else { + StudyVoteResponseDTO.VoteDTO voteDTO = voteQueryService.getVoteInProgress(studyId, voteId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_FOUND, voteDTO); + } + } + + @Tag(name = "스터디 투표") + @Operation(summary = "[스터디 투표] 마감된 투표 현황 불러오기", description = """ + ## [스터디 투표] 내 스터디 > 스터디 > 투표 > 마감된 투표 > n명 참여 클릭, 로그인한 회원이 참여하는 특정 스터디의 투표를 불러옵니다. + 마감된 투표에 대하여 항목별 투표 회원 목록을 반환합니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "voteId", description = "마감된 스터디 투표의 id를 입력합니다.") + @GetMapping("/studies/{studyId}/votes/{voteId}/details") + public ApiResponse getCompletedVoteDetail( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistVote Long voteId) { + StudyVoteResponseDTO.CompletedVoteDetailDTO completedVoteDetailDTO = voteQueryService.getCompletedVoteDetail(studyId, voteId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_VOTE_DETAIL_STATUS_FOUND, completedVoteDetailDTO); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/spot/study/presentation/dto/request/StudyVoteRequestDTO.java b/src/main/java/com/example/spot/vote/presentation/dto/request/StudyVoteRequestDTO.java similarity index 95% rename from src/main/java/com/example/spot/study/presentation/dto/request/StudyVoteRequestDTO.java rename to src/main/java/com/example/spot/vote/presentation/dto/request/StudyVoteRequestDTO.java index c079b667..ff00660f 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/request/StudyVoteRequestDTO.java +++ b/src/main/java/com/example/spot/vote/presentation/dto/request/StudyVoteRequestDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.request; +package com.example.spot.vote.presentation.dto.request; import com.example.spot.common.presentation.validator.TextLength; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/example/spot/study/presentation/dto/response/StudyVoteResponseDTO.java b/src/main/java/com/example/spot/vote/presentation/dto/response/StudyVoteResponseDTO.java similarity index 99% rename from src/main/java/com/example/spot/study/presentation/dto/response/StudyVoteResponseDTO.java rename to src/main/java/com/example/spot/vote/presentation/dto/response/StudyVoteResponseDTO.java index 5630f316..7ef16f7d 100644 --- a/src/main/java/com/example/spot/study/presentation/dto/response/StudyVoteResponseDTO.java +++ b/src/main/java/com/example/spot/vote/presentation/dto/response/StudyVoteResponseDTO.java @@ -1,4 +1,4 @@ -package com.example.spot.study.presentation.dto.response; +package com.example.spot.vote.presentation.dto.response; import com.example.spot.member.domain.Member; import com.example.spot.vote.domain.Vote; From 4614ba5822a3eea20130279b277c1ceb3a05b2bf Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 17:27:19 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[SPOT-286][REFACTOR]=20=EC=9E=98=EB=AA=BB?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=EB=90=9C=20story=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../story/application/StoryQueryService.java | 9 ++ .../application/StoryQueryServiceImpl.java | 77 ++++++++++++- .../story/web/controller/StoryController.java | 32 +++++- .../application/StudyCommandService.java | 3 +- .../StudyMemberCommandService.java | 5 + .../application/StudyMemberQueryService.java | 6 - .../StudyMemberQueryServiceImpl.java | 106 +++++------------- .../study/application/StudyQueryService.java | 5 +- .../controller/MemberStudyController.java | 30 +---- 9 files changed, 157 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/example/spot/story/application/StoryQueryService.java b/src/main/java/com/example/spot/story/application/StoryQueryService.java index 2369ffc7..846f751e 100644 --- a/src/main/java/com/example/spot/story/application/StoryQueryService.java +++ b/src/main/java/com/example/spot/story/application/StoryQueryService.java @@ -3,6 +3,8 @@ import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; import com.example.spot.story.web.dto.response.StoryResDTO; +import com.example.spot.story.web.dto.response.StoryResponseDTO; +import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import org.springframework.data.domain.PageRequest; public interface StoryQueryService { @@ -13,6 +15,13 @@ public interface StoryQueryService { // 스터디 게시글 불러오기 StoryResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean likeOrScrap); + // 스터디 공지 게시글 불러오기 + StoryResponseDTO findStudyAnnouncementPost(Long studyId); + // 스터디 게시글 댓글 목록 불러오기 StoryCommentResponseDTO.CommentReplyListDTO getAllComments(Long studyId, Long postId); + + // 스터디 이미지 목록 조회 + StudyImageResponseDTO.ImageListDTO getAllStudyImages(Long studyId, PageRequest pageRequest); + } diff --git a/src/main/java/com/example/spot/story/application/StoryQueryServiceImpl.java b/src/main/java/com/example/spot/story/application/StoryQueryServiceImpl.java index d5839068..0f0c6b99 100644 --- a/src/main/java/com/example/spot/story/application/StoryQueryServiceImpl.java +++ b/src/main/java/com/example/spot/story/application/StoryQueryServiceImpl.java @@ -1,11 +1,13 @@ package com.example.spot.story.application; import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.GeneralException; import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.story.domain.Story; import com.example.spot.story.domain.enums.StoryCategory; +import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.story.domain.enums.StoryCategoryQuery; import com.example.spot.study.domain.Study; @@ -19,6 +21,7 @@ import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; import com.example.spot.story.web.dto.response.StoryResDTO; +import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; @@ -154,7 +157,43 @@ public StoryResDTO.PostDetailDTO getPost(Long studyId, Long postId, Boolean like return StoryResDTO.PostDetailDTO.toDTO(story, commentNum, isLiked, isWriter); } -/* ----------------------------- 스터디 게시글 댓글 관련 API ------------------------------------- */ + /** + * 스터디 최근 공지사항을 1개 조회합니다. + * + * @param studyId 스터디 ID + * @return 제목과 내용을 반환합니다. + * @throws GeneralException 스터디 공지사항이 존재하지 않는 경우 + * @throws GeneralException 스터디 멤버가 아닌 경우 + */ + @Override + public StoryResponseDTO findStudyAnnouncementPost(Long studyId) { + + // 로그인한 회원이 해당 스터디 회원인지 확인 + if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) + throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_ANNOUNCEMENT_POST); + + // 스터디 공지사항 조회 + Story story = storyRepository.findByStudyIdAndIsAnnouncement( + studyId, true).orElseThrow(() -> new GeneralException(ErrorStatus._STUDY_POST_NOT_FOUND)); + + // DTO로 변환하여 반환 + return StoryResponseDTO.builder() + .title(story.getTitle()) + .content(story.getContent()).build(); + } + + /** + * 회원이 스터디 구성원인지 확인합니다. + * + * @param memberId 확인 하려는 회원 ID + * @param studyId 확인 하려는 스터디 ID + * @return 스터디 참여 여부를 반환합니다. + */ + private boolean isMember(Long memberId, Long studyId) { + return studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED).isPresent(); + } + + /* ----------------------------- 스터디 게시글 댓글 관련 API ------------------------------------- */ /** * 특정 스터디 게시글의 모든 댓글을 조회하는 메서드입니다. @@ -193,4 +232,40 @@ public StoryCommentResponseDTO.CommentReplyListDTO getAllComments(Long studyId, return StoryCommentResponseDTO.CommentReplyListDTO.toDTO(story.getId(), storyComments, member, defaultImage); } + +/* ----------------------------- 스터디 갤러리 관련 API ------------------------------------- */ + + /** + * 스터디 게시판에 업로드한 이미지 목록을 불러오는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param pageRequest 페이징에 필요한 페이지 번호와 크기를 입력 받습니다. + * @return 스터디 아이디와 해당 스터디에 업로드된 이미지 목록을 반환합니다. + */ + @Override + public StudyImageResponseDTO.ImageListDTO getAllStudyImages(Long studyId, PageRequest pageRequest) { + + //=== Exception ===// + Long memberId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(memberId); + studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + memberRepository.findById(memberId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + //=== Feature ===// + List images = storyRepository.findAllByStudyId(studyId, pageRequest) + .stream() + .sorted(Comparator.comparing(Story::getCreatedAt).reversed()) + .flatMap(studyPost -> studyPost.getImages().stream()) + .map(StudyImageResponseDTO.ImageDTO::toDTO) + .toList(); + + return StudyImageResponseDTO.ImageListDTO.toDTO(studyId, images); + + } + } diff --git a/src/main/java/com/example/spot/story/web/controller/StoryController.java b/src/main/java/com/example/spot/story/web/controller/StoryController.java index eaa5e167..05c7caed 100644 --- a/src/main/java/com/example/spot/story/web/controller/StoryController.java +++ b/src/main/java/com/example/spot/story/web/controller/StoryController.java @@ -3,9 +3,9 @@ import com.example.spot.common.api.ApiResponse; import com.example.spot.common.api.code.status.SuccessStatus; import com.example.spot.story.domain.enums.StoryCategoryQuery; -import com.example.spot.common.application.s3.S3ImageService; import com.example.spot.story.application.StoryCommandService; import com.example.spot.story.application.StoryQueryService; +import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.study.domain.validation.annotation.ExistStudy; import com.example.spot.story.domain.validation.annotation.ExistStory; import com.example.spot.story.domain.validation.annotation.ExistStoryComment; @@ -14,6 +14,7 @@ import com.example.spot.story.web.dto.response.StoryCommentResponseDTO; import com.example.spot.story.web.dto.response.StoryResDTO; +import com.example.spot.study.presentation.dto.response.StudyImageResponseDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -33,7 +34,6 @@ public class StoryController { private final StoryQueryService storyQueryService; private final StoryCommandService storyCommandService; - private final S3ImageService s3ImageService; /* ----------------------------- 스터디 게시글 관련 API ------------------------------------- */ @@ -120,6 +120,17 @@ public ApiResponse getPost( return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, postDetailDTO); } + @Tag(name = "스터디 상세 정보") + @Operation(summary = "[스터디 상세 정보] 스터디 최근 공지 1개 불러오기", description = """ + ## [스터디 상세 정보] 내 스터디 > 스터디 클릭, 로그인한 회원이 참여하는 특정 스터디의 최근 공지 1개를 불러옵니다. + study_post의 announced_at이 가장 최근인 공지 1개가 반환됩니다. + """) + @GetMapping("/studies/{studyId}/announce") + public ApiResponse getRecentAnnouncement(@PathVariable @ExistStudy Long studyId) { + StoryResponseDTO storyResponseDTO = storyQueryService.findStudyAnnouncementPost(studyId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, storyResponseDTO); + } + @Tag(name = "스터디 게시글") @Operation(summary = "[스터디 게시글] 좋아요 누르기", description = """ ## [스터디 게시글] 내 스터디 > 스터디 > 게시판 > 게시글 클릭, 로그인한 회원이 참여하는 특정 스터디의 게시글에 좋아요를 누릅니다. @@ -286,4 +297,21 @@ public ApiResponse getAllComments( return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_COMMENT_FOUND, commentReplyListDTO); } +/* ----------------------------- 스터디 게시글 이미지 관련 API ------------------------------------- */ + + @Tag(name = "스터디 게시글 - 갤러리") + @Operation(summary = "[스터디 게시글 - 갤러리] 스터디 이미지 목록 불러오기", description = """ + ## [스터디 게시글] 내 스터디 > 스터디 > 갤러리 클릭, 로그인한 회원이 참여하는 스터디의 이미지 목록을 불러옵니다. + 스터디에 존재하는 모든 게시글의 이미지를 최신순으로 반환합니다. + """) + @Parameter(name = "studyId", description = "이미지 목록을 불러올 스터디의 id를 입력합니다.", required = true) + @GetMapping("/studies/{studyId}/images") + public ApiResponse getAllStudyImages( + @PathVariable @ExistStudy Long studyId, + @RequestParam @Min(0) Integer offset, + @RequestParam @Min(1) Integer limit) { + StudyImageResponseDTO.ImageListDTO imageListDTO = storyQueryService.getAllStudyImages(studyId, PageRequest.of(offset, limit)); + return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_IMAGES_FOUND, imageListDTO); + } + } diff --git a/src/main/java/com/example/spot/study/application/StudyCommandService.java b/src/main/java/com/example/spot/study/application/StudyCommandService.java index ee5737a3..06c5274d 100644 --- a/src/main/java/com/example/spot/study/application/StudyCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyCommandService.java @@ -8,11 +8,12 @@ public interface StudyCommandService { + // 스터디 참여 신청하기 StudyJoinResponseDTO.JoinDTO applyToStudy(Long studyId, StudyJoinRequestDTO.StudyJoinDTO studyJoinRequestDTO); + // 스터디 등록하기 StudyRegisterResponseDTO.RegisterDTO registerStudy(StudyRegisterRequestDTO.RegisterDTO studyRegisterRequestDTO); - // TODO 스터디 좋아요 Member 도메인 하위로 옮기기 StudyLikeResponseDTO likeStudy(Long memberId, Long studyId); diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java index 52ff0f00..c55210e0 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java @@ -16,14 +16,19 @@ public interface StudyMemberCommandService { + // 스터디 탈퇴 StudyWithdrawalResponseDTO.WithdrawalDTO withdrawFromStudy(Long studyId); + + // 스터디 호스트 탈퇴 StudyWithdrawalResponseDTO.WithdrawalDTO withdrawHostFromStudy(Long studyId, StudyHostWithdrawRequestDTO requestDTO); + // 스터디 종료 StudyTerminationResponseDTO.TerminationDTO terminateStudy(Long studyId, String performance); // 스터디 신청 수락 StudyApplyResponseDTO acceptAndRejectStudyApply(Long memberId, Long studyId, boolean isAccept); + // 스터디 승인 거절 테스트 StudyApplyResponseDTO acceptAndRejectStudyApplyForTest(Long memberId, Long studyId, boolean isAccept); // 스터디 회원 신고 diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java index 66c2ba9e..c925c5f5 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryService.java @@ -18,9 +18,6 @@ public interface StudyMemberQueryService { - // 스터디 공지 게시글 불러오기 - StoryResponseDTO findStudyAnnouncementPost(Long studyId); - // 참여하는 회원 목록 불러오기 StudyMemberResponseDTO findStudyMembers(Long studyId); @@ -36,7 +33,4 @@ public interface StudyMemberQueryService { // 스터디 신청 여부 확인 StudyApplicantDTO isApplied(Long studyId); - // 스터디 이미지 목록 조회 - StudyImageResponseDTO.ImageListDTO getAllStudyImages(Long studyId, PageRequest pageRequest); - } diff --git a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java index e61f194a..69368081 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberQueryServiceImpl.java @@ -41,33 +41,9 @@ public class StudyMemberQueryServiceImpl implements StudyMemberQueryService { private final StoryRepository storyRepository; private final StudyMemberRepository studyMemberRepository; - - /** - * 스터디 최근 공지사항을 1개 조회합니다. - * @param studyId 스터디 ID - * @return 제목과 내용을 반환합니다. - * @throws GeneralException 스터디 공지사항이 존재하지 않는 경우 - * @throws GeneralException 스터디 멤버가 아닌 경우 - */ - @Override - public StoryResponseDTO findStudyAnnouncementPost(Long studyId) { - - // 로그인한 회원이 해당 스터디 회원인지 확인 - if (!isMember(SecurityUtils.getCurrentUserId(), studyId)) - throw new GeneralException(ErrorStatus._ONLY_STUDY_MEMBER_CAN_ACCESS_ANNOUNCEMENT_POST); - - // 스터디 공지사항 조회 - Story story = storyRepository.findByStudyIdAndIsAnnouncement( - studyId, true).orElseThrow(() -> new GeneralException(ErrorStatus._STUDY_POST_NOT_FOUND)); - - // DTO로 변환하여 반환 - return StoryResponseDTO.builder() - .title(story.getTitle()) - .content(story.getContent()).build(); - } - /** * 특정 스터디의 회원 목록을 전체 조회 합니다. 가입된 스터디가 아니더라도 회원 목록을 조회할 수 있습니다. + * * @param studyId 스터디 ID * @return 스터디에 참여하는 회원 목록을 반환합니다. * @throws GeneralException 스터디 할 일이 존재하지 않는 경우 @@ -85,10 +61,10 @@ public StudyMemberResponseDTO findStudyMembers(Long studyId) { // DTO로 변환하여 반환 List memberDTOS = memberStudies.stream().map(memberStudy -> StudyMemberDTO.builder() - .memberId(memberStudy.getMember().getId()) - .nickname(memberStudy.getMember().getName()) - .profileImage(memberStudy.getMember().getProfileImage()) - .build()).toList(); + .memberId(memberStudy.getMember().getId()) + .nickname(memberStudy.getMember().getName()) + .profileImage(memberStudy.getMember().getProfileImage()) + .build()).toList(); // DTO로 변환하여 반환 return new StudyMemberResponseDTO(memberDTOS); } @@ -96,6 +72,7 @@ public StudyMemberResponseDTO findStudyMembers(Long studyId) { /** * 회원이 모집중인 스터디에 신청한 회원 목록을 불러옵니다. + * * @param studyId 스터디 ID * @return 스터디 신청자 목록을 반환합니다. * @throws GeneralException 스터디 신청자가 존재 하지 않는 경우 @@ -117,10 +94,10 @@ public StudyMemberResponseDTO findStudyApplicants(Long studyId) { // DTO로 변환하여 반환 List memberDTOS = memberStudies.stream().map(memberStudy -> StudyMemberDTO.builder() - .memberId(memberStudy.getMember().getId()) - .nickname(memberStudy.getMember().getName()) - .profileImage(memberStudy.getMember().getProfileImage()) - .build()).toList(); + .memberId(memberStudy.getMember().getId()) + .nickname(memberStudy.getMember().getName()) + .profileImage(memberStudy.getMember().getProfileImage()) + .build()).toList(); // DTO로 변환하여 반환 return new StudyMemberResponseDTO(memberDTOS); @@ -148,7 +125,8 @@ public StudyMemberResDTO.StudyHostDTO getStudyHost(Long studyId) { /** * 스터디 신청자의 정보를 조회합니다. - * @param studyId 스터디 ID + * + * @param studyId 스터디 ID * @param memberId 회원 ID * @return 스터디 신청자 정보를 반환합니다. * @throws GeneralException 스터디 신청자가 존재하지 않는 경우 @@ -164,7 +142,7 @@ public StudyApplyMemberDTO findStudyApplication(Long studyId, Long memberId) { // 스터디 신청자 조회 StudyMember studyMember = studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPLIED) - .orElseThrow(() -> new GeneralException(ErrorStatus._STUDY_APPLICANT_NOT_FOUND)); + .orElseThrow(() -> new GeneralException(ErrorStatus._STUDY_APPLICANT_NOT_FOUND)); // 스터디 장은 스터디에 신청할 수 없음 if (studyMember.getIsOwned()) @@ -172,16 +150,17 @@ public StudyApplyMemberDTO findStudyApplication(Long studyId, Long memberId) { // DTO로 변환하여 반환 return StudyApplyMemberDTO.builder() - .memberId(studyMember.getMember().getId()) - .studyId(studyMember.getStudy().getId()) - .introduction(studyMember.getIntroduction()) - .nickname(studyMember.getMember().getName()) - .profileImage(studyMember.getMember().getProfileImage()) - .build(); + .memberId(studyMember.getMember().getId()) + .studyId(studyMember.getStudy().getId()) + .introduction(studyMember.getIntroduction()) + .nickname(studyMember.getMember().getName()) + .profileImage(studyMember.getMember().getProfileImage()) + .build(); } /** * 해당 스터디 신청 여부를 조회합니다. true: 신청, false: 미신청 + * * @param studyId 스터디 ID * @return 신청 여부와 스터디 ID를 반환합니다. * @throws GeneralException 이미 스터디 멤버인 경우 @@ -197,16 +176,17 @@ public StudyApplicantDTO isApplied(Long studyId) { // DTO로 변환하여 반환 return StudyApplicantDTO.builder() - .isApplied(studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(currentUserId, studyId, StudyApplicationStatus.APPLIED)) - .studyId(studyId) - .build(); + .isApplied(studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(currentUserId, studyId, StudyApplicationStatus.APPLIED)) + .studyId(studyId) + .build(); } /** * 회원이 스터디 장인지 확인합니다. + * * @param memberId 확인 하려는 회원 ID - * @param studyId 확인 하려는 스터디 ID + * @param studyId 확인 하려는 스터디 ID * @return 스터디 장 여부를 반환합니다. */ private boolean isOwner(Long memberId, Long studyId) { @@ -215,45 +195,13 @@ private boolean isOwner(Long memberId, Long studyId) { /** * 회원이 스터디 구성원인지 확인합니다. + * * @param memberId 확인 하려는 회원 ID - * @param studyId 확인 하려는 스터디 ID + * @param studyId 확인 하려는 스터디 ID * @return 스터디 참여 여부를 반환합니다. */ private boolean isMember(Long memberId, Long studyId) { return studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED).isPresent(); } -/* ----------------------------- 스터디 갤러리 관련 API ------------------------------------- */ - - /** - * 스터디 게시판에 업로드한 이미지 목록을 불러오는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param pageRequest 페이징에 필요한 페이지 번호와 크기를 입력 받습니다. - * @return 스터디 아이디와 해당 스터디에 업로드된 이미지 목록을 반환합니다. - */ - @Override - public StudyImageResponseDTO.ImageListDTO getAllStudyImages(Long studyId, PageRequest pageRequest) { - //=== Exception ===// - Long memberId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(memberId); - studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - memberRepository.findById(memberId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - //=== Feature ===// - List images = storyRepository.findAllByStudyId(studyId, pageRequest) - .stream() - .sorted(Comparator.comparing(Story::getCreatedAt).reversed()) - .flatMap(studyPost -> studyPost.getImages().stream()) - .map(StudyImageResponseDTO.ImageDTO::toDTO) - .toList(); - - return StudyImageResponseDTO.ImageListDTO.toDTO(studyId, images); - - } } diff --git a/src/main/java/com/example/spot/study/application/StudyQueryService.java b/src/main/java/com/example/spot/study/application/StudyQueryService.java index c74cdf21..754341a7 100644 --- a/src/main/java/com/example/spot/study/application/StudyQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyQueryService.java @@ -19,16 +19,19 @@ public interface StudyQueryService { // 스터디 정보 조회 StudyInfoResponseDTO.StudyInfoDTO getStudyInfo(Long studyId); - // 마이페이지 용 스터디 갯수 조회 + // 마이페이지 용 스터디 개수 조회 MyPageDTO getMyPageStudyCount(Long memberId); + // 스터디 목록 조회 StudyPreviewDTO findStudies(Pageable pageable, StudySortBy sortBy); + // 조건별 스터디 목록 조회 StudyPreviewDTO findStudiesByConditions(Pageable pageable, SearchRequestStudyDTO request, StudySortBy sortBy); // 내 추천 스터디 조회 StudyPreviewDTO findRecommendStudies(Long memberId); + // 회원별 관심 Best 스터디 3개 조회 StudyPreviewDTO findInterestedStudies(Long memberId); // 내 관심사 스터디 페이징 조회 diff --git a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java index 5b2d7501..19e548e4 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java @@ -178,17 +178,6 @@ public ApiResponse rejectApplicantForTest( /* ----------------------------- 스터디 상세 정보 관련 API ------------------------------------- */ - @Tag(name = "스터디 상세 정보") - @Operation(summary = "[스터디 상세 정보] 스터디 최근 공지 1개 불러오기", description = """ - ## [스터디 상세 정보] 내 스터디 > 스터디 클릭, 로그인한 회원이 참여하는 특정 스터디의 최근 공지 1개를 불러옵니다. - study_post의 announced_at이 가장 최근인 공지 1개가 반환됩니다. - """) - @GetMapping("/studies/{studyId}/announce") - public ApiResponse getRecentAnnouncement(@PathVariable @ExistStudy Long studyId) { - StoryResponseDTO storyResponseDTO = studyMemberQueryService.findStudyAnnouncementPost(studyId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_FOUND, storyResponseDTO); - } - @Tag(name = "스터디 상세 정보") @Operation(summary = "[스터디 상세 정보] 스터디에 참여하는 회원 목록 불러오기", description = """ ## [스터디 상세 정보] 로그인한 회원이 참여하는 특정 스터디의 회원 목록을 전체 조회 합니다. @@ -214,21 +203,10 @@ public ApiResponse getStudyHost( return ApiResponse.onSuccess(SuccessStatus._STUDY_HOST_FOUND, studyHostDTO); } - /* ----------------------------- 스터디 갤러리 관련 API ------------------------------------- */ - @Tag(name = "스터디 이미지") - @Operation(summary = "[스터디 갤러리] 스터디 이미지 목록 불러오기", description = """ - ## [스터디 갤러리] 내 스터디 > 스터디 > 갤러리 클릭, 로그인한 회원이 참여하는 스터디의 이미지 목록을 불러옵니다. - study_post에 존재하는 모든 게시글의 이미지를 최신순으로 반환합니다. - """) - @Parameter(name = "studyId", description = "이미지 목록을 불러올 스터디의 id를 입력합니다.", required = true) - @GetMapping("/studies/{studyId}/images") - public ApiResponse getAllStudyImages( - @PathVariable @ExistStudy Long studyId, - @RequestParam @Min(0) Integer offset, - @RequestParam @Min(1) Integer limit) { - StudyImageResponseDTO.ImageListDTO imageListDTO = studyMemberQueryService.getAllStudyImages(studyId, PageRequest.of(offset, limit)); - return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_IMAGES_FOUND, imageListDTO); - } + /* ----------------------------- 스터디 이미지 관련 API ------------------------------------- */ + + + /* ----------------------------- 스터디 회원 신고 관련 API ------------------------------------- */ From e67f6b4e0a8657169d24c1d5c30a49428ee97cc9 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 17:44:04 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[SPOT-286][REFACTOR]=20Report=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/application/PostCommandService.java | 3 - .../application/PostCommandServiceImpl.java | 28 ---- .../controller/PostController.java | 11 -- .../application/ReportCommandService.java | 19 +++ .../application/ReportCommandServiceImpl.java | 156 ++++++++++++++++++ .../domain}/StoryReport.java | 2 +- .../domain}/StoryReportRepository.java | 3 +- .../controller/ReportController.java | 59 +++++++ .../application/StoryCommandServiceImpl.java | 2 +- .../com/example/spot/story/domain/Story.java | 2 +- .../StudyMemberCommandService.java | 7 - .../StudyMemberCommandServiceImpl.java | 94 +---------- .../study/application/StudyQueryService.java | 3 +- .../controller/MemberStudyController.java | 36 ---- .../service/post/PostCommandServiceTest.java | 10 +- .../studypost/StoryCommandServiceTest.java | 2 +- 16 files changed, 248 insertions(+), 189 deletions(-) create mode 100644 src/main/java/com/example/spot/report/application/ReportCommandService.java create mode 100644 src/main/java/com/example/spot/report/application/ReportCommandServiceImpl.java rename src/main/java/com/example/spot/{story/domain/association => report/domain}/StoryReport.java (94%) rename src/main/java/com/example/spot/{story/domain/repository => report/domain}/StoryReportRepository.java (69%) create mode 100644 src/main/java/com/example/spot/report/presentation/controller/ReportController.java diff --git a/src/main/java/com/example/spot/post/application/PostCommandService.java b/src/main/java/com/example/spot/post/application/PostCommandService.java index 5ee24f7c..1290ad8a 100644 --- a/src/main/java/com/example/spot/post/application/PostCommandService.java +++ b/src/main/java/com/example/spot/post/application/PostCommandService.java @@ -53,7 +53,4 @@ public interface PostCommandService { //게시글 스크랩 모두 취소 ScrapsPostDeleteResponse cancelPostScraps(ScrapAllDeleteRequest request); - - //게시글 신고 - PostReportResponse reportPost(Long postId, Long memberId); } diff --git a/src/main/java/com/example/spot/post/application/PostCommandServiceImpl.java b/src/main/java/com/example/spot/post/application/PostCommandServiceImpl.java index db6c555c..6845e985 100644 --- a/src/main/java/com/example/spot/post/application/PostCommandServiceImpl.java +++ b/src/main/java/com/example/spot/post/application/PostCommandServiceImpl.java @@ -599,32 +599,4 @@ public ScrapsPostDeleteResponse cancelPostScraps(ScrapAllDeleteRequest request) .cancelScraps(deletePostResponses) .build(); } - - @Override - public PostReportResponse reportPost(Long postId, Long memberId) { - - // 동일한 게시글에 대한 중복 신고 방지 - if (postReportRepository.existsByPostIdAndMemberId(postId, memberId)) { - throw new PostHandler(ErrorStatus._POST_ALREADY_REPORTED); - } - - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - Post post = postRepository.findById(postId) - .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); - - if (post.getMember().getId().equals(memberId)) { - throw new PostHandler(ErrorStatus._POST_REPORT_SELF); - } - - PostReport postReport = PostReport.builder() - .postStatus(PostStatus.신고접수) - .post(post) - .member(member).build(); - - postReportRepository.save(postReport); - - return PostReportResponse.toDTO(postId, memberId); - } } diff --git a/src/main/java/com/example/spot/post/presentation/controller/PostController.java b/src/main/java/com/example/spot/post/presentation/controller/PostController.java index 73290afe..7cdafead 100644 --- a/src/main/java/com/example/spot/post/presentation/controller/PostController.java +++ b/src/main/java/com/example/spot/post/presentation/controller/PostController.java @@ -343,15 +343,4 @@ public ApiResponse deleteAllPostScrap( ScrapsPostDeleteResponse response = postCommandService.cancelPostScraps(request); return ApiResponse.onSuccess(SuccessStatus._NO_CONTENT, response); } - - @Tag(name = "게시글 신고", description = "게시글 신고 관련 API") - @Operation(summary = "[게시판] 게시글 신고 API", description = "게시글 ID와 회원 ID를 받아 게시글을 신고합니다.") - @PostMapping("/{postId}/report") - public ApiResponse reportPost( - @PathVariable @ExistPost Long postId - ) { - PostReportResponse response = postCommandService.reportPost(postId, SecurityUtils.getCurrentUserId()); - return ApiResponse.onSuccess(SuccessStatus._OK, response); - } - } diff --git a/src/main/java/com/example/spot/report/application/ReportCommandService.java b/src/main/java/com/example/spot/report/application/ReportCommandService.java new file mode 100644 index 00000000..43c150c4 --- /dev/null +++ b/src/main/java/com/example/spot/report/application/ReportCommandService.java @@ -0,0 +1,19 @@ +package com.example.spot.report.application; + +import com.example.spot.member.presentation.dto.MemberResponseDTO; +import com.example.spot.report.presentation.dto.PostReportResponse; +import com.example.spot.story.web.dto.response.StoryResDTO; +import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; +import jakarta.validation.Valid; + +public interface ReportCommandService { + + // 게시글 신고 + PostReportResponse reportPost(Long postId, Long memberId); + + // 스터디 회원 신고 + MemberResponseDTO.ReportedMemberDTO reportStudyMember(Long studyId, Long memberId, @Valid StudyMemberReportDTO studyMemberReportDTO); + + // 스터디 게시글 신고 + StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId); +} diff --git a/src/main/java/com/example/spot/report/application/ReportCommandServiceImpl.java b/src/main/java/com/example/spot/report/application/ReportCommandServiceImpl.java new file mode 100644 index 00000000..bbae7fbc --- /dev/null +++ b/src/main/java/com/example/spot/report/application/ReportCommandServiceImpl.java @@ -0,0 +1,156 @@ +package com.example.spot.report.application; + +import com.example.spot.common.api.code.status.ErrorStatus; +import com.example.spot.common.api.exception.handler.MemberHandler; +import com.example.spot.common.api.exception.handler.PostHandler; +import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.common.security.utils.SecurityUtils; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.MemberRepository; +import com.example.spot.member.presentation.dto.MemberResponseDTO; +import com.example.spot.post.domain.Post; +import com.example.spot.post.domain.PostRepository; +import com.example.spot.post.domain.enums.PostStatus; +import com.example.spot.report.domain.*; +import com.example.spot.report.presentation.dto.PostReportResponse; +import com.example.spot.story.domain.Story; +import com.example.spot.story.domain.StoryRepository; +import com.example.spot.story.web.dto.response.StoryResDTO; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.StudyRepository; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class ReportCommandServiceImpl implements ReportCommandService { + + private final MemberRepository memberRepository; + private final PostRepository postRepository; + private final StudyRepository studyRepository; + private final StudyMemberRepository studyMemberRepository; + private final StoryRepository storyRepository; + + private final MemberReportRepository memberReportRepository; + private final StoryReportRepository storyReportRepository; + private final PostReportRepository postReportRepository; + + @Override + public PostReportResponse reportPost(Long postId, Long memberId) { + + // 동일한 게시글에 대한 중복 신고 방지 + if (postReportRepository.existsByPostIdAndMemberId(postId, memberId)) { + throw new PostHandler(ErrorStatus._POST_ALREADY_REPORTED); + } + + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + + Post post = postRepository.findById(postId) + .orElseThrow(() -> new PostHandler(ErrorStatus._POST_NOT_FOUND)); + + if (post.getMember().getId().equals(memberId)) { + throw new PostHandler(ErrorStatus._POST_REPORT_SELF); + } + + PostReport postReport = PostReport.builder() + .postStatus(PostStatus.신고접수) + .post(post) + .member(member).build(); + + postReportRepository.save(postReport); + + return PostReportResponse.toDTO(postId, memberId); + } + + /** + * 스터디원을 신고하고 신고 내역을 저장하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력 받습니다. + * @param memberId 신고할 회원의 아이디를 입력 받습니다. + * @param studyMemberReportDTO 신고 사유를 입력 받습니다. + * @return 신고를 당한 회원의 아이디와 이름을 반환합니다. + */ + @Override + public MemberResponseDTO.ReportedMemberDTO reportStudyMember(Long studyId, Long memberId, StudyMemberReportDTO studyMemberReportDTO) { + + //=== Exception ===// + Long reporterId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(reporterId); + + Member reporter = memberRepository.findById(reporterId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + Study study = studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(reporterId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 신고당한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 자기 자신을 신고할 수 없음 + if (reporterId.equals(memberId)) { + throw new StudyHandler(ErrorStatus._STUDY_MEMBER_REPORT_INVALID); + } + + + //=== Feature ===// + MemberReport memberReport = MemberReport.builder() + .content(studyMemberReportDTO.getContent()) + .member(member) + .build(); + + memberReport = memberReportRepository.save(memberReport); + member.addMemberReport(memberReport); + + return MemberResponseDTO.ReportedMemberDTO.toDTO(member); + } + + /** + * 스터디 게시글을 신고하고 신고 내역을 저장하는 메서드입니다. + * @param studyId 타겟 스터디의 아이디를 입력합니다. + * @param postId 신고할 게시글의 아이디를 입력합니다. + * @return 신고를 당한 스터디 게시글의 아이디와 제목을 반환합니다. + */ + @Override + public StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId) { + + //=== Exception ===// + Long reporterId = SecurityUtils.getCurrentUserId(); + SecurityUtils.verifyUserId(reporterId); + + memberRepository.findById(reporterId) + .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); + studyRepository.findById(studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + Story story = storyRepository.findById(postId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); + + // 로그인한 회원이 스터디 회원인지 확인 + studyMemberRepository.findByMemberIdAndStudyIdAndStatus(reporterId, studyId, StudyApplicationStatus.APPROVED) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); + + // 해당 스터디의 게시글인지 확인 + storyRepository.findByIdAndStudyId(postId, studyId) + .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_POST_NOT_FOUND)); + + //=== Feature ===// + StoryReport storyReport = StoryReport.builder() + .story(story) + .build(); + + storyReport = storyReportRepository.save(storyReport); + story.addStudyPostReport(storyReport); + + return StoryResDTO.PostPreviewDTO.toDTO(story); + } +} diff --git a/src/main/java/com/example/spot/story/domain/association/StoryReport.java b/src/main/java/com/example/spot/report/domain/StoryReport.java similarity index 94% rename from src/main/java/com/example/spot/story/domain/association/StoryReport.java rename to src/main/java/com/example/spot/report/domain/StoryReport.java index 8d1aed8b..19951a27 100644 --- a/src/main/java/com/example/spot/story/domain/association/StoryReport.java +++ b/src/main/java/com/example/spot/report/domain/StoryReport.java @@ -1,4 +1,4 @@ -package com.example.spot.story.domain.association; +package com.example.spot.report.domain; import com.example.spot.story.domain.Story; import jakarta.persistence.*; diff --git a/src/main/java/com/example/spot/story/domain/repository/StoryReportRepository.java b/src/main/java/com/example/spot/report/domain/StoryReportRepository.java similarity index 69% rename from src/main/java/com/example/spot/story/domain/repository/StoryReportRepository.java rename to src/main/java/com/example/spot/report/domain/StoryReportRepository.java index 905963cf..16d70dd6 100644 --- a/src/main/java/com/example/spot/story/domain/repository/StoryReportRepository.java +++ b/src/main/java/com/example/spot/report/domain/StoryReportRepository.java @@ -1,6 +1,5 @@ -package com.example.spot.story.domain.repository; +package com.example.spot.report.domain; -import com.example.spot.story.domain.association.StoryReport; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/spot/report/presentation/controller/ReportController.java b/src/main/java/com/example/spot/report/presentation/controller/ReportController.java new file mode 100644 index 00000000..d15eab33 --- /dev/null +++ b/src/main/java/com/example/spot/report/presentation/controller/ReportController.java @@ -0,0 +1,59 @@ +package com.example.spot.report.presentation.controller; + +import com.example.spot.common.api.ApiResponse; +import com.example.spot.common.api.code.status.SuccessStatus; +import com.example.spot.member.domain.validation.annotation.ExistMember; +import com.example.spot.member.presentation.dto.MemberResponseDTO; +import com.example.spot.report.application.ReportCommandService; +import com.example.spot.story.domain.validation.annotation.ExistStory; +import com.example.spot.story.web.dto.response.StoryResDTO; +import com.example.spot.study.domain.validation.annotation.ExistStudy; +import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/spot") +@Validated +public class ReportController { + + private final ReportCommandService reportCommandService; + +/* ----------------------------- 스터디 회원 신고 관련 API ------------------------------------- */ + + @Tag(name = "스터디 신고") + @Operation(summary = "[스터디 신고] 스터디원 신고하기", description = """ + ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 다른 회원을 신고합니다. + 신고당한 회원의 id와 이름이 반환됩니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "memberId", description = "신고할 스터디원의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/members/{memberId}/reports") + public ApiResponse reportStudyMember( + @PathVariable @ExistStudy Long studyId, @PathVariable @ExistMember Long memberId, + @RequestBody @Valid StudyMemberReportDTO studyMemberReportDTO) { + MemberResponseDTO.ReportedMemberDTO reportedMemberDTO = reportCommandService.reportStudyMember(studyId, memberId, studyMemberReportDTO); + return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_REPORTED, reportedMemberDTO); + } + + @Tag(name = "스터디 신고") + @Operation(summary = "[스터디 신고] 스터디 게시글 신고하기", description = """ + ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 게시글을 신고합니다. + 신고당한 스터디 게시글의 id와 제목이 반환됩니다. + """) + @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) + @Parameter(name = "postId", description = "신고할 스터디 게시글의 id를 입력합니다.", required = true) + @PostMapping("/studies/{studyId}/posts/{postId}/reports") + public ApiResponse reportStudyPost( + @PathVariable @ExistStudy Long studyId, + @PathVariable @ExistStory Long postId) { + StoryResDTO.PostPreviewDTO postPreviewDTO = reportCommandService.reportStudyPost(studyId, postId); + return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_REPORTED, postPreviewDTO); + } +} diff --git a/src/main/java/com/example/spot/story/application/StoryCommandServiceImpl.java b/src/main/java/com/example/spot/story/application/StoryCommandServiceImpl.java index d754e532..03c8d3d4 100644 --- a/src/main/java/com/example/spot/story/application/StoryCommandServiceImpl.java +++ b/src/main/java/com/example/spot/story/application/StoryCommandServiceImpl.java @@ -15,7 +15,7 @@ import com.example.spot.story.domain.repository.LikedStoryRepository; import com.example.spot.story.domain.repository.StoryCommentRepository; import com.example.spot.story.domain.repository.StoryImageRepository; -import com.example.spot.story.domain.repository.StoryReportRepository; +import com.example.spot.report.domain.StoryReportRepository; import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.notification.domain.enums.NotifyType; diff --git a/src/main/java/com/example/spot/story/domain/Story.java b/src/main/java/com/example/spot/story/domain/Story.java index c5d199b4..8306ec88 100644 --- a/src/main/java/com/example/spot/story/domain/Story.java +++ b/src/main/java/com/example/spot/story/domain/Story.java @@ -5,7 +5,7 @@ import com.example.spot.story.domain.association.LikedStory; import com.example.spot.story.domain.association.StoryComment; import com.example.spot.story.domain.association.StoryImage; -import com.example.spot.story.domain.association.StoryReport; +import com.example.spot.report.domain.StoryReport; import com.example.spot.story.domain.enums.StoryCategory; import com.example.spot.study.domain.Study; import com.example.spot.story.web.dto.request.StoryRequestDTO; diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java index c55210e0..ad50e46d 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandService.java @@ -30,11 +30,4 @@ public interface StudyMemberCommandService { // 스터디 승인 거절 테스트 StudyApplyResponseDTO acceptAndRejectStudyApplyForTest(Long memberId, Long studyId, boolean isAccept); - - // 스터디 회원 신고 - MemberResponseDTO.ReportedMemberDTO reportStudyMember(Long studyId, Long memberId, @Valid StudyMemberReportDTO studyMemberReportDTO); - - // 스터디 게시글 신고 - StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId); - } diff --git a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java index 1fbaf3be..c7660a3f 100644 --- a/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java +++ b/src/main/java/com/example/spot/study/application/StudyMemberCommandServiceImpl.java @@ -5,10 +5,7 @@ import com.example.spot.common.api.exception.handler.MemberHandler; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; -import com.example.spot.report.domain.MemberReport; import com.example.spot.notification.domain.Notification; -import com.example.spot.story.domain.Story; -import com.example.spot.story.domain.association.StoryReport; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.notification.domain.enums.NotifyType; import com.example.spot.member.domain.enums.Status; @@ -18,17 +15,14 @@ import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.notification.domain.NotificationRepository; -import com.example.spot.story.domain.repository.StoryReportRepository; +import com.example.spot.report.domain.StoryReportRepository; import com.example.spot.story.domain.StoryRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.study.presentation.dto.request.StudyHostWithdrawRequestDTO; -import com.example.spot.study.presentation.dto.request.StudyMemberReportDTO; -import com.example.spot.story.web.dto.response.StoryResDTO; import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO; import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.common.application.s3.S3ImageService; -import com.example.spot.member.presentation.dto.MemberResponseDTO; import com.example.spot.study.presentation.dto.response.StudyWithdrawalResponseDTO.WithdrawalDTO; import com.example.spot.study.presentation.dto.response.StudyApplyResponseDTO; @@ -279,90 +273,4 @@ public StudyApplyResponseDTO acceptAndRejectStudyApplyForTest(Long memberId, Lon private boolean isOwner(Long memberId, Long studyId) { return studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(memberId, studyId, true).isPresent(); } - - /** - * 스터디원을 신고하고 신고 내역을 저장하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력 받습니다. - * @param memberId 신고할 회원의 아이디를 입력 받습니다. - * @param studyMemberReportDTO 신고 사유를 입력 받습니다. - * @return 신고를 당한 회원의 아이디와 이름을 반환합니다. - */ - @Override - public MemberResponseDTO.ReportedMemberDTO reportStudyMember(Long studyId, Long memberId, StudyMemberReportDTO studyMemberReportDTO) { - - //=== Exception ===// - Long reporterId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(reporterId); - - Member reporter = memberRepository.findById(reporterId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - Study study = studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(reporterId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 신고당한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(memberId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 자기 자신을 신고할 수 없음 - if (reporterId.equals(memberId)) { - throw new StudyHandler(ErrorStatus._STUDY_MEMBER_REPORT_INVALID); - } - - - //=== Feature ===// - MemberReport memberReport = MemberReport.builder() - .content(studyMemberReportDTO.getContent()) - .member(member) - .build(); - - memberReport = memberReportRepository.save(memberReport); - member.addMemberReport(memberReport); - - return MemberResponseDTO.ReportedMemberDTO.toDTO(member); - } - - /** - * 스터디 게시글을 신고하고 신고 내역을 저장하는 메서드입니다. - * @param studyId 타겟 스터디의 아이디를 입력합니다. - * @param postId 신고할 게시글의 아이디를 입력합니다. - * @return 신고를 당한 스터디 게시글의 아이디와 제목을 반환합니다. - */ - @Override - public StoryResDTO.PostPreviewDTO reportStudyPost(Long studyId, Long postId) { - - //=== Exception ===// - Long reporterId = SecurityUtils.getCurrentUserId(); - SecurityUtils.verifyUserId(reporterId); - - memberRepository.findById(reporterId) - .orElseThrow(() -> new MemberHandler(ErrorStatus._MEMBER_NOT_FOUND)); - studyRepository.findById(studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - Story story = storyRepository.findById(postId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_NOT_FOUND)); - - // 로그인한 회원이 스터디 회원인지 확인 - studyMemberRepository.findByMemberIdAndStudyIdAndStatus(reporterId, studyId, StudyApplicationStatus.APPROVED) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_MEMBER_NOT_FOUND)); - - // 해당 스터디의 게시글인지 확인 - storyRepository.findByIdAndStudyId(postId, studyId) - .orElseThrow(() -> new StudyHandler(ErrorStatus._STUDY_POST_NOT_FOUND)); - - //=== Feature ===// - StoryReport storyReport = StoryReport.builder() - .story(story) - .build(); - - storyReport = storyReportRepository.save(storyReport); - story.addStudyPostReport(storyReport); - - return StoryResDTO.PostPreviewDTO.toDTO(story); - } } diff --git a/src/main/java/com/example/spot/study/application/StudyQueryService.java b/src/main/java/com/example/spot/study/application/StudyQueryService.java index 754341a7..6c281a23 100644 --- a/src/main/java/com/example/spot/study/application/StudyQueryService.java +++ b/src/main/java/com/example/spot/study/application/StudyQueryService.java @@ -39,8 +39,7 @@ StudyPreviewDTO findInterestStudiesByConditionsAll(Pageable pageable, Long membe SearchRequestStudyDTO request, StudySortBy sortBy); // 내 특정 관심사 스터디 페이징 조회 - StudyPreviewDTO findInterestStudiesByConditionsSpecific(Pageable pageable, Long memberId, - SearchRequestStudyDTO request, ThemeType theme, StudySortBy sortBy); + StudyPreviewDTO findInterestStudiesByConditionsSpecific(Pageable pageable, Long memberId, SearchRequestStudyDTO request, ThemeType theme, StudySortBy sortBy); // 내 관심 지역 스터디 페이징 조회 StudyPreviewDTO findInterestRegionStudiesByConditionsAll( diff --git a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java index 19e548e4..22efb976 100644 --- a/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java +++ b/src/main/java/com/example/spot/study/presentation/controller/MemberStudyController.java @@ -203,40 +203,4 @@ public ApiResponse getStudyHost( return ApiResponse.onSuccess(SuccessStatus._STUDY_HOST_FOUND, studyHostDTO); } - /* ----------------------------- 스터디 이미지 관련 API ------------------------------------- */ - - - - - /* ----------------------------- 스터디 회원 신고 관련 API ------------------------------------- */ - - @Tag(name = "스터디 신고") - @Operation(summary = "[스터디 신고] 스터디원 신고하기", description = """ - ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 다른 회원을 신고합니다. - 신고당한 회원의 id와 이름이 반환됩니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "memberId", description = "신고할 스터디원의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/members/{memberId}/reports") - public ApiResponse reportStudyMember( - @PathVariable @ExistStudy Long studyId, @PathVariable @ExistMember Long memberId, - @RequestBody @Valid StudyMemberReportDTO studyMemberReportDTO) { - MemberResponseDTO.ReportedMemberDTO reportedMemberDTO = studyMemberCommandService.reportStudyMember(studyId, memberId, studyMemberReportDTO); - return ApiResponse.onSuccess(SuccessStatus._STUDY_MEMBER_REPORTED, reportedMemberDTO); - } - - @Tag(name = "스터디 신고") - @Operation(summary = "[스터디 신고] 스터디 게시글 신고하기", description = """ - ## [스터디 신고] 로그인한 회원이 참여하는 스터디의 게시글을 신고합니다. - 신고당한 스터디 게시글의 id와 제목이 반환됩니다. - """) - @Parameter(name = "studyId", description = "스터디의 id를 입력합니다.", required = true) - @Parameter(name = "postId", description = "신고할 스터디 게시글의 id를 입력합니다.", required = true) - @PostMapping("/studies/{studyId}/posts/{postId}/reports") - public ApiResponse reportStudyPost( - @PathVariable @ExistStudy Long studyId, - @PathVariable @ExistStory Long postId) { - StoryResDTO.PostPreviewDTO postPreviewDTO = studyMemberCommandService.reportStudyPost(studyId, postId); - return ApiResponse.onSuccess(SuccessStatus._STUDY_POST_REPORTED, postPreviewDTO); - } } \ No newline at end of file diff --git a/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java b/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java index 9087bd0e..09d81785 100644 --- a/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/post/PostCommandServiceTest.java @@ -6,6 +6,7 @@ import com.example.spot.member.domain.Member; import com.example.spot.post.domain.Post; import com.example.spot.comment.domain.PostComment; +import com.example.spot.report.application.ReportCommandServiceImpl; import com.example.spot.report.domain.PostReport; import com.example.spot.post.domain.enums.Board; import com.example.spot.post.domain.association.MemberScrap; @@ -92,6 +93,9 @@ class PostCommandServiceTest { @InjectMocks private PostCommandServiceImpl postCommandService; + @InjectMocks + private ReportCommandServiceImpl reportCommandService; + private static Member member1; private static Member member2; @@ -1016,7 +1020,7 @@ void reportPost_Success() { when(postReportRepository.save(any(PostReport.class))).thenReturn(postReport); // when - PostReportResponse result = postCommandService.reportPost(postId, memberId); + PostReportResponse result = reportCommandService.reportPost(postId, memberId); // then assertNotNull(result); @@ -1036,7 +1040,7 @@ void reportPost_SelfReport_Fail() { when(postReportRepository.existsByPostIdAndMemberId(postId, memberId)).thenReturn(false); // when & then - assertThrows(PostHandler.class, () -> postCommandService.reportPost(postId, memberId)); + assertThrows(PostHandler.class, () -> reportCommandService.reportPost(postId, memberId)); } @Test @@ -1051,7 +1055,7 @@ void reportPost_DuplicateReport_Fail() { when(postReportRepository.existsByPostIdAndMemberId(postId, memberId)).thenReturn(true); // when & then - assertThrows(PostHandler.class, () -> postCommandService.reportPost(postId, memberId)); + assertThrows(PostHandler.class, () -> reportCommandService.reportPost(postId, memberId)); } diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java index 4dbce5f0..a0d3294f 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java @@ -12,7 +12,7 @@ import com.example.spot.story.domain.repository.LikedStoryRepository; import com.example.spot.story.domain.repository.StoryCommentRepository; import com.example.spot.story.domain.repository.StoryImageRepository; -import com.example.spot.story.domain.repository.StoryReportRepository; +import com.example.spot.report.domain.StoryReportRepository; import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; From fd69aab3d97af38ee246d977184d8c3fd7abffc4 Mon Sep 17 00:00:00 2001 From: dvlp-sy Date: Mon, 2 Jun 2025 18:21:45 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[SPOT-286][TEST]=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AttendanceCommandServiceTest.java} | 38 +- .../AttendanceQueryServiceTest.java} | 24 +- .../ScheduleCommandServiceTest.java} | 20 +- .../ScheduleQueryServiceTest.java} | 62 ++- .../StoryCommandServiceTest.java | 2 +- .../StoryQueryServiceTest.java | 70 ++- .../StudyMemberCommandServiceTest.java | 175 +++++++ .../StudyMemberQueryServiceTest.java | 257 +++++++++ .../studymember/ToDoQueryServiceTest.java | 495 ------------------ .../ToDoCommandServiceTest.java | 218 +------- .../service/todo/ToDoQueryServiceTest.java | 198 +++++++ 11 files changed, 805 insertions(+), 754 deletions(-) rename src/test/java/com/example/spot/service/{study/studyschedule/StudyAttendanceCommandServiceTest.java => schedule/AttendanceCommandServiceTest.java} (92%) rename src/test/java/com/example/spot/service/{study/studyschedule/StudyAttendanceQueryServiceTest.java => schedule/AttendanceQueryServiceTest.java} (90%) rename src/test/java/com/example/spot/service/{study/studyschedule/StudyScheduleCommandServiceTest.java => schedule/ScheduleCommandServiceTest.java} (93%) rename src/test/java/com/example/spot/service/{study/studyschedule/StudyScheduleQueryServiceTest.java => schedule/ScheduleQueryServiceTest.java} (81%) rename src/test/java/com/example/spot/service/{study/studypost => story}/StoryCommandServiceTest.java (99%) rename src/test/java/com/example/spot/service/{study/studypost => story}/StoryQueryServiceTest.java (88%) create mode 100644 src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java create mode 100644 src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java delete mode 100644 src/test/java/com/example/spot/service/study/studymember/ToDoQueryServiceTest.java rename src/test/java/com/example/spot/service/{study/studymember => todo}/ToDoCommandServiceTest.java (50%) create mode 100644 src/test/java/com/example/spot/service/todo/ToDoQueryServiceTest.java diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java b/src/test/java/com/example/spot/service/schedule/AttendanceCommandServiceTest.java similarity index 92% rename from src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java rename to src/test/java/com/example/spot/service/schedule/AttendanceCommandServiceTest.java index 8b29935d..0d231df4 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/schedule/AttendanceCommandServiceTest.java @@ -1,7 +1,8 @@ -package com.example.spot.service.study.studyschedule; +package com.example.spot.service.schedule; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; +import com.example.spot.schedule.application.ScheduleCommandServiceImpl; import com.example.spot.schedule.domain.Schedule; import com.example.spot.schedule.domain.association.Quiz; import com.example.spot.schedule.domain.association.QuizSubmission; @@ -15,7 +16,6 @@ import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.StudyMemberCommandServiceImpl; import com.example.spot.schedule.presentation.dto.request.StudyQuizRequestDTO; import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import org.junit.jupiter.api.BeforeEach; @@ -45,7 +45,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class StudyAttendanceCommandServiceTest { +class AttendanceCommandServiceTest { @Mock private MemberRepository memberRepository; @@ -63,7 +63,7 @@ class StudyAttendanceCommandServiceTest { private QuizSubmissionRepository quizSubmissionRepository; @InjectMocks - private StudyMemberCommandServiceImpl memberStudyCommandService; + private ScheduleCommandServiceImpl scheduleCommandService; private static Study study; private static Member member1; @@ -130,7 +130,7 @@ void createAttendanceQuiz_Success() { when(quizRepository.save(any(Quiz.class))).thenReturn(quiz2); // when - StudyQuizResponseDTO.QuizDTO result = memberStudyCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO); + StudyQuizResponseDTO.QuizDTO result = scheduleCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO); // then assertThat(result).isNotNull(); @@ -154,7 +154,7 @@ void createAttendanceQuiz_StudyNotFound_Fail() { when(quizRepository.save(any(Quiz.class))).thenReturn(quiz2); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); } @Test @@ -173,7 +173,7 @@ void createAttendanceQuiz_NotStudyMember_Fail() { when(quizRepository.save(any(Quiz.class))).thenReturn(quiz2); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); } @Test @@ -192,7 +192,7 @@ void createAttendanceQuiz_NotOwner_Fail() { when(quizRepository.save(any(Quiz.class))).thenReturn(quiz2); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); } @Test @@ -214,7 +214,7 @@ void createAttendanceQuiz_ScheduleNotFound_Fail() { when(quizRepository.save(any(Quiz.class))).thenReturn(quiz2); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.createAttendanceQuiz(studyId, schedule.getId(), quizRequestDTO)); } @Test @@ -242,7 +242,7 @@ void attendantStudy_Success() { .thenReturn(List.of(member1Attendance)); // when - StudyQuizResponseDTO.AttendanceDTO result = memberStudyCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO); + StudyQuizResponseDTO.AttendanceDTO result = scheduleCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO); // then assertThat(result).isNotNull(); @@ -276,7 +276,7 @@ void attendantStudy_NotStudyMember_Fail() { .thenReturn(List.of()); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); } @Test @@ -307,7 +307,7 @@ void attendantStudy_QuizNotFound_Fail() { .thenReturn(List.of(member1Attendance)); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); } @Test @@ -341,7 +341,7 @@ void attendantStudy_QuizTimeOver_Fail() { .thenReturn(List.of()); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); } @Test @@ -370,7 +370,7 @@ void attendantStudy_QuizTryOver_Fail() { .thenReturn(List.of(member1Attendance, member1Attendance2, member1Attendance3)); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); } @Test @@ -398,7 +398,7 @@ void attendantStudy_QuizAlreadyCorrect_Fail() { .thenReturn(List.of(ownerAttendance)); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.attendantStudy(studyId, scheduleId, attendanceRequestDTO)); } @Test @@ -426,7 +426,7 @@ void deleteAttendanceQuiz_Success() { .thenReturn(List.of(member1Attendance, member1Attendance2, member1Attendance3, ownerAttendance)); // when - StudyQuizResponseDTO.QuizDTO result = memberStudyCommandService.deleteAttendanceQuiz(studyId, scheduleId, date); + StudyQuizResponseDTO.QuizDTO result = scheduleCommandService.deleteAttendanceQuiz(studyId, scheduleId, date); // then assertThat(result).isNotNull(); @@ -460,7 +460,7 @@ void deleteAttendanceQuiz_QuizNotFound_Fail() { .thenReturn(List.of(member1Attendance, member1Attendance2, member1Attendance3, ownerAttendance)); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.deleteAttendanceQuiz(studyId, scheduleId, date)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.deleteAttendanceQuiz(studyId, scheduleId, date)); } @Test @@ -486,7 +486,7 @@ void deleteAttendanceQuiz_NotStudyMember_Fail() { .thenReturn(List.of(member1Attendance, member1Attendance2, member1Attendance3, ownerAttendance)); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.deleteAttendanceQuiz(studyId, scheduleId, date)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.deleteAttendanceQuiz(studyId, scheduleId, date)); } @Test @@ -514,7 +514,7 @@ void deleteAttendanceQuiz_NotStudyOwner_Fail() { .thenReturn(List.of(member1Attendance, member1Attendance2, member1Attendance3, ownerAttendance)); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.deleteAttendanceQuiz(studyId, scheduleId, date)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.deleteAttendanceQuiz(studyId, scheduleId, date)); } /*-------------------------------------------------------- Utils ------------------------------------------------------------------------*/ diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java b/src/test/java/com/example/spot/service/schedule/AttendanceQueryServiceTest.java similarity index 90% rename from src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java rename to src/test/java/com/example/spot/service/schedule/AttendanceQueryServiceTest.java index 86a77e74..fbbeba70 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyAttendanceQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/schedule/AttendanceQueryServiceTest.java @@ -1,7 +1,8 @@ -package com.example.spot.service.study.studyschedule; +package com.example.spot.service.schedule; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; +import com.example.spot.schedule.application.ScheduleQueryServiceImpl; import com.example.spot.schedule.domain.Schedule; import com.example.spot.schedule.domain.association.Quiz; import com.example.spot.schedule.domain.association.QuizSubmission; @@ -15,7 +16,6 @@ import com.example.spot.member.domain.MemberRepository; import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.StudyMemberQueryServiceImpl; import com.example.spot.schedule.presentation.dto.response.StudyQuizResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -43,7 +43,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class StudyAttendanceQueryServiceTest { +class AttendanceQueryServiceTest { @Mock private MemberRepository memberRepository; @@ -61,7 +61,7 @@ class StudyAttendanceQueryServiceTest { private QuizSubmissionRepository quizSubmissionRepository; @InjectMocks - private StudyMemberQueryServiceImpl memberStudyQueryService; + private ScheduleQueryServiceImpl scheduleQueryService; private static Study study; private static Member member1; @@ -118,7 +118,7 @@ void getAllAttendances_Success() { getAuthentication(member1.getId()); // when - StudyQuizResponseDTO.AttendanceListDTO result = memberStudyQueryService.getAllAttendances(studyId, schedule.getId(), date); + StudyQuizResponseDTO.AttendanceListDTO result = scheduleQueryService.getAllAttendances(studyId, schedule.getId(), date); // then assertThat(result).isNotNull(); @@ -141,7 +141,7 @@ void getAllAttendances_StudyNotFound_Fail() { getAuthentication(member2.getId()); // when & then - assertThrows(StudyHandler.class, () -> memberStudyQueryService.getAllAttendances(studyId, schedule.getId(), date)); + assertThrows(StudyHandler.class, () -> scheduleQueryService.getAllAttendances(studyId, schedule.getId(), date)); } @Test @@ -155,7 +155,7 @@ void getAllAttendances_NotStudyMember_Fail() { getAuthentication(member2.getId()); // when & then - assertThrows(StudyHandler.class, () -> memberStudyQueryService.getAllAttendances(studyId, schedule.getId(), date)); + assertThrows(StudyHandler.class, () -> scheduleQueryService.getAllAttendances(studyId, schedule.getId(), date)); } @Test @@ -169,7 +169,7 @@ void getAllAttendances_ScheduleNotFound_Fail() { getAuthentication(member1.getId()); // when & then - assertThrows(StudyHandler.class, () -> memberStudyQueryService.getAllAttendances(studyId, 2L, date)); + assertThrows(StudyHandler.class, () -> scheduleQueryService.getAllAttendances(studyId, 2L, date)); } @Test @@ -183,7 +183,7 @@ void getAttendanceQuiz_Success() { getAuthentication(member1.getId()); // when - StudyQuizResponseDTO.QuizDTO result = memberStudyQueryService.getAttendanceQuiz(studyId, schedule.getId(), date); + StudyQuizResponseDTO.QuizDTO result = scheduleQueryService.getAttendanceQuiz(studyId, schedule.getId(), date); // then assertThat(result).isNotNull(); @@ -202,7 +202,7 @@ void getAttendanceQuiz_StudyNotFound_Fail() { getAuthentication(member1.getId()); // when - assertThrows(StudyHandler.class, () -> memberStudyQueryService.getAttendanceQuiz(studyId, schedule.getId(), date)); + assertThrows(StudyHandler.class, () -> scheduleQueryService.getAttendanceQuiz(studyId, schedule.getId(), date)); } @Test @@ -216,7 +216,7 @@ void getAttendanceQuiz_NotStudyMember_Fail() { getAuthentication(member2.getId()); // when - assertThrows(StudyHandler.class, () -> memberStudyQueryService.getAttendanceQuiz(studyId, schedule.getId(), date)); + assertThrows(StudyHandler.class, () -> scheduleQueryService.getAttendanceQuiz(studyId, schedule.getId(), date)); } @Test @@ -230,7 +230,7 @@ void getAttendanceQuiz_ScheduleNotFound_Fail() { getAuthentication(member1.getId()); // when - assertThrows(StudyHandler.class, () -> memberStudyQueryService.getAttendanceQuiz(studyId, 2L, date)); + assertThrows(StudyHandler.class, () -> scheduleQueryService.getAttendanceQuiz(studyId, 2L, date)); } diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java b/src/test/java/com/example/spot/service/schedule/ScheduleCommandServiceTest.java similarity index 93% rename from src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java rename to src/test/java/com/example/spot/service/schedule/ScheduleCommandServiceTest.java index d5eeca24..747ac933 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/schedule/ScheduleCommandServiceTest.java @@ -1,8 +1,9 @@ -package com.example.spot.service.study.studyschedule; +package com.example.spot.service.schedule; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.notification.domain.Notification; +import com.example.spot.schedule.application.ScheduleCommandServiceImpl; import com.example.spot.schedule.domain.Schedule; import com.example.spot.schedule.domain.enums.SchedulePeriod; import com.example.spot.study.domain.association.StudyMember; @@ -14,7 +15,6 @@ import com.example.spot.notification.domain.NotificationRepository; import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.StudyMemberCommandServiceImpl; import com.example.spot.schedule.presentation.dto.request.ScheduleRequestDTO; import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import org.junit.jupiter.api.BeforeEach; @@ -45,7 +45,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class StudyScheduleCommandServiceTest { +class ScheduleCommandServiceTest { @Mock private MemberRepository memberRepository; @@ -65,7 +65,7 @@ class StudyScheduleCommandServiceTest { private NotificationRepository notificationRepository; @InjectMocks - private StudyMemberCommandServiceImpl memberStudyCommandService; + private ScheduleCommandServiceImpl scheduleCommandService; private static Study study1; private static Member member1; @@ -160,7 +160,7 @@ private void addScheduleSuccess(SchedulePeriod schedulePeriod) { when(scheduleRepository.save(schedule)).thenReturn(schedule); // when - ScheduleResponseDTO.ScheduleDTO result = memberStudyCommandService.addSchedule(studyId, scheduleRequestDTO); + ScheduleResponseDTO.ScheduleDTO result = scheduleCommandService.addSchedule(studyId, scheduleRequestDTO); // then assertThat(result).isNotNull(); @@ -250,7 +250,7 @@ private void addScheduleFail(LocalDateTime startedAt, LocalDateTime finishedAt, when(scheduleRepository.save(schedule)).thenReturn(schedule); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.addSchedule(studyId, scheduleRequestDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.addSchedule(studyId, scheduleRequestDTO)); } @Test @@ -273,7 +273,7 @@ void modSchedule_Success() { when(scheduleRepository.save(schedule)).thenReturn(schedule); // when - ScheduleResponseDTO.ScheduleDTO result = memberStudyCommandService.modSchedule(studyId, scheduleId, scheduleModDTO); + ScheduleResponseDTO.ScheduleDTO result = scheduleCommandService.modSchedule(studyId, scheduleId, scheduleModDTO); // then assertThat(result).isNotNull(); @@ -295,7 +295,7 @@ void modSchedule_NotStudyMember_Fail() { ScheduleRequestDTO.ScheduleDTO scheduleModDTO = getScheduleModDTO(memberId, scheduleId, studyId, member2, study1); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.modSchedule(studyId, scheduleId, scheduleModDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.modSchedule(studyId, scheduleId, scheduleModDTO)); } @Test @@ -310,7 +310,7 @@ void modSchedule_NotCreator_Fail() { ScheduleRequestDTO.ScheduleDTO scheduleModDTO = getScheduleModDTO(memberId, scheduleId, studyId, member1, study1); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.modSchedule(studyId, scheduleId, scheduleModDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.modSchedule(studyId, scheduleId, scheduleModDTO)); } @Test @@ -325,7 +325,7 @@ void modSchedule_NotStudySchedule_Fail() { ScheduleRequestDTO.ScheduleDTO scheduleModDTO = getScheduleModDTO(memberId, scheduleId, studyId, member1, study1); // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.modSchedule(studyId, scheduleId, scheduleModDTO)); + assertThrows(StudyHandler.class, () -> scheduleCommandService.modSchedule(studyId, scheduleId, scheduleModDTO)); } private ScheduleRequestDTO.ScheduleDTO getScheduleModDTO( diff --git a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java b/src/test/java/com/example/spot/service/schedule/ScheduleQueryServiceTest.java similarity index 81% rename from src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java rename to src/test/java/com/example/spot/service/schedule/ScheduleQueryServiceTest.java index fa3cdb47..da73c3fe 100644 --- a/src/test/java/com/example/spot/service/study/studyschedule/StudyScheduleQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/schedule/ScheduleQueryServiceTest.java @@ -1,9 +1,13 @@ -package com.example.spot.service.study.studyschedule; +package com.example.spot.service.schedule; +import com.example.spot.common.api.exception.GeneralException; import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.common.security.utils.SecurityUtils; import com.example.spot.member.domain.Member; +import com.example.spot.schedule.application.ScheduleQueryServiceImpl; import com.example.spot.schedule.domain.Schedule; import com.example.spot.schedule.domain.enums.SchedulePeriod; +import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; import com.example.spot.study.domain.association.StudyMember; @@ -12,7 +16,6 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.schedule.domain.ScheduleRepository; import com.example.spot.study.domain.StudyRepository; -import com.example.spot.study.application.StudyMemberQueryServiceImpl; import com.example.spot.schedule.presentation.dto.response.ScheduleResponseDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -23,6 +26,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.data.domain.Pageable; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -33,15 +37,17 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) -class StudyScheduleQueryServiceTest { +class ScheduleQueryServiceTest { @Mock private MemberRepository memberRepository; @@ -50,12 +56,11 @@ class StudyScheduleQueryServiceTest { private StudyRepository studyRepository; @Mock private StudyMemberRepository studyMemberRepository; - @Mock private ScheduleRepository scheduleRepository; @InjectMocks - private StudyMemberQueryServiceImpl memberStudyQueryService; + private ScheduleQueryServiceImpl scheduleQueryService; private static Study study1; private static Study study2; @@ -86,6 +91,9 @@ void setUp() { when(studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(member1.getId(), study1.getId(), StudyApplicationStatus.APPROVED)).thenReturn(true); when(studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(member2.getId(), study1.getId(), StudyApplicationStatus.APPROVED)).thenReturn(false); when(studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(owner.getId(), study1.getId(), StudyApplicationStatus.APPROVED)).thenReturn(true); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus( + member1.getId(), study1.getId(),StudyApplicationStatus.APPROVED) + ).thenReturn(Optional.of(member1Study1)); when(scheduleRepository.findById(schedule1.getId())).thenReturn(Optional.of(schedule1)); when(scheduleRepository.findById(schedule2.getId())).thenReturn(Optional.of(schedule2)); @@ -107,7 +115,7 @@ void getMonthlySchedules_Success() { getAuthentication(memberId); // when - ScheduleResponseDTO.MonthlyScheduleListDTO result = memberStudyQueryService.getMonthlySchedules(studyId, 2024, 1); + ScheduleResponseDTO.MonthlyScheduleListDTO result = scheduleQueryService.getMonthlySchedules(studyId, 2024, 1); // then assertThat(result).isNotNull(); @@ -129,7 +137,7 @@ void getMonthlySchedules_NotStudyMember_Fail() { getAuthentication(memberId); // when - ScheduleResponseDTO.MonthlyScheduleListDTO result = memberStudyQueryService.getMonthlySchedules(studyId, 2024, 1); + ScheduleResponseDTO.MonthlyScheduleListDTO result = scheduleQueryService.getMonthlySchedules(studyId, 2024, 1); // then assertThat(result).isNotNull(); @@ -152,7 +160,7 @@ void getSchedule_Success() { getAuthentication(memberId); // when - ScheduleResponseDTO.MonthlyScheduleDTO result = memberStudyQueryService.getSchedule(scheduleId, studyId); + ScheduleResponseDTO.MonthlyScheduleDTO result = scheduleQueryService.getSchedule(scheduleId, studyId); // then assertThat(result).isNotNull(); @@ -172,10 +180,44 @@ void getSchedule_NotStudySchedule_Fail() { getAuthentication(memberId); // when & then - assertThrows(StudyHandler.class, () -> memberStudyQueryService.getSchedule(scheduleId, studyId)); + assertThrows(StudyHandler.class, () -> scheduleQueryService.getSchedule(scheduleId, studyId)); + } + +/* ------------------------------------------------ 스터디 모임 목록 조회 --------------------------------------------------- */ + + @Test + @DisplayName("스터디 모임 목록 조회 - 로그인 한 회원이 해당 스터디 회원이 아닌 경우") + void 스터디_모임_목록_조회_실패_1(){ + + // given + Long memberId = 1L; + Long studyId = 2L; + + // 사용자 인증 정보 생성 + getAuthentication(memberId); + + // when & then + assertThrows(GeneralException.class, () -> scheduleQueryService.findStudySchedule(studyId, Pageable.unpaged())); } -/*-------------------------------------------------------- Utils ------------------------------------------------------------------------*/ + @Test + @DisplayName("스터디 모임 목록 조회 - 스터디 모임 일정이 존재하지 않는 경우") + void 스터디_모임_목록_조회_실패_2(){ + + // given + Long memberId = 1L; + Long studyId = 2L; + + // 사용자 인증 정보 생성 + getAuthentication(memberId); + + // when & then + assertThrows(GeneralException.class, () -> scheduleQueryService.findStudySchedule(studyId, Pageable.unpaged())); + } + + + + /*-------------------------------------------------------- Utils ------------------------------------------------------------------------*/ private static void initMember() { member1 = Member.builder() diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java b/src/test/java/com/example/spot/service/story/StoryCommandServiceTest.java similarity index 99% rename from src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java rename to src/test/java/com/example/spot/service/story/StoryCommandServiceTest.java index a0d3294f..bbb68a6e 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/story/StoryCommandServiceTest.java @@ -1,4 +1,4 @@ -package com.example.spot.service.study.studypost; +package com.example.spot.service.story; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; diff --git a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java b/src/test/java/com/example/spot/service/story/StoryQueryServiceTest.java similarity index 88% rename from src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java rename to src/test/java/com/example/spot/service/story/StoryQueryServiceTest.java index ebfa07d0..35bca252 100644 --- a/src/test/java/com/example/spot/service/study/studypost/StoryQueryServiceTest.java +++ b/src/test/java/com/example/spot/service/story/StoryQueryServiceTest.java @@ -1,5 +1,6 @@ -package com.example.spot.service.study.studypost; +package com.example.spot.service.story; +import com.example.spot.common.api.exception.GeneralException; import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.story.domain.Story; @@ -10,6 +11,7 @@ import com.example.spot.story.domain.repository.LikedStoryRepository; import com.example.spot.story.domain.repository.StoryCommentRepository; import com.example.spot.story.domain.StoryRepository; +import com.example.spot.story.web.dto.response.StoryResponseDTO; import com.example.spot.study.domain.association.StudyMember; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Gender; @@ -375,7 +377,71 @@ void getPost_NotStudyPost_Fail() { assertThrows(StudyHandler.class, () ->studyPostQueryService.getPost(studyId, postId, false)); } -/*-------------------------------------------------------- 댓글 목록 조회 ------------------------------------------------------------------------*/ +/* ------------------------------------------------ 스터디 공지사항 조회 --------------------------------------------------- */ + + @Test + @DisplayName("스터디 공지사항 조회 - 성공") + void 스터디_공지사항_조회_성공(){ + + // given + long studyId = 1L; + String title = "공지"; + String content = "공지입니다."; + Story story = Story.builder() + .title(title) + .content(content) + .storyCategory(StoryCategory.WELCOME) + .isAnnouncement(true) + .build(); + + StudyMember studyMember = StudyMember.builder() + .introduction(title).study(study).member(owner).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); + + when(storyRepository.findByStudyIdAndIsAnnouncement(studyId, true)).thenReturn(Optional.of(story)); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( + Optional.ofNullable(studyMember)); + + // when + StoryResponseDTO responseDTO = studyPostQueryService.findStudyAnnouncementPost(studyId); + + // then + assertEquals(title,responseDTO.getTitle()); + assertEquals(content, responseDTO.getContent()); + } + + @Test + @DisplayName("스터디 공지사항 조회 - 로그인 한 회원이 해당 스터디 회원이 아닌 경우") + void 스터디_공지사항_조회_실패_1(){ + + // given + long studyId = 1L; + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( + Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> studyPostQueryService.findStudyAnnouncementPost(studyId)); + } + + @Test + @DisplayName("스터디 공지사항 조회 - 스터디 공지 글이 없는 경우") + void 스터디_공지사항_조회_실패_2(){ + + // given + long studyId = 1L; + StudyMember studyMember = StudyMember.builder() + .introduction("title").study(study).member(owner).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); + + + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( + Optional.ofNullable(studyMember)); + when(storyRepository.findByStudyIdAndIsAnnouncement(studyId, true)).thenReturn(Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> studyPostQueryService.findStudyAnnouncementPost(studyId)); + } + + + /*-------------------------------------------------------- 댓글 목록 조회 ------------------------------------------------------------------------*/ @Test @DisplayName("스터디 게시글 댓글 목록 조회 - (성공)") diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java new file mode 100644 index 00000000..388c2bd1 --- /dev/null +++ b/src/test/java/com/example/spot/service/study/studymember/StudyMemberCommandServiceTest.java @@ -0,0 +1,175 @@ +package com.example.spot.service.study.studymember; + +import com.example.spot.common.api.exception.handler.StudyHandler; +import com.example.spot.member.domain.Member; +import com.example.spot.member.domain.MemberRepository; +import com.example.spot.member.domain.enums.Status; +import com.example.spot.study.application.StudyMemberCommandServiceImpl; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.StudyRepository; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.study.presentation.dto.response.StudyTerminationResponseDTO; +import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class StudyMemberCommandServiceTest { + + @InjectMocks + private StudyMemberCommandServiceImpl studyMemberCommandService; + + @Mock + private StudyRepository studyRepository; + + @Mock + private StudyMemberRepository studyMemberRepository; + + @Mock + private MemberRepository memberRepository; + + @Mock + private Study study; + + @Mock + private Member member; + + @Mock + private StudyMember studyMember; + + private ToDoListRequestDTO.ToDoListCreateDTO requestDTO; + + @BeforeEach + void init() { + requestDTO = ToDoListRequestDTO.ToDoListCreateDTO.builder() + .content("test") + .date(LocalDate.EPOCH) + .build(); + + Authentication authentication = new UsernamePasswordAuthenticationToken("1", null, Collections.emptyList()); + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); + } + + @Test + @DisplayName("스터디 종료 - (성공)") + void terminateStudy_Success() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .status(Status.ON) + .build(); + studyMember = StudyMember.builder() + .member(member) + .study(study) + .isOwned(true) + .status(StudyApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.of(studyMember)); + + // when + StudyTerminationResponseDTO.TerminationDTO result = studyMemberCommandService.terminateStudy(1L, "스터디 성과"); + + // then + assertNotNull(result); + assertThat(result.getStudyId()).isEqualTo(1L); + assertThat(result.getStudyName()).isEqualTo("스터디"); + assertThat(result.getStatus()).isEqualTo(Status.OFF); + } + + @Test + @DisplayName("스터디 종료 - 스터디장이 아닌 경우 (실패)") + void terminateStudy_NotStudyOwner_Fail() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .status(Status.ON) + .build(); + studyMember = StudyMember.builder() + .member(member) + .study(study) + .isOwned(false) + .status(StudyApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.of(studyMember)); + + // when & then + assertThrows(StudyHandler.class, () -> studyMemberCommandService.terminateStudy(1L, "스터디 성과")); + } + + @Test + @DisplayName("스터디 종료 - 진행중인 스터디가 아닌 경우 (실패)") + void terminateStudy_AlreadyTerminated_Fail() { + + // given + member = Member.builder() + .id(1L) + .name("회원1") + .build(); + study = Study.builder() + .id(1L) + .title("스터디") + .status(Status.OFF) + .build(); + studyMember = StudyMember.builder() + .member(member) + .study(study) + .isOwned(false) + .status(StudyApplicationStatus.APPROVED) + .build(); + + when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); + when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.of(studyMember)); + + // when & then + assertThrows(StudyHandler.class, () -> studyMemberCommandService.terminateStudy(1L, "스터디 성과")); + } + + +} diff --git a/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java new file mode 100644 index 00000000..9b291732 --- /dev/null +++ b/src/test/java/com/example/spot/service/study/studymember/StudyMemberQueryServiceTest.java @@ -0,0 +1,257 @@ +package com.example.spot.service.study.studymember; + +import com.example.spot.common.api.exception.GeneralException; +import com.example.spot.member.domain.Member; +import com.example.spot.schedule.domain.ScheduleRepository; +import com.example.spot.story.domain.StoryRepository; +import com.example.spot.study.application.StudyMemberQueryServiceImpl; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; +import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; +import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplyMemberDTO; +import com.example.spot.todo.domain.ToDo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class StudyMemberQueryServiceTest { + + @InjectMocks + private StudyMemberQueryServiceImpl studyMemberQueryService; + + @Mock + private StudyMemberRepository studyMemberRepository; + + @Mock + private StoryRepository storyRepository; + @Mock + private ScheduleRepository scheduleRepository; + + private static Member member; + private static Member member2; + private static Study study; + private static StudyMember studyMember; + private static StudyMember studyMember2; + private static StudyMember apply; + private static ToDo toDo; + + @BeforeEach + void setup(){ + member = Member.builder() + .id(1L) + .build(); + member2 = Member.builder() + .id(2L) + .build(); + study = Study.builder() + .build(); + studyMember = StudyMember.builder() + .introduction("title").study(study).member(member).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); + apply = StudyMember.builder() + .introduction("title").study(study).member(member).isOwned(false).status(StudyApplicationStatus.APPLIED).build(); + studyMember2 = StudyMember.builder() + .introduction("title").study(study).member(member2).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); + toDo = ToDo.builder() + .id(1L) + .build(); + + Long studyId = 1L; + + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( + Optional.ofNullable(studyMember)); + Authentication authentication = new UsernamePasswordAuthenticationToken("1", null, Collections.emptyList()); + // SecurityContext 생성 및 설정 + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); + } + + /* ------------------------------------------------ 특정 스터디 회원 목록 전체 조회 --------------------------------------------------- */ + + @Test + @DisplayName("특정 스터디 회원 목록 조회 - 성공") + void 특정_스터디_회원_목록_조회_성공() { + + // given + Long studyId = 1L; + when(studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED)).thenReturn(List.of(studyMember)); + + // when + StudyMemberResponseDTO responseDTO = studyMemberQueryService.findStudyMembers(studyId); + + // then + assertEquals(1, responseDTO.getTotalElements()); + assertEquals(1L, responseDTO.getMembers().get(0).getMemberId()); + + } + + @Test + @DisplayName("특정 스터디 회원 목록 조회 - 스터디에 멤버가 존재하지 않는 경우") + void 특정_스터디_회원_목록_조회_실패() { + + // given + Long studyId = 1L; + when(studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED)).thenReturn(List.of()); + + // when & then + assertThrows(GeneralException.class, () -> studyMemberQueryService.findStudyMembers(studyId)); + } + + + /* ------------------------------------------------ 모집중인 스터디에 신청한 회원 목록 조회 --------------------------------------------------- */ + + @Test + @DisplayName("모집중인 스터디에 신청한 회원 목록 조회 - 성공") + void 스터디_신청_회원_목록_조회_성공() { + + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 1L, true)).thenReturn( + Optional.ofNullable(studyMember)); + when(studyMemberRepository.findAllByStudyIdAndStatus(1L, StudyApplicationStatus.APPLIED)) + .thenReturn(List.of(apply)); + + // when + StudyMemberResponseDTO responseDTO = studyMemberQueryService.findStudyApplicants(1L); + + // then + assertEquals(1, responseDTO.getTotalElements()); + assertEquals(1L, responseDTO.getMembers().get(0).getMemberId()); + } + + @Test + @DisplayName("모집중인 스터디에 신청한 회원 목록 조회 - 로그인 한 회원이 해당 스터디 장이 아닌 경우") + void 스터디_신청_회원_목록_조회_실패_1() { + + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 1L, true)).thenReturn( + Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> studyMemberQueryService.findStudyApplicants(1L)); + } + + @Test + @DisplayName("모집중인 스터디에 신청한 회원 목록 조회 - 스터디 신청자가 존재하지 않는 경우") + void 스터디_신청_회원_목록_조회_실패_2() { + + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 1L, true)).thenReturn( + Optional.ofNullable(studyMember)); + when(studyMemberRepository.findAllByStudyIdAndStatus(1L, StudyApplicationStatus.APPLIED)) + .thenReturn(List.of()); + + // when & then + assertThrows(GeneralException.class, () -> studyMemberQueryService.findStudyApplicants(1L)); + } + + + /* ------------------------------------------------ 스터디 신청자 정보 조회 --------------------------------------------------- */ + + @Test + @DisplayName("스터디 신청자 정보 조회 - 성공") + void 스터디_신청자_정보_조회_성공() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( + Optional.ofNullable(studyMember)); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) + .thenReturn(Optional.ofNullable(apply)); + + // when + StudyApplyMemberDTO responseDTO = studyMemberQueryService.findStudyApplication(100L, 1L); + + // then + assertEquals(1L, responseDTO.getMemberId()); + } + + @Test + @DisplayName("스터디 신청자 정보 조회 - 로그인 한 회원이 스터디 장이 아닌 경우") + void 스터디_신청자_정보_조회_실패_1() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( + Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> studyMemberQueryService.findStudyApplication(100L, 1L)); + } + + @Test + @DisplayName("스터디 신청자 정보 조회 - 스터디 신청자가 없는 경우 ") + void 스터디_신청자_정보_조회_실패_2() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( + Optional.ofNullable(studyMember)); + + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) + .thenReturn(Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> studyMemberQueryService.findStudyApplication(100L, 1L)); + } + + @Test + @DisplayName("스터디 신청자 정보 조회 - 스터디 신청자가 스터디 장인 경우") + void 스터디_신청자_정보_조회_실패_3() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( + Optional.ofNullable(studyMember)); + + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) + .thenReturn(Optional.ofNullable(studyMember)); + + // when & then + assertThrows(GeneralException.class, () -> studyMemberQueryService.findStudyApplication(100L, 1L)); + } + + + /* ------------------------------------------------ 스터디 신청 여부 조회 --------------------------------------------------- */ + + @Test + @DisplayName("스터디 신청 여부 조회 - 성공") + void 스터디_신청_여부_조회_성공() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.empty()); + when(studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) + .thenReturn(true); + + // when + StudyApplicantDTO responseDTO = studyMemberQueryService.isApplied(100L); + + // then + assertEquals(100L, responseDTO.getStudyId()); + assertEquals(true, responseDTO.isApplied()); + } + + @Test + @DisplayName("스터디 신청 여부 조회 - 이미 가입 된 경우") + void 스터디_신청_여부_조회_실패() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.ofNullable(studyMember)); + // when & then + assertThrows(GeneralException.class, () -> studyMemberQueryService.isApplied(100L)); + } +} diff --git a/src/test/java/com/example/spot/service/study/studymember/ToDoQueryServiceTest.java b/src/test/java/com/example/spot/service/study/studymember/ToDoQueryServiceTest.java deleted file mode 100644 index e9a30bee..00000000 --- a/src/test/java/com/example/spot/service/study/studymember/ToDoQueryServiceTest.java +++ /dev/null @@ -1,495 +0,0 @@ -package com.example.spot.service.study.studymember; - -import com.example.spot.common.api.exception.GeneralException; -import com.example.spot.member.domain.Member; -import com.example.spot.story.domain.Story; -import com.example.spot.study.domain.association.StudyMember; -import com.example.spot.schedule.domain.Schedule; -import com.example.spot.todo.domain.ToDo; -import com.example.spot.study.domain.enums.StudyApplicationStatus; -import com.example.spot.story.domain.enums.StoryCategory; -import com.example.spot.study.domain.Study; -import com.example.spot.member.domain.MemberRepository; -import com.example.spot.study.domain.repository.StudyMemberRepository; -import com.example.spot.schedule.domain.ScheduleRepository; -import com.example.spot.story.domain.StoryRepository; -import com.example.spot.todo.domain.ToDoRepository; -import com.example.spot.common.security.utils.SecurityUtils; -import com.example.spot.study.application.StudyMemberQueryServiceImpl; -import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO; -import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplicantDTO; -import com.example.spot.study.presentation.dto.response.StudyMemberResponseDTO.StudyApplyMemberDTO; -import com.example.spot.story.web.dto.response.StoryResponseDTO; -import com.example.spot.schedule.presentation.dto.response.StudyScheduleResponseDTO; -import java.time.LocalDate; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.when; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class ToDoQueryServiceTest { - - @InjectMocks - private StudyMemberQueryServiceImpl memberStudyQueryService; - - @Mock - private StudyMemberRepository studyMemberRepository; - @Mock - private MemberRepository memberRepository; - @Mock - private StoryRepository storyRepository; - @Mock - private ScheduleRepository scheduleRepository; - @Mock - private ToDoRepository toDoRepository; - @Mock - private SecurityUtils securityUtils; - - private static Member member; - private static Member member2; - private static Study study; - private static StudyMember studyMember; - private static StudyMember studyMember2; - private static StudyMember apply; - private static ToDo toDo; - - @BeforeEach - void setup(){ - member = Member.builder() - .id(1L) - .build(); - member2 = Member.builder() - .id(2L) - .build(); - - - study = Study.builder() - .build(); - - studyMember = StudyMember.builder() - .introduction("title").study(study).member(member).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); - - apply = StudyMember.builder() - .introduction("title").study(study).member(member).isOwned(false).status(StudyApplicationStatus.APPLIED).build(); - studyMember2 = StudyMember.builder() - .introduction("title").study(study).member(member2).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); - toDo = ToDo.builder() - .id(1L) - .build(); - - Long studyId = 1L; - - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( - Optional.ofNullable(studyMember)); - Authentication authentication = new UsernamePasswordAuthenticationToken("1", null, Collections.emptyList()); - // SecurityContext 생성 및 설정 - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(authentication); - SecurityContextHolder.setContext(securityContext); - } - - /* ------------------------------------------------ 스터디 공지사항 조회 --------------------------------------------------- */ - - @Test - @DisplayName("스터디 공지사항 조회 - 성공") - void 스터디_공지사항_조회_성공(){ - - // given - long studyId = 1L; - String title = "공지"; - String content = "공지입니다."; - Story story = Story.builder() - .title(title) - .content(content) - .storyCategory(StoryCategory.WELCOME) - .isAnnouncement(true) - .build(); - - StudyMember studyMember = StudyMember.builder() - .introduction(title).study(study).member(member).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); - - when(storyRepository.findByStudyIdAndIsAnnouncement(studyId, true)).thenReturn(Optional.of(story)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( - Optional.ofNullable(studyMember)); - - // when - StoryResponseDTO responseDTO = memberStudyQueryService.findStudyAnnouncementPost(studyId); - - // then - assertEquals(title,responseDTO.getTitle()); - assertEquals(content, responseDTO.getContent()); - } - - @Test - @DisplayName("스터디 공지사항 조회 - 로그인 한 회원이 해당 스터디 회원이 아닌 경우") - void 스터디_공지사항_조회_실패_1(){ - - // given - long studyId = 1L; - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( - Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyAnnouncementPost(studyId)); - } - - @Test - @DisplayName("스터디 공지사항 조회 - 스터디 공지 글이 없는 경우") - void 스터디_공지사항_조회_실패_2(){ - - // given - long studyId = 1L; - StudyMember studyMember = StudyMember.builder() - .introduction("title").study(study).member(member).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); - - - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( - Optional.ofNullable(studyMember)); - when(storyRepository.findByStudyIdAndIsAnnouncement(studyId, true)).thenReturn(Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyAnnouncementPost(studyId)); - } - - /* ------------------------------------------------ 스터디 모임 목록 조회 --------------------------------------------------- */ - - @Test - @DisplayName("스터디 모임 목록 조회 - 성공") - void 스터디_모임_목록_조회_성공(){ - // given - Long studyId = 1L; - String title = "title"; - - Schedule schedule1 = Schedule.builder().id(1L).title(title).build(); - Schedule schedule2 = Schedule.builder().id(2L).title("title1").build(); - when(scheduleRepository.findAllByStudyId(studyId, Pageable.unpaged())).thenReturn(List.of(schedule1, schedule2)); - - // when - StudyScheduleResponseDTO responseDTO = memberStudyQueryService.findStudySchedule(studyId, Pageable.unpaged()); - - // then - assertEquals(2, responseDTO.getTotalElements()); - assertEquals(title, responseDTO.getSchedules().get(0).getTitle()); - } - - @Test - @DisplayName("스터디 모임 목록 조회 - 로그인 한 회원이 해당 스터디 회원이 아닌 경우") - void 스터디_모임_목록_조회_실패_1(){ - // given - long studyId = 1L; - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( - Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudySchedule(studyId, Pageable.unpaged())); - } - - @Test - @DisplayName("스터디 모임 목록 조회 - 스터디 모임 일정이 존재하지 않는 경우") - void 스터디_모임_목록_조회_실패_2(){ - // given - long studyId = 1L; - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( - Optional.of(studyMember)); - when(scheduleRepository.findAllByStudyId(studyId, Pageable.unpaged())).thenReturn(Collections.emptyList()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudySchedule(studyId, Pageable.unpaged())); - } - - - /* ------------------------------------------------ 특정 스터디 회원 목록 전체 조회 --------------------------------------------------- */ - - @Test - @DisplayName("특정 스터디 회원 목록 조회 - 성공") - void 특정_스터디_회원_목록_조회_성공() { - - // given - Long studyId = 1L; - when(studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED)).thenReturn(List.of(studyMember)); - - // when - StudyMemberResponseDTO responseDTO = memberStudyQueryService.findStudyMembers(studyId); - - // then - assertEquals(1, responseDTO.getTotalElements()); - assertEquals(1L, responseDTO.getMembers().get(0).getMemberId()); - - } - - @Test - @DisplayName("특정 스터디 회원 목록 조회 - 스터디에 멤버가 존재하지 않는 경우") - void 특정_스터디_회원_목록_조회_실패() { - - // given - Long studyId = 1L; - when(studyMemberRepository.findAllByStudyIdAndStatus(studyId, StudyApplicationStatus.APPROVED)).thenReturn(List.of()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyMembers(studyId)); - } - - - /* ------------------------------------------------ 모집중인 스터디에 신청한 회원 목록 조회 --------------------------------------------------- */ - - @Test - @DisplayName("모집중인 스터디에 신청한 회원 목록 조회 - 성공") - void 스터디_신청_회원_목록_조회_성공() { - - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 1L, true)).thenReturn( - Optional.ofNullable(studyMember)); - when(studyMemberRepository.findAllByStudyIdAndStatus(1L, StudyApplicationStatus.APPLIED)) - .thenReturn(List.of(apply)); - - // when - StudyMemberResponseDTO responseDTO = memberStudyQueryService.findStudyApplicants(1L); - - // then - assertEquals(1, responseDTO.getTotalElements()); - assertEquals(1L, responseDTO.getMembers().get(0).getMemberId()); - } - - @Test - @DisplayName("모집중인 스터디에 신청한 회원 목록 조회 - 로그인 한 회원이 해당 스터디 장이 아닌 경우") - void 스터디_신청_회원_목록_조회_실패_1() { - - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 1L, true)).thenReturn( - Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyApplicants(1L)); - } - - @Test - @DisplayName("모집중인 스터디에 신청한 회원 목록 조회 - 스터디 신청자가 존재하지 않는 경우") - void 스터디_신청_회원_목록_조회_실패_2() { - - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 1L, true)).thenReturn( - Optional.ofNullable(studyMember)); - when(studyMemberRepository.findAllByStudyIdAndStatus(1L, StudyApplicationStatus.APPLIED)) - .thenReturn(List.of()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyApplicants(1L)); - } - - - /* ------------------------------------------------ 스터디 신청자 정보 조회 --------------------------------------------------- */ - - @Test - @DisplayName("스터디 신청자 정보 조회 - 성공") - void 스터디_신청자_정보_조회_성공() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( - Optional.ofNullable(studyMember)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) - .thenReturn(Optional.ofNullable(apply)); - - // when - StudyApplyMemberDTO responseDTO = memberStudyQueryService.findStudyApplication(100L, 1L); - - // then - assertEquals(1L, responseDTO.getMemberId()); - } - - @Test - @DisplayName("스터디 신청자 정보 조회 - 로그인 한 회원이 스터디 장이 아닌 경우") - void 스터디_신청자_정보_조회_실패_1() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( - Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyApplication(100L, 1L)); - } - - @Test - @DisplayName("스터디 신청자 정보 조회 - 스터디 신청자가 없는 경우 ") - void 스터디_신청자_정보_조회_실패_2() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( - Optional.ofNullable(studyMember)); - - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) - .thenReturn(Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyApplication(100L, 1L)); - } - - @Test - @DisplayName("스터디 신청자 정보 조회 - 스터디 신청자가 스터디 장인 경우") - void 스터디_신청자_정보_조회_실패_3() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndIsOwned(1L, 100L, true)).thenReturn( - Optional.ofNullable(studyMember)); - - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) - .thenReturn(Optional.ofNullable(studyMember)); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.findStudyApplication(100L, 1L)); - } - - - /* ------------------------------------------------ 스터디 신청 여부 조회 --------------------------------------------------- */ - - @Test - @DisplayName("스터디 신청 여부 조회 - 성공") - void 스터디_신청_여부_조회_성공() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.empty()); - when(studyMemberRepository.existsByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPLIED)) - .thenReturn(true); - - // when - StudyApplicantDTO responseDTO = memberStudyQueryService.isApplied(100L); - - // then - assertEquals(100L, responseDTO.getStudyId()); - assertEquals(true, responseDTO.isApplied()); - } - - @Test - @DisplayName("스터디 신청 여부 조회 - 이미 가입 된 경우") - void 스터디_신청_여부_조회_실패() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.ofNullable(studyMember)); - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.isApplied(100L)); - } - - /* ------------------------------------------------ To-Do 조회 --------------------------------------------------- */ - - @Test - @DisplayName("To-Do 조회 - 성공") - void ToDo_조회_성공() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.ofNullable(studyMember)); - when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) - .thenReturn(List.of(toDo)); - when(toDoRepository.countByStudyIdAndMemberIdAndDate(anyLong(), anyLong(), any())) - .thenReturn(1L); - - // when - ToDoListSearchResponseDTO responseDTO = memberStudyQueryService.getToDoList(1L, LocalDate.MAX, PageRequest.of(0, 10)); - - // then - assertEquals(1, responseDTO.getTotalElements()); - assertEquals(1L, responseDTO.getContent().get(0).getId()); - } - - @Test - @DisplayName("To-Do 조회 - 로그인 한 회원이 스터디 회원이 아닌 경우") - void ToDo_조회_시_로그인_한_회원이_스터디_회원이_아닌_경우() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.getToDoList(100L, LocalDate.MAX, PageRequest.of(0, 10))); - } - - @Test - @DisplayName("To-Do 조회 - 회원의 To-Do가 존재하지 않는 경우") - void ToDo가_존재하지_않는_경우() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.ofNullable(studyMember)); - when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) - .thenReturn(List.of()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.getToDoList(100L, LocalDate.MAX, PageRequest.of(0, 10))); - } - - /* ------------------------------------------------ 다른 스터디원의 To-Do 조회 --------------------------------------------------- */ - - @Test - @DisplayName("특정 스터디 원 To-Do 조회 - 성공") - void 스터디_원_ToDo_조회_성공() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.ofNullable(studyMember)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(2L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.ofNullable(studyMember)); - when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) - .thenReturn(List.of(toDo)); - when(toDoRepository.countByStudyIdAndMemberIdAndDate(anyLong(), anyLong(), any())) - .thenReturn(1L); - - // when - ToDoListSearchResponseDTO responseDTO = memberStudyQueryService.getMemberToDoList(1L, 2L, LocalDate.MAX, PageRequest.of(0, 10)); - - // then - assertEquals(1, responseDTO.getTotalElements()); - assertEquals(1L, responseDTO.getContent().get(0).getId()); - } - - @Test - @DisplayName("특정 스터디 원 To-Do 조회 - 로그인 한 회원이 스터디 회원이 아닌 경우") - void 스터디_원_ToDo_조회_시_로그인_한_회원이_스터디_회원이_아닌_경우() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.getMemberToDoList(100L, 2L, LocalDate.MAX, PageRequest.of(0, 10))); - } - - @Test - @DisplayName("특정 스터디 원 To-Do 조회 - 조회 하려는 회원이 스터디 회원이 아닌 경우") - void 스터디_원_ToDo_조회_시_조회_하려는_회원이_스터디_회원이_아닌_경우() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.ofNullable(studyMember)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(2L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.empty()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.getMemberToDoList(100L, 2L, LocalDate.MAX, PageRequest.of(0, 10))); - } - - @Test - @DisplayName("특정 스터디 원 To-Do 조회 - 회원의 To-Do가 존재하지 않는 경우") - void 스터디_원_ToDo가_존재하지_않는_경우() { - // given - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.ofNullable(studyMember)); - when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) - .thenReturn(List.of()); - - // when & then - assertThrows(GeneralException.class, () -> memberStudyQueryService.getToDoList(100L, LocalDate.MAX, PageRequest.of(0, 10))); - } - -} diff --git a/src/test/java/com/example/spot/service/study/studymember/ToDoCommandServiceTest.java b/src/test/java/com/example/spot/service/todo/ToDoCommandServiceTest.java similarity index 50% rename from src/test/java/com/example/spot/service/study/studymember/ToDoCommandServiceTest.java rename to src/test/java/com/example/spot/service/todo/ToDoCommandServiceTest.java index 6ac0f348..ea7d9f99 100644 --- a/src/test/java/com/example/spot/service/study/studymember/ToDoCommandServiceTest.java +++ b/src/test/java/com/example/spot/service/todo/ToDoCommandServiceTest.java @@ -1,4 +1,4 @@ -package com.example.spot.service.study.studymember; +package com.example.spot.service.todo; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -10,6 +10,7 @@ import com.example.spot.common.api.exception.handler.StudyHandler; import com.example.spot.member.domain.Member; import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.todo.application.ToDoCommandServiceImpl; import com.example.spot.todo.domain.ToDo; import com.example.spot.study.domain.enums.StudyApplicationStatus; import com.example.spot.member.domain.enums.Status; @@ -18,7 +19,6 @@ import com.example.spot.study.domain.repository.StudyMemberRepository; import com.example.spot.study.domain.StudyRepository; import com.example.spot.todo.domain.ToDoRepository; -import com.example.spot.study.application.StudyMemberCommandServiceImpl; import com.example.spot.todo.presentation.dto.request.ToDoListRequestDTO.ToDoListCreateDTO; import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListCreateResponseDTO; import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListUpdateResponseDTO; @@ -48,7 +48,7 @@ public class ToDoCommandServiceTest { @InjectMocks - private StudyMemberCommandServiceImpl memberStudyCommandService; + private ToDoCommandServiceImpl toDoCommandService; @Mock private StudyRepository studyRepository; @@ -94,198 +94,6 @@ void init() { SecurityContextHolder.setContext(securityContext); } - /* ---------------------------- 진행중인 스터디 관련 메서드 ---------------------------- */ - - @Test - @DisplayName("스터디 탈퇴 - (성공)") - void withdrawFromStudy_Success() { - - // given - member = Member.builder() - .id(1L) - .name("회원1") - .build(); - study = Study.builder() - .id(1L) - .title("스터디") - .build(); - studyMember = StudyMember.builder() - .member(member) - .study(study) - .isOwned(false) - .status(StudyApplicationStatus.APPROVED) - .build(); - - when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); - when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.of(studyMember)); - - // when - StudyWithdrawalResponseDTO.WithdrawalDTO result = memberStudyCommandService.withdrawFromStudy(1L); - - // then - assertNotNull(result); - assertThat(result.getStudyId()).isEqualTo(1L); - assertThat(result.getStudyName()).isEqualTo("스터디"); - assertThat(result.getMemberId()).isEqualTo(1L); - assertThat(result.getMemberName()).isEqualTo("회원1"); - } - - @Test - @DisplayName("스터디 탈퇴 - 스터디 회원이 아닌 경우 (실패)") - void withdrawFromStudy_NotStudyMember_Fail() { - - // given - member = Member.builder() - .id(1L) - .name("회원1") - .build(); - study = Study.builder() - .id(1L) - .title("스터디") - .build(); - studyMember = StudyMember.builder() - .member(member) - .study(study) - .isOwned(false) - .status(StudyApplicationStatus.APPLIED) - .build(); - - when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); - when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.empty()); - - // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.withdrawFromStudy(1L)); - } - - @Test - @DisplayName("스터디 탈퇴 - 스터디장인 경우 (실패)") - void withdrawFromStudy_StudyOwner_Fail() { - - // given - member = Member.builder() - .id(1L) - .name("회원1") - .build(); - study = Study.builder() - .id(1L) - .title("스터디") - .build(); - studyMember = StudyMember.builder() - .member(member) - .study(study) - .isOwned(true) - .status(StudyApplicationStatus.APPROVED) - .build(); - - when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); - when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.of(studyMember)); - - // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.withdrawFromStudy(1L)); - } - - @Test - @DisplayName("스터디 종료 - (성공)") - void terminateStudy_Success() { - - // given - member = Member.builder() - .id(1L) - .name("회원1") - .build(); - study = Study.builder() - .id(1L) - .title("스터디") - .status(Status.ON) - .build(); - studyMember = StudyMember.builder() - .member(member) - .study(study) - .isOwned(true) - .status(StudyApplicationStatus.APPROVED) - .build(); - - when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); - when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.of(studyMember)); - - // when - StudyTerminationResponseDTO.TerminationDTO result = memberStudyCommandService.terminateStudy(1L, "스터디 성과"); - - // then - assertNotNull(result); - assertThat(result.getStudyId()).isEqualTo(1L); - assertThat(result.getStudyName()).isEqualTo("스터디"); - assertThat(result.getStatus()).isEqualTo(Status.OFF); - } - - @Test - @DisplayName("스터디 종료 - 스터디장이 아닌 경우 (실패)") - void terminateStudy_NotStudyOwner_Fail() { - - // given - member = Member.builder() - .id(1L) - .name("회원1") - .build(); - study = Study.builder() - .id(1L) - .title("스터디") - .status(Status.ON) - .build(); - studyMember = StudyMember.builder() - .member(member) - .study(study) - .isOwned(false) - .status(StudyApplicationStatus.APPROVED) - .build(); - - when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); - when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.of(studyMember)); - - // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.terminateStudy(1L, "스터디 성과")); - } - - @Test - @DisplayName("스터디 종료 - 진행중인 스터디가 아닌 경우 (실패)") - void terminateStudy_AlreadyTerminated_Fail() { - - // given - member = Member.builder() - .id(1L) - .name("회원1") - .build(); - study = Study.builder() - .id(1L) - .title("스터디") - .status(Status.OFF) - .build(); - studyMember = StudyMember.builder() - .member(member) - .study(study) - .isOwned(false) - .status(StudyApplicationStatus.APPROVED) - .build(); - - when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); - when(studyRepository.findById(1L)).thenReturn(Optional.of(study)); - when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) - .thenReturn(Optional.of(studyMember)); - - // when & then - assertThrows(StudyHandler.class, () -> memberStudyCommandService.terminateStudy(1L, "스터디 성과")); - } - /* ---------------------------- To-Do 생성 관련 메서드 ---------------------------- */ @@ -301,7 +109,7 @@ void createToDoList() { when(toDoRepository.save(any())).thenReturn(toDo); // when - ToDoListCreateResponseDTO responseDTO = memberStudyCommandService.createToDoList(1L, requestDTO); + ToDoListCreateResponseDTO responseDTO = toDoCommandService.createToDoList(1L, requestDTO); // then assertEquals(responseDTO.getContent(), requestDTO.getContent()); @@ -318,7 +126,7 @@ void createToDoList() { // when & then assertThrows(StudyHandler.class, () -> { - memberStudyCommandService.createToDoList(1L, requestDTO); + toDoCommandService.createToDoList(1L, requestDTO); }); } @@ -331,7 +139,7 @@ void createToDoList() { when(toDoRepository.findById(anyLong())).thenReturn(Optional.ofNullable(toDo)); // when - ToDoListUpdateResponseDTO responseDTO = memberStudyCommandService.updateToDoList(1L,1L, requestDTO); + ToDoListUpdateResponseDTO responseDTO = toDoCommandService.updateToDoList(1L,1L, requestDTO); // then assertEquals(false, responseDTO.isDone()); @@ -346,7 +154,7 @@ void createToDoList() { // when & then assertThrows(StudyHandler.class, () -> { - memberStudyCommandService.updateToDoList(1L,1L, requestDTO); + toDoCommandService.updateToDoList(1L,1L, requestDTO); }); } @@ -360,7 +168,7 @@ void createToDoList() { // when & then assertThrows(StudyHandler.class, () -> { - memberStudyCommandService.updateToDoList(1L,1L, requestDTO); + toDoCommandService.updateToDoList(1L,1L, requestDTO); }); } @@ -374,7 +182,7 @@ void createToDoList() { // when & then assertThrows(StudyHandler.class, () -> { - memberStudyCommandService.updateToDoList(1L,1L, requestDTO); + toDoCommandService.updateToDoList(1L,1L, requestDTO); }); } @@ -389,7 +197,7 @@ void createToDoList() { Optional.ofNullable(studyMember)); // when - ToDoListUpdateResponseDTO responseDTO = memberStudyCommandService.deleteToDoList(1L, 1L); + ToDoListUpdateResponseDTO responseDTO = toDoCommandService.deleteToDoList(1L, 1L); // then verify(toDoRepository, times(1)).deleteById(1L); @@ -403,7 +211,7 @@ void createToDoList() { // when & then assertThrows(StudyHandler.class, () -> { - memberStudyCommandService.deleteToDoList(1L, 1L); + toDoCommandService.deleteToDoList(1L, 1L); }); } @@ -417,7 +225,7 @@ void createToDoList() { // when & then assertThrows(StudyHandler.class, () -> { - memberStudyCommandService.deleteToDoList(1L, 1L); + toDoCommandService.deleteToDoList(1L, 1L); }); } @@ -431,7 +239,7 @@ void createToDoList() { // when & then assertThrows(StudyHandler.class, () -> { - memberStudyCommandService.deleteToDoList(1L, 1L); + toDoCommandService.deleteToDoList(1L, 1L); }); } diff --git a/src/test/java/com/example/spot/service/todo/ToDoQueryServiceTest.java b/src/test/java/com/example/spot/service/todo/ToDoQueryServiceTest.java new file mode 100644 index 00000000..4aec7331 --- /dev/null +++ b/src/test/java/com/example/spot/service/todo/ToDoQueryServiceTest.java @@ -0,0 +1,198 @@ +package com.example.spot.service.todo; + +import com.example.spot.common.api.exception.GeneralException; +import com.example.spot.member.domain.Member; +import com.example.spot.study.domain.association.StudyMember; +import com.example.spot.todo.application.ToDoQueryServiceImpl; +import com.example.spot.todo.domain.ToDo; +import com.example.spot.study.domain.enums.StudyApplicationStatus; +import com.example.spot.study.domain.Study; +import com.example.spot.study.domain.repository.StudyMemberRepository; +import com.example.spot.todo.domain.ToDoRepository; +import com.example.spot.todo.presentation.dto.response.ToDoListResponseDTO.ToDoListSearchResponseDTO; +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.when; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ToDoQueryServiceTest { + + @InjectMocks + private ToDoQueryServiceImpl toDoQueryService; + + @Mock + private StudyMemberRepository studyMemberRepository; + @Mock + private ToDoRepository toDoRepository; + + private static Member member; + private static Member member2; + private static Study study; + private static StudyMember studyMember; + private static StudyMember studyMember2; + private static StudyMember apply; + private static ToDo toDo; + + @BeforeEach + void setup(){ + member = Member.builder() + .id(1L) + .build(); + member2 = Member.builder() + .id(2L) + .build(); + + + study = Study.builder() + .build(); + + studyMember = StudyMember.builder() + .introduction("title").study(study).member(member).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); + + apply = StudyMember.builder() + .introduction("title").study(study).member(member).isOwned(false).status(StudyApplicationStatus.APPLIED).build(); + studyMember2 = StudyMember.builder() + .introduction("title").study(study).member(member2).isOwned(true).status(StudyApplicationStatus.APPROVED).build(); + toDo = ToDo.builder() + .id(1L) + .build(); + + Long studyId = 1L; + + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, studyId, StudyApplicationStatus.APPROVED)).thenReturn( + Optional.ofNullable(studyMember)); + Authentication authentication = new UsernamePasswordAuthenticationToken("1", null, Collections.emptyList()); + // SecurityContext 생성 및 설정 + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + SecurityContextHolder.setContext(securityContext); + } + +/* ------------------------------------------------ To-Do 조회 --------------------------------------------------- */ + + @Test + @DisplayName("To-Do 조회 - 성공") + void ToDo_조회_성공() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.ofNullable(studyMember)); + when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) + .thenReturn(List.of(toDo)); + when(toDoRepository.countByStudyIdAndMemberIdAndDate(anyLong(), anyLong(), any())) + .thenReturn(1L); + + // when + ToDoListSearchResponseDTO responseDTO = toDoQueryService.getToDoList(1L, LocalDate.MAX, PageRequest.of(0, 10)); + + // then + assertEquals(1, responseDTO.getTotalElements()); + assertEquals(1L, responseDTO.getContent().get(0).getId()); + } + + @Test + @DisplayName("To-Do 조회 - 로그인 한 회원이 스터디 회원이 아닌 경우") + void ToDo_조회_시_로그인_한_회원이_스터디_회원이_아닌_경우() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> toDoQueryService.getToDoList(100L, LocalDate.MAX, PageRequest.of(0, 10))); + } + + @Test + @DisplayName("To-Do 조회 - 회원의 To-Do가 존재하지 않는 경우") + void ToDo가_존재하지_않는_경우() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.ofNullable(studyMember)); + when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) + .thenReturn(List.of()); + + // when & then + assertThrows(GeneralException.class, () -> toDoQueryService.getToDoList(100L, LocalDate.MAX, PageRequest.of(0, 10))); + } + + /* ------------------------------------------------ 다른 스터디원의 To-Do 조회 --------------------------------------------------- */ + + @Test + @DisplayName("특정 스터디 원 To-Do 조회 - 성공") + void 스터디_원_ToDo_조회_성공() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 1L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.ofNullable(studyMember)); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(2L, 1L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.ofNullable(studyMember)); + when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) + .thenReturn(List.of(toDo)); + when(toDoRepository.countByStudyIdAndMemberIdAndDate(anyLong(), anyLong(), any())) + .thenReturn(1L); + + // when + ToDoListSearchResponseDTO responseDTO = toDoQueryService.getMemberToDoList(1L, 2L, LocalDate.MAX, PageRequest.of(0, 10)); + + // then + assertEquals(1, responseDTO.getTotalElements()); + assertEquals(1L, responseDTO.getContent().get(0).getId()); + } + + @Test + @DisplayName("특정 스터디 원 To-Do 조회 - 로그인 한 회원이 스터디 회원이 아닌 경우") + void 스터디_원_ToDo_조회_시_로그인_한_회원이_스터디_회원이_아닌_경우() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> toDoQueryService.getMemberToDoList(100L, 2L, LocalDate.MAX, PageRequest.of(0, 10))); + } + + @Test + @DisplayName("특정 스터디 원 To-Do 조회 - 조회 하려는 회원이 스터디 회원이 아닌 경우") + void 스터디_원_ToDo_조회_시_조회_하려는_회원이_스터디_회원이_아닌_경우() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.ofNullable(studyMember)); + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(2L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.empty()); + + // when & then + assertThrows(GeneralException.class, () -> toDoQueryService.getMemberToDoList(100L, 2L, LocalDate.MAX, PageRequest.of(0, 10))); + } + + @Test + @DisplayName("특정 스터디 원 To-Do 조회 - 회원의 To-Do가 존재하지 않는 경우") + void 스터디_원_ToDo가_존재하지_않는_경우() { + // given + when(studyMemberRepository.findByMemberIdAndStudyIdAndStatus(1L, 100L, StudyApplicationStatus.APPROVED)) + .thenReturn(Optional.ofNullable(studyMember)); + when(toDoRepository.findByStudyIdAndMemberIdAndDateOrderByCreatedAtDesc(anyLong(), anyLong(), any(), any())) + .thenReturn(List.of()); + + // when & then + assertThrows(GeneralException.class, () -> toDoQueryService.getToDoList(100L, LocalDate.MAX, PageRequest.of(0, 10))); + } + +}