From 82d70995477c08d023f563132b075a05378a3417 Mon Sep 17 00:00:00 2001 From: pparkjs Date: Sun, 6 Apr 2025 16:55:55 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[feat]=20=ED=81=AC=EB=A3=A8=20=EC=9E=90?= =?UTF-8?q?=EA=B8=B0=EC=86=8C=EA=B0=9C=20=EA=B4=80=EB=A0=A8=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewServiceTest.java | 152 ++++++++++++++++-- .../com/retrip/crew/common/ServiceTest.java | 15 +- .../crew/common/config/QuerydslConfig.java | 6 + 3 files changed, 160 insertions(+), 13 deletions(-) diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java index 42c5e2a..28f752b 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,24 +1,23 @@ package com.retrip.crew.application.in; -import com.retrip.crew.application.in.request.crew.CrewCreateRequest; -import com.retrip.crew.application.in.request.crew.CrewOrder; -import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; -import com.retrip.crew.application.in.request.demand.CreateDemandRequest; -import com.retrip.crew.application.in.request.demand.DemandOrder; -import com.retrip.crew.application.in.response.crew.CrewCreateResponse; -import com.retrip.crew.application.in.response.crew.CrewDetailResponse; -import com.retrip.crew.application.in.response.crew.CrewListResponse; -import com.retrip.crew.application.in.response.crew.CrewUpdateResponse; -import com.retrip.crew.application.in.response.demand.CreateDemandResponse; -import com.retrip.crew.application.in.response.demand.CrewsOfDemandResponse; -import com.retrip.crew.application.in.response.demand.DemandsResponse; +import com.retrip.crew.application.in.request.CreateDemandRequest; +import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.CrewOrder; +import com.retrip.crew.application.in.request.CrewUpdateRequest; +import com.retrip.crew.application.in.response.*; import com.retrip.crew.common.ServiceTest; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMemberRole; import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.entity.Introduction; +import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.exception.common.InvalidAccessException; import com.retrip.crew.domain.exception.DuplicateDemandException; import com.retrip.crew.domain.vo.DemandStatus; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -222,4 +221,133 @@ class CrewServiceTest extends ServiceTest { () -> assertThat(response.members().getFirst().memberId()).isEqualTo(MEMBER_ID) ); } + + @Test + void 자기소개를_등록한다() { + // given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + IntroductionCreateRequest request = new IntroductionCreateRequest(MEMBER_ID, "정수의 자기소개!", "안녕하세요!"); + + // when + IntroductionCreateResponse response = crewService.createIntroduction(crew.getId(), request); + + // then + assertThat(response.crewId()).isNotNull(); + } + + @Test + void 내가_작성한_자기소개를_수정할_수_있다() { + // given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + + Introduction introduction = introductionRepository.save(Introduction.create( + MEMBER_ID, + "정수의 자기소개!", + "안녕하세요!", + crew + )); + IntroductionUpdateRequest request = new IntroductionUpdateRequest(MEMBER_ID, "변경된 자기소개!", "안녕!"); + + // when + crewService.updateIntroduction(crew.getId(), introduction.getId(), request); + Introduction result = crewService.findIntroductionByIdAndCrewId(introduction.getId(), crew.getId()); + + // then + assertThat(result.getTitle()).isEqualTo("변경된 자기소개!"); + assertThat(result.getContent()).isEqualTo("안녕!"); + } + + @Test + void 일반_멤버는_다른_멤버의_자기소개를_수정할_수_없다() { + // given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + + Introduction introduction = introductionRepository.save(Introduction.create( + MEMBER_ID, + "정수의 자기소개!", + "안녕하세요!", + crew + )); + + UUID otherMemberId = UUID.randomUUID(); + IntroductionUpdateRequest request = new IntroductionUpdateRequest(otherMemberId, "수정된 소개", "안"); + + // when & then + assertThrows(InvalidAccessException.class, () -> crewService.updateIntroduction(crew.getId(), introduction.getId(), request)); + } + + @Test + void 리더가_자기소개를_삭제할_수_있다() { + // given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + + Introduction introduction = introductionRepository.save(Introduction.create( + MEMBER_ID, + "정수의 자기소개!", + "안녕하세요!", + crew + )); + + entityManager.flush(); + entityManager.clear(); + + IntroductionDeleteRequest request = new IntroductionDeleteRequest(MEMBER_ID); + + // when + crewService.deleteIntroduction(crew.getId(), introduction.getId(), request); + + entityManager.flush(); + entityManager.clear(); + + Optional introductionOptional = introductionRepository.findById(introduction.getId()); + + // then + assertThat(introductionOptional.isEmpty()).isTrue(); + } + + @Test + void 자기소개를_조회한다() { + // given + Crew crew = crewRepository.save(Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + MEMBER_ID + )); + + Introduction introduction = introductionRepository.save(Introduction.create( + MEMBER_ID, + "정수의 자기소개!", + "안녕하세요!", + crew + )); + + // when + IntroductionDetailResponse response = crewService.getIntroduction(crew.getId(), introduction.getId()); + + // then + assertThat(response.introductionId()).isEqualTo(introduction.getId()); + assertThat(response.content()).isEqualTo("안녕하세요!"); + assertThat(response.title()).isEqualTo("정수의 자기소개!"); + } } diff --git a/src/test/java/com/retrip/crew/common/ServiceTest.java b/src/test/java/com/retrip/crew/common/ServiceTest.java index cce29ed..0a7d9b3 100644 --- a/src/test/java/com/retrip/crew/common/ServiceTest.java +++ b/src/test/java/com/retrip/crew/common/ServiceTest.java @@ -5,11 +5,15 @@ import com.retrip.crew.application.out.repository.CrewMemberRepository; import com.retrip.crew.application.out.repository.CrewQueryRepository; import com.retrip.crew.application.out.repository.CrewRepository; +import com.retrip.crew.application.out.repository.IntroductionQueryRepository; +import com.retrip.crew.application.out.repository.IntroductionRepository; import com.retrip.crew.common.config.QuerydslConfig; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; import org.springframework.context.annotation.Import; @DataJpaTest @@ -29,10 +33,19 @@ public class ServiceTest { @Autowired protected CrewDemandRepository demandRepository; + @Autowired + protected IntroductionRepository introductionRepository; + + @Autowired + protected IntroductionQueryRepository introductionQueryRepository; + + @Autowired + protected TestEntityManager entityManager; + protected CrewService crewService; @BeforeEach void setUp() { - crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository, demandRepository); + crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository, demandRepository, introductionRepository, introductionQueryRepository); } } diff --git a/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java b/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java index 7331886..d5c1b22 100644 --- a/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java +++ b/src/test/java/com/retrip/crew/common/config/QuerydslConfig.java @@ -2,6 +2,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.retrip.crew.infra.adapter.out.persistence.mysql.query.CrewQuerydslRepository; +import com.retrip.crew.infra.adapter.out.persistence.mysql.query.IntroductionQuerydslRepository; import jakarta.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; @@ -24,4 +25,9 @@ public JPAQueryFactory jpaQueryFactory() { public CrewQuerydslRepository crewQuerydslRepository(JPAQueryFactory jpaQueryFactory) { return new CrewQuerydslRepository(jpaQueryFactory); } + + @Bean + public IntroductionQuerydslRepository introductionQuerydslRepository(JPAQueryFactory jpaQueryFactory) { + return new IntroductionQuerydslRepository(jpaQueryFactory); + } } From f201b16ae1dc23ef6edf55af6dc384a4d78cccce Mon Sep 17 00:00:00 2001 From: pparkjs Date: Sun, 6 Apr 2025 16:57:27 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[feat]=20=ED=81=AC=EB=A3=A8=20=EC=9E=90?= =?UTF-8?q?=EA=B8=B0=EC=86=8C=EA=B0=9C=20=EA=B4=80=EB=A0=A8=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 82 +++++++++++++++-- .../in/request/IntroductionCreateRequest.java | 21 +++++ .../in/request/IntroductionDeleteRequest.java | 12 +++ .../in/request/IntroductionUpdateRequest.java | 16 ++++ .../response/IntroductionCreateResponse.java | 27 ++++++ .../response/IntroductionDetailResponse.java | 32 +++++++ .../in/response/IntroductionListResponse.java | 21 +++++ .../response/IntroductionUpdateResponse.java | 27 ++++++ .../in/usecase/ManageCrewUseCase.java | 1 + .../in/usecase/ManageIntroductionUseCase.java | 24 +++++ .../IntroductionQueryRepository.java | 13 +++ .../repository/IntroductionRepository.java | 11 +++ .../com/retrip/crew/domain/entity/Crew.java | 13 ++- .../crew/domain/entity/Introduction.java | 66 +++++++++++++- .../crew/domain/entity/Introductions.java | 12 ++- .../IntroductionNotFoundException.java | 12 +++ .../domain/exception/common/ErrorCode.java | 4 +- .../common/InvalidAccessException.java | 21 +++++ .../in/presentation/rest/CrewController.java | 90 ++++++++++++++++++- .../query/IntroductionQuerydslRepository.java | 51 +++++++++++ 20 files changed, 536 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/request/IntroductionCreateRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/request/IntroductionDeleteRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/request/IntroductionUpdateRequest.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/IntroductionDetailResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/IntroductionListResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/IntroductionUpdateResponse.java create mode 100644 src/main/java/com/retrip/crew/application/in/usecase/ManageIntroductionUseCase.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/IntroductionQueryRepository.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/IntroductionRepository.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/IntroductionNotFoundException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/common/InvalidAccessException.java create mode 100644 src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/IntroductionQuerydslRepository.java diff --git a/src/main/java/com/retrip/crew/application/in/CrewService.java b/src/main/java/com/retrip/crew/application/in/CrewService.java index 33bae4b..f826b26 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -1,5 +1,13 @@ package com.retrip.crew.application.in; +import com.retrip.crew.application.in.request.CreateDemandRequest; +import com.retrip.crew.application.in.request.IntroductionCreateRequest; +import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.CrewOrder; +import com.retrip.crew.application.in.request.CrewUpdateRequest; +import com.retrip.crew.application.in.request.IntroductionDeleteRequest; +import com.retrip.crew.application.in.request.IntroductionUpdateRequest; +import com.retrip.crew.application.in.response.*; import com.retrip.crew.application.in.request.crew.CrewCreateRequest; import com.retrip.crew.application.in.request.crew.CrewOrder; import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; @@ -13,20 +21,27 @@ import com.retrip.crew.application.in.usecase.GetCrewUseCase; import com.retrip.crew.application.in.usecase.ManageCrewUseCase; import com.retrip.crew.application.in.usecase.ManageDemandUseCase; +import com.retrip.crew.application.in.usecase.ManageIntroductionUseCase; import com.retrip.crew.application.in.usecase.UpdateRecruitmentUseCase; import com.retrip.crew.application.out.repository.CrewDemandRepository; import com.retrip.crew.application.out.repository.CrewMemberRepository; import com.retrip.crew.application.out.repository.CrewQueryRepository; import com.retrip.crew.application.out.repository.CrewRepository; +import com.retrip.crew.application.out.repository.IntroductionQueryRepository; +import com.retrip.crew.application.out.repository.IntroductionRepository; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.entity.Introduction; import com.retrip.crew.domain.entity.Recruitment; import com.retrip.crew.domain.exception.CrewNotFoundException; +import com.retrip.crew.domain.exception.IntroductionNotFoundException; import com.retrip.crew.domain.exception.NotCrewLeaderException; import com.retrip.crew.domain.exception.common.BusinessException; import com.retrip.crew.domain.exception.common.EntityNotFoundException; import com.retrip.crew.domain.vo.CrewDescription; import com.retrip.crew.domain.vo.CrewTitle; +import com.retrip.crew.domain.vo.IntroductionContent; +import com.retrip.crew.domain.vo.IntroductionTitle; import com.retrip.crew.domain.vo.DemandStatus; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import com.retrip.crew.infra.util.PaginationUtils; @@ -42,11 +57,14 @@ @Service @Transactional @RequiredArgsConstructor -public class CrewService implements ManageCrewUseCase, UpdateRecruitmentUseCase, ManageDemandUseCase, GetCrewUseCase { +public class CrewService implements ManageCrewUseCase, UpdateRecruitmentUseCase, ManageDemandUseCase, GetCrewUseCase, ManageIntroductionUseCase{ + private final CrewRepository crewRepository; private final CrewMemberRepository crewMemberRepository; private final CrewQueryRepository crewQueryRepository; private final CrewDemandRepository demandRepository; + private final IntroductionRepository introductionRepository; + private final IntroductionQueryRepository introductionQueryRepository; @Override public CrewCreateResponse createCrew(CrewCreateRequest request) { @@ -56,7 +74,7 @@ public CrewCreateResponse createCrew(CrewCreateRequest request) { @Override public CrewUpdateResponse updateCrew(UUID crewId, CrewUpdateRequest request) { - Crew crew = findById(crewId); + Crew crew = findCrewById(crewId); CrewTitle title = new CrewTitle(request.title()); CrewDescription description = new CrewDescription(request.description()); Recruitment recruitment = crew.getRecruitment(); @@ -68,21 +86,21 @@ public CrewUpdateResponse updateCrew(UUID crewId, CrewUpdateRequest request) { @Override public ChangeRecruitmentStatusResponse startRecruitment(UUID crewId) { - Crew crew = findById(crewId); + Crew crew = findCrewById(crewId); crew.startRecruitment(); return ChangeRecruitmentStatusResponse.of(crew); } @Override public ChangeRecruitmentStatusResponse stopRecruitment(UUID crewId) { - Crew crew = findById(crewId); + Crew crew = findCrewById(crewId); crew.stopRecruitment(); return ChangeRecruitmentStatusResponse.of(crew); } @Override public CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest request) { - Crew crew = findById(crewId); + Crew crew = findCrewById(crewId); Demand demand = crew.demand(request.memberId()); return CreateDemandResponse.of(crew.getId(), demand); } @@ -152,13 +170,63 @@ public ScrollPageResponse getCrews(Pageable pageable, String k @Override @Transactional(readOnly = true) public CrewDetailResponse getCrewDetail(UUID crewId) { - Crew crew = findById(crewId); + Crew crew = findCrewById(crewId); int memberCount = crewMemberRepository.countByCrewId(crewId); return CrewDetailResponse.of(crew, memberCount); } - private Crew findById(UUID crewId){ + @Override + public IntroductionCreateResponse createIntroduction(UUID crewId, IntroductionCreateRequest request) { + Crew crew = findCrewById(crewId); + Introduction introduction = request.to(crew); + crew.addIntroduction(introduction); + return IntroductionCreateResponse.from(introduction); + } + + @Override + public IntroductionUpdateResponse updateIntroduction(UUID crewId, UUID introductionId, IntroductionUpdateRequest request) { + Introduction introduction = findIntroductionByIdAndCrewId(introductionId, crewId); + introduction.update( + new IntroductionTitle(request.title()), + new IntroductionContent(request.content()), + request.loginMemberId() + ); + return IntroductionUpdateResponse.from(introduction); + } + + @Override + public void deleteIntroduction(UUID crewId, UUID introductionId, IntroductionDeleteRequest request) { + Crew crew = findCrewById(crewId); + Introduction introduction = findIntroductionById(introductionId); + introduction.delete(request.loginMemberId(), crew); + } + + @Override + public IntroductionDetailResponse getIntroduction(UUID crewId, UUID introductionId) { + Introduction introduction = findIntroductionByIdAndCrewId(introductionId, crewId); + return IntroductionDetailResponse.of(introduction); + } + + @Override + public ScrollPageResponse getIntroductions(UUID crewId, Pageable pageable) { + findCrewById(crewId); + Slice result = introductionQueryRepository.getIntroductions(crewId, pageable); + Long totalCount = introductionQueryRepository.getIntroductionCount(crewId); + return ScrollPageResponse.of(totalCount, result.hasNext(), result.getContent()); + } + + public Crew findCrewById(UUID crewId){ return crewRepository.findById(crewId) .orElseThrow(CrewNotFoundException::new); } + + public Introduction findIntroductionById(UUID introductionId){ + return introductionRepository.findById(introductionId) + .orElseThrow(IntroductionNotFoundException::new); + } + + public Introduction findIntroductionByIdAndCrewId(UUID introductionId, UUID crewId) { + return introductionRepository.findByIdAndCrewId(introductionId, crewId) + .orElseThrow(IntroductionNotFoundException::new); + } } diff --git a/src/main/java/com/retrip/crew/application/in/request/IntroductionCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/IntroductionCreateRequest.java new file mode 100644 index 0000000..2df92e5 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/IntroductionCreateRequest.java @@ -0,0 +1,21 @@ +package com.retrip.crew.application.in.request; + +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.Introduction; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema(description = "크루 자기소개 등록 Request") +public record IntroductionCreateRequest( + + @Schema(description = "로그인한 사용자 - 임시 방편") + UUID loginMemberId, + @Schema(description = "자기소개 제목") + String title, + @Schema(description = "자기소개 본문") + String content +) { + public Introduction to(Crew crew){ + return Introduction.create(this.loginMemberId, this.title, this.content, crew); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/request/IntroductionDeleteRequest.java b/src/main/java/com/retrip/crew/application/in/request/IntroductionDeleteRequest.java new file mode 100644 index 0000000..30937f9 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/IntroductionDeleteRequest.java @@ -0,0 +1,12 @@ +package com.retrip.crew.application.in.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema(description = "크루 자기소개 삭제 Request") +public record IntroductionDeleteRequest( + + @Schema(description = "로그인한 사용자 - 임시 방편") + UUID loginMemberId +) { +} diff --git a/src/main/java/com/retrip/crew/application/in/request/IntroductionUpdateRequest.java b/src/main/java/com/retrip/crew/application/in/request/IntroductionUpdateRequest.java new file mode 100644 index 0000000..e374a44 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/IntroductionUpdateRequest.java @@ -0,0 +1,16 @@ +package com.retrip.crew.application.in.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema(description = "크루 자기소개 수정 Request") +public record IntroductionUpdateRequest( + + @Schema(description = "로그인한 사용자 - 임시 방편") + UUID loginMemberId, + @Schema(description = "자기소개 제목") + String title, + @Schema(description = "자기소개 본문") + String content +) { +} diff --git a/src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java new file mode 100644 index 0000000..ba0eb16 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java @@ -0,0 +1,27 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Introduction; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema(description = "크루 자기소개 등록 Response") +public record IntroductionCreateResponse( + + @Schema(description = "크루 ID") + UUID crewId, + @Schema(description = "자기소개 ID") + UUID introductionId, + @Schema(description = "자기소개 제목") + String title, + @Schema(description = "자기소개 본문") + String content +) { + public static IntroductionCreateResponse from(Introduction introduction){ + return new IntroductionCreateResponse( + introduction.getCrew().getId(), + introduction.getId(), + introduction.getTitle(), + introduction.getContent() + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/IntroductionDetailResponse.java b/src/main/java/com/retrip/crew/application/in/response/IntroductionDetailResponse.java new file mode 100644 index 0000000..1f7a951 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/IntroductionDetailResponse.java @@ -0,0 +1,32 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Introduction; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; +import lombok.Builder; + +@Builder +@Schema(description = "크루 자기소개 상세 Response") +public record IntroductionDetailResponse( + + @Schema(description = "크루 ID") + UUID crewId, + @Schema(description = "자기소개 ID") + UUID introductionId, + @Schema(description = "자기소개 등록한 사용자 ID") + UUID memberId, + @Schema(description = "자기소개 제목") + String title, + @Schema(description = "자기소개 본문") + String content +) { + public static IntroductionDetailResponse of(Introduction introduction){ + return IntroductionDetailResponse.builder() + .crewId(introduction.getCrew().getId()) + .introductionId(introduction.getId()) + .memberId(introduction.getMemberId()) + .title(introduction.getTitle()) + .content(introduction.getContent()) + .build(); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/IntroductionListResponse.java b/src/main/java/com/retrip/crew/application/in/response/IntroductionListResponse.java new file mode 100644 index 0000000..f625f4e --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/IntroductionListResponse.java @@ -0,0 +1,21 @@ +package com.retrip.crew.application.in.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import java.util.UUID; + +@Schema(description = "크루 자기소개 리스트 Response") +public record IntroductionListResponse( + + @Schema(description = "크루 ID") + UUID crewId, + @Schema(description = "자기소개 ID") + UUID introductionId, + @Schema(description = "자기소개 제목") + String title, + @Schema(description = "자기소개 등록일자") + LocalDateTime createdAt, + @Schema(description = "자기소개 수정일자") + LocalDateTime editedAt +) { +} diff --git a/src/main/java/com/retrip/crew/application/in/response/IntroductionUpdateResponse.java b/src/main/java/com/retrip/crew/application/in/response/IntroductionUpdateResponse.java new file mode 100644 index 0000000..c3f92c4 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/IntroductionUpdateResponse.java @@ -0,0 +1,27 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Introduction; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; + +@Schema(description = "크루 자기소개 수정 Response") +public record IntroductionUpdateResponse( + + @Schema(description = "크루 ID") + UUID crewId, + @Schema(description = "자기소개 ID") + UUID introductionId, + @Schema(description = "자기소개 제목") + String title, + @Schema(description = "자기소개 본문") + String content +) { + public static IntroductionUpdateResponse from(Introduction introduction){ + return new IntroductionUpdateResponse( + introduction.getCrew().getId(), + introduction.getId(), + introduction.getTitle(), + introduction.getContent() + ); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java index 61df32d..6131c7f 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageCrewUseCase.java @@ -11,4 +11,5 @@ public interface ManageCrewUseCase { CrewCreateResponse createCrew(CrewCreateRequest request); CrewUpdateResponse updateCrew(UUID crewId, CrewUpdateRequest request); + } diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageIntroductionUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageIntroductionUseCase.java new file mode 100644 index 0000000..6f0c4db --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageIntroductionUseCase.java @@ -0,0 +1,24 @@ +package com.retrip.crew.application.in.usecase; + +import com.retrip.crew.application.in.request.IntroductionCreateRequest; +import com.retrip.crew.application.in.request.IntroductionDeleteRequest; +import com.retrip.crew.application.in.request.IntroductionUpdateRequest; +import com.retrip.crew.application.in.response.IntroductionCreateResponse; +import com.retrip.crew.application.in.response.IntroductionDetailResponse; +import com.retrip.crew.application.in.response.IntroductionListResponse; +import com.retrip.crew.application.in.response.IntroductionUpdateResponse; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; +import java.util.UUID; +import org.springframework.data.domain.Pageable; + +public interface ManageIntroductionUseCase { + IntroductionCreateResponse createIntroduction(UUID crewId, IntroductionCreateRequest request); + + IntroductionUpdateResponse updateIntroduction(UUID crewId, UUID introductionId, IntroductionUpdateRequest request); + + void deleteIntroduction(UUID crewId, UUID introductionId, IntroductionDeleteRequest request); + + IntroductionDetailResponse getIntroduction(UUID crewId, UUID introductionId); + + ScrollPageResponse getIntroductions(UUID crewId, Pageable pageable); +} diff --git a/src/main/java/com/retrip/crew/application/out/repository/IntroductionQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/IntroductionQueryRepository.java new file mode 100644 index 0000000..c7f5d36 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/IntroductionQueryRepository.java @@ -0,0 +1,13 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.application.in.response.IntroductionListResponse; +import java.util.UUID; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; + +public interface IntroductionQueryRepository { + + Slice getIntroductions(UUID crewId, Pageable pageable); + + Long getIntroductionCount(UUID crewId); +} diff --git a/src/main/java/com/retrip/crew/application/out/repository/IntroductionRepository.java b/src/main/java/com/retrip/crew/application/out/repository/IntroductionRepository.java new file mode 100644 index 0000000..8060c27 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/IntroductionRepository.java @@ -0,0 +1,11 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.domain.entity.Introduction; +import java.util.Optional; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface IntroductionRepository extends JpaRepository { + + Optional findByIdAndCrewId(UUID introductionId, UUID crewId); +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Crew.java b/src/main/java/com/retrip/crew/domain/entity/Crew.java index b3898d0..114f580 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -2,13 +2,16 @@ import com.retrip.crew.domain.vo.CrewDescription; import com.retrip.crew.domain.vo.CrewTitle; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Version; +import java.util.UUID; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.UUID; - @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -69,6 +72,10 @@ public void stopRecruitment() { this.recruitment.stop(); } + public void addIntroduction(Introduction introduction){ + this.introductions.addIntroduction(introduction); + } + public void update(CrewTitle title, CrewDescription description) { this.title = title; this.description = description; diff --git a/src/main/java/com/retrip/crew/domain/entity/Introduction.java b/src/main/java/com/retrip/crew/domain/entity/Introduction.java index 1753df1..63b0db9 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Introduction.java +++ b/src/main/java/com/retrip/crew/domain/entity/Introduction.java @@ -1,14 +1,21 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.common.InvalidAccessException; import com.retrip.crew.domain.vo.IntroductionContent; import com.retrip.crew.domain.vo.IntroductionTitle; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.ForeignKey; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import java.util.UUID; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.UUID; - @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @@ -17,6 +24,8 @@ public class Introduction extends BaseEntity { @Column(columnDefinition = "varbinary(16)") private UUID id; + private UUID memberId; + @Embedded private IntroductionTitle title; @@ -31,4 +40,55 @@ public class Introduction extends BaseEntity { foreignKey = @ForeignKey(name = "fk_self_introduce_board_to_crew") ) private Crew crew; + + public Introduction(UUID loginMemberId, String title, String content, Crew crew){ + this.id = UUID.randomUUID(); + this.memberId = loginMemberId; + this.title = new IntroductionTitle(title); + this.content = new IntroductionContent(content); + this.crew = crew; + } + + public static Introduction create(UUID loginMemberId, String title, String content, Crew crew) { + return new Introduction(loginMemberId, title, content, crew); + } + + public String getTitle(){ + return title.getValue(); + } + + public String getContent(){ + return content.getValue(); + } + + public void update(IntroductionTitle introductionTitle, IntroductionContent introductionContent, UUID loginMemberId) { + validateIntroductionOwner(loginMemberId); + this.title = introductionTitle; + this.content = introductionContent; + } + + public void validateIntroductionOwner(UUID loginMemberId){ + if(!isOwner(loginMemberId)){ + throw new InvalidAccessException(); + } + } + + public void validateIntroductionOwnerAndLeader(UUID loginMemberId){ + if(!isOwner(loginMemberId) || !isLeader(loginMemberId)){ + throw new InvalidAccessException(); + } + } + + public boolean isOwner(UUID loginMemberId){ + return this.memberId.equals(loginMemberId); + } + + public boolean isLeader(UUID loginMemberId){ + return this.getCrew().getLeader().getMemberId().equals(loginMemberId); + } + + public void delete(UUID loginMemberId, Crew crew) { + validateIntroductionOwnerAndLeader(loginMemberId); + crew.getIntroductions().deleteIntroduction(this); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Introductions.java b/src/main/java/com/retrip/crew/domain/entity/Introductions.java index 6d269aa..7128b03 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Introductions.java +++ b/src/main/java/com/retrip/crew/domain/entity/Introductions.java @@ -3,14 +3,14 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; -import lombok.Getter; - import java.util.ArrayList; import java.util.List; +import lombok.Getter; @Getter @Embeddable public class Introductions { + @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) private List values = new ArrayList<>(); @@ -21,4 +21,12 @@ public Introductions() { private List createEmptyValues() { return new ArrayList<>(); } + + public void addIntroduction(Introduction introduction){ + this.values.add(introduction); + } + + public void deleteIntroduction(Introduction introduction) { + this.values.remove(introduction); + } } diff --git a/src/main/java/com/retrip/crew/domain/exception/IntroductionNotFoundException.java b/src/main/java/com/retrip/crew/domain/exception/IntroductionNotFoundException.java new file mode 100644 index 0000000..bd9eb75 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/IntroductionNotFoundException.java @@ -0,0 +1,12 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.EntityNotFoundException; +import com.retrip.crew.domain.exception.common.ErrorCode; + +public class IntroductionNotFoundException extends EntityNotFoundException { + private static final ErrorCode errorCode = ErrorCode.INTRODUCTION_NOT_FOUND; + + public IntroductionNotFoundException() { + super(errorCode); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java b/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java index 4f2ae0b..bea0b8c 100644 --- a/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java +++ b/src/main/java/com/retrip/crew/domain/exception/common/ErrorCode.java @@ -17,7 +17,9 @@ public enum ErrorCode { NOT_CREW_LEADER(BAD_REQUEST, "Crew-002", "크루 리더가 아니면 접근할 수 없습니다."), UNABLE_TO_START_RECRUITMENT(BAD_REQUEST, "Crew-003", "크루 멤버를 모집할 수 없습니다."), DUPLICATE_DEMAND(BAD_REQUEST, "Crew-004", "이미 크루 참여를 요청한 사용자는 다시 요청할 수 없습니다."), - ILLEGAL_DEMAND_STATE(BAD_REQUEST, "Crew-004", "이미 크루 참여를 요청한 사용자는 다시 요청할 수 없습니다.") + ILLEGAL_DEMAND_STATE(BAD_REQUEST, "Crew-004", "이미 크루 참여를 요청한 사용자는 다시 요청할 수 없습니다."), + INTRODUCTION_NOT_FOUND(BAD_REQUEST, "Crew-005", "크루 자기소개 엔티티를 찾을 수 없습니다."), + INVALID_ACCESS(FORBIDDEN, "Crew-006","접근 권한이 존재하지 않습니다."), ; private final HttpStatus status; diff --git a/src/main/java/com/retrip/crew/domain/exception/common/InvalidAccessException.java b/src/main/java/com/retrip/crew/domain/exception/common/InvalidAccessException.java new file mode 100644 index 0000000..be1c306 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/common/InvalidAccessException.java @@ -0,0 +1,21 @@ +package com.retrip.crew.domain.exception.common; + +public class InvalidAccessException extends BusinessException { + private static final ErrorCode errorCode = ErrorCode.INVALID_ACCESS; + + public InvalidAccessException() { + super(errorCode); + } + public InvalidAccessException(ErrorCode errorCode) { + super(errorCode); + } + + public InvalidAccessException(String message) { + super(errorCode, message); + } + + public InvalidAccessException(ErrorCode errorCode, String message) { + super(errorCode, message); + } + +} diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java index 5dcfa2d..fa6730f 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java @@ -1,5 +1,13 @@ package com.retrip.crew.infra.adapter.in.presentation.rest; +import com.retrip.crew.application.in.request.CreateDemandRequest; +import com.retrip.crew.application.in.request.IntroductionCreateRequest; +import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.CrewOrder; +import com.retrip.crew.application.in.request.CrewUpdateRequest; +import com.retrip.crew.application.in.request.IntroductionDeleteRequest; +import com.retrip.crew.application.in.request.IntroductionUpdateRequest; +import com.retrip.crew.application.in.response.*; import com.retrip.crew.application.in.request.crew.CrewCreateRequest; import com.retrip.crew.application.in.request.crew.CrewOrder; import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; @@ -13,6 +21,7 @@ import com.retrip.crew.application.in.usecase.GetCrewUseCase; import com.retrip.crew.application.in.usecase.ManageCrewUseCase; import com.retrip.crew.application.in.usecase.ManageDemandUseCase; +import com.retrip.crew.application.in.usecase.ManageIntroductionUseCase; import com.retrip.crew.application.in.usecase.UpdateRecruitmentUseCase; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; @@ -34,24 +43,48 @@ public class CrewController { private final ManageCrewUseCase manageCrewUseCase; private final GetCrewUseCase getCrewUseCase; + private final ManageIntroductionUseCase manageIntroductionUseCase; - @PostMapping @Schema(description = "크루 생성") + @PostMapping public ApiResponse createCrew(@RequestBody CrewCreateRequest request) { CrewCreateResponse crew = manageCrewUseCase.createCrew(request); return ApiResponse.created(crew); } - @PutMapping("/{crewId}") @Schema(description = "크루 정보 수정") + @PutMapping("/{crewId}") public ApiResponse updateCrew( @PathVariable UUID crewId, @RequestBody CrewUpdateRequest request) { CrewUpdateResponse crew = manageCrewUseCase.updateCrew(crewId, request); return ApiResponse.ok(crew); } - @GetMapping + @Schema(description = "크루 모집 시작") + @PutMapping("/{crewId}/recruitments/start") + public ApiResponse startRecruitment(@PathVariable final UUID crewId) { + ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.startRecruitment(crewId); + return ApiResponse.ok(recruitment); + } + + @Schema(description = "크루 모집 중지") + @PutMapping("/{crewId}/recruitments/stop") + public ApiResponse stopRecruitment(@PathVariable final UUID crewId) { + ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.stopRecruitment(crewId); + return ApiResponse.ok(recruitment); + } + + @Schema(description = "크루 참여 요청") + @PostMapping("/{crewId}/demands") + public ApiResponse createDemand( + @PathVariable final UUID crewId, + @RequestBody CreateDemandRequest request) { + CreateDemandResponse demand = manageDemandUseCase.createDemand(crewId, request); + return ApiResponse.created(demand); + } + @Schema(description = "크루 리스트 조회") + @GetMapping public ResponseEntity>> getCrews( @RequestParam(name = "keyword", required = false) String keyword, @RequestParam(name = "order", defaultValue = "DATE") CrewOrder order, @@ -62,12 +95,61 @@ public ResponseEntity>> getCrew return ResponseEntity.ok().body(ApiResponse.ok(response)); } - @GetMapping("/{crewId}") @Schema(description = "크루 상세 조회") + @GetMapping("/{crewId}") public ResponseEntity> getCrewDetail( @PathVariable("crewId") UUID crewId ) { CrewDetailResponse response = getCrewUseCase.getCrewDetail(crewId); return ResponseEntity.ok().body(ApiResponse.ok(response)); } + + @Schema(description = "크루 자기소개 등록") + @PostMapping("/{crewId}/introductions") + public ApiResponse createIntroduction( + @PathVariable final UUID crewId, + @RequestBody IntroductionCreateRequest request) { + IntroductionCreateResponse response = manageIntroductionUseCase.createIntroduction(crewId, request); + return ApiResponse.created(response); + } + + @Schema(description = "크루 자기소개 수정") + @PutMapping("/{crewId}/introductions/{introductionId}") + public ApiResponse updateIntroduction( + @PathVariable("crewId") final UUID crewId, + @PathVariable("introductionId") final UUID introductionId, + @RequestBody IntroductionUpdateRequest request) { + IntroductionUpdateResponse response = manageIntroductionUseCase.updateIntroduction(crewId, introductionId, request); + return ApiResponse.created(response); + } + + @Schema(description = "크루 자기소개 삭제") + @DeleteMapping("/{crewId}/introductions/{introductionId}") + public ApiResponse deleteIntroduction( + @PathVariable("crewId") final UUID crewId, + @PathVariable("introductionId") final UUID introductionId, + @RequestBody IntroductionDeleteRequest request) { + manageIntroductionUseCase.deleteIntroduction(crewId, introductionId, request); + return ApiResponse.created(null); + } + + @Schema(description = "크루 자기소개 상세 조회") + @GetMapping("/{crewId}/introductions/{introductionId}") + public ApiResponse getIntroduction( + @PathVariable("crewId") final UUID crewId, + @PathVariable("introductionId") final UUID introductionId + ) { + IntroductionDetailResponse response = manageIntroductionUseCase.getIntroduction(crewId, introductionId); + return ApiResponse.ok(response); + } + + @Schema(description = "크루 자기소개 리스트 조회") + @GetMapping("/{crewId}/introductions") + public ApiResponse> getIntroductions( + @PathVariable("crewId") final UUID crewId, + @PageableDefault(size = 10) Pageable pageable + ) { + ScrollPageResponse response = manageIntroductionUseCase.getIntroductions(crewId, pageable); + return ApiResponse.ok(response); + } } diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/IntroductionQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/IntroductionQuerydslRepository.java new file mode 100644 index 0000000..8795921 --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/IntroductionQuerydslRepository.java @@ -0,0 +1,51 @@ +package com.retrip.crew.infra.adapter.out.persistence.mysql.query; + +import static com.retrip.crew.domain.entity.QCrew.crew; +import static com.retrip.crew.domain.entity.QIntroduction.introduction; +import static com.retrip.crew.infra.util.PaginationUtils.checkEndPage; + +import com.querydsl.core.types.Projections; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.retrip.crew.application.in.response.IntroductionListResponse; +import com.retrip.crew.application.out.repository.IntroductionQueryRepository; +import java.util.List; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class IntroductionQuerydslRepository implements IntroductionQueryRepository { + + private final JPAQueryFactory query; + + @Override + public Slice getIntroductions(UUID crewId, Pageable pageable) { + List fetch = query.select(Projections.constructor(IntroductionListResponse.class, + introduction.crew.id.as("crewId"), + introduction.id.as("introductionId"), + introduction.title.value.as("title"), + introduction.createdAt.as("createdAt"), + introduction.editedAt.as("editedAt") + ) + ) + .from(introduction) + .join(crew).on(crew.id.eq(introduction.crew.id)) + .where(introduction.crew.id.eq(crewId)) + .limit(pageable.getPageSize() + 1) + .offset(pageable.getOffset()) + .orderBy(introduction.editedAt.desc()) + .fetch(); + return checkEndPage(pageable, fetch); + } + + @Override + public Long getIntroductionCount(UUID crewId) { + return query.select(introduction.count()) + .from(introduction) + .where(introduction.crew.id.eq(crewId)) + .fetchOne(); + } +} From 28cdaba06fe1b66719c3f55ba59f1142345e2947 Mon Sep 17 00:00:00 2001 From: pparkjs Date: Sat, 26 Apr 2025 11:05:02 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[feat]=20=ED=81=AC=EB=A3=A8=20=EC=9E=90?= =?UTF-8?q?=EA=B8=B0=EC=86=8C=EA=B0=9C=20=EA=B4=80=EB=A0=A8=20API=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20(#8?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 27 +++-- .../response/IntroductionCreateResponse.java | 2 +- .../crew/domain/entity/Introduction.java | 2 +- .../in/presentation/rest/CrewController.java | 51 +++----- .../crew/application/in/CrewServiceTest.java | 114 ++++++++++-------- 5 files changed, 96 insertions(+), 100 deletions(-) diff --git a/src/main/java/com/retrip/crew/application/in/CrewService.java b/src/main/java/com/retrip/crew/application/in/CrewService.java index f826b26..8958dd1 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -1,23 +1,27 @@ package com.retrip.crew.application.in; -import com.retrip.crew.application.in.request.CreateDemandRequest; import com.retrip.crew.application.in.request.IntroductionCreateRequest; -import com.retrip.crew.application.in.request.CrewCreateRequest; -import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.request.CrewUpdateRequest; import com.retrip.crew.application.in.request.IntroductionDeleteRequest; import com.retrip.crew.application.in.request.IntroductionUpdateRequest; -import com.retrip.crew.application.in.response.*; import com.retrip.crew.application.in.request.crew.CrewCreateRequest; import com.retrip.crew.application.in.request.crew.CrewOrder; import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; import com.retrip.crew.application.in.request.demand.CreateDemandRequest; import com.retrip.crew.application.in.request.demand.DemandOrder; +import com.retrip.crew.application.in.response.IntroductionCreateResponse; +import com.retrip.crew.application.in.response.IntroductionDetailResponse; +import com.retrip.crew.application.in.response.IntroductionListResponse; +import com.retrip.crew.application.in.response.IntroductionUpdateResponse; import com.retrip.crew.application.in.response.crew.CrewCreateResponse; import com.retrip.crew.application.in.response.crew.CrewDetailResponse; import com.retrip.crew.application.in.response.crew.CrewListResponse; import com.retrip.crew.application.in.response.crew.CrewUpdateResponse; -import com.retrip.crew.application.in.response.demand.*; +import com.retrip.crew.application.in.response.demand.ApproveDemandResponse; +import com.retrip.crew.application.in.response.demand.ChangeRecruitmentStatusResponse; +import com.retrip.crew.application.in.response.demand.CreateDemandResponse; +import com.retrip.crew.application.in.response.demand.CrewsOfDemandResponse; +import com.retrip.crew.application.in.response.demand.DemandsResponse; +import com.retrip.crew.application.in.response.demand.RejectDemandResponse; import com.retrip.crew.application.in.usecase.GetCrewUseCase; import com.retrip.crew.application.in.usecase.ManageCrewUseCase; import com.retrip.crew.application.in.usecase.ManageDemandUseCase; @@ -40,11 +44,12 @@ import com.retrip.crew.domain.exception.common.EntityNotFoundException; import com.retrip.crew.domain.vo.CrewDescription; import com.retrip.crew.domain.vo.CrewTitle; +import com.retrip.crew.domain.vo.DemandStatus; import com.retrip.crew.domain.vo.IntroductionContent; import com.retrip.crew.domain.vo.IntroductionTitle; -import com.retrip.crew.domain.vo.DemandStatus; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import com.retrip.crew.infra.util.PaginationUtils; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -52,8 +57,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.UUID; - @Service @Transactional @RequiredArgsConstructor @@ -108,7 +111,7 @@ public CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest reques @Override public Page getDemands( UUID crewId, UUID memberId, String status, Pageable pageable, DemandOrder order, String sort) { - Crew crew = findById(crewId); + Crew crew = findCrewById(crewId); throwIfNotLeader(crew, memberId, new NotCrewLeaderException()); Page demands = demandRepository.findByCrewIdAndStatus( crewId, DemandStatus.valueOf(status), PaginationUtils.createPageRequest(pageable, order.getField(), sort)); @@ -118,7 +121,7 @@ public Page getDemands( @Override public Page getCrewsOfDemand( UUID crewId, UUID demandId, UUID memberId, Pageable pageable, CrewOrder order, String sort) { - Crew crew = findById(crewId); + Crew crew = findCrewById(crewId); throwIfNotLeader(crew, memberId, new NotCrewLeaderException()); Demand demand = findDemandByIdAndCrewId(demandId, crewId); return crewQueryRepository.findAllContainsMember(pageable, demand.getMemberId()); @@ -180,7 +183,7 @@ public IntroductionCreateResponse createIntroduction(UUID crewId, IntroductionCr Crew crew = findCrewById(crewId); Introduction introduction = request.to(crew); crew.addIntroduction(introduction); - return IntroductionCreateResponse.from(introduction); + return IntroductionCreateResponse.of(introduction); } @Override diff --git a/src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java index ba0eb16..fb3a265 100644 --- a/src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/IntroductionCreateResponse.java @@ -16,7 +16,7 @@ public record IntroductionCreateResponse( @Schema(description = "자기소개 본문") String content ) { - public static IntroductionCreateResponse from(Introduction introduction){ + public static IntroductionCreateResponse of(Introduction introduction){ return new IntroductionCreateResponse( introduction.getCrew().getId(), introduction.getId(), diff --git a/src/main/java/com/retrip/crew/domain/entity/Introduction.java b/src/main/java/com/retrip/crew/domain/entity/Introduction.java index 63b0db9..0a10ce2 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Introduction.java +++ b/src/main/java/com/retrip/crew/domain/entity/Introduction.java @@ -37,7 +37,7 @@ public class Introduction extends BaseEntity { name = "crew_id", nullable = false, columnDefinition = "varbinary(16)", - foreignKey = @ForeignKey(name = "fk_self_introduce_board_to_crew") + foreignKey = @ForeignKey(name = "fk_introduction_to_crew") ) private Crew crew; diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java index fa6730f..cc3c6b4 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/CrewController.java @@ -1,40 +1,40 @@ package com.retrip.crew.infra.adapter.in.presentation.rest; -import com.retrip.crew.application.in.request.CreateDemandRequest; import com.retrip.crew.application.in.request.IntroductionCreateRequest; -import com.retrip.crew.application.in.request.CrewCreateRequest; -import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.request.CrewUpdateRequest; import com.retrip.crew.application.in.request.IntroductionDeleteRequest; import com.retrip.crew.application.in.request.IntroductionUpdateRequest; -import com.retrip.crew.application.in.response.*; import com.retrip.crew.application.in.request.crew.CrewCreateRequest; import com.retrip.crew.application.in.request.crew.CrewOrder; import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; -import com.retrip.crew.application.in.request.demand.CreateDemandRequest; -import com.retrip.crew.application.in.request.demand.DemandOrder; +import com.retrip.crew.application.in.response.IntroductionCreateResponse; +import com.retrip.crew.application.in.response.IntroductionDetailResponse; +import com.retrip.crew.application.in.response.IntroductionListResponse; +import com.retrip.crew.application.in.response.IntroductionUpdateResponse; import com.retrip.crew.application.in.response.crew.CrewCreateResponse; import com.retrip.crew.application.in.response.crew.CrewDetailResponse; import com.retrip.crew.application.in.response.crew.CrewListResponse; import com.retrip.crew.application.in.response.crew.CrewUpdateResponse; -import com.retrip.crew.application.in.response.demand.*; import com.retrip.crew.application.in.usecase.GetCrewUseCase; import com.retrip.crew.application.in.usecase.ManageCrewUseCase; -import com.retrip.crew.application.in.usecase.ManageDemandUseCase; import com.retrip.crew.application.in.usecase.ManageIntroductionUseCase; -import com.retrip.crew.application.in.usecase.UpdateRecruitmentUseCase; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.UUID; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.UUID; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @RequiredArgsConstructor @RequestMapping("/crews") @@ -60,29 +60,6 @@ public ApiResponse updateCrew( return ApiResponse.ok(crew); } - @Schema(description = "크루 모집 시작") - @PutMapping("/{crewId}/recruitments/start") - public ApiResponse startRecruitment(@PathVariable final UUID crewId) { - ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.startRecruitment(crewId); - return ApiResponse.ok(recruitment); - } - - @Schema(description = "크루 모집 중지") - @PutMapping("/{crewId}/recruitments/stop") - public ApiResponse stopRecruitment(@PathVariable final UUID crewId) { - ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.stopRecruitment(crewId); - return ApiResponse.ok(recruitment); - } - - @Schema(description = "크루 참여 요청") - @PostMapping("/{crewId}/demands") - public ApiResponse createDemand( - @PathVariable final UUID crewId, - @RequestBody CreateDemandRequest request) { - CreateDemandResponse demand = manageDemandUseCase.createDemand(crewId, request); - return ApiResponse.created(demand); - } - @Schema(description = "크루 리스트 조회") @GetMapping public ResponseEntity>> getCrews( diff --git a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java index 28f752b..8ef1a5e 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,18 +1,45 @@ package com.retrip.crew.application.in; -import com.retrip.crew.application.in.request.CreateDemandRequest; -import com.retrip.crew.application.in.request.CrewCreateRequest; -import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.request.CrewUpdateRequest; -import com.retrip.crew.application.in.response.*; +import static com.retrip.crew.common.fixture.CrewFixture.LEADER_ID; +import static com.retrip.crew.common.fixture.CrewFixture.MEMBER_ID; +import static com.retrip.crew.common.fixture.CrewFixture.createCrew; +import static com.retrip.crew.common.fixture.CrewFixture.createCrewRequest; +import static com.retrip.crew.common.fixture.CrewFixture.createCrewWithMembers; +import static com.retrip.crew.common.fixture.CrewFixture.createMultipleCrews; +import static com.retrip.crew.common.fixture.CrewFixture.정수_ID; +import static com.retrip.crew.common.fixture.CrewFixture.준호_ID; +import static com.retrip.crew.common.fixture.CrewFixture.지수_ID; +import static com.retrip.crew.common.fixture.CrewFixture.혁진_ID; +import static com.retrip.crew.common.fixture.CrewFixture.홍석_ID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.retrip.crew.application.in.request.IntroductionCreateRequest; +import com.retrip.crew.application.in.request.IntroductionDeleteRequest; +import com.retrip.crew.application.in.request.IntroductionUpdateRequest; +import com.retrip.crew.application.in.request.crew.CrewCreateRequest; +import com.retrip.crew.application.in.request.crew.CrewOrder; +import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; +import com.retrip.crew.application.in.request.demand.CreateDemandRequest; +import com.retrip.crew.application.in.request.demand.DemandOrder; +import com.retrip.crew.application.in.response.IntroductionCreateResponse; +import com.retrip.crew.application.in.response.IntroductionDetailResponse; +import com.retrip.crew.application.in.response.crew.CrewCreateResponse; +import com.retrip.crew.application.in.response.crew.CrewDetailResponse; +import com.retrip.crew.application.in.response.crew.CrewListResponse; +import com.retrip.crew.application.in.response.crew.CrewUpdateResponse; +import com.retrip.crew.application.in.response.demand.CreateDemandResponse; +import com.retrip.crew.application.in.response.demand.CrewsOfDemandResponse; +import com.retrip.crew.application.in.response.demand.DemandsResponse; import com.retrip.crew.common.ServiceTest; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMemberRole; import com.retrip.crew.domain.entity.Demand; import com.retrip.crew.domain.entity.Introduction; -import com.retrip.crew.domain.exception.common.IllegalStateException; -import com.retrip.crew.domain.exception.common.InvalidAccessException; import com.retrip.crew.domain.exception.DuplicateDemandException; +import com.retrip.crew.domain.exception.common.InvalidAccessException; import com.retrip.crew.domain.vo.DemandStatus; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import java.util.List; @@ -26,14 +53,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.test.util.ReflectionTestUtils; -import java.util.List; -import java.util.UUID; - -import static com.retrip.crew.common.fixture.CrewFixture.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - class CrewServiceTest extends ServiceTest { @Test void 크루를_생성_한다() { @@ -243,24 +262,26 @@ class CrewServiceTest extends ServiceTest { @Test void 내가_작성한_자기소개를_수정할_수_있다() { // given - Crew crew = crewRepository.save(Crew.create( + Crew crew = Crew.create( "속초 크루원 구함", "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", 100, MEMBER_ID - )); - - Introduction introduction = introductionRepository.save(Introduction.create( + ); + Introduction introduction = Introduction.create( MEMBER_ID, "정수의 자기소개!", "안녕하세요!", crew - )); + ); + crew.addIntroduction(introduction); + Crew savedCrew = crewRepository.save(crew); + IntroductionUpdateRequest request = new IntroductionUpdateRequest(MEMBER_ID, "변경된 자기소개!", "안녕!"); // when - crewService.updateIntroduction(crew.getId(), introduction.getId(), request); - Introduction result = crewService.findIntroductionByIdAndCrewId(introduction.getId(), crew.getId()); + crewService.updateIntroduction(savedCrew.getId(), introduction.getId(), request); + Introduction result = crewService.findIntroductionByIdAndCrewId(introduction.getId(), savedCrew.getId()); // then assertThat(result.getTitle()).isEqualTo("변경된 자기소개!"); @@ -270,80 +291,75 @@ class CrewServiceTest extends ServiceTest { @Test void 일반_멤버는_다른_멤버의_자기소개를_수정할_수_없다() { // given - Crew crew = crewRepository.save(Crew.create( + Crew crew = Crew.create( "속초 크루원 구함", "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", 100, MEMBER_ID - )); - - Introduction introduction = introductionRepository.save(Introduction.create( + ); + Introduction introduction = Introduction.create( MEMBER_ID, "정수의 자기소개!", "안녕하세요!", crew - )); + ); + crew.addIntroduction(introduction); + Crew savedCrew = crewRepository.save(crew); UUID otherMemberId = UUID.randomUUID(); IntroductionUpdateRequest request = new IntroductionUpdateRequest(otherMemberId, "수정된 소개", "안"); // when & then - assertThrows(InvalidAccessException.class, () -> crewService.updateIntroduction(crew.getId(), introduction.getId(), request)); + assertThrows(InvalidAccessException.class, () -> crewService.updateIntroduction(savedCrew.getId(), introduction.getId(), request)); } @Test void 리더가_자기소개를_삭제할_수_있다() { // given - Crew crew = crewRepository.save(Crew.create( + Crew crew = Crew.create( "속초 크루원 구함", "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", 100, MEMBER_ID - )); - - Introduction introduction = introductionRepository.save(Introduction.create( + ); + Introduction introduction = Introduction.create( MEMBER_ID, "정수의 자기소개!", "안녕하세요!", crew - )); - - entityManager.flush(); - entityManager.clear(); + ); + crew.addIntroduction(introduction); + Crew savedCrew = crewRepository.save(crew); IntroductionDeleteRequest request = new IntroductionDeleteRequest(MEMBER_ID); // when - crewService.deleteIntroduction(crew.getId(), introduction.getId(), request); - - entityManager.flush(); - entityManager.clear(); - - Optional introductionOptional = introductionRepository.findById(introduction.getId()); + crewService.deleteIntroduction(savedCrew.getId(), introduction.getId(), request); // then - assertThat(introductionOptional.isEmpty()).isTrue(); + assertThat(savedCrew.getIntroductions().getValues().isEmpty()).isTrue(); } @Test void 자기소개를_조회한다() { // given - Crew crew = crewRepository.save(Crew.create( + Crew crew = Crew.create( "속초 크루원 구함", "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", 100, MEMBER_ID - )); - - Introduction introduction = introductionRepository.save(Introduction.create( + ); + Introduction introduction = Introduction.create( MEMBER_ID, "정수의 자기소개!", "안녕하세요!", crew - )); + ); + crew.addIntroduction(introduction); + Crew savedCrew = crewRepository.save(crew); // when - IntroductionDetailResponse response = crewService.getIntroduction(crew.getId(), introduction.getId()); + IntroductionDetailResponse response = crewService.getIntroduction(savedCrew.getId(), introduction.getId()); // then assertThat(response.introductionId()).isEqualTo(introduction.getId());