Skip to content

Commit

Permalink
[BE] feat: Club 생성 참여 탈퇴 API (#207)
Browse files Browse the repository at this point in the history
  • Loading branch information
jimi567 authored and takoyakimchi committed Oct 23, 2024
1 parent b04e109 commit a41305d
Show file tree
Hide file tree
Showing 25 changed files with 1,084 additions and 161 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Collections;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
Expand Down Expand Up @@ -41,7 +42,7 @@ public ResponseEntity<ApiResponse<ErrorResponse>> handle(MethodArgumentNotValidE
List<String> detail = exception.getBindingResult()
.getFieldErrors()
.stream()
.map(fieldError -> fieldError.getDefaultMessage())
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.toList();

ErrorResponse errorResponse = new ErrorResponse(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package com.woowacourse.friendogly.club.controller;

import com.woowacourse.friendogly.auth.Auth;
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.FindSearchingClubResponse;
import com.woowacourse.friendogly.club.dto.response.SaveClubMemberResponse;
import com.woowacourse.friendogly.club.dto.response.SaveClubResponse;
import com.woowacourse.friendogly.club.service.ClubCommandService;
import com.woowacourse.friendogly.club.service.ClubQueryService;
import com.woowacourse.friendogly.common.ApiResponse;
import jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
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.PathVariable;
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.RestController;

Expand All @@ -24,7 +35,37 @@ public ClubController(ClubCommandService clubCommandService, ClubQueryService cl
}

@GetMapping("/searching")
public ResponseEntity<List<FindSearchingClubResponse>> findSearching(@Valid FindSearchingClubRequest request) {
return ResponseEntity.ok(clubQueryService.findSearching(request));
public ApiResponse<List<FindSearchingClubResponse>> findSearching(@Valid FindSearchingClubRequest request) {
return ApiResponse.ofSuccess(clubQueryService.findSearching(request));
}

@PostMapping
public ResponseEntity<ApiResponse<SaveClubResponse>> save(
@Auth Long memberId,
@Valid @RequestBody SaveClubRequest request
) {
SaveClubResponse response = clubCommandService.save(memberId, request);
return ResponseEntity.created(URI.create("/clubs" + response.id())).body(ApiResponse.ofSuccess(response));
}

@PostMapping("/{clubId}/members")
public ResponseEntity<ApiResponse<SaveClubMemberResponse>> saveClubMember(
@PathVariable Long clubId,
@Auth Long memberId,
@Valid @RequestBody SaveClubMemberRequest request
) {
SaveClubMemberResponse response = clubCommandService.saveClubMember(clubId, memberId, request);
return ResponseEntity.created(URI.create("/clubs/" + clubId + "/members/" + response.clubMemberId()))
.body(ApiResponse.ofSuccess(response));
}

@DeleteMapping("/{clubId}/members")
public ResponseEntity<Void> deleteClubMember(
@Auth Long memberId,
@PathVariable("clubId") Long clubId
) {
clubCommandService.deleteClubMember(clubId, memberId);
return ResponseEntity.noContent().build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import com.woowacourse.friendogly.exception.FriendoglyException;
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 jakarta.persistence.CascadeType;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
Expand All @@ -17,7 +19,11 @@
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import lombok.AccessLevel;
import lombok.Builder;
Expand Down Expand Up @@ -71,8 +77,14 @@ public class Club {
@Column(name = "status", nullable = false)
private Status status;

@OneToMany(mappedBy = "club", orphanRemoval = true, cascade = CascadeType.ALL)
List<ClubMember> clubMembers = new ArrayList<>();

@OneToMany(mappedBy = "club", orphanRemoval = true, cascade = CascadeType.ALL)
List<ClubPet> clubPets = new ArrayList<>();

@Builder
public Club(
private Club(
String title,
String content,
String address,
Expand All @@ -97,7 +109,6 @@ public Club(
this.createdAt = createdAt;
}


private void validateOwner(Member owner) {
if (owner == null) {
throw new FriendoglyException("모임 방장 정보는 필수 입니다.");
Expand All @@ -112,9 +123,10 @@ public static Club create(
Member owner,
Set<Gender> allowedGender,
Set<SizeType> allowedSize,
String imageUrl
String imageUrl,
List<Pet> participatingPets
) {
return Club.builder()
Club club = Club.builder()
.title(title)
.content(content)
.address(address)
Expand All @@ -126,6 +138,84 @@ public static Club create(
.createdAt(LocalDateTime.now())
.imageUrl(imageUrl)
.build();
club.addClubMember(club.owner);
club.addClubPet(participatingPets);
return club;
}

public void addClubMember(Member newMember) {
validateAlreadyExists(newMember);
validateMemberCapacity();
ClubMember clubMember = ClubMember.create(this, newMember);
clubMembers.add(clubMember);
clubMember.updateClub(this);
}

private void validateAlreadyExists(Member newMember) {
if (clubMembers.stream()
.anyMatch(clubMember -> Objects.equals(clubMember.getMember().getId(), newMember.getId()))
) {
throw new FriendoglyException("이미 참여 중인 모임입니다.");
}
}

private void validateMemberCapacity() {
if (memberCapacity.isCapacityReached(countClubMember())) {
throw new FriendoglyException("최대 인원을 초과하여 모임에 참여할 수 없습니다.");
}
}

public void addClubPet(List<Pet> pets) {
List<ClubPet> clubPets = pets.stream()
.peek(this::validateParticipatePet)
.map(pet -> new ClubPet(this, pet))
.peek(clubPet -> clubPet.updateClub(this))
.toList();
this.clubPets.addAll(clubPets);
}

private void validateParticipatePet(Pet pet) {
if (!allowedGenders.contains(pet.getGender()) || !allowedSizes.contains(pet.getSizeType())) {
throw new FriendoglyException("모임에 데려갈 수 없는 강아지가 있습니다.");
}
}

public int countClubMember() {
return clubMembers.size();
}

public boolean isEmpty() {
return clubMembers.isEmpty();
}

public void removeClubMember(Member member) {
ClubMember target = clubMembers.stream()
.filter(e -> e.getMember().getId().equals(member.getId()))
.findAny()
.orElseThrow(() -> new FriendoglyException("참여 중인 모임이 아닙니다."));
clubMembers.remove(target);
if (isOwner(target)) {
updateOwner();
}
target.updateClub(null);
removeClubPets(member);
}

private void updateOwner() {
if (!isEmpty()) {
this.owner = clubMembers.get(0).getMember();
}
}

private boolean isOwner(ClubMember target) {
return owner.getId().equals(target.getMember().getId());
}

private void removeClubPets(Member member) {
List<ClubPet> participatingMemberPets = clubPets.stream()
.filter(clubPet -> clubPet.getPet().getMember().getId().equals(member.getId()))
.toList();
clubPets.removeAll(participatingMemberPets);
participatingMemberPets.forEach(clubPet -> clubPet.updateClub(null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import com.woowacourse.friendogly.exception.FriendoglyException;
import com.woowacourse.friendogly.member.domain.Member;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -31,12 +33,25 @@ public class ClubMember {
@JoinColumn(name = "member_id", nullable = false)
private Member member;

@Column(name = "create_at", nullable = false)
private LocalDateTime createdAt;

@Builder
public ClubMember(Club club, Member member) {
public ClubMember(Club club, Member member, LocalDateTime createdAt) {
validateClub(club);
validateMember(member);
validateCreateAt(createdAt);
this.club = club;
this.member = member;
this.createdAt = createdAt;
}

public static ClubMember create(Club club, Member member) {
return ClubMember.builder()
.club(club)
.member(member)
.createdAt(LocalDateTime.now())
.build();
}

private void validateClub(Club club) {
Expand All @@ -45,9 +60,19 @@ private void validateClub(Club club) {
}
}

private void validateCreateAt(LocalDateTime createdAt) {
if (createdAt == null) {
throw new FriendoglyException("방에 참가한 시간은 필수입니다.");
}
}

private void validateMember(Member member) {
if (member == null) {
throw new FriendoglyException("모임에 참여하는 회원 정보는 필수입니다.");
}
}

public void updateClub(Club club) {
this.club = club;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@ private void validatePet(Pet pet) {
throw new FriendoglyException("모임에 참여하는 회원의 반려견 정보는 필수입니다.");
}
}

public void updateClub(Club club) {
this.club = club;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@ private void validate(int value) {
);
}
}

public boolean isCapacityReached(int memberCount) {
return memberCount >= value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.woowacourse.friendogly.club.dto.request;

import jakarta.validation.constraints.NotEmpty;
import java.util.List;

public record SaveClubMemberRequest(
@NotEmpty(message = "모임에 참석 할 강아지를 1마리 이상 선택해주세요.")
List<Long> participatingPetsId
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.woowacourse.friendogly.club.dto.request;

import com.woowacourse.friendogly.pet.domain.Gender;
import com.woowacourse.friendogly.pet.domain.SizeType;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import java.util.List;
import java.util.Set;

public record SaveClubRequest(
@NotBlank(message = "제목을 작성해주세요.")
@Size(min = 1, max = 100, message = "제목은 1글자 100글자 사이입니다.")
String title,

@NotBlank(message = "본문을 작성해주세요.")
@Size(min = 1, max = 1000, message = "본문은 1글자 1000글자 사이입니다.")
String content,

@NotBlank(message = "모임 사진을 등록해주세요.")
String imageUrl,

@NotBlank(message = "주소를 읽을 수 없습니다. 다시 시도해주세요.")
String address,

@NotEmpty(message = "모임에 참여가능한 성별을 선택해주세요.")
Set<Gender> allowedGenders,

@NotEmpty(message = "모임에 참여가능한 댕댕이 사이즈를 선택해주세요.")
Set<SizeType> allowedSizes,

@Min(value = 1, message = "모임 최대 인원은 1명 이상입니다.")
@Max(value = 5, message = "모임 최대 인원은 5명 이하입니다.")
int memberCapacity,

@NotEmpty(message = "모임에 참여할 댕댕이를 선택해주세요.")
List<Long> participatingPetsId
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ public record FindSearchingClubResponse(
String address,
Status status,
LocalDateTime createdAt,
Set<SizeType> allowedSize,
Set<Gender> allowedGender,
Set<SizeType> allowedSize,
int memberCapacity,
int currentMemberCount,
String imageUrl,
List<String> petImageUrls

) {

public FindSearchingClubResponse(
Club club,
int currentMemberCount,
List<String> petImageUrls
) {
this(
Expand All @@ -37,12 +37,12 @@ public FindSearchingClubResponse(
club.getAddress().getValue(),
club.getStatus(),
club.getCreatedAt(),
club.getAllowedSizes(),
club.getAllowedGenders(),
club.getAllowedSizes(),
club.getMemberCapacity().getValue(),
currentMemberCount,
club.countClubMember(),
club.getImageUrl(),
petImageUrls
);
}

}
Loading

0 comments on commit a41305d

Please sign in to comment.