From 48bc21582b45ff11b1deac6c01520f9e62e65eed Mon Sep 17 00:00:00 2001 From: mandykr Date: Sat, 22 Mar 2025 17:16:14 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=ED=81=AC=EB=A3=A8=20=EB=A6=AC?= =?UTF-8?q?=EB=8D=94=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=ED=81=AC=EB=A3=A8=20Fixture=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EA=B8=B0=EC=A1=B4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/retrip/crew/domain/entity/Crew.java | 1 - .../retrip/crew/domain/entity/CrewMember.java | 5 +- .../crew/domain/entity/CrewMemberRole.java | 4 ++ .../crew/domain/entity/CrewMembers.java | 9 +++ .../crew/domain/entity/Recruitment.java | 2 +- .../crew/application/in/CrewServiceTest.java | 31 +++------- .../com/retrip/crew/common/ServiceTest.java | 4 -- .../crew/common/fixture/CrewFixture.java | 61 +++++++++++++++++++ .../crew/domain/entity/CrewMembersTest.java | 33 ++++++++++ .../retrip/crew/domain/entity/CrewTest.java | 41 ++----------- 10 files changed, 128 insertions(+), 63 deletions(-) create mode 100644 src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java 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 fd2180e..f67dcdf 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -81,5 +81,4 @@ public Demand demand(UUID memberId) { public String getDescription(){ return description.getValue(); } - } diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMember.java b/src/main/java/com/retrip/crew/domain/entity/CrewMember.java index 7a2c6f1..fd290b2 100644 --- a/src/main/java/com/retrip/crew/domain/entity/CrewMember.java +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMember.java @@ -5,7 +5,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.List; import java.util.UUID; @Entity @@ -37,4 +36,8 @@ public CrewMember(Crew crew, UUID memberId, CrewMemberRole crewMemberRole) { this.memberId = memberId; this.crewMemberRole = CrewMemberRole.valueOf(crewMemberRole.name()); } + + public boolean isLeader() { + return CrewMemberRole.isLeaderRole(this.crewMemberRole); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java b/src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java index 86072f2..bd39ab5 100644 --- a/src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMemberRole.java @@ -21,5 +21,9 @@ public static CrewMemberRole codeOf(String code) { .findAny() .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 코드입니다.")); } + + public static boolean isLeaderRole(CrewMemberRole role) { + return LEADER == role; + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java index 4a358b1..992d844 100644 --- a/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java @@ -1,5 +1,6 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; @@ -36,4 +37,12 @@ public CrewMember getLeader() { public int getSize() { return this.values.size(); } + + public boolean isLeader(UUID memberId) { + return this.values.stream() + .filter(m -> memberId.equals(m.getMemberId())) + .findFirst() + .orElseThrow(() -> new InvalidValueException("크루 멤버가 아닙니다.")) + .isLeader(); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index e445277..bffbb6f 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -44,7 +44,7 @@ public void stop() { } private boolean isRecruitmentComplete(int membersSize) { - return this.maxMembers == membersSize; + return this.maxMembers <= membersSize; } public void updateMaxMembers(int maxMembers) { 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 a58c76a..8a451a6 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -18,8 +18,7 @@ import java.util.List; import java.util.UUID; -import static com.retrip.crew.common.fixture.CrewFixture.createCrewRequest; -import static com.retrip.crew.common.fixture.CrewFixture.createMultipleCrews; +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; @@ -29,7 +28,7 @@ class CrewServiceTest extends ServiceTest { void 크루를_생성_한다() { //given CrewCreateRequest request = createCrewRequest( - MEMBER_ID, + LEADER_ID, "속초 크루원 구함", "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", 100 @@ -46,12 +45,7 @@ class CrewServiceTest extends ServiceTest { @Test void 크루의_제목_설명_최대_인원수를_수정한다() { // given - Crew crew = crewRepository.save(Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - MEMBER_ID - )); + Crew crew = crewRepository.save(createCrew(LEADER_ID)); CrewUpdateRequest request = new CrewUpdateRequest( "강릉 크루원 구함", "강릉 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", @@ -70,16 +64,14 @@ class CrewServiceTest extends ServiceTest { @Test void 크루_참여_요청을_생성한다() { - Crew crew = crewRepository.save(Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - MEMBER_ID - )); + // given + Crew crew = crewRepository.save(createCrew(LEADER_ID)); CreateDemandRequest request = new CreateDemandRequest(MEMBER_ID); + // when CreateDemandResponse response = crewService.createDemand(crew.getId(), request); + // then List demands = crew.getRecruitment().getDemands(); assertAll( () -> assertThat(demands.size()).isEqualTo(1), @@ -89,18 +81,15 @@ class CrewServiceTest extends ServiceTest { @Test void 이미_요청한_사용자는_다시_크루에_요청할_수_없다() { - Crew crew = Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - MEMBER_ID - ); + // given + Crew crew = createCrew(LEADER_ID); crew.demand(MEMBER_ID); crew.demand(UUID.randomUUID()); crew.demand(UUID.randomUUID()); Crew save = crewRepository.save(crew); CreateDemandRequest request = new CreateDemandRequest(MEMBER_ID); + // when, then assertThatThrownBy(() -> crewService.createDemand(save.getId(), request)) .isExactlyInstanceOf(IllegalStateException.class); } diff --git a/src/test/java/com/retrip/crew/common/ServiceTest.java b/src/test/java/com/retrip/crew/common/ServiceTest.java index a6452c3..e21b146 100644 --- a/src/test/java/com/retrip/crew/common/ServiceTest.java +++ b/src/test/java/com/retrip/crew/common/ServiceTest.java @@ -11,8 +11,6 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; -import java.util.UUID; - @DataJpaTest @Import(QuerydslConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @@ -29,8 +27,6 @@ public class ServiceTest { protected CrewService crewService; - protected UUID MEMBER_ID = UUID.fromString("13c8ab91-76bc-4f70-93e9-89f1a65dc640"); - @BeforeEach void setUp() { crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository); diff --git a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java index 34ee232..7f325a4 100644 --- a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java +++ b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java @@ -1,12 +1,25 @@ package com.retrip.crew.common.fixture; import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.domain.entity.Crew; +import com.retrip.crew.domain.entity.CrewMember; +import com.retrip.crew.domain.entity.CrewMemberRole; +import com.retrip.crew.domain.entity.CrewMembers; +import org.springframework.test.util.ReflectionTestUtils; + import java.util.List; import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.IntStream; public abstract class CrewFixture { + public static final UUID LEADER_ID = UUID.fromString("caec62d1-f29d-477d-9743-292f48cc66bb"); + public static final UUID MEMBER_ID = UUID.fromString("85e05380-3693-4f3f-b74b-203715d15df8"); + public static final UUID 정수_ID = UUID.fromString("a7f7215b-081a-42f4-b3e2-f06393de2f8b"); + public static final UUID 홍석_ID = UUID.fromString("bf97d20b-d1f7-46a9-8362-11b9fa02d67d"); + public static final UUID 준호_ID = UUID.fromString("8b9b67fd-1d88-4b30-bfea-cd8f89fc10d9"); + public static final UUID 지수_ID = UUID.fromString("de3b60d2-5672-464d-8769-bf5c9de5eaff"); + public static final UUID 혁진_ID = UUID.fromString("42880aaf-4b97-4b0c-8a8a-72df4bb592f6"); public static CrewCreateRequest createCrewRequest(UUID memberId, String title, String description, int maxMembers) { return new CrewCreateRequest( @@ -26,4 +39,52 @@ public static List createMultipleCrews(int count, UUID member }) .collect(Collectors.toList()); } + + public static Crew createCrew(UUID leaderId) { + return Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 5, + leaderId); + } + + public static Crew createCrewWithMembers(UUID leaderId) { + Crew crew = Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + 100, + leaderId); + List crewMemberList = List.of( + new CrewMember(crew, leaderId, CrewMemberRole.LEADER), + new CrewMember(crew, 정수_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 홍석_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 준호_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 지수_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 혁진_ID, CrewMemberRole.PARTICIPANT) + ); + CrewMembers crewMembers = new CrewMembers(crew, leaderId); + ReflectionTestUtils.setField(crewMembers, "values", crewMemberList); + ReflectionTestUtils.setField(crew, "crewMembers", crewMembers); + return crew; + } + + public static Crew createCrewWithMembers(UUID leaderId, int maxMembers) { + Crew crew = Crew.create( + "속초 크루원 구함", + "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", + maxMembers, + leaderId); + List crewMemberList = List.of( + new CrewMember(crew, leaderId, CrewMemberRole.LEADER), + new CrewMember(crew, 정수_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 홍석_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 준호_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 지수_ID, CrewMemberRole.PARTICIPANT), + new CrewMember(crew, 혁진_ID, CrewMemberRole.PARTICIPANT) + ); + CrewMembers crewMembers = new CrewMembers(crew, leaderId); + ReflectionTestUtils.setField(crewMembers, "values", crewMemberList); + ReflectionTestUtils.setField(crew, "crewMembers", crewMembers); + return crew; + } } diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java new file mode 100644 index 0000000..c98cf89 --- /dev/null +++ b/src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java @@ -0,0 +1,33 @@ +package com.retrip.crew.domain.entity; + +import org.junit.jupiter.api.Test; + +import static com.retrip.crew.common.fixture.CrewFixture.*; +import static org.assertj.core.api.Assertions.assertThat; + +class CrewMembersTest { + + @Test + void 사용자가_리더이면_true를_반환한다() { + // given + Crew crew = createCrewWithMembers(MEMBER_ID); + + // when + boolean result = crew.getCrewMembers().isLeader(MEMBER_ID); + + // then + assertThat(result).isTrue(); + } + + @Test + void 사용자가_리더가_아니면_false를_반환한다() { + // given + Crew crew = createCrewWithMembers(LEADER_ID); + + // when + boolean result = crew.getCrewMembers().isLeader(정수_ID); + + // then + assertThat(result).isFalse(); + } +} diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java index cb9ed60..81c1e8f 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java @@ -3,11 +3,8 @@ import com.retrip.crew.domain.exception.common.IllegalStateException; import com.retrip.crew.domain.vo.RecruitmentStatus; import org.junit.jupiter.api.Test; -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.AssertionsForClassTypes.*; class CrewTest { @@ -17,18 +14,14 @@ class CrewTest { "속초 크루원 구함", "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", 100, - UUID.randomUUID()) + LEADER_ID) ).doesNotThrowAnyException(); } @Test void 크루가_생성되면_모집을_시작한다() { // given, when - Crew crew = Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - UUID.randomUUID()); + Crew crew = createCrew(LEADER_ID); // then assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.RECRUITING); @@ -37,11 +30,7 @@ class CrewTest { @Test void 크루_모집을_중지한다() { // given - Crew crew = Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - UUID.randomUUID()); + Crew crew = createCrew(LEADER_ID); // when crew.stopRecruitment(); @@ -53,11 +42,7 @@ class CrewTest { @Test void 크루_모집을_시작한다() { // given - Crew crew = Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 100, - UUID.randomUUID()); + Crew crew = createCrewWithMembers(LEADER_ID, 100); crew.stopRecruitment(); // when @@ -70,22 +55,8 @@ class CrewTest { @Test void 크루_멤버가_최대_모집_인원과_같으면_모집을_시작할_수_없다() { // given - Crew crew = Crew.create( - "속초 크루원 구함", - "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 5, - UUID.randomUUID()); - List crewMemberList = List.of( - new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.LEADER), - new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT), - new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT), - new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT), - new CrewMember(crew, UUID.randomUUID(), CrewMemberRole.PARTICIPANT) - ); + Crew crew = createCrewWithMembers(LEADER_ID, 6); crew.stopRecruitment(); - CrewMembers crewMembers = new CrewMembers(crew, UUID.randomUUID()); - ReflectionTestUtils.setField(crewMembers, "values", crewMemberList); - ReflectionTestUtils.setField(crew, "crewMembers", crewMembers); // when, then assertThatThrownBy(crew::startRecruitment) From 324b72aa7dc5f75b57dfed3f313e0528fa2dcaef Mon Sep 17 00:00:00 2001 From: mandykr Date: Sat, 22 Mar 2025 18:04:38 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=EC=8A=B9=EC=9D=B8=20=EB=8C=80?= =?UTF-8?q?=EA=B8=B0=EC=A4=91=EC=9D=B8=20=EC=B0=B8=EC=97=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=AA=A9=EB=A1=9D=EC=9D=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 27 ++++++++++++--- .../application/in/request/DemandOrder.java | 12 +++++++ .../in/response/PendingDemandsResponse.java | 17 ++++++++++ .../in/usecase/ManageDemandUseCase.java | 7 ++++ .../out/repository/CrewDemandRepository.java | 13 ++++++++ .../com/retrip/crew/domain/entity/Demand.java | 7 +++- .../exception/NotCrewLeaderException.java | 12 +++++++ .../domain/exception/common/ErrorCode.java | 3 +- .../retrip/crew/domain/vo/DemandStatus.java | 25 ++++++++++++++ .../in/presentation/rest/CrewController.java | 19 ++++++++--- .../crew/application/in/CrewServiceTest.java | 33 ++++++++++++++++--- .../com/retrip/crew/common/ServiceTest.java | 6 +++- .../crew/common/fixture/CrewFixture.java | 2 +- 13 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/request/DemandOrder.java create mode 100644 src/main/java/com/retrip/crew/application/in/response/PendingDemandsResponse.java create mode 100644 src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/NotCrewLeaderException.java create mode 100644 src/main/java/com/retrip/crew/domain/vo/DemandStatus.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 ed978f0..782d7a0 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -1,14 +1,12 @@ 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.request.*; import com.retrip.crew.application.in.response.*; 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.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; @@ -16,11 +14,15 @@ import com.retrip.crew.domain.entity.Demand; import com.retrip.crew.domain.entity.Recruitment; import com.retrip.crew.domain.exception.CrewNotFoundException; +import com.retrip.crew.domain.exception.NotCrewLeaderException; +import com.retrip.crew.domain.exception.common.BusinessException; 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.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import com.retrip.crew.infra.util.PaginationUtils; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -35,6 +37,7 @@ public class CrewService implements ManageCrewUseCase, UpdateRecruitmentUseCase, private final CrewRepository crewRepository; private final CrewMemberRepository crewMemberRepository; private final CrewQueryRepository crewQueryRepository; + private final CrewDemandRepository demandRepository; @Override public CrewCreateResponse createCrew(CrewCreateRequest request) { @@ -75,6 +78,22 @@ public CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest reques return CreateDemandResponse.of(crew.getId(), demand); } + @Override + public Page getPendingDemands( + UUID crewId, UUID memberId, Pageable pageable, DemandOrder order, String sort) { + Crew crew = findById(crewId); + throwIfNotLeader(crew, memberId, new NotCrewLeaderException()); + Page demands = demandRepository.findByCrewIdAndStatus( + crewId, DemandStatus.PENDING, PaginationUtils.createPageRequest(pageable, order.getField(), sort)); + return demands.map(d -> PendingDemandsResponse.of(crewId, d)); + } + + private static void throwIfNotLeader(Crew crew, UUID memberId, BusinessException exception) { + if (!crew.getCrewMembers().isLeader(memberId)) { + throw exception; + } + } + @Override @Transactional(readOnly = true) public ScrollPageResponse getCrews(Pageable pageable, String keyword, CrewOrder order, String sort) { diff --git a/src/main/java/com/retrip/crew/application/in/request/DemandOrder.java b/src/main/java/com/retrip/crew/application/in/request/DemandOrder.java new file mode 100644 index 0000000..3d1e090 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/request/DemandOrder.java @@ -0,0 +1,12 @@ +package com.retrip.crew.application.in.request; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum DemandOrder { + DATE("createdAt"); + + private final String field; +} diff --git a/src/main/java/com/retrip/crew/application/in/response/PendingDemandsResponse.java b/src/main/java/com/retrip/crew/application/in/response/PendingDemandsResponse.java new file mode 100644 index 0000000..efe8a61 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/PendingDemandsResponse.java @@ -0,0 +1,17 @@ +package com.retrip.crew.application.in.response; + +import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.vo.DemandStatus; + +import java.util.UUID; + +public record PendingDemandsResponse( + UUID crewId, + UUID demandId, + UUID memberId, + DemandStatus status +) { + public static PendingDemandsResponse of(UUID crewId, Demand demand) { + return new PendingDemandsResponse(crewId, demand.getId(), demand.getMemberId(), demand.getStatus()); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java index ae7008f..9b597de 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java @@ -1,10 +1,17 @@ package com.retrip.crew.application.in.usecase; import com.retrip.crew.application.in.request.CreateDemandRequest; +import com.retrip.crew.application.in.request.DemandOrder; import com.retrip.crew.application.in.response.CreateDemandResponse; +import com.retrip.crew.application.in.response.PendingDemandsResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import java.util.UUID; public interface ManageDemandUseCase { CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest request); + + Page getPendingDemands( + UUID crewId, UUID memberId, Pageable pageable, DemandOrder order, String sort); } diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java new file mode 100644 index 0000000..6813888 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java @@ -0,0 +1,13 @@ +package com.retrip.crew.application.out.repository; + +import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.vo.DemandStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.UUID; + +public interface CrewDemandRepository extends JpaRepository { + Page findByCrewIdAndStatus(UUID crewId, DemandStatus pending, Pageable pageRequest); +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Demand.java b/src/main/java/com/retrip/crew/domain/entity/Demand.java index 8fae633..c097142 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Demand.java +++ b/src/main/java/com/retrip/crew/domain/entity/Demand.java @@ -1,5 +1,6 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.vo.DemandStatus; import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Getter; @@ -7,14 +8,17 @@ import java.util.UUID; +import static com.retrip.crew.domain.vo.DemandStatus.PENDING; + @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) @Getter -public class Demand { +public class Demand extends BaseEntity { @Id @Column(columnDefinition = "varbinary(16)") private UUID id; private UUID memberId; + private DemandStatus status; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn( @@ -28,6 +32,7 @@ public class Demand { public Demand(UUID memberId, Crew crew) { this.id = UUID.randomUUID(); this.memberId = memberId; + this.status = PENDING; this.crew = crew; } } diff --git a/src/main/java/com/retrip/crew/domain/exception/NotCrewLeaderException.java b/src/main/java/com/retrip/crew/domain/exception/NotCrewLeaderException.java new file mode 100644 index 0000000..f035c07 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/NotCrewLeaderException.java @@ -0,0 +1,12 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.ErrorCode; +import com.retrip.crew.domain.exception.common.InvalidValueException; + +public class NotCrewLeaderException extends InvalidValueException { + private static final ErrorCode errorCode = ErrorCode.NOT_CREW_LEADER; + + public NotCrewLeaderException() { + 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 4d05d8e..576eb88 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 @@ -13,7 +13,8 @@ public enum ErrorCode { ENTITY_NOT_FOUND(BAD_REQUEST, "Common-004", "Entity not found"), ILLEGAL_STATE(BAD_REQUEST, "Common-005", "Illegal state"), - CREW_NOT_FOUND(BAD_REQUEST, "Crew-001", "크루 엔티티를 찾을 수 없습니다.") + CREW_NOT_FOUND(BAD_REQUEST, "Crew-001", "크루 엔티티를 찾을 수 없습니다."), + NOT_CREW_LEADER(BAD_REQUEST, "Crew-002", "크루 리더가 아니면 접근할 수 없습니다.") ; private final HttpStatus status; diff --git a/src/main/java/com/retrip/crew/domain/vo/DemandStatus.java b/src/main/java/com/retrip/crew/domain/vo/DemandStatus.java new file mode 100644 index 0000000..2775da1 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/vo/DemandStatus.java @@ -0,0 +1,25 @@ +package com.retrip.crew.domain.vo; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@Getter +@AllArgsConstructor +public enum DemandStatus { + PENDING("PENDING", "대기"), + APPROVED("APPROVED", "승인"), + REJECTED("REJECTED", "거절"), + CANCELED("CANCELED", "취소"); + + private final String code; + private final String viewName; + + public static DemandStatus codeOf(String code) { + return Arrays.stream(DemandStatus.values()) + .filter(participantStatus -> participantStatus.getCode().equals(code)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 코드입니다.")); + } +} 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 573ff92..277240d 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,9 +1,6 @@ package com.retrip.crew.infra.adapter.in.presentation.rest; -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.request.*; import com.retrip.crew.application.in.response.*; import com.retrip.crew.application.in.usecase.GetCrewUseCase; import com.retrip.crew.application.in.usecase.ManageCrewUseCase; @@ -14,6 +11,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; 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; @@ -69,6 +67,19 @@ public ApiResponse createDemand( return ApiResponse.created(demand); } + @GetMapping("/{crewId}/demands/pending") + @Schema(description = "크루 참여 요청 대기 목록 조회") + public ApiResponse> getDemands( + @PathVariable final UUID crewId, + @RequestParam final UUID memberId, + @PageableDefault(size = 10) Pageable pageable, + @RequestParam(name = "order", defaultValue = "DATE") DemandOrder order, + @RequestParam(name = "sort", defaultValue = "asc") String sort) { + Page demands = + manageDemandUseCase.getPendingDemands(crewId, memberId, pageable, order, sort); + return ApiResponse.ok(demands); + } + @GetMapping @Schema(description = "크루 리스트 조회") 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 8a451a6..bebfed0 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,19 +1,19 @@ 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.request.*; 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.exception.common.IllegalStateException; +import com.retrip.crew.domain.vo.DemandStatus; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import org.junit.jupiter.api.Test; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.test.util.ReflectionTestUtils; import java.util.List; import java.util.UUID; @@ -94,6 +94,31 @@ class CrewServiceTest extends ServiceTest { .isExactlyInstanceOf(IllegalStateException.class); } + @Test + void 리더가_크루_요청_대기_목록을_조회한다() { + // given + Crew crew = crewRepository.save(createCrew(LEADER_ID)); + Demand 정수 = new Demand(정수_ID, crew); + Demand 홍석 = new Demand(홍석_ID, crew); + Demand 준호 = new Demand(준호_ID, crew); + Demand 지수 = new Demand(지수_ID, crew); + Demand 혁진 = new Demand(혁진_ID, crew); + List demands = List.of(정수, 홍석, 준호, 지수, 혁진); + + ReflectionTestUtils.setField(지수, "status", DemandStatus.APPROVED); + ReflectionTestUtils.setField(혁진, "status", DemandStatus.REJECTED); + ReflectionTestUtils.setField(crew.getRecruitment(), "demands", demands); + + Pageable pageable = PageRequest.of(0, 10); + + // when + Page response = + crewService.getPendingDemands(crew.getId(), LEADER_ID, pageable, DemandOrder.DATE, "desc"); + + // then + assertThat(response.getTotalElements()).isEqualTo(3); + } + @Test void 크루를_검색_및_정렬_필터링하여_조회한다(){ //given diff --git a/src/test/java/com/retrip/crew/common/ServiceTest.java b/src/test/java/com/retrip/crew/common/ServiceTest.java index e21b146..cce29ed 100644 --- a/src/test/java/com/retrip/crew/common/ServiceTest.java +++ b/src/test/java/com/retrip/crew/common/ServiceTest.java @@ -1,6 +1,7 @@ package com.retrip.crew.common; import com.retrip.crew.application.in.CrewService; +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; @@ -25,10 +26,13 @@ public class ServiceTest { @Autowired protected CrewQueryRepository crewQueryRepository; + @Autowired + protected CrewDemandRepository demandRepository; + protected CrewService crewService; @BeforeEach void setUp() { - crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository); + crewService = new CrewService(crewRepository, crewMemberRepository, crewQueryRepository, demandRepository); } } diff --git a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java index 7f325a4..5696139 100644 --- a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java +++ b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java @@ -44,7 +44,7 @@ public static Crew createCrew(UUID leaderId) { return Crew.create( "속초 크루원 구함", "속초 친구 구합니다! 나이는 20~40.. 많은 가입 부탁드립니다.", - 5, + 100, leaderId); } From f63547654720585b1d6792235c3eebaddc32fcc9 Mon Sep 17 00:00:00 2001 From: mandykr Date: Sat, 22 Mar 2025 18:22:52 +0900 Subject: [PATCH 03/14] =?UTF-8?q?fix:=20=EB=88=84=EB=9D=BD=EB=90=9C=20stat?= =?UTF-8?q?us=20converter=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../converter/DemandStatusConverter.java | 24 +++++++++++++++++++ .../converter/RecruitmentStatusConverter.java | 24 +++++++++++++++++++ .../crew/domain/entity/Recruitment.java | 3 +++ 3 files changed, 51 insertions(+) create mode 100644 src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java create mode 100644 src/main/java/com/retrip/crew/domain/converter/RecruitmentStatusConverter.java diff --git a/src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java b/src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java new file mode 100644 index 0000000..95e1c4f --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java @@ -0,0 +1,24 @@ +package com.retrip.crew.domain.converter; + +import com.retrip.crew.domain.vo.DemandStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter(autoApply = true) +public class DemandStatusConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(DemandStatus status) { + if(status == null){ + throw new NullPointerException("crewMemberRole을 DB 칼럼으로 변경하는 과정에서 null이 포함되었습니다."); + } + return status.getCode(); + } + + @Override + public DemandStatus convertToEntityAttribute(String dbData) { + if(dbData == null){ + throw new NullPointerException("CrewMember 테이블의 role 값이 null입니다."); + } + return DemandStatus.codeOf(dbData); + } +} diff --git a/src/main/java/com/retrip/crew/domain/converter/RecruitmentStatusConverter.java b/src/main/java/com/retrip/crew/domain/converter/RecruitmentStatusConverter.java new file mode 100644 index 0000000..c1364c2 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/converter/RecruitmentStatusConverter.java @@ -0,0 +1,24 @@ +package com.retrip.crew.domain.converter; + +import com.retrip.crew.domain.vo.RecruitmentStatus; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter(autoApply = true) +public class RecruitmentStatusConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(RecruitmentStatus status) { + if(status == null){ + throw new NullPointerException("crewMemberRole을 DB 칼럼으로 변경하는 과정에서 null이 포함되었습니다."); + } + return status.getCode(); + } + + @Override + public RecruitmentStatus convertToEntityAttribute(String dbData) { + if(dbData == null){ + throw new NullPointerException("CrewMember 테이블의 role 값이 null입니다."); + } + return RecruitmentStatus.codeOf(dbData); + } +} diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index bffbb6f..e720a32 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -3,6 +3,7 @@ import com.retrip.crew.domain.exception.common.IllegalStateException; import com.retrip.crew.domain.vo.RecruitmentStatus; import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; import lombok.AccessLevel; @@ -21,6 +22,8 @@ @Embeddable public class Recruitment { private int maxMembers; + + @Column(name = "recruitment_status") private RecruitmentStatus status; @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) From 8426fcbe1243bab2cd883c475f72e636997e20be Mon Sep 17 00:00:00 2001 From: mandykr Date: Sun, 23 Mar 2025 18:42:24 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20=EC=83=81=ED=83=9C=EB=B3=84=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EC=9A=94=EC=B2=AD=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../retrip/crew/application/in/CrewService.java | 8 ++++---- ...ngDemandsResponse.java => DemandsResponse.java} | 6 +++--- .../in/usecase/ManageDemandUseCase.java | 6 +++--- .../in/presentation/rest/CrewController.java | 11 ++++++----- .../crew/application/in/CrewServiceTest.java | 14 +++++++++----- 5 files changed, 25 insertions(+), 20 deletions(-) rename src/main/java/com/retrip/crew/application/in/response/{PendingDemandsResponse.java => DemandsResponse.java} (55%) 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 782d7a0..1c410ba 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -79,13 +79,13 @@ public CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest reques } @Override - public Page getPendingDemands( - UUID crewId, UUID memberId, Pageable pageable, DemandOrder order, String sort) { + public Page getDemands( + UUID crewId, UUID memberId, String status, Pageable pageable, DemandOrder order, String sort) { Crew crew = findById(crewId); throwIfNotLeader(crew, memberId, new NotCrewLeaderException()); Page demands = demandRepository.findByCrewIdAndStatus( - crewId, DemandStatus.PENDING, PaginationUtils.createPageRequest(pageable, order.getField(), sort)); - return demands.map(d -> PendingDemandsResponse.of(crewId, d)); + crewId, DemandStatus.valueOf(status), PaginationUtils.createPageRequest(pageable, order.getField(), sort)); + return demands.map(d -> DemandsResponse.of(crewId, d)); } private static void throwIfNotLeader(Crew crew, UUID memberId, BusinessException exception) { diff --git a/src/main/java/com/retrip/crew/application/in/response/PendingDemandsResponse.java b/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java similarity index 55% rename from src/main/java/com/retrip/crew/application/in/response/PendingDemandsResponse.java rename to src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java index efe8a61..aea155f 100644 --- a/src/main/java/com/retrip/crew/application/in/response/PendingDemandsResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java @@ -5,13 +5,13 @@ import java.util.UUID; -public record PendingDemandsResponse( +public record DemandsResponse( UUID crewId, UUID demandId, UUID memberId, DemandStatus status ) { - public static PendingDemandsResponse of(UUID crewId, Demand demand) { - return new PendingDemandsResponse(crewId, demand.getId(), demand.getMemberId(), demand.getStatus()); + public static DemandsResponse of(UUID crewId, Demand demand) { + return new DemandsResponse(crewId, demand.getId(), demand.getMemberId(), demand.getStatus()); } } diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java index 9b597de..6e4d751 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java @@ -3,7 +3,7 @@ import com.retrip.crew.application.in.request.CreateDemandRequest; import com.retrip.crew.application.in.request.DemandOrder; import com.retrip.crew.application.in.response.CreateDemandResponse; -import com.retrip.crew.application.in.response.PendingDemandsResponse; +import com.retrip.crew.application.in.response.DemandsResponse; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -12,6 +12,6 @@ public interface ManageDemandUseCase { CreateDemandResponse createDemand(UUID crewId, CreateDemandRequest request); - Page getPendingDemands( - UUID crewId, UUID memberId, Pageable pageable, DemandOrder order, String sort); + Page getDemands( + UUID crewId, UUID memberId, String status, Pageable pageable, DemandOrder order, String sort); } 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 277240d..d189c43 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 @@ -67,16 +67,17 @@ public ApiResponse createDemand( return ApiResponse.created(demand); } - @GetMapping("/{crewId}/demands/pending") - @Schema(description = "크루 참여 요청 대기 목록 조회") - public ApiResponse> getDemands( + @GetMapping("/{crewId}/demands") + @Schema(description = "크루 참여 요청 목록 조회") + public ApiResponse> getDemands( @PathVariable final UUID crewId, @RequestParam final UUID memberId, + @RequestParam final String status, @PageableDefault(size = 10) Pageable pageable, @RequestParam(name = "order", defaultValue = "DATE") DemandOrder order, @RequestParam(name = "sort", defaultValue = "asc") String sort) { - Page demands = - manageDemandUseCase.getPendingDemands(crewId, memberId, pageable, order, sort); + Page demands = + manageDemandUseCase.getDemands(crewId, memberId, status, pageable, order, sort); return ApiResponse.ok(demands); } 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 bebfed0..012f2bd 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -10,6 +10,8 @@ import com.retrip.crew.domain.vo.DemandStatus; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -94,8 +96,9 @@ class CrewServiceTest extends ServiceTest { .isExactlyInstanceOf(IllegalStateException.class); } - @Test - void 리더가_크루_요청_대기_목록을_조회한다() { + @ParameterizedTest + @CsvSource({"PENDING,2", "APPROVED,2", "REJECTED,1"}) + void 리더가_크루_요청_목록을_조회한다(String status, int expected) { // given Crew crew = crewRepository.save(createCrew(LEADER_ID)); Demand 정수 = new Demand(정수_ID, crew); @@ -105,6 +108,7 @@ class CrewServiceTest extends ServiceTest { Demand 혁진 = new Demand(혁진_ID, crew); List demands = List.of(정수, 홍석, 준호, 지수, 혁진); + ReflectionTestUtils.setField(준호, "status", DemandStatus.APPROVED); ReflectionTestUtils.setField(지수, "status", DemandStatus.APPROVED); ReflectionTestUtils.setField(혁진, "status", DemandStatus.REJECTED); ReflectionTestUtils.setField(crew.getRecruitment(), "demands", demands); @@ -112,11 +116,11 @@ class CrewServiceTest extends ServiceTest { Pageable pageable = PageRequest.of(0, 10); // when - Page response = - crewService.getPendingDemands(crew.getId(), LEADER_ID, pageable, DemandOrder.DATE, "desc"); + Page response = + crewService.getDemands(crew.getId(), LEADER_ID, status, pageable, DemandOrder.DATE, "desc"); // then - assertThat(response.getTotalElements()).isEqualTo(3); + assertThat(response.getTotalElements()).isEqualTo(expected); } @Test From 5a9e34aaed144e0c53457598ea8c8ad8d09cb9fa Mon Sep 17 00:00:00 2001 From: mandykr Date: Sun, 23 Mar 2025 19:22:32 +0900 Subject: [PATCH 05/14] =?UTF-8?q?chore:=20API=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=91=EB=8B=B5=EC=9D=98=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=84=A4=EB=AA=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/request/CreateDemandRequest.java | 3 +++ .../in/response/ChangeRecruitmentStatusResponse.java | 1 + .../application/in/response/CreateDemandResponse.java | 5 +++++ .../crew/application/in/response/CrewUpdateResponse.java | 1 + .../crew/application/in/response/DemandsResponse.java | 8 ++++++++ 5 files changed, 18 insertions(+) diff --git a/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java b/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java index 537f1ea..5fb4be3 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java @@ -1,7 +1,10 @@ package com.retrip.crew.application.in.request; +import io.swagger.v3.oas.annotations.media.Schema; + import java.util.UUID; +@Schema(description = "크루 참여 요청 생성 Request") public record CreateDemandRequest( UUID memberId ) { diff --git a/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java b/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java index 0a56b73..81b3ce3 100644 --- a/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java @@ -6,6 +6,7 @@ import java.util.UUID; +@Schema(description = "크루 모집 상태 변경 Response") public record ChangeRecruitmentStatusResponse( @Schema(description = "크루 ID") UUID id, diff --git a/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java index 1c42d65..a31fb16 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java @@ -1,11 +1,16 @@ package com.retrip.crew.application.in.response; import com.retrip.crew.domain.entity.Demand; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; +@Schema(description = "크루 참여 요청 생성 Response") public record CreateDemandResponse( + @Schema(description = "크루 ID") UUID crewId, + + @Schema(description = "참여 요청자 ID") UUID memberId ) { public static CreateDemandResponse of(UUID crewId, Demand demand) { diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java index 2e513c6..e156ee3 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java @@ -6,6 +6,7 @@ import java.util.UUID; +@Schema(description = "크루 정보 변경 Response") public record CrewUpdateResponse( @Schema(description = "크루 ID") UUID id, diff --git a/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java b/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java index aea155f..ff24ed9 100644 --- a/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java @@ -2,13 +2,21 @@ import com.retrip.crew.domain.entity.Demand; import com.retrip.crew.domain.vo.DemandStatus; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; public record DemandsResponse( + @Schema(description = "크루 ID") UUID crewId, + + @Schema(description = "참여 요청 ID") UUID demandId, + + @Schema(description = "참여 요청자 ID") UUID memberId, + + @Schema(description = "참여 요청 상태") DemandStatus status ) { public static DemandsResponse of(UUID crewId, Demand demand) { From de4ec16df34f44a78a8457132f02a7ef5ec7e130 Mon Sep 17 00:00:00 2001 From: mandykr Date: Mon, 24 Mar 2025 12:23:35 +0900 Subject: [PATCH 06/14] =?UTF-8?q?feat:=20=EC=B0=B8=EC=97=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=EC=9E=90=EA=B0=80=20=EC=86=8D=ED=95=9C=20=ED=81=AC?= =?UTF-8?q?=EB=A3=A8=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 15 +++++ .../in/response/CrewsOfDemandResponse.java | 27 ++++++++ .../in/usecase/ManageDemandUseCase.java | 4 ++ .../out/repository/CrewQueryRepository.java | 6 ++ .../crew/domain/entity/Announcements.java | 2 - .../in/presentation/rest/CrewController.java | 14 ++++ .../mysql/query/CrewQuerydslRepository.java | 65 ++++++++++++++++++- .../crew/application/in/CrewServiceTest.java | 27 +++++++- src/test/{java => }/resources/application.yml | 6 +- 9 files changed, 155 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/response/CrewsOfDemandResponse.java rename src/test/{java => }/resources/application.yml (89%) 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 1c410ba..738679a 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -16,6 +16,7 @@ import com.retrip.crew.domain.exception.CrewNotFoundException; 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.DemandStatus; @@ -88,12 +89,26 @@ public Page getDemands( return demands.map(d -> DemandsResponse.of(crewId, d)); } + @Override + public Page getCrewsOfDemand( + UUID crewId, UUID demandId, UUID memberId, Pageable pageable, CrewOrder order, String sort) { + Crew crew = findById(crewId); + throwIfNotLeader(crew, memberId, new NotCrewLeaderException()); + Demand demand = findDemandById(demandId); + return crewQueryRepository.findAllContainsMember(pageable, demand.getMemberId()); + } + private static void throwIfNotLeader(Crew crew, UUID memberId, BusinessException exception) { if (!crew.getCrewMembers().isLeader(memberId)) { throw exception; } } + private Demand findDemandById(UUID demandId) { + return demandRepository.findById(demandId) + .orElseThrow(() -> new EntityNotFoundException("참여 요청 엔티티를 찾을 수 없습니다.")); + } + @Override @Transactional(readOnly = true) public ScrollPageResponse getCrews(Pageable pageable, String keyword, CrewOrder order, String sort) { diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewsOfDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/CrewsOfDemandResponse.java new file mode 100644 index 0000000..3c3a528 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/CrewsOfDemandResponse.java @@ -0,0 +1,27 @@ +package com.retrip.crew.application.in.response; + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +@Schema(description = "참여자가 속한 크루 목록 조회 Response") +public record CrewsOfDemandResponse( + @Schema(description = "참여 요청자 ID") + UUID memberId, + + @Schema(description = "크루 ID") + UUID id, + + @Schema(description = "크루 타이틀") + String title, + + @Schema(description = "크루 리더 ID") + UUID leaderId, + + @Schema(description = "크루원 현재 인원수") + long memberCount, + + @Schema(description = "크루원 최대 인원수") + int maxMemberCount +) { +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java index 6e4d751..bbef31e 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java @@ -1,8 +1,10 @@ package com.retrip.crew.application.in.usecase; import com.retrip.crew.application.in.request.CreateDemandRequest; +import com.retrip.crew.application.in.request.CrewOrder; import com.retrip.crew.application.in.request.DemandOrder; import com.retrip.crew.application.in.response.CreateDemandResponse; +import com.retrip.crew.application.in.response.CrewsOfDemandResponse; import com.retrip.crew.application.in.response.DemandsResponse; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -14,4 +16,6 @@ public interface ManageDemandUseCase { Page getDemands( UUID crewId, UUID memberId, String status, Pageable pageable, DemandOrder order, String sort); + + Page getCrewsOfDemand(UUID crewId, UUID demandId, UUID memberId, Pageable pageable, CrewOrder order, String sort); } diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java index 6a1c089..1e41412 100644 --- a/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java @@ -1,10 +1,16 @@ package com.retrip.crew.application.out.repository; import com.retrip.crew.application.in.response.CrewListResponse; +import com.retrip.crew.application.in.response.CrewsOfDemandResponse; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import java.util.UUID; + public interface CrewQueryRepository { Slice getCrews(Pageable pageable, String keyword); Long getCrewCount(String keyword); + + Page findAllContainsMember(Pageable pageable, UUID memberId); } diff --git a/src/main/java/com/retrip/crew/domain/entity/Announcements.java b/src/main/java/com/retrip/crew/domain/entity/Announcements.java index 9a85944..e354fd6 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Announcements.java +++ b/src/main/java/com/retrip/crew/domain/entity/Announcements.java @@ -3,9 +3,7 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; import jakarta.persistence.OneToMany; -import lombok.AccessLevel; import lombok.Getter; -import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; 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 d189c43..56bfc9e 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 @@ -81,6 +81,20 @@ public ApiResponse> getDemands( return ApiResponse.ok(demands); } + @GetMapping("/{crewId}/demands/{demandId}/crews") + @Schema(description = "크루 참여 요청자가 속한 크루 목록 조회") + public ApiResponse> getCrewsOfDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId, + @PageableDefault(size = 10) Pageable pageable, + @RequestParam(name = "order", defaultValue = "DATE") CrewOrder order, + @RequestParam(name = "sort", defaultValue = "asc") String sort) { + Page demands = + manageDemandUseCase.getCrewsOfDemand(crewId, demandId, memberId, pageable, order, sort); + return ApiResponse.ok(demands); + } + @GetMapping @Schema(description = "크루 리스트 조회") public ResponseEntity>> getCrews( diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java index 13e7a09..8d1e230 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java @@ -7,18 +7,25 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.NumberPath; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import com.retrip.crew.application.in.request.CrewOrder; import com.retrip.crew.application.in.response.CrewListResponse; +import com.retrip.crew.application.in.response.CrewsOfDemandResponse; import com.retrip.crew.application.out.repository.CrewQueryRepository; +import com.retrip.crew.domain.entity.CrewMemberRole; import com.retrip.crew.domain.entity.QCrewMember; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; +import org.springframework.data.support.PageableExecutionUtils; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.UUID; import static com.querydsl.jpa.JPAExpressions.select; import static com.retrip.crew.domain.entity.QCrew.crew; @@ -39,11 +46,16 @@ public Slice getCrews(Pageable pageable, String keyword) { List fetch = query.select(Projections.constructor(CrewListResponse.class, crew.id.as("id"), crew.title.value.as("title"), - crewMember.id.as("leaderId"), + JPAExpressions.select(subCrewMember.memberId.as("leaderId")) + .from(subCrewMember) + .where( + subCrewMember.crewMemberRole.eq(CrewMemberRole.LEADER), + subCrewMember.crew.id.eq(crewMember.crew.id) + ), ExpressionUtils.as( select(subCrewMember.count()) - .from(subCrewMember) - .where(subCrewMember.crew.id.eq(crew.id)) ,MemberCountAlias), + .from(subCrewMember) + .where(subCrewMember.crew.id.eq(crew.id)), MemberCountAlias), crew.recruitment.maxMembers.as("maxMemberCount") ) ) @@ -73,6 +85,53 @@ private BooleanExpression crewTitleContains(String title) { return title == null ? null : crew.title.value.contains(title); } + @Override + public Page findAllContainsMember(Pageable pageable, UUID memberId) { + OrderSpecifier orderSpecifier = createCrewOrderSpecifier(pageable); + Expressions.numberPath(Long.class, "memberCount"); + QCrewMember subCrewMember = new QCrewMember("subCrewMember"); + List result = + query.select( + Projections.constructor(CrewsOfDemandResponse.class, + crewMember.memberId.as("memberId"), + crew.id.as("id"), + crew.title.value.as("title"), + JPAExpressions.select(subCrewMember.memberId.as("leaderId")) + .from(subCrewMember) + .where( + subCrewMember.crewMemberRole.eq(CrewMemberRole.LEADER), + subCrewMember.crew.id.eq(crewMember.crew.id) + ), + JPAExpressions.select(subCrewMember.count().as("memberCount")) + .from(subCrewMember) + .where(subCrewMember.crew.id.eq(crew.id)), + crew.recruitment.maxMembers.as("maxMemberCount") + ) + ) + .from(crew) + .rightJoin(crewMember) + .on(crewMember.crew.id.eq(crew.id)) + .where( + crewMemberContains(memberId) + ) + .limit(pageable.getPageSize() + 1) + .offset(pageable.getOffset()) + .orderBy(orderSpecifier) + .fetch(); + + JPAQuery countQuery = query.select(crew.count()) + .from(crew) + .rightJoin(crewMember).on(crewMember.crew.id.eq(crew.id)) + .where( + crewMemberContains(memberId) + ); + return PageableExecutionUtils.getPage(result, pageable, countQuery::fetchOne); + } + + private BooleanExpression crewMemberContains(UUID memberId) { + return memberId == null ? null : crewMember.memberId.eq(memberId); + } + private OrderSpecifier createCrewOrderSpecifier(Pageable pageable) { if (!pageable.getSort().isEmpty()) { for (Sort.Order order : pageable.getSort()) { 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 012f2bd..c31973d 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -124,7 +124,30 @@ class CrewServiceTest extends ServiceTest { } @Test - void 크루를_검색_및_정렬_필터링하여_조회한다(){ + void 참여_요청자가_속한_크루_목록을_조회한다() { + // given + Crew crew = createCrew(LEADER_ID); + Demand demand = new Demand(홍석_ID, crew); + ReflectionTestUtils.setField(crew.getRecruitment(), "demands", List.of(demand)); + + crewRepository.save(crew); + crewRepository.save(createCrewWithMembers(MEMBER_ID)); + crewRepository.save(createCrewWithMembers(MEMBER_ID)); + crewRepository.save(createCrewWithMembers(MEMBER_ID)); + crewRepository.save(createCrewWithMembers(MEMBER_ID)); + crewRepository.save(createCrewWithMembers(MEMBER_ID)); + Pageable pageable = PageRequest.of(0, 10); + + // when + Page response = + crewService.getCrewsOfDemand(crew.getId(), demand.getId(), LEADER_ID, pageable, CrewOrder.DATE, "desc"); + + // then + assertThat(response.getTotalElements()).isEqualTo(5); + } + + @Test + void 크루를_검색_및_정렬_필터링하여_조회한다() { //given List requests = createMultipleCrews( 10, @@ -154,7 +177,7 @@ class CrewServiceTest extends ServiceTest { } @Test - void 크루_상세를_조회한다(){ + void 크루_상세를_조회한다() { //given CrewCreateRequest request = createCrewRequest( MEMBER_ID, diff --git a/src/test/java/resources/application.yml b/src/test/resources/application.yml similarity index 89% rename from src/test/java/resources/application.yml rename to src/test/resources/application.yml index 1efe678..c5a6870 100644 --- a/src/test/java/resources/application.yml +++ b/src/test/resources/application.yml @@ -5,11 +5,9 @@ spring: datasource: driver-class-name: org.h2.Driver url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL - username: sa - password: jpa: hibernate: - ddl-auto: create-drop + ddl-auto: create properties: hibernate: show_sql: true @@ -17,9 +15,9 @@ spring: dialect: org.hibernate.dialect.MySQL8Dialect default_batch_fetch_size: 100 open-in-view: false - #logging logging: level: org.hibernate.type.descriptor.sql: trace + org.hibernate.orm.jdbc.bind: TRACE org.springframework.web.client.RestTemplate: DEBUG From f6ea90ea0aa338b40f0157a7253e5a114ddd8f80 Mon Sep 17 00:00:00 2001 From: mandykr Date: Mon, 24 Mar 2025 17:37:03 +0900 Subject: [PATCH 07/14] =?UTF-8?q?feat:=20=EC=B0=B8=EC=97=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=B7=A8=EC=86=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 13 ++++- .../in/usecase/ManageDemandUseCase.java | 2 + .../out/repository/CrewDemandRepository.java | 3 + .../com/retrip/crew/domain/entity/Crew.java | 4 ++ .../com/retrip/crew/domain/entity/Demand.java | 9 +++ .../crew/domain/entity/Recruitment.java | 16 ++++++ .../in/presentation/rest/CrewController.java | 10 ++++ .../presentation/rest/common/ApiResponse.java | 4 ++ .../crew/domain/entity/RecruitmentTest.java | 55 ++++++++++++++++++- 9 files changed, 112 insertions(+), 4 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 738679a..17fd1a7 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -94,7 +94,7 @@ public Page getCrewsOfDemand( UUID crewId, UUID demandId, UUID memberId, Pageable pageable, CrewOrder order, String sort) { Crew crew = findById(crewId); throwIfNotLeader(crew, memberId, new NotCrewLeaderException()); - Demand demand = findDemandById(demandId); + Demand demand = findDemandByIdAndCrewId(demandId, crewId); return crewQueryRepository.findAllContainsMember(pageable, demand.getMemberId()); } @@ -104,8 +104,15 @@ private static void throwIfNotLeader(Crew crew, UUID memberId, BusinessException } } - private Demand findDemandById(UUID demandId) { - return demandRepository.findById(demandId) + @Override + public void cancelDemand(UUID crewId, UUID demandId, UUID memberId) { + Crew crew = findById(crewId); + Demand demand = findDemandByIdAndCrewId(demandId, crewId); + crew.cancelDemand(demand); + } + + private Demand findDemandByIdAndCrewId(UUID demandId, UUID crewId) { + return demandRepository.findByIdAndCrewId(demandId, crewId) .orElseThrow(() -> new EntityNotFoundException("참여 요청 엔티티를 찾을 수 없습니다.")); } diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java index bbef31e..ce2da92 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java @@ -18,4 +18,6 @@ Page getDemands( UUID crewId, UUID memberId, String status, Pageable pageable, DemandOrder order, String sort); Page getCrewsOfDemand(UUID crewId, UUID demandId, UUID memberId, Pageable pageable, CrewOrder order, String sort); + + void cancelDemand(UUID crewId, UUID demandId, UUID memberId); } diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java index 6813888..eb27226 100644 --- a/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java @@ -6,8 +6,11 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; import java.util.UUID; public interface CrewDemandRepository extends JpaRepository { Page findByCrewIdAndStatus(UUID crewId, DemandStatus pending, Pageable pageRequest); + + Optional findByIdAndCrewId(UUID demandId, 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 f67dcdf..4071d77 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -81,4 +81,8 @@ public Demand demand(UUID memberId) { public String getDescription(){ return description.getValue(); } + + public void cancelDemand(Demand demand) { + recruitment.cancelDemand(demand); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Demand.java b/src/main/java/com/retrip/crew/domain/entity/Demand.java index c097142..e70fbcc 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Demand.java +++ b/src/main/java/com/retrip/crew/domain/entity/Demand.java @@ -8,6 +8,7 @@ import java.util.UUID; +import static com.retrip.crew.domain.vo.DemandStatus.CANCELED; import static com.retrip.crew.domain.vo.DemandStatus.PENDING; @Entity @@ -35,4 +36,12 @@ public Demand(UUID memberId, Crew crew) { this.status = PENDING; this.crew = crew; } + + public boolean isNotPending() { + return this.status != PENDING; + } + + public void cancel() { + this.status = CANCELED; + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index e720a32..abf7026 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -1,6 +1,7 @@ package com.retrip.crew.domain.entity; import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.exception.common.InvalidValueException; import com.retrip.crew.domain.vo.RecruitmentStatus; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; @@ -68,4 +69,19 @@ private boolean isDuplicate(UUID memberId) { .map(Demand::getMemberId) .anyMatch(id -> id.equals(memberId)); } + + public void cancelDemand(Demand demand) { + Demand find = findDemand(demand); + if (find.isNotPending()) { + throw new IllegalStateException("참여 요청이 대기중이 아니면 취소할 수 없습니다."); + } + find.cancel(); + } + + private Demand findDemand(Demand demand) { + return demands.stream() + .filter(d -> d.equals(demand)) + .findFirst() + .orElseThrow(() -> new InvalidValueException("참여 요청을 찾을 수 없습니다.")); + } } 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 56bfc9e..f6c7053 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 @@ -67,6 +67,16 @@ public ApiResponse createDemand( return ApiResponse.created(demand); } + @PutMapping("/{crewId}/demands/{demandId}/cancel") + @Schema(description = "크루 참여 요청 취소") + public ApiResponse cancelDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId) { + manageDemandUseCase.cancelDemand(crewId, demandId, memberId); + return ApiResponse.noContent(); + } + @GetMapping("/{crewId}/demands") @Schema(description = "크루 참여 요청 목록 조회") public ApiResponse> getDemands( diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java index b35985d..8749da5 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/common/ApiResponse.java @@ -23,6 +23,10 @@ public static ApiResponse ok(T data) { return success(data, OK); } + public static ApiResponse noContent() { + return success(null, NO_CONTENT); + } + public static ApiResponse of(T data, HttpStatus status) { return success(data, status); } diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java index 298ebf5..6ceacdd 100644 --- a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -1,8 +1,17 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.vo.DemandStatus; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.springframework.test.util.ReflectionTestUtils; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import java.util.List; + +import static com.retrip.crew.common.fixture.CrewFixture.*; +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE; class RecruitmentTest { @Test @@ -10,4 +19,48 @@ class RecruitmentTest { assertThatCode(() -> new Recruitment(100)) .doesNotThrowAnyException(); } + + @Test + void 참여_요청을_취소한다() { + // given + Crew crew = createCrew(LEADER_ID); + Recruitment recruitment = new Recruitment(100); + Demand demand = new Demand(정수_ID, crew); + List demands = List.of( + demand, + new Demand(홍석_ID, crew), + new Demand(준호_ID, crew), + new Demand(지수_ID, crew), + new Demand(혁진_ID, crew) + ); + ReflectionTestUtils.setField(recruitment, "demands", demands); + + // when + recruitment.cancelDemand(demand); + + // then + assertThat(demand.getStatus()).isEqualTo(DemandStatus.CANCELED); + } + + @ParameterizedTest + @EnumSource(mode = EXCLUDE, names = {"PENDING"}) + void 대기_상태가_아니면_참여_요청을_취소한다(DemandStatus status) { + // given + Crew crew = createCrew(LEADER_ID); + Recruitment recruitment = new Recruitment(100); + Demand demand = new Demand(정수_ID, crew); + List demands = List.of( + demand, + new Demand(홍석_ID, crew), + new Demand(준호_ID, crew), + new Demand(지수_ID, crew), + new Demand(혁진_ID, crew) + ); + ReflectionTestUtils.setField(demand, "status", status); + ReflectionTestUtils.setField(recruitment, "demands", demands); + + // when, then + assertThatThrownBy(() -> recruitment.cancelDemand(demand)) + .isExactlyInstanceOf(IllegalStateException.class); + } } From 6dba95b93db6e8013c56d8039380df949b4044ff Mon Sep 17 00:00:00 2001 From: mandykr Date: Tue, 25 Mar 2025 09:43:21 +0900 Subject: [PATCH 08/14] =?UTF-8?q?feat:=20=EC=B0=B8=EC=97=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EA=B1=B0=EC=A0=88=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20application.in=20=ED=8C=A8=ED=82=A4=EC=A7=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 20 +++++++++++++-- .../request/{ => crew}/CrewCreateRequest.java | 2 +- .../in/request/{ => crew}/CrewOrder.java | 2 +- .../request/{ => crew}/CrewUpdateRequest.java | 2 +- .../{ => demand}/CreateDemandRequest.java | 2 +- .../in/request/{ => demand}/DemandOrder.java | 2 +- .../{ => crew}/CrewCreateResponse.java | 2 +- .../{ => crew}/CrewDetailResponse.java | 2 +- .../response/{ => crew}/CrewListResponse.java | 2 +- .../{ => crew}/CrewUpdateResponse.java | 2 +- .../ChangeRecruitmentStatusResponse.java | 2 +- .../{ => demand}/CreateDemandResponse.java | 2 +- .../{ => demand}/CrewsOfDemandResponse.java | 2 +- .../{ => demand}/DemandsResponse.java | 3 ++- .../response/demand/RejectDemandResponse.java | 25 +++++++++++++++++++ .../in/usecase/GetCrewUseCase.java | 6 ++--- .../in/usecase/ManageCrewUseCase.java | 8 +++--- .../in/usecase/ManageDemandUseCase.java | 15 ++++++----- .../in/usecase/UpdateRecruitmentUseCase.java | 2 +- .../out/repository/CrewQueryRepository.java | 4 +-- .../com/retrip/crew/domain/entity/Crew.java | 4 +++ .../com/retrip/crew/domain/entity/Demand.java | 7 ++++-- .../crew/domain/entity/Recruitment.java | 8 ++++++ .../in/presentation/rest/CrewController.java | 22 ++++++++++++++-- .../mysql/query/CrewQuerydslRepository.java | 6 ++--- .../crew/application/in/CrewServiceTest.java | 14 +++++++++-- .../crew/common/fixture/CrewFixture.java | 2 +- 27 files changed, 129 insertions(+), 41 deletions(-) rename src/main/java/com/retrip/crew/application/in/request/{ => crew}/CrewCreateRequest.java (93%) rename src/main/java/com/retrip/crew/application/in/request/{ => crew}/CrewOrder.java (77%) rename src/main/java/com/retrip/crew/application/in/request/{ => crew}/CrewUpdateRequest.java (90%) rename src/main/java/com/retrip/crew/application/in/request/{ => demand}/CreateDemandRequest.java (78%) rename src/main/java/com/retrip/crew/application/in/request/{ => demand}/DemandOrder.java (76%) rename src/main/java/com/retrip/crew/application/in/response/{ => crew}/CrewCreateResponse.java (95%) rename src/main/java/com/retrip/crew/application/in/response/{ => crew}/CrewDetailResponse.java (97%) rename src/main/java/com/retrip/crew/application/in/response/{ => crew}/CrewListResponse.java (91%) rename src/main/java/com/retrip/crew/application/in/response/{ => crew}/CrewUpdateResponse.java (95%) rename src/main/java/com/retrip/crew/application/in/response/{ => demand}/ChangeRecruitmentStatusResponse.java (91%) rename src/main/java/com/retrip/crew/application/in/response/{ => demand}/CreateDemandResponse.java (90%) rename src/main/java/com/retrip/crew/application/in/response/{ => demand}/CrewsOfDemandResponse.java (92%) rename src/main/java/com/retrip/crew/application/in/response/{ => demand}/DemandsResponse.java (85%) create mode 100644 src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.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 17fd1a7..7945c10 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -1,7 +1,15 @@ package com.retrip.crew.application.in; -import com.retrip.crew.application.in.request.*; -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.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; @@ -111,6 +119,14 @@ public void cancelDemand(UUID crewId, UUID demandId, UUID memberId) { crew.cancelDemand(demand); } + @Override + public RejectDemandResponse rejectDemand(UUID crewId, UUID demandId, UUID memberId) { + Crew crew = findById(crewId); + Demand demand = findDemandByIdAndCrewId(demandId, crewId); + crew.rejectDemand(demand); + return RejectDemandResponse.of(demand); + } + private Demand findDemandByIdAndCrewId(UUID demandId, UUID crewId) { return demandRepository.findByIdAndCrewId(demandId, crewId) .orElseThrow(() -> new EntityNotFoundException("참여 요청 엔티티를 찾을 수 없습니다.")); diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java b/src/main/java/com/retrip/crew/application/in/request/crew/CrewCreateRequest.java similarity index 93% rename from src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java rename to src/main/java/com/retrip/crew/application/in/request/crew/CrewCreateRequest.java index 145bd3a..ce499e2 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewCreateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/crew/CrewCreateRequest.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.request; +package com.retrip.crew.application.in.request.crew; import com.retrip.crew.domain.entity.Crew; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewOrder.java b/src/main/java/com/retrip/crew/application/in/request/crew/CrewOrder.java similarity index 77% rename from src/main/java/com/retrip/crew/application/in/request/CrewOrder.java rename to src/main/java/com/retrip/crew/application/in/request/crew/CrewOrder.java index 4baabff..6a8de25 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewOrder.java +++ b/src/main/java/com/retrip/crew/application/in/request/crew/CrewOrder.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.request; +package com.retrip.crew.application.in.request.crew; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/retrip/crew/application/in/request/CrewUpdateRequest.java b/src/main/java/com/retrip/crew/application/in/request/crew/CrewUpdateRequest.java similarity index 90% rename from src/main/java/com/retrip/crew/application/in/request/CrewUpdateRequest.java rename to src/main/java/com/retrip/crew/application/in/request/crew/CrewUpdateRequest.java index 9d5a509..eb94d66 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CrewUpdateRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/crew/CrewUpdateRequest.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.request; +package com.retrip.crew.application.in.request.crew; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Size; diff --git a/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java b/src/main/java/com/retrip/crew/application/in/request/demand/CreateDemandRequest.java similarity index 78% rename from src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java rename to src/main/java/com/retrip/crew/application/in/request/demand/CreateDemandRequest.java index 5fb4be3..44f3ec6 100644 --- a/src/main/java/com/retrip/crew/application/in/request/CreateDemandRequest.java +++ b/src/main/java/com/retrip/crew/application/in/request/demand/CreateDemandRequest.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.request; +package com.retrip.crew.application.in.request.demand; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/com/retrip/crew/application/in/request/DemandOrder.java b/src/main/java/com/retrip/crew/application/in/request/demand/DemandOrder.java similarity index 76% rename from src/main/java/com/retrip/crew/application/in/request/DemandOrder.java rename to src/main/java/com/retrip/crew/application/in/request/demand/DemandOrder.java index 3d1e090..a83e287 100644 --- a/src/main/java/com/retrip/crew/application/in/request/DemandOrder.java +++ b/src/main/java/com/retrip/crew/application/in/request/demand/DemandOrder.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.request; +package com.retrip.crew.application.in.request.demand; import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java b/src/main/java/com/retrip/crew/application/in/response/crew/CrewCreateResponse.java similarity index 95% rename from src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java rename to src/main/java/com/retrip/crew/application/in/response/crew/CrewCreateResponse.java index ad231f9..d928c7c 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewCreateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/crew/CrewCreateResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.crew; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.vo.RecruitmentStatus; diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java b/src/main/java/com/retrip/crew/application/in/response/crew/CrewDetailResponse.java similarity index 97% rename from src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java rename to src/main/java/com/retrip/crew/application/in/response/crew/CrewDetailResponse.java index bc95740..9fca2a1 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewDetailResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/crew/CrewDetailResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.crew; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMember; diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewListResponse.java b/src/main/java/com/retrip/crew/application/in/response/crew/CrewListResponse.java similarity index 91% rename from src/main/java/com/retrip/crew/application/in/response/CrewListResponse.java rename to src/main/java/com/retrip/crew/application/in/response/crew/CrewListResponse.java index 7376a82..bf4588e 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewListResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/crew/CrewListResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.crew; import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java b/src/main/java/com/retrip/crew/application/in/response/crew/CrewUpdateResponse.java similarity index 95% rename from src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java rename to src/main/java/com/retrip/crew/application/in/response/crew/CrewUpdateResponse.java index e156ee3..28f8e08 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewUpdateResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/crew/CrewUpdateResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.crew; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.vo.RecruitmentStatus; diff --git a/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java b/src/main/java/com/retrip/crew/application/in/response/demand/ChangeRecruitmentStatusResponse.java similarity index 91% rename from src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java rename to src/main/java/com/retrip/crew/application/in/response/demand/ChangeRecruitmentStatusResponse.java index 81b3ce3..2da6f59 100644 --- a/src/main/java/com/retrip/crew/application/in/response/ChangeRecruitmentStatusResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/demand/ChangeRecruitmentStatusResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.demand; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.vo.RecruitmentStatus; diff --git a/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/demand/CreateDemandResponse.java similarity index 90% rename from src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java rename to src/main/java/com/retrip/crew/application/in/response/demand/CreateDemandResponse.java index a31fb16..2945e4f 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CreateDemandResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/demand/CreateDemandResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.demand; import com.retrip.crew.domain.entity.Demand; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/com/retrip/crew/application/in/response/CrewsOfDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/demand/CrewsOfDemandResponse.java similarity index 92% rename from src/main/java/com/retrip/crew/application/in/response/CrewsOfDemandResponse.java rename to src/main/java/com/retrip/crew/application/in/response/demand/CrewsOfDemandResponse.java index 3c3a528..142e2cf 100644 --- a/src/main/java/com/retrip/crew/application/in/response/CrewsOfDemandResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/demand/CrewsOfDemandResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.demand; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java b/src/main/java/com/retrip/crew/application/in/response/demand/DemandsResponse.java similarity index 85% rename from src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java rename to src/main/java/com/retrip/crew/application/in/response/demand/DemandsResponse.java index ff24ed9..651289f 100644 --- a/src/main/java/com/retrip/crew/application/in/response/DemandsResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/demand/DemandsResponse.java @@ -1,4 +1,4 @@ -package com.retrip.crew.application.in.response; +package com.retrip.crew.application.in.response.demand; import com.retrip.crew.domain.entity.Demand; import com.retrip.crew.domain.vo.DemandStatus; @@ -6,6 +6,7 @@ import java.util.UUID; +@Schema(description = "참여 요청 목록 조회 Response") public record DemandsResponse( @Schema(description = "크루 ID") UUID crewId, diff --git a/src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.java new file mode 100644 index 0000000..b93afbb --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.java @@ -0,0 +1,25 @@ +package com.retrip.crew.application.in.response.demand; + +import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.vo.DemandStatus; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +public record RejectDemandResponse( + @Schema(description = "크루 ID") + UUID crewId, + + @Schema(description = "참여 요청 ID") + UUID demandId, + + @Schema(description = "참여 요청자 ID") + UUID memberId, + + @Schema(description = "참여 요청 상태") + DemandStatus status +) { + public static RejectDemandResponse of(Demand demand) { + return new RejectDemandResponse(demand.getCrew().getId(), demand.getId(), demand.getMemberId(), demand.getStatus()); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java index 34b9bc4..c158c73 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/GetCrewUseCase.java @@ -1,8 +1,8 @@ package com.retrip.crew.application.in.usecase; -import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.response.CrewDetailResponse; -import com.retrip.crew.application.in.response.CrewListResponse; +import com.retrip.crew.application.in.request.crew.CrewOrder; +import com.retrip.crew.application.in.response.crew.CrewDetailResponse; +import com.retrip.crew.application.in.response.crew.CrewListResponse; import com.retrip.crew.infra.adapter.in.presentation.rest.common.ScrollPageResponse; import java.util.UUID; import org.springframework.data.domain.Pageable; 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 b8fb910..61df32d 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 @@ -1,9 +1,9 @@ package com.retrip.crew.application.in.usecase; -import com.retrip.crew.application.in.request.CrewCreateRequest; -import com.retrip.crew.application.in.request.CrewUpdateRequest; -import com.retrip.crew.application.in.response.CrewCreateResponse; -import com.retrip.crew.application.in.response.CrewUpdateResponse; +import com.retrip.crew.application.in.request.crew.CrewCreateRequest; +import com.retrip.crew.application.in.request.crew.CrewUpdateRequest; +import com.retrip.crew.application.in.response.crew.CrewCreateResponse; +import com.retrip.crew.application.in.response.crew.CrewUpdateResponse; import java.util.UUID; diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java index ce2da92..5dd828a 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java @@ -1,11 +1,12 @@ package com.retrip.crew.application.in.usecase; -import com.retrip.crew.application.in.request.CreateDemandRequest; -import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.request.DemandOrder; -import com.retrip.crew.application.in.response.CreateDemandResponse; -import com.retrip.crew.application.in.response.CrewsOfDemandResponse; -import com.retrip.crew.application.in.response.DemandsResponse; +import com.retrip.crew.application.in.request.demand.CreateDemandRequest; +import com.retrip.crew.application.in.request.crew.CrewOrder; +import com.retrip.crew.application.in.request.demand.DemandOrder; +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 org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -20,4 +21,6 @@ Page getDemands( Page getCrewsOfDemand(UUID crewId, UUID demandId, UUID memberId, Pageable pageable, CrewOrder order, String sort); void cancelDemand(UUID crewId, UUID demandId, UUID memberId); + + RejectDemandResponse rejectDemand(UUID crewId, UUID demandId, UUID memberId); } diff --git a/src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java index f96e831..4f03516 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/UpdateRecruitmentUseCase.java @@ -1,6 +1,6 @@ package com.retrip.crew.application.in.usecase; -import com.retrip.crew.application.in.response.ChangeRecruitmentStatusResponse; +import com.retrip.crew.application.in.response.demand.ChangeRecruitmentStatusResponse; import java.util.UUID; diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java index 1e41412..836dcec 100644 --- a/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewQueryRepository.java @@ -1,7 +1,7 @@ package com.retrip.crew.application.out.repository; -import com.retrip.crew.application.in.response.CrewListResponse; -import com.retrip.crew.application.in.response.CrewsOfDemandResponse; +import com.retrip.crew.application.in.response.crew.CrewListResponse; +import com.retrip.crew.application.in.response.demand.CrewsOfDemandResponse; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; 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 4071d77..16a7c27 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -85,4 +85,8 @@ public String getDescription(){ public void cancelDemand(Demand demand) { recruitment.cancelDemand(demand); } + + public void rejectDemand(Demand demand) { + recruitment.rejectDemand(demand); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Demand.java b/src/main/java/com/retrip/crew/domain/entity/Demand.java index e70fbcc..3542ab8 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Demand.java +++ b/src/main/java/com/retrip/crew/domain/entity/Demand.java @@ -8,8 +8,7 @@ import java.util.UUID; -import static com.retrip.crew.domain.vo.DemandStatus.CANCELED; -import static com.retrip.crew.domain.vo.DemandStatus.PENDING; +import static com.retrip.crew.domain.vo.DemandStatus.*; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED, force = true) @@ -44,4 +43,8 @@ public boolean isNotPending() { public void cancel() { this.status = CANCELED; } + + public void reject() { + this.status = REJECTED; + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index abf7026..e45f9f1 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -78,6 +78,14 @@ public void cancelDemand(Demand demand) { find.cancel(); } + public void rejectDemand(Demand demand) { + Demand find = findDemand(demand); + if (find.isNotPending()) { + throw new IllegalStateException("참여 요청이 대기중이 아니면 거절할 수 없습니다."); + } + find.reject(); + } + private Demand findDemand(Demand demand) { return demands.stream() .filter(d -> d.equals(demand)) 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 f6c7053..fd1b92b 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,7 +1,15 @@ package com.retrip.crew.infra.adapter.in.presentation.rest; -import com.retrip.crew.application.in.request.*; -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.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; @@ -105,6 +113,16 @@ public ApiResponse> getCrewsOfDemand( return ApiResponse.ok(demands); } + @PutMapping("/{crewId}/demands/{demandId}/reject") + @Schema(description = "크루 참여 요청 거절") + public ApiResponse rejectDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId) { + RejectDemandResponse demand = manageDemandUseCase.rejectDemand(crewId, demandId, memberId); + return ApiResponse.ok(demand); + } + @GetMapping @Schema(description = "크루 리스트 조회") public ResponseEntity>> getCrews( diff --git a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java index 8d1e230..1f8f53c 100644 --- a/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java +++ b/src/main/java/com/retrip/crew/infra/adapter/out/persistence/mysql/query/CrewQuerydslRepository.java @@ -10,9 +10,9 @@ import com.querydsl.jpa.JPAExpressions; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; -import com.retrip.crew.application.in.request.CrewOrder; -import com.retrip.crew.application.in.response.CrewListResponse; -import com.retrip.crew.application.in.response.CrewsOfDemandResponse; +import com.retrip.crew.application.in.request.crew.CrewOrder; +import com.retrip.crew.application.in.response.crew.CrewListResponse; +import com.retrip.crew.application.in.response.demand.CrewsOfDemandResponse; import com.retrip.crew.application.out.repository.CrewQueryRepository; import com.retrip.crew.domain.entity.CrewMemberRole; import com.retrip.crew.domain.entity.QCrewMember; 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 c31973d..0cd5617 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -1,7 +1,17 @@ package com.retrip.crew.application.in; -import com.retrip.crew.application.in.request.*; -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.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; diff --git a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java index 5696139..e634962 100644 --- a/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java +++ b/src/test/java/com/retrip/crew/common/fixture/CrewFixture.java @@ -1,6 +1,6 @@ package com.retrip.crew.common.fixture; -import com.retrip.crew.application.in.request.CrewCreateRequest; +import com.retrip.crew.application.in.request.crew.CrewCreateRequest; import com.retrip.crew.domain.entity.Crew; import com.retrip.crew.domain.entity.CrewMember; import com.retrip.crew.domain.entity.CrewMemberRole; From 8d8dc6243899570f3588c7ce1cd56247eb900cb6 Mon Sep 17 00:00:00 2001 From: mandykr Date: Tue, 25 Mar 2025 10:35:41 +0900 Subject: [PATCH 09/14] =?UTF-8?q?feat:=20=EC=B7=A8=EC=86=8C=ED=95=9C=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EC=9A=94=EC=B2=AD=20=EC=9E=AC=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/retrip/crew/domain/entity/Demand.java | 12 +++++++++ .../crew/domain/entity/Recruitment.java | 20 +++++++++++++-- .../crew/domain/entity/RecruitmentTest.java | 25 ++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/retrip/crew/domain/entity/Demand.java b/src/main/java/com/retrip/crew/domain/entity/Demand.java index 3542ab8..3fb1e63 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Demand.java +++ b/src/main/java/com/retrip/crew/domain/entity/Demand.java @@ -47,4 +47,16 @@ public void cancel() { public void reject() { this.status = REJECTED; } + + public boolean isCanceled() { + return this.status == CANCELED; + } + + public boolean isEqualTo(UUID memberId) { + return this.memberId.equals(memberId); + } + + public void restore() { + this.status = PENDING; + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index e45f9f1..9381bd0 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -13,6 +13,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; import static com.retrip.crew.domain.vo.RecruitmentStatus.RECRUITING; @@ -59,6 +60,11 @@ public Demand addDemand(UUID memberId, Crew crew) { if (isDuplicate(memberId)) { throw new IllegalStateException("이미 요청한 사용자는 다시 요청할 수 없습니다."); } + Optional reDemand = findReDemand(memberId); + if (reDemand.isPresent()) { + reDemand.get().restore(); + return reDemand.get(); + } Demand demand = new Demand(memberId, crew); demands.add(demand); return demand; @@ -66,8 +72,18 @@ public Demand addDemand(UUID memberId, Crew crew) { private boolean isDuplicate(UUID memberId) { return demands.stream() - .map(Demand::getMemberId) - .anyMatch(id -> id.equals(memberId)); + .anyMatch(d -> d.isEqualTo(memberId) && !d.isCanceled()); + } + + private Optional findReDemand(UUID memberId) { + return demands.stream() + .filter(d -> isReDemand(memberId, d)) + .findFirst(); + } + + private static boolean isReDemand(UUID memberId, Demand demand) { + return demand.isEqualTo(memberId) + && demand.isCanceled(); } public void cancelDemand(Demand demand) { diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java index 6ceacdd..0b6e7d2 100644 --- a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -42,9 +42,32 @@ class RecruitmentTest { assertThat(demand.getStatus()).isEqualTo(DemandStatus.CANCELED); } + @Test + void 취소한_참여_요청을_재요청_한다() { + // given + Crew crew = createCrew(LEADER_ID); + Recruitment recruitment = new Recruitment(100); + Demand demand = new Demand(정수_ID, crew); + List demands = List.of( + demand, + new Demand(홍석_ID, crew), + new Demand(준호_ID, crew), + new Demand(지수_ID, crew), + new Demand(혁진_ID, crew) + ); + ReflectionTestUtils.setField(demand, "status", DemandStatus.CANCELED); + ReflectionTestUtils.setField(recruitment, "demands", demands); + + // when + recruitment.addDemand(정수_ID, crew); + + // then + assertThat(demand.getStatus()).isEqualTo(DemandStatus.PENDING); + } + @ParameterizedTest @EnumSource(mode = EXCLUDE, names = {"PENDING"}) - void 대기_상태가_아니면_참여_요청을_취소한다(DemandStatus status) { + void 대기_상태가_아니면_참여_요청을_취소할_수_있다(DemandStatus status) { // given Crew crew = createCrew(LEADER_ID); Recruitment recruitment = new Recruitment(100); From 3088aad73d40f85f3a8765e8c2a34feb1afa44dd Mon Sep 17 00:00:00 2001 From: mandykr Date: Tue, 25 Mar 2025 11:25:55 +0900 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20=EC=B0=B8=EC=97=AC=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EC=8A=B9=EC=9D=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/application/in/CrewService.java | 8 ++++++ .../demand/ApproveDemandResponse.java | 26 +++++++++++++++++++ .../response/demand/RejectDemandResponse.java | 1 + .../in/usecase/ManageDemandUseCase.java | 7 +++-- .../com/retrip/crew/domain/entity/Crew.java | 5 ++++ .../crew/domain/entity/CrewMembers.java | 22 +++++++++++++--- .../com/retrip/crew/domain/entity/Demand.java | 4 +++ .../crew/domain/entity/Recruitment.java | 18 +++++++++---- .../in/presentation/rest/CrewController.java | 10 +++++++ .../crew/domain/entity/CrewMembersTest.java | 22 ++++++++++++++++ .../crew/domain/entity/RecruitmentTest.java | 15 +++++++++++ 11 files changed, 125 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/retrip/crew/application/in/response/demand/ApproveDemandResponse.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 7945c10..4b11b6c 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -127,6 +127,14 @@ public RejectDemandResponse rejectDemand(UUID crewId, UUID demandId, UUID member return RejectDemandResponse.of(demand); } + @Override + public ApproveDemandResponse approveDemand(UUID crewId, UUID demandId, UUID memberId) { + Crew crew = findById(crewId); + Demand demand = findDemandByIdAndCrewId(demandId, crewId); + crew.approveDemand(demand); + return ApproveDemandResponse.of(demand); + } + private Demand findDemandByIdAndCrewId(UUID demandId, UUID crewId) { return demandRepository.findByIdAndCrewId(demandId, crewId) .orElseThrow(() -> new EntityNotFoundException("참여 요청 엔티티를 찾을 수 없습니다.")); diff --git a/src/main/java/com/retrip/crew/application/in/response/demand/ApproveDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/demand/ApproveDemandResponse.java new file mode 100644 index 0000000..d7c9696 --- /dev/null +++ b/src/main/java/com/retrip/crew/application/in/response/demand/ApproveDemandResponse.java @@ -0,0 +1,26 @@ +package com.retrip.crew.application.in.response.demand; + +import com.retrip.crew.domain.entity.Demand; +import com.retrip.crew.domain.vo.DemandStatus; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.util.UUID; + +@Schema(description = "크루 참여 요청 승인 Response") +public record ApproveDemandResponse( + @Schema(description = "크루 ID") + UUID crewId, + + @Schema(description = "참여 요청 ID") + UUID demandId, + + @Schema(description = "참여 요청자 ID") + UUID memberId, + + @Schema(description = "참여 요청 상태") + DemandStatus status +) { + public static ApproveDemandResponse of(Demand demand) { + return new ApproveDemandResponse(demand.getCrew().getId(), demand.getId(), demand.getMemberId(), demand.getStatus()); + } +} diff --git a/src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.java b/src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.java index b93afbb..3e9f00a 100644 --- a/src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.java +++ b/src/main/java/com/retrip/crew/application/in/response/demand/RejectDemandResponse.java @@ -6,6 +6,7 @@ import java.util.UUID; +@Schema(description = "크루 참여 요청 거절 Response") public record RejectDemandResponse( @Schema(description = "크루 ID") UUID crewId, diff --git a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java index 5dd828a..0f0c976 100644 --- a/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java +++ b/src/main/java/com/retrip/crew/application/in/usecase/ManageDemandUseCase.java @@ -3,10 +3,7 @@ import com.retrip.crew.application.in.request.demand.CreateDemandRequest; import com.retrip.crew.application.in.request.crew.CrewOrder; import com.retrip.crew.application.in.request.demand.DemandOrder; -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.response.demand.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -23,4 +20,6 @@ Page getDemands( void cancelDemand(UUID crewId, UUID demandId, UUID memberId); RejectDemandResponse rejectDemand(UUID crewId, UUID demandId, UUID memberId); + + ApproveDemandResponse approveDemand(UUID crewId, UUID demandId, UUID memberId); } 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 16a7c27..b3898d0 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Crew.java +++ b/src/main/java/com/retrip/crew/domain/entity/Crew.java @@ -89,4 +89,9 @@ public void cancelDemand(Demand demand) { public void rejectDemand(Demand demand) { recruitment.rejectDemand(demand); } + + public void approveDemand(Demand demand) { + recruitment.approveDemand(demand); + crewMembers.addMember(demand, this); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java index 992d844..eab4d38 100644 --- a/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java +++ b/src/main/java/com/retrip/crew/domain/entity/CrewMembers.java @@ -1,5 +1,6 @@ package com.retrip.crew.domain.entity; +import com.retrip.crew.domain.exception.common.IllegalStateException; import com.retrip.crew.domain.exception.common.InvalidValueException; import jakarta.persistence.CascadeType; import jakarta.persistence.Embeddable; @@ -19,12 +20,12 @@ public class CrewMembers { @OneToMany(mappedBy = "crew", cascade = CascadeType.ALL, orphanRemoval = true) private List values = new ArrayList<>(); - public CrewMembers(Crew crew, UUID member) { - this.values = createLeader(crew, member); + public CrewMembers(Crew crew, UUID leaderId) { + this.values.add(createLeader(crew, leaderId)); } - private List createLeader(Crew crew, UUID memberId) { - return List.of(new CrewMember(crew, memberId, CrewMemberRole.LEADER)); + private CrewMember createLeader(Crew crew, UUID memberId) { + return new CrewMember(crew, memberId, CrewMemberRole.LEADER); } public CrewMember getLeader() { @@ -45,4 +46,17 @@ public boolean isLeader(UUID memberId) { .orElseThrow(() -> new InvalidValueException("크루 멤버가 아닙니다.")) .isLeader(); } + + public void addMember(Demand demand, Crew crew) { + if (isDuplicate(demand)) { + throw new IllegalStateException("사용자는 이미 크루 멤버 입니다."); + } + CrewMember member = new CrewMember(crew, demand.getMemberId(), CrewMemberRole.PARTICIPANT); + values.add(member); + } + + private boolean isDuplicate(Demand demand) { + return this.values.stream() + .anyMatch(m -> m.getMemberId().equals(demand.getMemberId())); + } } diff --git a/src/main/java/com/retrip/crew/domain/entity/Demand.java b/src/main/java/com/retrip/crew/domain/entity/Demand.java index 3fb1e63..59a2257 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Demand.java +++ b/src/main/java/com/retrip/crew/domain/entity/Demand.java @@ -44,6 +44,10 @@ public void cancel() { this.status = CANCELED; } + public void approve() { + this.status = APPROVED; + } + public void reject() { this.status = REJECTED; } diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index 9381bd0..7365bbf 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -88,18 +88,26 @@ private static boolean isReDemand(UUID memberId, Demand demand) { public void cancelDemand(Demand demand) { Demand find = findDemand(demand); - if (find.isNotPending()) { - throw new IllegalStateException("참여 요청이 대기중이 아니면 취소할 수 없습니다."); - } + throwIfNotPending(find); find.cancel(); } + public void approveDemand(Demand demand) { + Demand find = findDemand(demand); + throwIfNotPending(find); + find.approve(); + } + public void rejectDemand(Demand demand) { Demand find = findDemand(demand); + throwIfNotPending(find); + find.reject(); + } + + private static void throwIfNotPending(Demand find) { if (find.isNotPending()) { - throw new IllegalStateException("참여 요청이 대기중이 아니면 거절할 수 없습니다."); + throw new IllegalStateException("참여 요청의 상태가 대기중이 아닙니다."); } - find.reject(); } private Demand findDemand(Demand demand) { 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 fd1b92b..dd3c7bd 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 @@ -113,6 +113,16 @@ public ApiResponse> getCrewsOfDemand( return ApiResponse.ok(demands); } + @PutMapping("/{crewId}/demands/{demandId}/approve") + @Schema(description = "크루 참여 요청 승인") + public ApiResponse approveDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId) { + ApproveDemandResponse demand = manageDemandUseCase.approveDemand(crewId, demandId, memberId); + return ApiResponse.ok(demand); + } + @PutMapping("/{crewId}/demands/{demandId}/reject") @Schema(description = "크루 참여 요청 거절") public ApiResponse rejectDemand( diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java index c98cf89..b9f0c1d 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewMembersTest.java @@ -2,8 +2,12 @@ import org.junit.jupiter.api.Test; +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.junit.jupiter.api.Assertions.assertAll; class CrewMembersTest { @@ -30,4 +34,22 @@ class CrewMembersTest { // then assertThat(result).isFalse(); } + + @Test + void 사용자를_크루_멤버에_추가한다() { + // given + Crew crew = createCrew(LEADER_ID); + Demand demand = new Demand(정수_ID, crew); + CrewMembers crewMembers = new CrewMembers(crew, LEADER_ID); + + // when + crewMembers.addMember(demand, crew); + + // then + List ids = crewMembers.getValues().stream().map(CrewMember::getMemberId).toList(); + assertAll( + () -> assertThat(crewMembers.getSize()).isEqualTo(2), + () -> assertThat(ids.contains(정수_ID)).isTrue() + ); + } } diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java index 0b6e7d2..b2d0dd8 100644 --- a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -86,4 +86,19 @@ class RecruitmentTest { assertThatThrownBy(() -> recruitment.cancelDemand(demand)) .isExactlyInstanceOf(IllegalStateException.class); } + + @Test + void 참여_요청을_승인한다() { + // given + Crew crew = createCrew(LEADER_ID); + Recruitment recruitment = new Recruitment(100); + Demand demand = new Demand(정수_ID, crew); + ReflectionTestUtils.setField(recruitment, "demands", List.of(demand)); + + // when + recruitment.approveDemand(demand); + + // then + assertThat(demand.getStatus()).isEqualTo(DemandStatus.APPROVED); + } } From 958b4a45c959b2c08f13c4664faefb46b2532667 Mon Sep 17 00:00:00 2001 From: mandykr Date: Mon, 7 Apr 2025 12:46:35 +0900 Subject: [PATCH 11/14] =?UTF-8?q?chore:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../retrip/crew/domain/converter/DemandStatusConverter.java | 4 ++-- .../crew/domain/converter/RecruitmentStatusConverter.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java b/src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java index 95e1c4f..13d351b 100644 --- a/src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java +++ b/src/main/java/com/retrip/crew/domain/converter/DemandStatusConverter.java @@ -9,7 +9,7 @@ public class DemandStatusConverter implements AttributeConverter Date: Mon, 7 Apr 2025 17:41:43 +0900 Subject: [PATCH 12/14] =?UTF-8?q?refactor:=20crew=EC=99=80=20demand?= =?UTF-8?q?=EB=A5=BC=20=EC=A1=B0=EC=9D=B8=ED=95=B4=EC=84=9C=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20-?= =?UTF-8?q?=20crew=20=EC=A1=B0=ED=9A=8C=20->=20demand=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=EB=90=9C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20crew=EC=99=80=20demand=EB=A5=BC=20=EC=A1=B0?= =?UTF-8?q?=EC=9D=B8=ED=95=B4=EC=84=9C=20=EC=A1=B0=ED=9A=8C=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/retrip/crew/application/in/CrewService.java | 8 ++++---- .../out/repository/CrewDemandRepository.java | 4 +++- .../retrip/crew/application/in/CrewServiceTest.java | 13 +++++++++++++ 3 files changed, 20 insertions(+), 5 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 4b11b6c..33bae4b 100644 --- a/src/main/java/com/retrip/crew/application/in/CrewService.java +++ b/src/main/java/com/retrip/crew/application/in/CrewService.java @@ -114,29 +114,29 @@ private static void throwIfNotLeader(Crew crew, UUID memberId, BusinessException @Override public void cancelDemand(UUID crewId, UUID demandId, UUID memberId) { - Crew crew = findById(crewId); Demand demand = findDemandByIdAndCrewId(demandId, crewId); + Crew crew = demand.getCrew(); crew.cancelDemand(demand); } @Override public RejectDemandResponse rejectDemand(UUID crewId, UUID demandId, UUID memberId) { - Crew crew = findById(crewId); Demand demand = findDemandByIdAndCrewId(demandId, crewId); + Crew crew = demand.getCrew(); crew.rejectDemand(demand); return RejectDemandResponse.of(demand); } @Override public ApproveDemandResponse approveDemand(UUID crewId, UUID demandId, UUID memberId) { - Crew crew = findById(crewId); Demand demand = findDemandByIdAndCrewId(demandId, crewId); + Crew crew = demand.getCrew(); crew.approveDemand(demand); return ApproveDemandResponse.of(demand); } private Demand findDemandByIdAndCrewId(UUID demandId, UUID crewId) { - return demandRepository.findByIdAndCrewId(demandId, crewId) + return demandRepository.findCrewByIdAndCrewId(demandId, crewId) .orElseThrow(() -> new EntityNotFoundException("참여 요청 엔티티를 찾을 수 없습니다.")); } diff --git a/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java b/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java index eb27226..9d9f8d9 100644 --- a/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java +++ b/src/main/java/com/retrip/crew/application/out/repository/CrewDemandRepository.java @@ -5,6 +5,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.Optional; import java.util.UUID; @@ -12,5 +13,6 @@ public interface CrewDemandRepository extends JpaRepository { Page findByCrewIdAndStatus(UUID crewId, DemandStatus pending, Pageable pageRequest); - Optional findByIdAndCrewId(UUID demandId, UUID crewId); + @Query("select d, c from Demand d join fetch d.crew c where d.id = :demandId and c.id = :crewId") + Optional findCrewByIdAndCrewId(UUID demandId, UUID crewId); } 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 0cd5617..09ead24 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -106,6 +106,19 @@ class CrewServiceTest extends ServiceTest { .isExactlyInstanceOf(IllegalStateException.class); } + @Test + void 크루_참여_요청을_취소한다() { + // given + Crew crew = crewRepository.save(createCrew(LEADER_ID)); + Demand demand = crew.demand(MEMBER_ID); + + // when + crewService.cancelDemand(crew.getId(), demand.getId(), MEMBER_ID); + + // then + assertThat(demand.getStatus()).isEqualTo(DemandStatus.CANCELED); + } + @ParameterizedTest @CsvSource({"PENDING,2", "APPROVED,2", "REJECTED,1"}) void 리더가_크루_요청_목록을_조회한다(String status, int expected) { From 751e3d020b92363096c3bcb997122ae6dd4a4d43 Mon Sep 17 00:00:00 2001 From: mandykr Date: Mon, 7 Apr 2025 18:00:10 +0900 Subject: [PATCH 13/14] =?UTF-8?q?feat:=20=ED=81=AC=EB=A3=A8=20=EC=B0=B8?= =?UTF-8?q?=EC=97=AC=20=EB=AA=A8=EC=A7=91=20=EA=B4=80=EB=A0=A8=20=EC=BB=A4?= =?UTF-8?q?=EC=8A=A4=ED=85=80=20=EC=98=88=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/retrip/crew/domain/entity/Recruitment.java | 10 ++++++---- .../domain/exception/DuplicateDemandException.java | 11 +++++++++++ .../domain/exception/IllegalDemandStateException.java | 11 +++++++++++ .../exception/UnableToStartRecruitmentException.java | 11 +++++++++++ .../crew/domain/exception/common/ErrorCode.java | 5 ++++- .../retrip/crew/application/in/CrewServiceTest.java | 4 ++-- .../java/com/retrip/crew/domain/entity/CrewTest.java | 4 ++-- .../retrip/crew/domain/entity/RecruitmentTest.java | 4 ++-- 8 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/retrip/crew/domain/exception/DuplicateDemandException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/IllegalDemandStateException.java create mode 100644 src/main/java/com/retrip/crew/domain/exception/UnableToStartRecruitmentException.java diff --git a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java index 7365bbf..50d14db 100644 --- a/src/main/java/com/retrip/crew/domain/entity/Recruitment.java +++ b/src/main/java/com/retrip/crew/domain/entity/Recruitment.java @@ -1,6 +1,8 @@ package com.retrip.crew.domain.entity; -import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.exception.DuplicateDemandException; +import com.retrip.crew.domain.exception.IllegalDemandStateException; +import com.retrip.crew.domain.exception.UnableToStartRecruitmentException; import com.retrip.crew.domain.exception.common.InvalidValueException; import com.retrip.crew.domain.vo.RecruitmentStatus; import jakarta.persistence.CascadeType; @@ -39,7 +41,7 @@ public Recruitment(int maxMembers) { public void start(int membersSize) { if (isRecruitmentComplete(membersSize)) { stop(); - throw new IllegalStateException("최대 인원을 모두 모집 완료하여 더 이상 멤버를 모집할 수 없습니다."); + throw new UnableToStartRecruitmentException("최대 인원을 모두 모집 완료하여 더 이상 멤버를 모집할 수 없습니다."); } this.status = RECRUITING; } @@ -58,7 +60,7 @@ public void updateMaxMembers(int maxMembers) { public Demand addDemand(UUID memberId, Crew crew) { if (isDuplicate(memberId)) { - throw new IllegalStateException("이미 요청한 사용자는 다시 요청할 수 없습니다."); + throw new DuplicateDemandException(); } Optional reDemand = findReDemand(memberId); if (reDemand.isPresent()) { @@ -106,7 +108,7 @@ public void rejectDemand(Demand demand) { private static void throwIfNotPending(Demand find) { if (find.isNotPending()) { - throw new IllegalStateException("참여 요청의 상태가 대기중이 아닙니다."); + throw new IllegalDemandStateException("참여 요청의 상태가 대기중이 아닙니다."); } } diff --git a/src/main/java/com/retrip/crew/domain/exception/DuplicateDemandException.java b/src/main/java/com/retrip/crew/domain/exception/DuplicateDemandException.java new file mode 100644 index 0000000..bb7a544 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/DuplicateDemandException.java @@ -0,0 +1,11 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.ErrorCode; +import com.retrip.crew.domain.exception.common.IllegalStateException; + +public class DuplicateDemandException extends IllegalStateException { + private static final ErrorCode errorCode = ErrorCode.DUPLICATE_DEMAND; + public DuplicateDemandException() { + super(errorCode); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/IllegalDemandStateException.java b/src/main/java/com/retrip/crew/domain/exception/IllegalDemandStateException.java new file mode 100644 index 0000000..2307fa7 --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/IllegalDemandStateException.java @@ -0,0 +1,11 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.ErrorCode; +import com.retrip.crew.domain.exception.common.IllegalStateException; + +public class IllegalDemandStateException extends IllegalStateException { + private static final ErrorCode errorCode = ErrorCode.ILLEGAL_DEMAND_STATE; + public IllegalDemandStateException(String message) { + super(errorCode, message); + } +} diff --git a/src/main/java/com/retrip/crew/domain/exception/UnableToStartRecruitmentException.java b/src/main/java/com/retrip/crew/domain/exception/UnableToStartRecruitmentException.java new file mode 100644 index 0000000..160410d --- /dev/null +++ b/src/main/java/com/retrip/crew/domain/exception/UnableToStartRecruitmentException.java @@ -0,0 +1,11 @@ +package com.retrip.crew.domain.exception; + +import com.retrip.crew.domain.exception.common.ErrorCode; +import com.retrip.crew.domain.exception.common.IllegalStateException; + +public class UnableToStartRecruitmentException extends IllegalStateException { + private static final ErrorCode errorCode = ErrorCode.UNABLE_TO_START_RECRUITMENT; + public UnableToStartRecruitmentException(String message) { + super(errorCode, message); + } +} 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 576eb88..4f2ae0b 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 @@ -14,7 +14,10 @@ public enum ErrorCode { ILLEGAL_STATE(BAD_REQUEST, "Common-005", "Illegal state"), CREW_NOT_FOUND(BAD_REQUEST, "Crew-001", "크루 엔티티를 찾을 수 없습니다."), - NOT_CREW_LEADER(BAD_REQUEST, "Crew-002", "크루 리더가 아니면 접근할 수 없습니다.") + 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", "이미 크루 참여를 요청한 사용자는 다시 요청할 수 없습니다.") ; private final HttpStatus status; 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 09ead24..42c5e2a 100644 --- a/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java +++ b/src/test/java/com/retrip/crew/application/in/CrewServiceTest.java @@ -16,7 +16,7 @@ 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.exception.common.IllegalStateException; +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 org.junit.jupiter.api.Test; @@ -103,7 +103,7 @@ class CrewServiceTest extends ServiceTest { // when, then assertThatThrownBy(() -> crewService.createDemand(save.getId(), request)) - .isExactlyInstanceOf(IllegalStateException.class); + .isExactlyInstanceOf(DuplicateDemandException.class); } @Test diff --git a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java index 81c1e8f..37d2c90 100644 --- a/src/test/java/com/retrip/crew/domain/entity/CrewTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/CrewTest.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.entity; -import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.exception.UnableToStartRecruitmentException; import com.retrip.crew.domain.vo.RecruitmentStatus; import org.junit.jupiter.api.Test; @@ -60,7 +60,7 @@ class CrewTest { // when, then assertThatThrownBy(crew::startRecruitment) - .isExactlyInstanceOf(IllegalStateException.class); + .isExactlyInstanceOf(UnableToStartRecruitmentException.class); assertThat(crew.getRecruitment().getStatus()).isEqualTo(RecruitmentStatus.STOPPED); } } diff --git a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java index b2d0dd8..221221e 100644 --- a/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java +++ b/src/test/java/com/retrip/crew/domain/entity/RecruitmentTest.java @@ -1,6 +1,6 @@ package com.retrip.crew.domain.entity; -import com.retrip.crew.domain.exception.common.IllegalStateException; +import com.retrip.crew.domain.exception.IllegalDemandStateException; import com.retrip.crew.domain.vo.DemandStatus; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -84,7 +84,7 @@ class RecruitmentTest { // when, then assertThatThrownBy(() -> recruitment.cancelDemand(demand)) - .isExactlyInstanceOf(IllegalStateException.class); + .isExactlyInstanceOf(IllegalDemandStateException.class); } @Test From 7dc18be4a04a12559493fb5441d002fe3fb30d58 Mon Sep 17 00:00:00 2001 From: mandykr Date: Mon, 7 Apr 2025 18:02:27 +0900 Subject: [PATCH 14/14] =?UTF-8?q?refactor:=20=ED=81=AC=EB=A3=A8=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=20=EB=AA=A8=EC=A7=91=20controller=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 --- .../in/presentation/rest/CrewController.java | 83 -------------- .../presentation/rest/DemandController.java | 108 ++++++++++++++++++ 2 files changed, 108 insertions(+), 83 deletions(-) create mode 100644 src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/DemandController.java 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 dd3c7bd..5dcfa2d 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 @@ -33,8 +33,6 @@ @Tag(name = "Crew", description = "크루 서비스") public class CrewController { private final ManageCrewUseCase manageCrewUseCase; - private final UpdateRecruitmentUseCase updateRecruitmentUseCase; - private final ManageDemandUseCase manageDemandUseCase; private final GetCrewUseCase getCrewUseCase; @PostMapping @@ -52,87 +50,6 @@ public ApiResponse updateCrew( return ApiResponse.ok(crew); } - @PutMapping("/{crewId}/recruitments/start") - @Schema(description = "크루 모집 시작") - public ApiResponse startRecruitment(@PathVariable final UUID crewId) { - ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.startRecruitment(crewId); - return ApiResponse.ok(recruitment); - } - - @PutMapping("/{crewId}/recruitments/stop") - @Schema(description = "크루 모집 중지") - public ApiResponse stopRecruitment(@PathVariable final UUID crewId) { - ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.stopRecruitment(crewId); - return ApiResponse.ok(recruitment); - } - - @PostMapping("/{crewId}/demands") - @Schema(description = "크루 참여 요청") - public ApiResponse createDemand( - @PathVariable final UUID crewId, - @RequestBody CreateDemandRequest request) { - CreateDemandResponse demand = manageDemandUseCase.createDemand(crewId, request); - return ApiResponse.created(demand); - } - - @PutMapping("/{crewId}/demands/{demandId}/cancel") - @Schema(description = "크루 참여 요청 취소") - public ApiResponse cancelDemand( - @PathVariable final UUID crewId, - @PathVariable final UUID demandId, - @RequestParam final UUID memberId) { - manageDemandUseCase.cancelDemand(crewId, demandId, memberId); - return ApiResponse.noContent(); - } - - @GetMapping("/{crewId}/demands") - @Schema(description = "크루 참여 요청 목록 조회") - public ApiResponse> getDemands( - @PathVariable final UUID crewId, - @RequestParam final UUID memberId, - @RequestParam final String status, - @PageableDefault(size = 10) Pageable pageable, - @RequestParam(name = "order", defaultValue = "DATE") DemandOrder order, - @RequestParam(name = "sort", defaultValue = "asc") String sort) { - Page demands = - manageDemandUseCase.getDemands(crewId, memberId, status, pageable, order, sort); - return ApiResponse.ok(demands); - } - - @GetMapping("/{crewId}/demands/{demandId}/crews") - @Schema(description = "크루 참여 요청자가 속한 크루 목록 조회") - public ApiResponse> getCrewsOfDemand( - @PathVariable final UUID crewId, - @PathVariable final UUID demandId, - @RequestParam final UUID memberId, - @PageableDefault(size = 10) Pageable pageable, - @RequestParam(name = "order", defaultValue = "DATE") CrewOrder order, - @RequestParam(name = "sort", defaultValue = "asc") String sort) { - Page demands = - manageDemandUseCase.getCrewsOfDemand(crewId, demandId, memberId, pageable, order, sort); - return ApiResponse.ok(demands); - } - - @PutMapping("/{crewId}/demands/{demandId}/approve") - @Schema(description = "크루 참여 요청 승인") - public ApiResponse approveDemand( - @PathVariable final UUID crewId, - @PathVariable final UUID demandId, - @RequestParam final UUID memberId) { - ApproveDemandResponse demand = manageDemandUseCase.approveDemand(crewId, demandId, memberId); - return ApiResponse.ok(demand); - } - - @PutMapping("/{crewId}/demands/{demandId}/reject") - @Schema(description = "크루 참여 요청 거절") - public ApiResponse rejectDemand( - @PathVariable final UUID crewId, - @PathVariable final UUID demandId, - @RequestParam final UUID memberId) { - RejectDemandResponse demand = manageDemandUseCase.rejectDemand(crewId, demandId, memberId); - return ApiResponse.ok(demand); - } - @GetMapping @Schema(description = "크루 리스트 조회") public ResponseEntity>> getCrews( diff --git a/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/DemandController.java b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/DemandController.java new file mode 100644 index 0000000..6337344 --- /dev/null +++ b/src/main/java/com/retrip/crew/infra/adapter/in/presentation/rest/DemandController.java @@ -0,0 +1,108 @@ +package com.retrip.crew.infra.adapter.in.presentation.rest; + +import com.retrip.crew.application.in.request.crew.CrewOrder; +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.demand.*; +import com.retrip.crew.application.in.usecase.ManageDemandUseCase; +import com.retrip.crew.application.in.usecase.UpdateRecruitmentUseCase; +import com.retrip.crew.infra.adapter.in.presentation.rest.common.ApiResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@RequiredArgsConstructor +@RequestMapping("/crews") +@RestController +@Tag(name = "Crew", description = "크루 참여 모집 서비스") +public class DemandController { + private final UpdateRecruitmentUseCase updateRecruitmentUseCase; + private final ManageDemandUseCase manageDemandUseCase; + + @PutMapping("/{crewId}/recruitments/start") + @Schema(description = "크루 모집 시작") + public ApiResponse startRecruitment(@PathVariable final UUID crewId) { + ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.startRecruitment(crewId); + return ApiResponse.ok(recruitment); + } + + @PutMapping("/{crewId}/recruitments/stop") + @Schema(description = "크루 모집 중지") + public ApiResponse stopRecruitment(@PathVariable final UUID crewId) { + ChangeRecruitmentStatusResponse recruitment = updateRecruitmentUseCase.stopRecruitment(crewId); + return ApiResponse.ok(recruitment); + } + + @PostMapping("/{crewId}/demands") + @Schema(description = "크루 참여 요청") + public ApiResponse createDemand( + @PathVariable final UUID crewId, + @RequestBody CreateDemandRequest request) { + CreateDemandResponse demand = manageDemandUseCase.createDemand(crewId, request); + return ApiResponse.created(demand); + } + + @PutMapping("/{crewId}/demands/{demandId}/cancel") + @Schema(description = "크루 참여 요청 취소") + public ApiResponse cancelDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId) { + manageDemandUseCase.cancelDemand(crewId, demandId, memberId); + return ApiResponse.noContent(); + } + + @GetMapping("/{crewId}/demands") + @Schema(description = "크루 참여 요청 목록 조회") + public ApiResponse> getDemands( + @PathVariable final UUID crewId, + @RequestParam final UUID memberId, + @RequestParam final String status, + @PageableDefault(size = 10) Pageable pageable, + @RequestParam(name = "order", defaultValue = "DATE") DemandOrder order, + @RequestParam(name = "sort", defaultValue = "asc") String sort) { + Page demands = + manageDemandUseCase.getDemands(crewId, memberId, status, pageable, order, sort); + return ApiResponse.ok(demands); + } + + @GetMapping("/{crewId}/demands/{demandId}/crews") + @Schema(description = "크루 참여 요청자가 속한 크루 목록 조회") + public ApiResponse> getCrewsOfDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId, + @PageableDefault(size = 10) Pageable pageable, + @RequestParam(name = "order", defaultValue = "DATE") CrewOrder order, + @RequestParam(name = "sort", defaultValue = "asc") String sort) { + Page demands = + manageDemandUseCase.getCrewsOfDemand(crewId, demandId, memberId, pageable, order, sort); + return ApiResponse.ok(demands); + } + + @PutMapping("/{crewId}/demands/{demandId}/approve") + @Schema(description = "크루 참여 요청 승인") + public ApiResponse approveDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId) { + ApproveDemandResponse demand = manageDemandUseCase.approveDemand(crewId, demandId, memberId); + return ApiResponse.ok(demand); + } + + @PutMapping("/{crewId}/demands/{demandId}/reject") + @Schema(description = "크루 참여 요청 거절") + public ApiResponse rejectDemand( + @PathVariable final UUID crewId, + @PathVariable final UUID demandId, + @RequestParam final UUID memberId) { + RejectDemandResponse demand = manageDemandUseCase.rejectDemand(crewId, demandId, memberId); + return ApiResponse.ok(demand); + } +}