[개발일지] 취미 커뮤니티 서비스 - 크루 참가 및 탈퇴 + 크루 세팅

zwon·2023년 10월 26일
0

개발일지

목록 보기
21/23

크루에 참여 및 탈퇴 기능과 크루 세팅하는 부분에 대해서 정리하고자 한다.
크루 세팅은 크루 공개/비공개, 크루원 모집 혹은 모집X인지, 크루 활동이 종료되었는지 아닌지를 설정하는 코드를 정리하고자 한다.

Crew

@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;
    }
  }

}
  • 코드를 보면 이해가 갈 것이다.
  • 그냥 단순히 true/false 또는 contains(...)를 이용해서 포함하고있는지 아닌지를 확인하는 코드들이라 설명이 필요해 보이지는 않는다.
  • 주석으로도 설명을 작성해놨다.

Dto

CrewSaveRequestDto

@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();
  }
}
  • 우선 크루를 처음 만들었을땐 크루 공개 여부 true, 크루 모집 true, 크루 종료 false로 세팅했다.

CrewResponseDto

@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());
  }
}

CrewRepository

  • 생략 (크루 CRUD와 동일)

CrewService

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();
  }

}

CrewViewController

  • 크루 CRUD 부분은 이 포스팅에선 생략하겠다.
@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와 똑같아서 크루 세팅이나 참가, 탈퇴와 관련된 부분만 가져오겠다

Thymeleaf

크루 소개 화면

...
<h2>👥크루원</h2>
  <p th:text="|크루장 : ${crewResponseDto.leader}|"></p>
  <div class="member" th:each="member : ${crewResponseDto.getUsers()}">
    <p th:text="|크루원 : ${member.nickname}|"></p>
  </div>
...
  • 크루원을 가져올 때 Crew 엔티티의 List<User> users = new ArrayList<>();를 통해 크루원들을 가져온다.

크루 설정

...
<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-->
...
  • 이 부분은 crewResponse로부터 크루 공개 여부, 크루 모집 여부, 크루 활동 종료 여부 등의 값들을 가져와서 true, false값을 가지고 버튼의 값을 변경한다.

크루 가입

  • fragment에 크루 헤더에 관해 작성한 부분인데 다음과 같이 값이 넘어온다.
<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>
  • 크루 설정은 크루장만 볼 수 있게 작성했다.
  • 멤버이면 크루 탈퇴 버튼이 보이고 멤버가 아니면 크루 가입 버튼이 보인다.

크루장인 경우

크루장인 아닌 경우

크루원이 아닌 경우

크루원인 경우

  • 관심있어요 기능은 크루원인 경우에 안보이게 했다.
profile
Backend 관련 지식을 정리하는 Back과사전

0개의 댓글