From b7faadd4b79c7883c0b038f998b0ca88f62e612a Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Wed, 31 Jul 2024 16:33:38 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EB=AA=A8=EC=9E=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- .../club/controller/ClubController.java | 7 + .../friendogly/club/domain/Club.java | 30 ++- .../response/ClubMemberDetailResponse.java | 14 ++ .../dto/response/ClubPetDetailResponse.java | 14 ++ .../club/dto/response/FindClubResponse.java | 62 +++++++ .../club/service/ClubQueryService.java | 22 ++- .../club/service/ClubCommandServiceTest.java | 3 +- .../friendogly/docs/ClubApiDocsTest.java | 174 +++++++++++------- 8 files changed, 249 insertions(+), 77 deletions(-) create mode 100644 backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubMemberDetailResponse.java create mode 100644 backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java create mode 100644 backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java index eaae127a5..6633f771c 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java @@ -4,6 +4,7 @@ import com.woowacourse.friendogly.club.dto.request.FindSearchingClubRequest; import com.woowacourse.friendogly.club.dto.request.SaveClubMemberRequest; import com.woowacourse.friendogly.club.dto.request.SaveClubRequest; +import com.woowacourse.friendogly.club.dto.response.FindClubResponse; import com.woowacourse.friendogly.club.dto.response.FindSearchingClubResponse; import com.woowacourse.friendogly.club.dto.response.SaveClubMemberResponse; import com.woowacourse.friendogly.club.dto.response.SaveClubResponse; @@ -34,6 +35,12 @@ public ClubController(ClubCommandService clubCommandService, ClubQueryService cl this.clubQueryService = clubQueryService; } + @GetMapping("/{id}") + public ApiResponse findById(@Auth Long memberId, @PathVariable Long id) { + return ApiResponse.ofSuccess(clubQueryService.findById(memberId, id)); + } + + // TODO: @ModelAttribute @GetMapping("/searching") public ApiResponse> findSearching(@Valid FindSearchingClubRequest request) { return ApiResponse.ofSuccess(clubQueryService.findSearching(request)); diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java b/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java index 63bd8245d..1be3e6e8d 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java @@ -137,12 +137,16 @@ public void addClubMember(Member member) { } private void validateAlreadyExists(Member member) { - if (clubMembers.stream() - .anyMatch(clubMember -> clubMember.isSameMember(member))) { + if (isAlreadyJoined(member)) { throw new FriendoglyException("이미 참여 중인 모임입니다."); } } + public boolean isAlreadyJoined(Member member) { + return clubMembers.stream() + .anyMatch(clubMember -> clubMember.isSameMember(member)); + } + private void validateMemberCapacity() { if (memberCapacity.isCapacityReached(countClubMember())) { throw new FriendoglyException("최대 인원을 초과하여 모임에 참여할 수 없습니다."); @@ -158,11 +162,23 @@ public void addClubPet(List pets) { } private void validateParticipatePet(Pet pet) { - if (!allowedGenders.contains(pet.getGender()) || !allowedSizes.contains(pet.getSizeType())) { + if (canNotJoin(pet)) { throw new FriendoglyException("모임에 데려갈 수 없는 강아지가 있습니다."); } } + private boolean canNotJoin(Pet pet) { + return !allowedGenders.contains(pet.getGender()) || !allowedSizes.contains(pet.getSizeType()); + } + + public boolean isJoinable(Member member, List pets) { + boolean hasJoinablePet = pets.stream() + .anyMatch(pet -> !canNotJoin(pet)); + boolean isNotFull = !this.memberCapacity.isCapacityReached(countClubMember()); + + return hasJoinablePet && isNotFull && isAlreadyJoined(member); + } + public void removeClubMember(Member member) { ClubMember targetClubMember = findTargetClubMember(member); clubMembers.remove(targetClubMember); @@ -197,14 +213,18 @@ public boolean isEmpty() { return clubMembers.isEmpty(); } - public boolean isOwner(ClubMember target) { - return findOwner().isSameMember(target.getClubMemberPk().getMember()); + public boolean isOwner(Member targetMember) { + return findOwner().isSameMember(targetMember); } public Name findOwnerName() { return findOwner().getClubMemberPk().getMember().getName(); } + public String findOwnerImageUrl() { + return findOwner().getClubMemberPk().getMember().getImageUrl(); + } + private ClubMember findOwner() { return clubMembers.stream() .min(Comparator.comparing(ClubMember::getCreatedAt)) diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubMemberDetailResponse.java b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubMemberDetailResponse.java new file mode 100644 index 000000000..75b518816 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubMemberDetailResponse.java @@ -0,0 +1,14 @@ +package com.woowacourse.friendogly.club.dto.response; + +import com.woowacourse.friendogly.member.domain.Member; + +public record ClubMemberDetailResponse( + Long id, + String name, + String imageUrl +) { + + public ClubMemberDetailResponse(Member member) { + this(member.getId(), member.getName().getValue(), member.getImageUrl()); + } +} diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java new file mode 100644 index 000000000..a3ca0b76f --- /dev/null +++ b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java @@ -0,0 +1,14 @@ +package com.woowacourse.friendogly.club.dto.response; + +import com.woowacourse.friendogly.pet.domain.Pet; + +public record ClubPetDetailResponse( + Long id, + String name, + String imageUrl +){ + + public ClubPetDetailResponse(Pet pet) { + this(pet.getId(),pet.getName().getValue(), pet.getImageUrl()); + } +} diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java new file mode 100644 index 000000000..98caa4dca --- /dev/null +++ b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java @@ -0,0 +1,62 @@ +package com.woowacourse.friendogly.club.dto.response; + +import com.woowacourse.friendogly.club.domain.Club; +import com.woowacourse.friendogly.club.domain.Status; +import com.woowacourse.friendogly.member.domain.Member; +import com.woowacourse.friendogly.pet.domain.Gender; +import com.woowacourse.friendogly.pet.domain.Pet; +import com.woowacourse.friendogly.pet.domain.SizeType; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +public record FindClubResponse( + Long id, + String title, + String content, + String ownerMemberName, + String address, + Status status, + LocalDateTime createdAt, + Set allowedGender, + Set allowedSize, + int memberCapacity, + int currentMemberCount, + String imageUrl, + String ownerImageUrl, + boolean isMine, + boolean alreadyParticipate, + boolean canParticipate, + List memberDetails, + List petDetails +) { + + public FindClubResponse(Club club, Member member, List myPets) { + this( + club.getId(), + club.getTitle().getValue(), + club.getContent().getValue(), + club.findOwnerName().getValue(), + club.getAddress().getValue(), + club.getStatus(), + club.getCreatedAt(), + club.getAllowedGenders(), + club.getAllowedSizes(), + club.getMemberCapacity().getValue(), + club.countClubMember(), + club.getImageUrl(), + club.findOwnerImageUrl(), + club.isOwner(member), + club.isAlreadyJoined(member), + club.isJoinable(member, myPets), + club.getClubMembers().stream() + .map(clubMember -> clubMember.getClubMemberPk().getMember()) + .map(ClubMemberDetailResponse::new) + .toList(), + club.getClubPets().stream() + .map(clubPet -> clubPet.getClubPetPk().getPet()) + .map(ClubPetDetailResponse::new) + .toList() + ); + } +} diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java index 5bd37bf14..d1193f87f 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java @@ -2,10 +2,14 @@ import com.woowacourse.friendogly.club.domain.Club; import com.woowacourse.friendogly.club.dto.request.FindSearchingClubRequest; +import com.woowacourse.friendogly.club.dto.response.FindClubResponse; import com.woowacourse.friendogly.club.dto.response.FindSearchingClubResponse; import com.woowacourse.friendogly.club.repository.ClubRepository; import com.woowacourse.friendogly.club.repository.ClubSpecification; +import com.woowacourse.friendogly.member.domain.Member; +import com.woowacourse.friendogly.member.repository.MemberRepository; import com.woowacourse.friendogly.pet.domain.Pet; +import com.woowacourse.friendogly.pet.repository.PetRepository; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -18,9 +22,17 @@ public class ClubQueryService { private final ClubRepository clubRepository; + private final MemberRepository memberRepository; + private final PetRepository petRepository; - public ClubQueryService(ClubRepository clubRepository) { + public ClubQueryService( + ClubRepository clubRepository, + MemberRepository memberRepository, + PetRepository petRepository + ) { this.clubRepository = clubRepository; + this.memberRepository = memberRepository; + this.petRepository = petRepository; } public List findSearching(FindSearchingClubRequest request) { @@ -45,4 +57,12 @@ private List collectOverviewPetImages(Club club) { .map(petList -> petList.get(0).getImageUrl()) .toList(); } + + public FindClubResponse findById(Long memberId, Long id) { + Club club = clubRepository.getById(id); + Member member = memberRepository.getById(memberId); + List pets = petRepository.findByMemberId(memberId); + + return new FindClubResponse(club, member, pets); + } } diff --git a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java index 1bba88c8e..be2285859 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.woowacourse.friendogly.club.domain.Club; -import com.woowacourse.friendogly.club.domain.ClubMember; import com.woowacourse.friendogly.club.dto.request.SaveClubMemberRequest; import com.woowacourse.friendogly.club.dto.request.SaveClubRequest; import com.woowacourse.friendogly.club.dto.response.SaveClubResponse; @@ -166,7 +165,7 @@ void deleteClubMember() { assertAll( () -> assertThat(savedClub.countClubMember()).isEqualTo(1), - () -> assertThat(savedClub.isOwner(ClubMember.create(savedClub, savedNewMember))).isTrue() + () -> assertThat(savedClub.isOwner(savedNewMember)).isTrue() ); } diff --git a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java index e49da2956..c931fc74f 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java @@ -5,6 +5,7 @@ import static com.epages.restdocs.apispec.ResourceDocumentation.parameterWithName; import static com.epages.restdocs.apispec.ResourceDocumentation.resource; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @@ -22,6 +23,9 @@ import com.woowacourse.friendogly.club.dto.request.FindSearchingClubRequest; import com.woowacourse.friendogly.club.dto.request.SaveClubMemberRequest; import com.woowacourse.friendogly.club.dto.request.SaveClubRequest; +import com.woowacourse.friendogly.club.dto.response.ClubMemberDetailResponse; +import com.woowacourse.friendogly.club.dto.response.ClubPetDetailResponse; +import com.woowacourse.friendogly.club.dto.response.FindClubResponse; import com.woowacourse.friendogly.club.dto.response.FindSearchingClubResponse; import com.woowacourse.friendogly.club.dto.response.SaveClubMemberResponse; import com.woowacourse.friendogly.club.dto.response.SaveClubResponse; @@ -113,33 +117,89 @@ void findSearching_200() throws Exception { fieldWithPath("isSuccess").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), fieldWithPath("data.[].id").type(JsonFieldType.NUMBER).description("모임 식별자"), fieldWithPath("data.[].title").type(JsonFieldType.STRING).description("모임 제목"), - fieldWithPath("data.[].content").type(JsonFieldType.STRING) - .description("모임 본문"), - fieldWithPath("data.[].ownerMemberName").type(JsonFieldType.STRING) - .description("모임 방장 이름"), - fieldWithPath("data.[].address").type(JsonFieldType.STRING) - .description("모임의 주소"), - fieldWithPath("data.[].status").type(JsonFieldType.STRING) - .description("모임 상태(OPEN , CLOSED)"), - fieldWithPath("data.[].createdAt").type(JsonFieldType.STRING) - .description("모임 생성 시간(LocalDateTime)"), - fieldWithPath("data.[].allowedGender").type(JsonFieldType.ARRAY) - .description("허용되는 팻 성별(MALE, FEMALE, MALE_NEUTERED, FEMALE_NEUTERED)"), - fieldWithPath("data.[].allowedSize").type(JsonFieldType.ARRAY) - .description("허용되는 팻 크기(SMALL,MEDIUM,LARGE)"), - fieldWithPath("data.[].memberCapacity").type(JsonFieldType.NUMBER) - .description("모임 최대 인원"), - fieldWithPath("data.[].currentMemberCount").type(JsonFieldType.NUMBER) - .description("모임 현재 인원"), - fieldWithPath("data.[].imageUrl").type(JsonFieldType.STRING) - .description("모임 프로필 사진"), - fieldWithPath("data.[].petImageUrls").type(JsonFieldType.ARRAY) - .description("모임 리스트에 나오는 팻 사진 url 리스트")) + fieldWithPath("data.[].content").type(JsonFieldType.STRING).description("모임 본문"), + fieldWithPath("data.[].ownerMemberName").type(JsonFieldType.STRING).description("모임 방장 이름"), + fieldWithPath("data.[].address").type(JsonFieldType.STRING).description("모임의 주소"), + fieldWithPath("data.[].status").type(JsonFieldType.STRING).description("모임 상태(OPEN , CLOSED)"), + fieldWithPath("data.[].createdAt").type(JsonFieldType.STRING).description("모임 생성 시간(LocalDateTime)"), + fieldWithPath("data.[].allowedGender").type(JsonFieldType.ARRAY).description("허용되는 팻 성별(MALE, FEMALE, MALE_NEUTERED, FEMALE_NEUTERED)"), + fieldWithPath("data.[].allowedSize").type(JsonFieldType.ARRAY).description("허용되는 팻 크기(SMALL,MEDIUM,LARGE)"), + fieldWithPath("data.[].memberCapacity").type(JsonFieldType.NUMBER).description("모임 최대 인원"), + fieldWithPath("data.[].currentMemberCount").type(JsonFieldType.NUMBER).description("모임 현재 인원"), + fieldWithPath("data.[].imageUrl").type(JsonFieldType.STRING).description("모임 프로필 사진"), + fieldWithPath("data.[].petImageUrls").type(JsonFieldType.ARRAY).description("모임 리스트에 나오는 팻 사진 url 리스트")) .responseSchema(Schema.schema("FindSearchingClubResponse")) .build())) ); } + @DisplayName("ID를 통해 모임의 상세 정보를 조회한다.") + @Test + void findById_200() throws Exception { + FindClubResponse response = new FindClubResponse( + 1L, + "모임 제목1", + "모임 본문 내용1", + "브라운", + "서울특별시 송파구 신정동 잠실 6동", + Status.OPEN, + LocalDateTime.now(), + Set.of(Gender.FEMALE, Gender.FEMALE_NEUTERED), + Set.of(SizeType.SMALL), + 4, + 1, + "https://clubImage1.com", + "https://OwnerProfileImage.com", + true, + true, + false, + List.of(new ClubMemberDetailResponse(2L, "정지호", "https://땡이 영공때 사진.com"), + new ClubMemberDetailResponse(3L, "쪙찌효", "https://땡이 근공때 사진.com")), + List.of(new ClubPetDetailResponse(1L, "땡이", "https://땡이의 귀여운 이미지.com"), + new ClubPetDetailResponse(2L, "땡이 동생", "https://땡이 동생의 귀여운 이미지.com")) + ); + + when(clubQueryService.findById(anyLong(), anyLong())) + .thenReturn(response); + + mockMvc.perform(get("/clubs/{id}",1L) + .header(HttpHeaders.AUTHORIZATION, 2L)) + .andExpect(status().isOk()) + .andDo(document("clubs/findById/200", + getDocumentRequest(), + getDocumentResponse(), + resource(ResourceSnippetParameters.builder() + .tag("Club API") + .summary("모임 상세 조회 API") + .responseFields( + fieldWithPath("isSuccess").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), + fieldWithPath("data.id").type(JsonFieldType.NUMBER).description("모임 식별자"), + fieldWithPath("data.title").type(JsonFieldType.STRING).description("모임 제목"), + fieldWithPath("data.content").type(JsonFieldType.STRING).description("모임 본문"), + fieldWithPath("data.ownerMemberName").type(JsonFieldType.STRING).description("모임 방장 이름"), + fieldWithPath("data.address").type(JsonFieldType.STRING).description("모임의 주소"), + fieldWithPath("data.status").type(JsonFieldType.STRING).description("모임 상태(OPEN , CLOSED)"), + fieldWithPath("data.createdAt").type(JsonFieldType.STRING).description("모임 생성 시간(LocalDateTime)"), + fieldWithPath("data.allowedGender").type(JsonFieldType.ARRAY).description("허용되는 팻 성별(MALE, FEMALE, MALE_NEUTERED, FEMALE_NEUTERED)"), + fieldWithPath("data.allowedSize").type(JsonFieldType.ARRAY).description("허용되는 팻 크기(SMALL,MEDIUM,LARGE)"), + fieldWithPath("data.memberCapacity").type(JsonFieldType.NUMBER).description("모임 최대 인원"), + fieldWithPath("data.currentMemberCount").type(JsonFieldType.NUMBER).description("모임 현재 인원"), + fieldWithPath("data.imageUrl").type(JsonFieldType.STRING).description("모임 프로필 사진"), + fieldWithPath("data.ownerImageUrl").type(JsonFieldType.STRING).description("모임 방장 프로필 사진"), + fieldWithPath("data.isMine").type(JsonFieldType.BOOLEAN).description("현재 로그인한 사용자가 방장인지 여부"), + fieldWithPath("data.alreadyParticipate").type(JsonFieldType.BOOLEAN).description("현재 로그인한 사용자가 모임에 참여 중인지 여부"), + fieldWithPath("data.canParticipate").type(JsonFieldType.BOOLEAN).description("현재 로그인한 사용자가 모임에 참여 가능한지 여부"), + fieldWithPath("data.memberDetails[].id").type(JsonFieldType.NUMBER).description("모임에 참여한 사용자 id"), + fieldWithPath("data.memberDetails[].name").type(JsonFieldType.STRING).description("모임에 참여한 사용자 이름"), + fieldWithPath("data.memberDetails[].imageUrl").type(JsonFieldType.STRING).description("모임에 참여한 사용자 이미지 URL"), + fieldWithPath("data.petDetails[].id").type(JsonFieldType.NUMBER).description("모임에 참여한 반려견 id"), + fieldWithPath("data.petDetails[].name").type(JsonFieldType.STRING).description("모임에 참여한 반려견 이름"), + fieldWithPath("data.petDetails[].imageUrl").type(JsonFieldType.STRING).description("모임에 참여한 반려견 이미지 URL")) + .responseSchema(Schema.schema("FindClubResponse")) + .build())) + ); + } + @DisplayName("모임을 생성한다.") @Test void save_201() throws Exception { @@ -189,17 +249,12 @@ void save_201() throws Exception { .requestFields( fieldWithPath("title").type(JsonFieldType.STRING).description("모임 제목"), fieldWithPath("content").type(JsonFieldType.STRING).description("모임 내용"), - fieldWithPath("imageUrl").type(JsonFieldType.STRING) - .description("모임 프로필 사진 url"), + fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("모임 프로필 사진 url"), fieldWithPath("address").type(JsonFieldType.STRING).description("모임 주소"), - fieldWithPath("allowedGenders").type(JsonFieldType.ARRAY) - .description("참여가능한 강아지 성별"), - fieldWithPath("allowedSizes").type(JsonFieldType.ARRAY) - .description("참여가능한 강아지 사이즈"), - fieldWithPath("memberCapacity").type(JsonFieldType.NUMBER) - .description("모임 최대 인원"), - fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY) - .description("모임에 참여하는 방장 강아지의 ID 리스트") + fieldWithPath("allowedGenders").type(JsonFieldType.ARRAY).description("참여가능한 강아지 성별"), + fieldWithPath("allowedSizes").type(JsonFieldType.ARRAY).description("참여가능한 강아지 사이즈"), + fieldWithPath("memberCapacity").type(JsonFieldType.NUMBER).description("모임 최대 인원"), + fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY).description("모임에 참여하는 방장 강아지의 ID 리스트") ) .responseHeaders( headerWithName(HttpHeaders.LOCATION).description("생성된 모임 리소스 Location")) @@ -208,27 +263,17 @@ void save_201() throws Exception { fieldWithPath("data.id").type(JsonFieldType.NUMBER).description("모임 식별자"), fieldWithPath("data.title").type(JsonFieldType.STRING).description("모임 제목"), fieldWithPath("data.content").type(JsonFieldType.STRING).description("모임 본문"), - fieldWithPath("data.ownerMemberName").type(JsonFieldType.STRING) - .description("모임 방장 이름"), + fieldWithPath("data.ownerMemberName").type(JsonFieldType.STRING).description("모임 방장 이름"), fieldWithPath("data.address").type(JsonFieldType.STRING).description("모임의 주소"), - fieldWithPath("data.status").type(JsonFieldType.STRING) - .description("모임 상태(OPEN , CLOSED)"), - fieldWithPath("data.createdAt").type(JsonFieldType.STRING) - .description("모임 생성 시간(LocalDateTime)"), - fieldWithPath("data.allowedSize").type(JsonFieldType.ARRAY) - .description("허용되는 팻 크기(SMALL,MEDIUM,LARGE)"), - fieldWithPath("data.allowedGender").type(JsonFieldType.ARRAY) - .description("허용되는 팻 성별(MALE, FEMALE, MALE_NEUTERED, FEMALE_NEUTERED)"), - fieldWithPath("data.memberCapacity").type(JsonFieldType.NUMBER) - .description("모임 최대 인원"), - fieldWithPath("data.currentMemberCount").type(JsonFieldType.NUMBER) - .description("모임 현재 인원(대부분의 경우 1)"), - fieldWithPath("data.imageUrl").type(JsonFieldType.STRING) - .description("모임 프로필 사진"), - fieldWithPath("data.petImageUrls").type(JsonFieldType.ARRAY) - .description("모임 리스트에 참여하는 팻 프로필 url"), - fieldWithPath("data.isMine").type(JsonFieldType.BOOLEAN) - .description("현재 회원의 글인지 판단하는 값(대부분의 경우 true)")) + fieldWithPath("data.status").type(JsonFieldType.STRING).description("모임 상태(OPEN , CLOSED)"), + fieldWithPath("data.createdAt").type(JsonFieldType.STRING).description("모임 생성 시간(LocalDateTime)"), + fieldWithPath("data.allowedSize").type(JsonFieldType.ARRAY).description("허용되는 팻 크기(SMALL,MEDIUM,LARGE)"), + fieldWithPath("data.allowedGender").type(JsonFieldType.ARRAY).description("허용되는 팻 성별(MALE, FEMALE, MALE_NEUTERED, FEMALE_NEUTERED)"), + fieldWithPath("data.memberCapacity").type(JsonFieldType.NUMBER).description("모임 최대 인원"), + fieldWithPath("data.currentMemberCount").type(JsonFieldType.NUMBER).description("모임 현재 인원(대부분의 경우 1)"), + fieldWithPath("data.imageUrl").type(JsonFieldType.STRING).description("모임 프로필 사진"), + fieldWithPath("data.petImageUrls").type(JsonFieldType.ARRAY).description("모임 리스트에 참여하는 팻 프로필 url"), + fieldWithPath("data.isMine").type(JsonFieldType.BOOLEAN).description("현재 회원의 글인지 판단하는 값(대부분의 경우 true)")) .requestSchema(Schema.schema("saveClubRequest")) .responseSchema(Schema.schema("saveClubResponse")) .build())) @@ -257,20 +302,16 @@ void saveClubMember_201() throws Exception { .summary("모임 참여 API") .pathParameters( parameterWithName("clubId").type(SimpleType.NUMBER).description("참여하는 모임의 ID")) - .requestHeaders(headerWithName(HttpHeaders.AUTHORIZATION).type(SimpleType.NUMBER) - .description("로그인 중인 회원 ID")) + .requestHeaders(headerWithName(HttpHeaders.AUTHORIZATION).type(SimpleType.NUMBER).description("로그인 중인 회원 ID")) .requestFields( - fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY) - .description("참여하는 팻 ID 리스트") + fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY).description("참여하는 팻 ID 리스트") ) .responseHeaders( - headerWithName(HttpHeaders.LOCATION).type(SimpleType.STRING) - .description("모임-회원 연관관계 리소스 Location") + headerWithName(HttpHeaders.LOCATION).type(SimpleType.STRING).description("모임-회원 연관관계 리소스 Location") ) .responseFields( fieldWithPath("isSuccess").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), - fieldWithPath("data.memberId").type(JsonFieldType.NUMBER) - .description("모임-회원 연관관계 ID(추후 채팅 생기면 변경)")) + fieldWithPath("data.memberId").type(JsonFieldType.NUMBER).description("모임-회원 연관관계 ID(추후 채팅 생기면 변경)")) .responseSchema(Schema.schema("SaveClubMemberResponse")) .build()) )); @@ -298,17 +339,14 @@ void saveClubMember_400() throws Exception { .summary("모임 참여 API") .pathParameters( parameterWithName("clubId").type(SimpleType.NUMBER).description("참여하는 모임의 ID")) - .requestHeaders(headerWithName(HttpHeaders.AUTHORIZATION).type(SimpleType.NUMBER) - .description("로그인 중인 회원 ID")) + .requestHeaders(headerWithName(HttpHeaders.AUTHORIZATION).type(SimpleType.NUMBER).description("로그인 중인 회원 ID")) .requestFields( - fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY) - .description("참여하는 팻 ID 리스트") + fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY).description("참여하는 팻 ID 리스트") ) .responseFields( fieldWithPath("isSuccess").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), fieldWithPath("data.errorCode").type(JsonFieldType.STRING).description("에러 코드"), - fieldWithPath("data.errorMessage").type(JsonFieldType.STRING) - .description("에러메세지"), + fieldWithPath("data.errorMessage").type(JsonFieldType.STRING).description("에러메세지"), fieldWithPath("data.detail").type(JsonFieldType.ARRAY).description("에러 디테일") ) .build()) @@ -366,13 +404,11 @@ void deleteClubMember_400() throws Exception { .responseFields( fieldWithPath("isSuccess").type(JsonFieldType.BOOLEAN).description("요청 성공 여부"), fieldWithPath("data.errorCode").type(JsonFieldType.STRING).description("에러 코드"), - fieldWithPath("data.errorMessage").type(JsonFieldType.STRING) - .description("에러메세지"), + fieldWithPath("data.errorMessage").type(JsonFieldType.STRING).description("에러메세지"), fieldWithPath("data.detail").type(JsonFieldType.ARRAY).description("에러 디테일") ) .build()) )); - } @Override From 5a34a16551cd4d930b7f3e37cda2233024198d8b Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Wed, 31 Jul 2024 17:54:25 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EB=AA=A8=EC=9E=84=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=A1=B0=EA=B1=B4=20=ED=95=84=EB=93=9C=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 Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- .../club/controller/ClubController.java | 7 +++- .../friendogly/club/domain/Club.java | 6 ++- .../club/domain/FilterCondition.java | 18 +++++++++ .../dto/request/FindSearchingClubRequest.java | 3 ++ .../club/service/ClubQueryService.java | 38 ++++++++++++++----- .../club/service/ClubQueryServiceTest.java | 7 +++- .../friendogly/docs/ClubApiDocsTest.java | 7 +++- 7 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java index 6633f771c..1ea8c49c7 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java @@ -42,8 +42,11 @@ public ApiResponse findById(@Auth Long memberId, @PathVariable // TODO: @ModelAttribute @GetMapping("/searching") - public ApiResponse> findSearching(@Valid FindSearchingClubRequest request) { - return ApiResponse.ofSuccess(clubQueryService.findSearching(request)); + public ApiResponse> findSearching( + @Auth Long memberId, + @Valid FindSearchingClubRequest request + ) { + return ApiResponse.ofSuccess(clubQueryService.findSearching(memberId, request)); } @PostMapping diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java b/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java index 1be3e6e8d..0d8a64dd6 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java @@ -176,7 +176,7 @@ public boolean isJoinable(Member member, List pets) { .anyMatch(pet -> !canNotJoin(pet)); boolean isNotFull = !this.memberCapacity.isCapacityReached(countClubMember()); - return hasJoinablePet && isNotFull && isAlreadyJoined(member); + return hasJoinablePet && isNotFull && isAlreadyJoined(member) && isOpen(); } public void removeClubMember(Member member) { @@ -213,6 +213,10 @@ public boolean isEmpty() { return clubMembers.isEmpty(); } + public boolean isOpen() { + return this.status == Status.OPEN; + } + public boolean isOwner(Member targetMember) { return findOwner().isSameMember(targetMember); } diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java b/backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java new file mode 100644 index 000000000..26de7d7f6 --- /dev/null +++ b/backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java @@ -0,0 +1,18 @@ +package com.woowacourse.friendogly.club.domain; + +import com.woowacourse.friendogly.exception.FriendoglyException; + +public enum FilterCondition { + + ALL, + OPEN, + ABLE_TO_JOIN; + + public static FilterCondition toFilterCondition(String filterCondition) { + try { + return valueOf(filterCondition); + } catch (IllegalArgumentException e) { + throw new FriendoglyException("존재하지 않는 FilterCondition 입니다."); + } + } +} diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/FindSearchingClubRequest.java b/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/FindSearchingClubRequest.java index a23f6e282..17aa0347c 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/FindSearchingClubRequest.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/FindSearchingClubRequest.java @@ -7,6 +7,9 @@ import java.util.Set; public record FindSearchingClubRequest( + @NotBlank(message = "필터링 조건은 필수입니다.") + String filterCondition, + @NotBlank(message = "주소 정보는 필수입니다.") String address, diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java index d1193f87f..89984633f 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java @@ -1,6 +1,7 @@ package com.woowacourse.friendogly.club.service; import com.woowacourse.friendogly.club.domain.Club; +import com.woowacourse.friendogly.club.domain.FilterCondition; import com.woowacourse.friendogly.club.dto.request.FindSearchingClubRequest; import com.woowacourse.friendogly.club.dto.response.FindClubResponse; import com.woowacourse.friendogly.club.dto.response.FindSearchingClubResponse; @@ -35,18 +36,43 @@ public ClubQueryService( this.petRepository = petRepository; } - public List findSearching(FindSearchingClubRequest request) { + public List findSearching(Long memberId, FindSearchingClubRequest request) { + Member member = memberRepository.getById(memberId); + List pets = petRepository.findByMemberId(memberId); + Specification spec = ClubSpecification.where() .equalsAddress(request.address()) .hasGenders(request.genderParams()) .hasSizeTypes(request.sizeParams()) .build(); - return clubRepository.findAll(spec).stream() + List clubs = clubRepository.findAll(spec); + + if (FilterCondition.toFilterCondition(request.filterCondition()) == FilterCondition.ABLE_TO_JOIN) { + return clubs.stream() + .filter(club -> club.isJoinable(member, pets)) + .map(club -> new FindSearchingClubResponse(club, collectOverviewPetImages(club))) + .toList(); + } + if (FilterCondition.toFilterCondition(request.filterCondition()) == FilterCondition.OPEN) { + return clubs.stream() + .filter(Club::isOpen) + .map(club -> new FindSearchingClubResponse(club, collectOverviewPetImages(club))) + .toList(); + } + return clubs.stream() .map(club -> new FindSearchingClubResponse(club, collectOverviewPetImages(club))) .toList(); } + public FindClubResponse findById(Long memberId, Long id) { + Club club = clubRepository.getById(id); + Member member = memberRepository.getById(memberId); + List pets = petRepository.findByMemberId(memberId); + + return new FindClubResponse(club, member, pets); + } + private List collectOverviewPetImages(Club club) { Map> groupPetsByMemberId = club.getClubPets() .stream() @@ -57,12 +83,4 @@ private List collectOverviewPetImages(Club club) { .map(petList -> petList.get(0).getImageUrl()) .toList(); } - - public FindClubResponse findById(Long memberId, Long id) { - Club club = clubRepository.getById(id); - Member member = memberRepository.getById(memberId); - List pets = petRepository.findByMemberId(memberId); - - return new FindClubResponse(club, member, pets); - } } diff --git a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java index 29195093e..a82636a45 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.woowacourse.friendogly.club.domain.Club; +import com.woowacourse.friendogly.club.domain.FilterCondition; import com.woowacourse.friendogly.club.dto.request.FindSearchingClubRequest; import com.woowacourse.friendogly.club.dto.response.FindSearchingClubResponse; import com.woowacourse.friendogly.member.domain.Member; @@ -43,12 +44,13 @@ void findSearching() { ); FindSearchingClubRequest request = new FindSearchingClubRequest( + FilterCondition.ALL.name(), address, Set.of(Gender.FEMALE), Set.of(SizeType.SMALL) ); - List responses = clubQueryService.findSearching(request); + List responses = clubQueryService.findSearching(savedMember.getId(), request); List expectedResponses = List.of( new FindSearchingClubResponse(club, List.of(petImageUrl)) ); @@ -82,12 +84,13 @@ void findSearching_Nothing() { ); FindSearchingClubRequest request = new FindSearchingClubRequest( + FilterCondition.ALL.name(), address, Set.of(Gender.MALE), Set.of(SizeType.SMALL) ); - List responses = clubQueryService.findSearching(request); + List responses = clubQueryService.findSearching(savedMember.getId(), request); assertThat(responses.isEmpty()).isTrue(); } diff --git a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java index c931fc74f..76819911c 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java @@ -19,6 +19,7 @@ import com.epages.restdocs.apispec.Schema; import com.epages.restdocs.apispec.SimpleType; import com.woowacourse.friendogly.club.controller.ClubController; +import com.woowacourse.friendogly.club.domain.FilterCondition; import com.woowacourse.friendogly.club.domain.Status; import com.woowacourse.friendogly.club.dto.request.FindSearchingClubRequest; import com.woowacourse.friendogly.club.dto.request.SaveClubMemberRequest; @@ -58,6 +59,7 @@ public class ClubApiDocsTest extends RestDocsTest { @Test void findSearching_200() throws Exception { FindSearchingClubRequest request = new FindSearchingClubRequest( + FilterCondition.ALL.name(), "서울특별시 송파구 신청동 잠실 6동", Set.of(Gender.FEMALE, Gender.FEMALE_NEUTERED), Set.of(SizeType.SMALL) @@ -95,10 +97,12 @@ void findSearching_200() throws Exception { ) ); - when(clubQueryService.findSearching(request)) + when(clubQueryService.findSearching(anyLong(), any())) .thenReturn(responses); mockMvc.perform(get("/clubs/searching") + .header(HttpHeaders.AUTHORIZATION, 1L) + .param("filterCondition", request.filterCondition()) .param("address", request.address()) .param("genderParams", request.genderParams().stream().map(Enum::name).toArray(String[]::new)) .param("sizeParams", request.sizeParams().stream().map(Enum::name).toArray(String[]::new))) @@ -110,6 +114,7 @@ void findSearching_200() throws Exception { .tag("Club API") .summary("모임 검색 조회 API") .queryParameters( + parameterWithName("filterCondition").description("모임의 필터 조건 (ALL, OPEN, ABLE_TO_JOIN)"), parameterWithName("address").description("모임의 주소"), parameterWithName("genderParams").description("모임에 참여가능한 팻 성별"), parameterWithName("sizeParams").description("모임에 참여가능한 팻 크기")) From 18b1ae13b42e4cadae8ce0b0da2fc39a8a8ede0d Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Thu, 1 Aug 2024 11:48:00 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EB=AA=A8=EC=9E=84=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EB=82=B4=20?= =?UTF-8?q?=EA=B0=95=EC=95=84=EC=A7=80=20=EC=97=AC=EB=B6=80=EB=A5=BC=20?= =?UTF-8?q?=ED=8C=90=EB=B3=84=ED=95=98=EB=8A=94=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- .../club/dto/response/ClubPetDetailResponse.java | 7 ++++--- .../friendogly/club/dto/response/FindClubResponse.java | 2 +- .../java/com/woowacourse/friendogly/pet/domain/Pet.java | 4 ++++ .../com/woowacourse/friendogly/docs/ClubApiDocsTest.java | 7 ++++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java index a3ca0b76f..3b768e205 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/ClubPetDetailResponse.java @@ -5,10 +5,11 @@ public record ClubPetDetailResponse( Long id, String name, - String imageUrl + String imageUrl, + boolean isMine ){ - public ClubPetDetailResponse(Pet pet) { - this(pet.getId(),pet.getName().getValue(), pet.getImageUrl()); + public ClubPetDetailResponse(Pet pet, boolean isMine) { + this(pet.getId(),pet.getName().getValue(), pet.getImageUrl(), isMine); } } diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java index 98caa4dca..dbf5f38d9 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/dto/response/FindClubResponse.java @@ -55,7 +55,7 @@ public FindClubResponse(Club club, Member member, List myPets) { .toList(), club.getClubPets().stream() .map(clubPet -> clubPet.getClubPetPk().getPet()) - .map(ClubPetDetailResponse::new) + .map(pet -> new ClubPetDetailResponse(pet, pet.isOwner(member))) .toList() ); } diff --git a/backend/src/main/java/com/woowacourse/friendogly/pet/domain/Pet.java b/backend/src/main/java/com/woowacourse/friendogly/pet/domain/Pet.java index 061cb83da..fd40a71e6 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/pet/domain/Pet.java +++ b/backend/src/main/java/com/woowacourse/friendogly/pet/domain/Pet.java @@ -76,4 +76,8 @@ private void validateMember(Member member) { throw new FriendoglyException("member는 null일 수 없습니다."); } } + + public boolean isOwner(Member member) { + return this.member.getId().equals(member.getId()); + } } diff --git a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java index 76819911c..11a7cfa3f 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java @@ -160,8 +160,8 @@ void findById_200() throws Exception { false, List.of(new ClubMemberDetailResponse(2L, "정지호", "https://땡이 영공때 사진.com"), new ClubMemberDetailResponse(3L, "쪙찌효", "https://땡이 근공때 사진.com")), - List.of(new ClubPetDetailResponse(1L, "땡이", "https://땡이의 귀여운 이미지.com"), - new ClubPetDetailResponse(2L, "땡이 동생", "https://땡이 동생의 귀여운 이미지.com")) + List.of(new ClubPetDetailResponse(1L, "땡이", "https://땡이의 귀여운 이미지.com", true), + new ClubPetDetailResponse(2L, "땡이 동생", "https://땡이 동생의 귀여운 이미지.com", true)) ); when(clubQueryService.findById(anyLong(), anyLong())) @@ -199,7 +199,8 @@ void findById_200() throws Exception { fieldWithPath("data.memberDetails[].imageUrl").type(JsonFieldType.STRING).description("모임에 참여한 사용자 이미지 URL"), fieldWithPath("data.petDetails[].id").type(JsonFieldType.NUMBER).description("모임에 참여한 반려견 id"), fieldWithPath("data.petDetails[].name").type(JsonFieldType.STRING).description("모임에 참여한 반려견 이름"), - fieldWithPath("data.petDetails[].imageUrl").type(JsonFieldType.STRING).description("모임에 참여한 반려견 이미지 URL")) + fieldWithPath("data.petDetails[].imageUrl").type(JsonFieldType.STRING).description("모임에 참여한 반려견 이미지 URL"), + fieldWithPath("data.petDetails[].isMine").type(JsonFieldType.BOOLEAN).description("로그인한 사용자의 반려견인지 여부")) .responseSchema(Schema.schema("FindClubResponse")) .build())) ); From ed753c17649ab5afce203d2a084039ee5aee4092 Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Thu, 1 Aug 2024 11:48:13 +0900 Subject: [PATCH 4/8] =?UTF-8?q?chore:=20=EB=AA=A8=EC=9E=84=20=EB=8D=94?= =?UTF-8?q?=EB=AF=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- backend/src/main/resources/data.sql | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/backend/src/main/resources/data.sql b/backend/src/main/resources/data.sql index 6b279dd1c..7742b062e 100644 --- a/backend/src/main/resources/data.sql +++ b/backend/src/main/resources/data.sql @@ -34,3 +34,28 @@ VALUES (1, '부', '곰돌이 컷 원조가 저에요~!', '2010-04-01', 'SMALL', 'https://mblogthumb-phinf.pstatic.net/MjAxNzA0MjhfMjg5/MDAxNDkzMzQzOTgxMDQy.4u283s2rrib7xSWR5IdL1r_yiipEITnM6VofjaJsZPUg.oTUM6w8LeF-orpapq_S8j45mjRxjofwrpq8jhQ5LC5Eg.PNG.fjvfeewt/20170428_104509.png?type=w800'), (8, '초코', '매일 저녁에 나와요!', '2017-12-01', 'MEDIUM', 'FEMALE', 'https://cdn.pixabay.com/photo/2019/12/08/21/09/animal-4682251_960_720.jpg'); + +INSERT INTO club (title, content, member_capacity, address, image_url, created_at, status) +VALUES ('전국구 강아지 모임', '주먹이 가장 매운 강이지 모임', 3, '서울 송파구 신천동', 'http://example.com/image.jpg', '2023-08-01 12:00:00', 'OPEN'), + ('미녀 강아지 모임', '예쁜 강아지 모임', 5, '서울 송파구 신천동', 'http://example.com/image.jpg', '2023-07-31 01:00:00', 'OPEN'); + +INSERT INTO club_gender (club_id, allowed_gender) +VALUES (1, 'MALE'), + (1, 'FEMALE'), + (2, 'MALE_NEUTERED'), + (2, 'FEMALE_NEUTERED'); + +INSERT INTO club_size (club_id, allowed_size) +VALUES (1, 'LARGE'), + (2, 'SMALL'), + (2, 'MEDIUM'); + +INSERT INTO club_member (club_id, member_id, created_at) +VALUES (1, 6, '2023-07-31 01:00:00'), + (2, 5, '2023-07-31 01:00:00'), + (2, 7, '2023-07-31 02:00:00'); + +INSERT INTO club_pet (club_id, pet_id) +VALUES (1, 6), + (2, 5), + (2, 7); From 4437d46cab35f30763bdd7d3ced2bb3b4f1983b7 Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Thu, 1 Aug 2024 15:02:50 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=AA=85=ED=99=95=ED=9E=88=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- .../club/controller/ClubController.java | 4 +-- .../friendogly/club/domain/Club.java | 8 ++--- .../club/domain/FilterCondition.java | 10 +++++- .../club/service/ClubQueryService.java | 34 +++++++++++-------- .../club/service/ClubQueryServiceTest.java | 4 +-- .../friendogly/docs/ClubApiDocsTest.java | 2 +- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java index 1ea8c49c7..bdd4465e2 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java @@ -42,11 +42,11 @@ public ApiResponse findById(@Auth Long memberId, @PathVariable // TODO: @ModelAttribute @GetMapping("/searching") - public ApiResponse> findSearching( + public ApiResponse> findByFilter( @Auth Long memberId, @Valid FindSearchingClubRequest request ) { - return ApiResponse.ofSuccess(clubQueryService.findSearching(memberId, request)); + return ApiResponse.ofSuccess(clubQueryService.findFindByFilter(memberId, request)); } @PostMapping diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java b/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java index 0d8a64dd6..994cf75ca 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/domain/Club.java @@ -162,18 +162,18 @@ public void addClubPet(List pets) { } private void validateParticipatePet(Pet pet) { - if (canNotJoin(pet)) { + if (!canJoinWith(pet)) { throw new FriendoglyException("모임에 데려갈 수 없는 강아지가 있습니다."); } } - private boolean canNotJoin(Pet pet) { - return !allowedGenders.contains(pet.getGender()) || !allowedSizes.contains(pet.getSizeType()); + private boolean canJoinWith(Pet pet) { + return allowedGenders.contains(pet.getGender()) && allowedSizes.contains(pet.getSizeType()); } public boolean isJoinable(Member member, List pets) { boolean hasJoinablePet = pets.stream() - .anyMatch(pet -> !canNotJoin(pet)); + .anyMatch(this::canJoinWith); boolean isNotFull = !this.memberCapacity.isCapacityReached(countClubMember()); return hasJoinablePet && isNotFull && isAlreadyJoined(member) && isOpen(); diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java b/backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java index 26de7d7f6..d7feddb23 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/domain/FilterCondition.java @@ -8,11 +8,19 @@ public enum FilterCondition { OPEN, ABLE_TO_JOIN; - public static FilterCondition toFilterCondition(String filterCondition) { + public static FilterCondition from(String filterCondition) { try { return valueOf(filterCondition); } catch (IllegalArgumentException e) { throw new FriendoglyException("존재하지 않는 FilterCondition 입니다."); } } + + public boolean isOpen() { + return this == OPEN; + } + + public boolean isAbleToJoin() { + return this == ABLE_TO_JOIN; + } } diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java index 89984633f..3d039301a 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubQueryService.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,7 +37,7 @@ public ClubQueryService( this.petRepository = petRepository; } - public List findSearching(Long memberId, FindSearchingClubRequest request) { + public List findFindByFilter(Long memberId, FindSearchingClubRequest request) { Member member = memberRepository.getById(memberId); List pets = petRepository.findByMemberId(memberId); @@ -48,23 +49,28 @@ public List findSearching(Long memberId, FindSearchin List clubs = clubRepository.findAll(spec); - if (FilterCondition.toFilterCondition(request.filterCondition()) == FilterCondition.ABLE_TO_JOIN) { - return clubs.stream() - .filter(club -> club.isJoinable(member, pets)) - .map(club -> new FindSearchingClubResponse(club, collectOverviewPetImages(club))) - .toList(); - } - if (FilterCondition.toFilterCondition(request.filterCondition()) == FilterCondition.OPEN) { - return clubs.stream() - .filter(Club::isOpen) - .map(club -> new FindSearchingClubResponse(club, collectOverviewPetImages(club))) - .toList(); - } - return clubs.stream() + FilterCondition filterCondition = FilterCondition.from(request.filterCondition()); + + return filterClubs(clubs.stream(), filterCondition, member, pets) .map(club -> new FindSearchingClubResponse(club, collectOverviewPetImages(club))) .toList(); } + private Stream filterClubs( + Stream clubStream, + FilterCondition filterCondition, + Member member, + List pets + ) { + if (filterCondition.isAbleToJoin()) { + return clubStream.filter(club -> club.isJoinable(member, pets)); + } + if (filterCondition.isOpen()) { + return clubStream.filter(Club::isOpen); + } + return clubStream; + } + public FindClubResponse findById(Long memberId, Long id) { Club club = clubRepository.getById(id); Member member = memberRepository.getById(memberId); diff --git a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java index a82636a45..10c84bf42 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubQueryServiceTest.java @@ -50,7 +50,7 @@ void findSearching() { Set.of(SizeType.SMALL) ); - List responses = clubQueryService.findSearching(savedMember.getId(), request); + List responses = clubQueryService.findFindByFilter(savedMember.getId(), request); List expectedResponses = List.of( new FindSearchingClubResponse(club, List.of(petImageUrl)) ); @@ -90,7 +90,7 @@ void findSearching_Nothing() { Set.of(SizeType.SMALL) ); - List responses = clubQueryService.findSearching(savedMember.getId(), request); + List responses = clubQueryService.findFindByFilter(savedMember.getId(), request); assertThat(responses.isEmpty()).isTrue(); } diff --git a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java index 11a7cfa3f..0878e60a3 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java @@ -97,7 +97,7 @@ void findSearching_200() throws Exception { ) ); - when(clubQueryService.findSearching(anyLong(), any())) + when(clubQueryService.findFindByFilter(anyLong(), any())) .thenReturn(responses); mockMvc.perform(get("/clubs/searching") From be75fca71ddec5ac7c79b50fe3abfcc5c6a36350 Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Thu, 1 Aug 2024 15:09:27 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20@ModelAttribute=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- .../friendogly/club/controller/ClubController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java index bdd4465e2..cd0be25f4 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java @@ -17,6 +17,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -40,11 +41,10 @@ public ApiResponse findById(@Auth Long memberId, @PathVariable return ApiResponse.ofSuccess(clubQueryService.findById(memberId, id)); } - // TODO: @ModelAttribute @GetMapping("/searching") public ApiResponse> findByFilter( @Auth Long memberId, - @Valid FindSearchingClubRequest request + @Valid @ModelAttribute FindSearchingClubRequest request ) { return ApiResponse.ofSuccess(clubQueryService.findFindByFilter(memberId, request)); } @@ -59,7 +59,7 @@ public ResponseEntity> save( } @PostMapping("/{clubId}/members") - public ResponseEntity> saveClubMember( + public ResponseEntity> joinClub( @PathVariable Long clubId, @Auth Long memberId, @Valid @RequestBody SaveClubMemberRequest request @@ -72,7 +72,7 @@ public ResponseEntity> saveClubMember( @DeleteMapping("/{clubId}/members") public ResponseEntity deleteClubMember( @Auth Long memberId, - @PathVariable("clubId") Long clubId + @PathVariable Long clubId ) { clubCommandService.deleteClubMember(clubId, memberId); return ResponseEntity.noContent().build(); From 05404ce3d0dd8e68c51810645d1f199562e6ed84 Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Thu, 1 Aug 2024 16:05:56 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EB=AA=A8=EC=9E=84=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=93=B1=EB=A1=9D=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- .../club/controller/ClubController.java | 7 ++- .../club/dto/request/SaveClubRequest.java | 3 -- .../club/service/ClubCommandService.java | 16 ++++-- .../club/service/ClubCommandServiceTest.java | 8 +-- .../friendogly/docs/ClubApiDocsTest.java | 50 ++++++++++++------- 5 files changed, 54 insertions(+), 30 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java index cd0be25f4..f874d3f7d 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/controller/ClubController.java @@ -22,7 +22,9 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/clubs") @@ -52,9 +54,10 @@ public ApiResponse> findByFilter( @PostMapping public ResponseEntity> save( @Auth Long memberId, - @Valid @RequestBody SaveClubRequest request + @Valid @RequestPart SaveClubRequest request, + @RequestPart(required = false) MultipartFile image ) { - SaveClubResponse response = clubCommandService.save(memberId, request); + SaveClubResponse response = clubCommandService.save(memberId, image, request); return ResponseEntity.created(URI.create("/clubs" + response.id())).body(ApiResponse.ofSuccess(response)); } diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/SaveClubRequest.java b/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/SaveClubRequest.java index 2c627804f..3988993fe 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/SaveClubRequest.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/dto/request/SaveClubRequest.java @@ -19,9 +19,6 @@ public record SaveClubRequest( @Size(min = 1, max = 1000, message = "본문은 1글자 1000글자 사이입니다.") String content, - @NotBlank(message = "모임 사진을 등록해주세요.") - String imageUrl, - @NotBlank(message = "주소를 읽을 수 없습니다. 다시 시도해주세요.") String address, diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java index 002b20111..59ef833ad 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java @@ -7,6 +7,7 @@ import com.woowacourse.friendogly.club.dto.response.SaveClubMemberResponse; import com.woowacourse.friendogly.club.dto.response.SaveClubResponse; import com.woowacourse.friendogly.club.repository.ClubRepository; +import com.woowacourse.friendogly.infra.FileStorageManager; import com.woowacourse.friendogly.member.domain.Member; import com.woowacourse.friendogly.member.repository.MemberRepository; import com.woowacourse.friendogly.pet.domain.Pet; @@ -14,6 +15,7 @@ import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @Service @Transactional @@ -22,21 +24,29 @@ public class ClubCommandService { private final ClubRepository clubRepository; private final MemberRepository memberRepository; private final PetRepository petRepository; + private final FileStorageManager fileStorageManager; public ClubCommandService( ClubRepository clubRepository, MemberRepository memberRepository, - PetRepository petRepository + PetRepository petRepository, + FileStorageManager fileStorageManager ) { this.clubRepository = clubRepository; this.memberRepository = memberRepository; this.petRepository = petRepository; + this.fileStorageManager = fileStorageManager; } - public SaveClubResponse save(Long memberId, SaveClubRequest request) { + public SaveClubResponse save(Long memberId, MultipartFile image, SaveClubRequest request) { Member member = memberRepository.getById(memberId); List participatingPets = mapToPets(request.participatingPetsId()); + String imageUrl = ""; + if (image != null && !image.isEmpty()) { + imageUrl = fileStorageManager.uploadFile(image); + } + Club newClub = Club.create( request.title(), request.content(), @@ -45,7 +55,7 @@ public SaveClubResponse save(Long memberId, SaveClubRequest request) { member, request.allowedGenders(), request.allowedSizes(), - request.imageUrl(), + imageUrl, participatingPets ); diff --git a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java index be2285859..45e715032 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; class ClubCommandServiceTest extends ClubServiceTest { @@ -42,19 +44,19 @@ void save() { SaveClubRequest request = new SaveClubRequest( "모임 제목", "모임 내용", - "https://clubImage.com", "서울특별시 송파구 신정동 잠실 5동", Set.of(Gender.FEMALE, Gender.FEMALE_NEUTERED), Set.of(SizeType.SMALL), 5, List.of(savedPet.getId()) ); - SaveClubResponse actual = clubCommandService.save(savedMember.getId(), request); + MockMultipartFile image = new MockMultipartFile( + "image", "image", MediaType.MULTIPART_FORM_DATA.toString(), "asdf".getBytes()); + SaveClubResponse actual = clubCommandService.save(savedMember.getId(), image, request); assertAll( () -> assertThat(actual.title()).isEqualTo("모임 제목"), () -> assertThat(actual.content()).isEqualTo("모임 내용"), - () -> assertThat(actual.imageUrl()).isEqualTo("https://clubImage.com"), () -> assertThat(actual.address()).isEqualTo("서울특별시 송파구 신정동 잠실 5동"), () -> assertThat(actual.allowedGender()).containsExactlyInAnyOrderElementsOf( Set.of(Gender.FEMALE, Gender.FEMALE_NEUTERED)) diff --git a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java index 0878e60a3..442c8d8c3 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/docs/ClubApiDocsTest.java @@ -11,8 +11,12 @@ import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.multipart; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestPartFields; +import static org.springframework.restdocs.request.RequestDocumentation.partWithName; +import static org.springframework.restdocs.request.RequestDocumentation.requestParts; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.epages.restdocs.apispec.ResourceSnippetParameters; @@ -43,7 +47,9 @@ import org.mockito.Mock; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.web.multipart.MultipartFile; public class ClubApiDocsTest extends RestDocsTest { @@ -54,7 +60,6 @@ public class ClubApiDocsTest extends RestDocsTest { @Mock private ClubQueryService clubQueryService; - @DisplayName("필터링 조건을 통해 모임 리스트를 조회한다.") @Test void findSearching_200() throws Exception { @@ -209,16 +214,20 @@ void findById_200() throws Exception { @DisplayName("모임을 생성한다.") @Test void save_201() throws Exception { - SaveClubRequest request = new SaveClubRequest( + SaveClubRequest requestDto = new SaveClubRequest( "모임 제목", "모임 내용", - "https://clubImage.com", "서울특별시 송파구 신정동 잠실 5동", Set.of(Gender.FEMALE, Gender.FEMALE_NEUTERED), Set.of(SizeType.SMALL), 5, List.of(1L) ); + MockMultipartFile image = new MockMultipartFile("image", "image", MediaType.MULTIPART_FORM_DATA.toString(), + "asdf".getBytes()); + MockMultipartFile request = new MockMultipartFile("request", "request", "application/json", + objectMapper.writeValueAsBytes(requestDto)); + SaveClubResponse response = new SaveClubResponse( 1L, "모임 제목", @@ -236,32 +245,35 @@ void save_201() throws Exception { true ); - when(clubCommandService.save(any(), any(SaveClubRequest.class))) + when(clubCommandService.save(any(), any(MultipartFile.class), any(SaveClubRequest.class))) .thenReturn(response); - mockMvc.perform(post("/clubs") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(request)) - .header(HttpHeaders.AUTHORIZATION, 1L) - .accept(MediaType.APPLICATION_JSON)) + mockMvc.perform(multipart("/clubs") + .file(image) + .file(request) + .header(HttpHeaders.AUTHORIZATION, 1L)) .andExpect(status().isCreated()) .andDo(document("clubs/post/201", getDocumentRequest(), getDocumentResponse(), + requestParts( + partWithName("image").description("모임 이미지 파일"), + partWithName("request").description("모임 등록 정보") + ), + requestPartFields( + "request", + fieldWithPath("title").type(JsonFieldType.STRING).description("모임 제목"), + fieldWithPath("content").type(JsonFieldType.STRING).description("모임 내용"), + fieldWithPath("address").type(JsonFieldType.STRING).description("모임 주소"), + fieldWithPath("allowedGenders").type(JsonFieldType.ARRAY).description("참여가능한 강아지 성별"), + fieldWithPath("allowedSizes").type(JsonFieldType.ARRAY).description("참여가능한 강아지 사이즈"), + fieldWithPath("memberCapacity").type(JsonFieldType.NUMBER).description("모임 최대 인원"), + fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY).description("모임에 참여하는 방장 강아지의 ID 리스트") + ), resource(ResourceSnippetParameters.builder() .tag("Club API") .summary("모임 생성 API") .requestHeaders(headerWithName(HttpHeaders.AUTHORIZATION).description("로그인 중 회원 정보")) - .requestFields( - fieldWithPath("title").type(JsonFieldType.STRING).description("모임 제목"), - fieldWithPath("content").type(JsonFieldType.STRING).description("모임 내용"), - fieldWithPath("imageUrl").type(JsonFieldType.STRING).description("모임 프로필 사진 url"), - fieldWithPath("address").type(JsonFieldType.STRING).description("모임 주소"), - fieldWithPath("allowedGenders").type(JsonFieldType.ARRAY).description("참여가능한 강아지 성별"), - fieldWithPath("allowedSizes").type(JsonFieldType.ARRAY).description("참여가능한 강아지 사이즈"), - fieldWithPath("memberCapacity").type(JsonFieldType.NUMBER).description("모임 최대 인원"), - fieldWithPath("participatingPetsId").type(JsonFieldType.ARRAY).description("모임에 참여하는 방장 강아지의 ID 리스트") - ) .responseHeaders( headerWithName(HttpHeaders.LOCATION).description("생성된 모임 리소스 Location")) .responseFields( From 011903240cf3b9b040ae557a1b7a7345e36e2615 Mon Sep 17 00:00:00 2001 From: J-I-H-O Date: Thu, 1 Aug 2024 16:33:44 +0900 Subject: [PATCH 8/8] =?UTF-8?q?feat:=20=EC=9E=90=EC=8B=A0=EC=9D=B4=20?= =?UTF-8?q?=EC=A3=BC=EC=9D=B8=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EB=B0=98?= =?UTF-8?q?=EB=A0=A4=EA=B2=AC=EA=B3=BC=20=EB=AA=A8=EC=9E=84=EC=97=90=20?= =?UTF-8?q?=EC=B0=B8=EC=97=AC=ED=95=98=EC=A7=80=20=EB=AA=BB=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: J-I-H-O Co-authored-by: jimi567 --- .../club/service/ClubCommandService.java | 16 ++++++-- .../club/service/ClubCommandServiceTest.java | 40 +++++++++++++++++-- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java index 59ef833ad..0ddeba84e 100644 --- a/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java +++ b/backend/src/main/java/com/woowacourse/friendogly/club/service/ClubCommandService.java @@ -7,6 +7,7 @@ import com.woowacourse.friendogly.club.dto.response.SaveClubMemberResponse; import com.woowacourse.friendogly.club.dto.response.SaveClubResponse; import com.woowacourse.friendogly.club.repository.ClubRepository; +import com.woowacourse.friendogly.exception.FriendoglyException; import com.woowacourse.friendogly.infra.FileStorageManager; import com.woowacourse.friendogly.member.domain.Member; import com.woowacourse.friendogly.member.repository.MemberRepository; @@ -40,7 +41,7 @@ public ClubCommandService( public SaveClubResponse save(Long memberId, MultipartFile image, SaveClubRequest request) { Member member = memberRepository.getById(memberId); - List participatingPets = mapToPets(request.participatingPetsId()); + List participatingPets = mapToPets(request.participatingPetsId(), member); String imageUrl = ""; if (image != null && !image.isEmpty()) { @@ -67,16 +68,23 @@ public SaveClubMemberResponse joinClub(Long clubId, Long memberId, SaveClubMembe Member member = memberRepository.getById(memberId); club.addClubMember(member); - club.addClubPet(mapToPets(request.participatingPetsId())); + club.addClubPet(mapToPets(request.participatingPetsId(), member)); //TODO : 채팅방 ID 넘기기 return new SaveClubMemberResponse(1L); } - private List mapToPets(List participatingPetsId) { - return participatingPetsId.stream() + private List mapToPets(List participatingPetsId, Member member) { + List participatingPets = participatingPetsId.stream() .map(petRepository::getById) .toList(); + boolean isNotOwner = participatingPets.stream() + .anyMatch(pet -> !pet.getMember().getId().equals(member.getId())); + + if (isNotOwner) { + throw new FriendoglyException("자신의 반려견만 모임에 데려갈 수 있습니다."); + } + return participatingPets; } public void deleteClubMember(Long clubId, Long memberId) { diff --git a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java index 45e715032..b0ced8746 100644 --- a/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java +++ b/backend/src/test/java/com/woowacourse/friendogly/club/service/ClubCommandServiceTest.java @@ -66,7 +66,7 @@ void save() { @DisplayName("회원이 모임에 참여한다.") @Transactional @Test - void saveClubMember() { + void joinClub() { Club savedClub = createSavedClub( savedMember, savedPet, @@ -91,7 +91,7 @@ void saveClubMember() { @DisplayName("이미 참여한 모임에는 참여할 수 없다.") @Test - void saveClubMember_FailAlreadyParticipating() { + void joinClub_FailAlreadyParticipating() { Club savedClub = createSavedClub( savedMember, savedPet, @@ -107,7 +107,7 @@ void saveClubMember_FailAlreadyParticipating() { @DisplayName("참여 가능한 강아지가 없다면 참여할 수 없다.") @Test - void saveClubMember_FailCanNotParticipate() { + void joinClub_FailCanNotParticipate() { Club savedClub = createSavedClub( savedMember, savedPet, @@ -140,6 +140,40 @@ void saveClubMember_FailCanNotParticipate() { .hasMessage("모임에 데려갈 수 없는 강아지가 있습니다."); } + @DisplayName("자신이 주인이 아닌 반려견을 모임에 참여시키는 경우 예외가 발생한다.") + @Test + void joinClub_FailUnMatchOwner() { + Club savedClub = createSavedClub( + savedMember, + savedPet, + Set.of(Gender.FEMALE, Gender.FEMALE_NEUTERED), + Set.of(SizeType.SMALL) + ); + Member newMember = Member.builder() + .name("위브") + .email("wiib@gmail.com") + .tag("tag123") + .build(); + Member savedNewMember = memberRepository.save(newMember); + Pet savedNewMemberPet = petRepository.save( + Pet.builder() + .name("땡순이") + .description("귀여워요") + .member(savedMember) + .birthDate(LocalDate.of(2020, 12, 1)) + .gender(Gender.FEMALE) + .sizeType(SizeType.SMALL) + .imageUrl("https://image.com") + .build() + ); + + SaveClubMemberRequest request = new SaveClubMemberRequest(List.of(savedNewMemberPet.getId())); + + assertThatThrownBy(() -> clubCommandService.joinClub(savedClub.getId(), savedNewMember.getId(), request)) + .isInstanceOf(FriendoglyException.class) + .hasMessage("자신의 반려견만 모임에 데려갈 수 있습니다."); + } + //영속성 컨텍스트를 프로덕션 코드와 통합시키기 위해 트랜잭셔널 추가 @DisplayName("참여 중인 회원을 삭제하고, 방장이면 방장을 위임한다.") @Transactional