크루에 참여 및 탈퇴 기능과 크루 세팅하는 부분에 대해서 정리하고자 한다.
크루 세팅은 크루 공개/비공개, 크루원 모집 혹은 모집X인지, 크루 활동이 종료되었는지 아닌지를 설정하는 코드를 정리하고자 한다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Builder
public class Crew extends BaseTimeEntity {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "user_id")
  private User user; // 크루장 - 로그인한 user
  private String name; // 크루이름
  private boolean type; //  true-온라인, false-오프라인
  private boolean cost; // true-유료탑승, false-무료탑승
  private boolean isRecruiting; // true - 모집중, false - 모집X,
  private boolean isPublished; // true - 공개O, false - 공개X
  private boolean isClosed; // true - 종료
  private String thumbnail; //이미지 경로 , 처음엔 기본 이미지 -> 크루 설정을 통해 배너 수정 가능
  @Lob // varchar보다 클 경우 사용하는 어노테이션
  private String description; // 항해 목적
  @Lob // varchar보다 클 경우 사용하는 어노테이션
  private String wisher; // 원하는 선원
  @Lob // varchar보다 클 경우 사용하는 어노테이션
  private String plan; // 크루즈 설명
 
  @ManyToMany // 크루에 속한 팀원
  @JoinTable(name = "user_crew")
  private List<User> users = new ArrayList<>();
  @OneToMany(mappedBy = "crew")
  private List<Like> likes = new ArrayList<>();
  // 크루 가입
  public void addUser(User user){
    if(!this.users.contains(user)) {
      this.users.add(user);
    }
  }
  // 크루 탈퇴
  public void removeUser(User user) {
    if (this.users.contains(user)) {
      this.users.remove(user);
    }
  }
  // 크루에 가입이 가능한지
  public boolean isJoinable(User user) { // (1)
    return this.isPublished && this.isRecruiting && !this.users.contains(user) && !this.isClosed;
  }
  // 크루 멤버인지
  public boolean isMember(User user) { // (2)
    if (this.user.equals(user)){ // 크루장인 경우
      return true;
    }
    return this.users.contains(user); // 크루원인 경우
  }
  // 크루장
  public void setUser(User user){
    this.user = user;
  }
  public void update(CrewUpdateRequestDto crewUpdateRequestDto){
    this.name = crewUpdateRequestDto.getName();
    this.type = crewUpdateRequestDto.isType();
    this.cost = crewUpdateRequestDto.isCost();
    this.description = crewUpdateRequestDto.getDescription();
    this.wisher = crewUpdateRequestDto.getWisher();
    this.plan = crewUpdateRequestDto.getPlan();
  }
  public void setPublished(){
    if (this.isPublished) {
      this.isPublished = false;
    } else {
      this.isPublished = true;
    }
  }
  public void setRecruited(){
    if (this.isRecruiting) {
      this.isRecruiting = false;
    } else {
      this.isRecruiting = true;
    }
  }
  public void setClosed(){
    if (this.isClosed) {
      this.isClosed = false;
    } else {
      this.isClosed = true;
    }
  }
}
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class CrewSaveRequestDto {
  private String name; // 크루이름
  private boolean type; //  true-온라인, false-오프라인
  private boolean cost; // true-유료탑승, false-무료탑승
  private String description; // 크루즈 설명
  private String wisher; // 원하는 선원
  private String plan; // 크루즈 설명
  public Crew toEntity(){
    return Crew.builder()
        .name(name)
        .type(type)
        .cost(cost)
        .wisher(wisher)
        .plan(plan)
        .description(description)
        // 이 부분
        .isPublished(true) // 처음 crew를 만들 땐 true
        .isRecruiting(true) // 처음 crew를 만들 땐 true
        .isClosed(false) // 처음 crew를 만들 땐 false
        //
        .build();
  }
}
@Getter
@NoArgsConstructor
public class CrewResponseDto {
  private Long id; // 크루 조회할 때 사용
  private String leader; // 크루장
  private String name; // 크루명
  private boolean type; //  true-온라인, false-오프라인
  private boolean cost; // true-유료탑승, false-무료탑승
  private String description; // 크루즈 설명
  private String wisher; // 원하는 선원
  private String plan; // 크루즈 설명
  private boolean isRecruit; // true - 모집중, false - 모집X
  private boolean isPublished; // true - 공개O, false - 공개X
  private boolean isClosed;
  private List<UserResponseDto> users;
  public CrewResponseDto(Crew crew) {
    this.id = crew.getId();
    this.leader = crew.getUser().getNickname();
    this.name = crew.getName();
    this.type = crew.isType();
    this.cost = crew.isCost();
    this.description = crew.getDescription();
    this.wisher = crew.getWisher();
    this.plan = crew.getPlan();
    this.isRecruit = crew.isRecruiting();
    this.isPublished = crew.isPublished();
    this.isClosed = crew.isClosed();
    this.users = crew.getUsers().stream().map(UserResponseDto::new).collect(Collectors.toList());
  }
}
public class CrewService {
  private final CrewRepository crewRepository;
  ...
  // 크루 참가
  public void addUser(Long id, User user){
    Crew crew = crewRepository.findById(id).orElseThrow(IllegalArgumentException::new);
    if (crew.isJoinable(user)){
      crew.addUser(user);
    }
  }
  public void leaveCrew(Long id, User user){
    Crew crew = crewRepository.findById(id).orElseThrow(IllegalArgumentException::new);
    if (crew.isMember(user)) {
      crew.removeUser(user);
    }
  }
  @Transactional
  public void update(Long id, CrewUpdateRequestDto crewUpdateRequestDto){
    Crew crew = crewRepository.findById(id).orElseThrow(IllegalArgumentException::new);
    crew.update(crewUpdateRequestDto); // 이렇게 엔티티로 Dto를 넘겨도 되나?
  }
  // 크루 세팅 - 크루 공개 여부
  public void setPublished(Long id){
    Crew crew = crewRepository.findById(id).orElseThrow(IllegalArgumentException::new);
    crew.setPublished();
  }
  // 크루 세팅 - 크루원 모집 여부
  public void setRecruited(Long id){
    Crew crew = crewRepository.findById(id).orElseThrow(IllegalArgumentException::new);
    crew.setRecruited();
  }
  // 크루 세팅 - 크루 종료 여부
  public void setClosed(Long id){
    Crew crew = crewRepository.findById(id).orElseThrow(IllegalArgumentException::new);
    crew.setClosed();
  }
}
@Controller
@RequiredArgsConstructor
@Transactional
@Slf4j
public class CrewViewController {
  private final CrewService crewService;
  // 크루 참가 신청
  @GetMapping("/crews/{id}/join")
  public String joinCrew(HttpSession session, @PathVariable Long id) {
    User loginUser = (User) session.getAttribute("loginUser");
    crewService.addUser(id, loginUser);
    return "redirect:/crews/" + id;
  }
  // 크루 탈퇴
  @GetMapping("/crews/{id}/leave")
  public String leaveCrew(HttpSession session, @PathVariable Long id) {
    User loginUser = (User) session.getAttribute("loginUser");
    crewService.leaveCrew(id, loginUser);
    return "redirect:/crews/" + id;
  }
  // 크루 detailView
  @GetMapping("/crews/{id}/log") // 활동일지
  public String crewActivity(@PathVariable Long id, Model model, HttpSession session) {
    User user = (User) session.getAttribute("loginUser");
    CrewResponseDto crewResponseDto = crewService.findById(id);
    UserResponseDto loginUser = new UserResponseDto(user);
    model.addAttribute("loginUser", loginUser);
    model.addAttribute("crewResponseDto", crewResponseDto);
    return "crew/log";
  }
  @GetMapping("/crews/{id}/meeting") // 모임
  public String crewMeeting(@PathVariable Long id, Model model, HttpSession session) {
    User user = (User) session.getAttribute("loginUser");
    CrewResponseDto crewResponseDto = crewService.findById(id);
    UserResponseDto loginUser = new UserResponseDto(user);
    model.addAttribute("loginUser", loginUser);
    model.addAttribute("crewResponseDto", crewResponseDto);
    return "crew/meeting";
  }
  @GetMapping("/crews/{id}/setting") // 설정 -> Crew Update
  public String crewSetting(@PathVariable Long id, Model model, HttpSession session) {
    User user = (User) session.getAttribute("loginUser");
    CrewResponseDto crewResponseDto = crewService.findById(id);
    UserResponseDto loginUser = new UserResponseDto(user);
    model.addAttribute("loginUser", loginUser);
    model.addAttribute("crewResponseDto", crewResponseDto);
    model.addAttribute("crewUpdateRequestDto", new CrewUpdateRequestDto());
    return "crew/setting";
  }
  @PostMapping("/crews/{id}/setting") // 설정 -> Crew Update
  public String crewSetting(@PathVariable Long id,
                            @ModelAttribute CrewUpdateRequestDto crewUpdateRequestDto) {
    crewService.update(id, crewUpdateRequestDto);
    return "redirect:/crews/" + id;
  }
  // 크루 공개 여부
  @PostMapping("/crews/{id}/published")
  public String publishedCrew(@PathVariable Long id) {
    crewService.setPublished(id);
    return "redirect:/crews/" + id + "/setting";
  }
  // 크루 인원 모집 여부
  @PostMapping("/crews/{id}/recruit")
  public String recruitCrew(@PathVariable Long id) {
    crewService.setRecruited(id);
    return "redirect:/crews/" + id + "/setting";
  }
  // 크루 종료 여부
  @PostMapping("/crews/{id}/close")
  public String closeCrew(@PathVariable Long id) {
    crewService.setClosed(id);
    return "redirect:/crews/" + id + "/setting";
  }
}
참고로 Thymeleaf는 바로 이전 게시글인 크루 CRUD와 똑같아서 크루 세팅이나 참가, 탈퇴와 관련된 부분만 가져오겠다
...
<h2>👥크루원</h2>
  <p th:text="|크루장 : ${crewResponseDto.leader}|"></p>
  <div class="member" th:each="member : ${crewResponseDto.getUsers()}">
    <p th:text="|크루원 : ${member.nickname}|"></p>
  </div>
...
...
<div class="crew-setting">
	<!--크루 공개-->
      <form th:action="@{/crews/{id}/published(id=${crewResponseDto.id})}" method="post">
        <button type="submit"  th:text="${crewResponseDto.published} ? '크루 비공개' : '크루원 공개'">크루 비공개</button>
      </form>
      <!--크루 인원 모집 -->
      <form th:action="@{/crews/{id}/recruit(id=${crewResponseDto.id})}" method="post">
        <button type="submit" th:text="${crewResponseDto.recruit} ? '크루원 모집 중단' : '크루원 모집하기'"></button>
      </form>
        <!--크루 종료, 활동일지 등 이런걸 추억으로 남기고 싶을 땐 종료하고 마이페이지 관심 크루, 종료 크루, 참여 크루 보여주자....-->
      <form th:action="@{/crews/{id}/close(id=${crewResponseDto.id})}" method="post">
        <button type="submit" th:text="${crewResponseDto.closed} ? '크루 활성화' : '크루 활동 종료'"></button>
      </form>
       ...
</div> <!--crew-setting-->
...


<nav th:replace="~{fragment/fragment :: crewDetailnav(${loginUser.name}, ${crewResponseDto.leader},
${crewResponseDto.users.contains(loginUser)}, ${crewResponseDto.recruit} )}"></nav>
<button type="button" th:if="${loginUserNicname == crewLeaderName}" 
        th:onclick="|location.href='@{/crews/{crewId}/setting(crewId=${crewResponseDto.id})}'|">
  크루 설정
</button>
<button type="button" th:if="${isMember}"
        th:onclick="|location.href='@{/crews/{crewId}/leave(crewId=${crewResponseDto.id})}'|">
  크루 탈퇴
</button>
<button type="button" th:if="${!isMember and loginUserNicname != crewLeaderName and isRecruit}" 
        th:onclick="|location.href='@{/crews/{crewId}/join(crewId=${crewResponseDto.id})}'|">
  크루 가입
</button>




