백엔드/Spring Boot

모여봄 #4 팀 관련 추가 기능들 구현

jhss9747 2025. 3. 8. 13:18
  • URL 할당 및 통합
  • 8개 기능 추가

1. GET/team/find모든 팀 조회 메서드

더보기

모든 팀을 조회할 수 있는 메서드가 없었기 때문에 새로 추가하였다.

  • 컨트롤러
/**
 *
 * @param teamName      (필터) 팀 이름
 * @param teamIntroduce (필터) 팀 상세
 * @param projectStatus (필터) 팀 상태
 * @param page          최소 출력 값
 * @param size          최대 출력 값
 * @return PageImpl     필터링 결과값 반환
 */
@GetMapping("/find")
@Operation(summary = "모든 팀 조회 메서드", description = "모든 팀을 조회하는 메서드 입니다.")
@Parameters({
        @Parameter(name = "teamName" , description = "팀 이름"),
        @Parameter(name = "teamIntroduce" , description = "팀 설명"),
        @Parameter(name = "projectStatus" , description = "프로젝트 상태"),
        @Parameter(name = "page" , description = "페이지 번호", example = "1"),
        @Parameter(name = "size" , description = "한 페이지 결과 수", example = "10")
})
public ResponseEntity<PageImpl<TeamResponseDto>> getTeams(
        @RequestParam(required = false)  String teamName,
        @RequestParam(required = false)  String teamIntroduce,
        @RequestParam(required = false)  ProjectStatus projectStatus,
        @RequestParam int page,
        @RequestParam int size){

    PageImpl<TeamResponseDto> teamSearch =
            teamService.allUserTeams(teamName, teamIntroduce, projectStatus, page, size);

    return ResponseEntity.status(HttpStatus.OK). body(teamSearch);
}
  • 서비스 상세
/**
 * 모든 팀 정보 조회
 * @param teamName      (필터) 팀 이름
 * @param teamIntroduce (필터) 팀 상세
 * @param projectStatus (필터) 팀 상태
 * @param page          최소 출력 값
 * @param size          최대 출력 값
 * @return PageImpl     필터링 결과값 반환
 */
public PageImpl<TeamResponseDto> allUserTeams(
        String teamName, String teamIntroduce, ProjectStatus projectStatus, int page, int size) {
    Pageable pageable = PageRequest.of(page, size);
    Page<TeamResponseDto> teams = teamUserRepository.findByStatusForTeams(projectStatus, pageable);

    List<TeamResponseDto> filteredTeams = teams.stream()
            .filter(team -> teamName == null || team.getTeamName().contains(teamName))
            .filter(team -> teamIntroduce == null || team.getTeamIntroduce().contains(teamIntroduce))
            .filter(team -> projectStatus == null || team.getProjectStatus().equals(projectStatus))
            .toList();

    return new PageImpl<>(filteredTeams, pageable, teams.getTotalElements());
}

 

  • 리포지토리
/**
 * 모든 팀 정보 조회
 * @param status  (필터) 팀 상태
 * @param pageable 페이징 크기
 * @return TeamResponseDto
 */
@Query("SELECT new com.beyond.backend.data.dto.teamDto.TeamResponseDto" +
        "(t.no, t.teamName, t.teamIntroduce, t.projectStatus, t.timePeriod) " +
        "FROM Team t " +
        "WHERE (:status IS NULL OR  t.projectStatus = :status)")
Page<TeamResponseDto> findByStatusForTeams(@Param("status") ProjectStatus status, Pageable pageable);
  • @Param("status") 의 값이 있다면 조건 검색, 없다면 모든 검색 결과 반환

2. GET/team/find/team팀원 목록 조회 메서드

더보기

해당 팀의 팀원들의 목록을 조회하는 메서드이다.

  • 컨트롤러
/**
 * 팀원 목록 조회 메서드
 * @param teamNo 팀번호
 * @return TeamMemberListDto
 * @throws Exception 팀이 존재하지 않습니다.
 */
@GetMapping("/find/team")
@Operation(summary = "팀원 목록 조회 메서드", description = "팀원 목록 조회 메서드입니다.")
@Parameters({@Parameter(name = "teamNo" , description = "팀 번호", example = "1")})
public ResponseEntity<List<TeamMemberListDto>> getUserTeams( @RequestParam Long teamNo ) throws Exception {
    List<TeamMemberListDto> teamMembers = teamService.getTeamMembers(teamNo);

    return ResponseEntity.status(HttpStatus.OK).body(teamMembers);
}

 

  • 서비스
/**
 * 팀원 목록 조회 서비스
 *
 * @param teamNo 팀번호
 * @return TeamMemberListDto
 * @throws Exception 팀이 존재하지 않습니다.
 */
@Override
public List<TeamMemberListDto> getTeamMembers(Long teamNo) throws Exception {
    teamRepository.findById(teamNo)
            .orElseThrow(() -> new IllegalArgumentException("팀이 존재하지 않습니다."));

    return teamUserRepository.findByTeamNoForNonLeader(teamNo);
}

 

  • 리포지토리
/**
 * 모든 팀 정보 조회
 * @param status  (필터) 팀 상태
 * @param pageable 페이징 크기
 * @return TeamResponseDto
 */
@Query("SELECT new com.beyond.backend.data.dto.teamDto.TeamResponseDto" +
        "(t.no, t.teamName, t.teamIntroduce, t.projectStatus, t.timePeriod) " +
        "FROM Team t " +
        "WHERE (:status IS NULL OR  t.projectStatus = :status)")
Page<TeamResponseDto> findByStatusForTeams(@Param("status") ProjectStatus status, Pageable pageable);

3. GET/team/find/user해당 유저의 모든 팀 조회 메서드

더보기

리포지토리의 SQL 쿼리에서 불필요한 JOIN문을 지웠다.

4

/**
 * 유저가 활동한 모든 팀 조회
 * @param userNo 유저번호
 * @param pageable 페이징 크기
 * @return TeamSearchDto
 */
@Query("SELECT new com.beyond.backend.data.dto.teamDto.TeamResponseDto" +
        "(t.no, t.teamName, t.teamIntroduce, t.projectStatus, t.timePeriod) " +
        "FROM Team t " +
        "JOIN t.teamUsers tu " +
        "WHERE tu.user.no = :userNo")
Page<TeamResponseDto> findByUserNoForUserTeams(@Param("userNo") Long userNo, Pageable pageable);

4. POST/team/joinRequest팀원 가입 신청 메서드

더보기

팀원이 가입 신청을 하면 중간 테이블에 등록 하도록 하였다.

  • 컨트롤러
/**
 * 팀원 가입 신청 메서드
 * @param teamNo 팀번호
 * @param userNo 유저번호
 * @return 없음
 * @throws Exception 팀이 존재하지 않습니다.
 * @throws Exception 유저가 존재하지 않습니다.
 * @throws Exception 모집중이 아닙니다!
 * @throws Exception 이미 등록되었거나 요청한 팀 입니다!!
 */
@PostMapping("/joinRequest")
@Operation(summary = "팀원 가입 신청 메서드", description = "팀원 가입 신청 메서드입니다.")
@Parameters({
        @Parameter(name = "teamNo" , description = "팀 번호", example = "1"),
        @Parameter(name = "userNo" , description = "유저 번호")
})
public ResponseEntity<String> teamJoinRequest (@RequestParam Long teamNo,
                                               @RequestParam Long userNo) throws Exception {
    teamService.teamJoinRequest(teamNo, userNo);

    return ResponseEntity.status(HttpStatus.OK).body("정상적으로 신청되었습니다.");
}

 

  • 서비스
/**
 * 팀원 신청
 *
 * @param teamNo 팀번호
 * @param userNo 유저번호
 * @throws Exception 팀이 존재하지 않습니다.
 * @throws Exception 유저가 존재하지 않습니다.
 * @throws Exception 모집중이 아닙니다!
 * @throws Exception 이미 등록되었거나 요청한 팀 입니다!!
 */
@Override
public void teamJoinRequest(Long teamNo, Long userNo) throws Exception {

    if (teamUserRepository.findByUserNoEquals(teamNo, userNo)) {
        throw new IllegalArgumentException("이미 등록되었거나 요청한 팀 입니다!!");
    }

    com.beyond.backend.data.entity.Team Team = teamRepository.findById(teamNo)
            .orElseThrow(() -> new IllegalArgumentException("팀이 존재하지 않습니다."));
    User user = userRepository.findById(userNo)
            .orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다."));

    if (Team.getProjectStatus() != ProjectStatus.OPEN) {
        throw new IllegalArgumentException("모집중이 아닙니다!");
    }

    TeamUser teamUser = TeamUser.builder()
            .user(user)
            .team(Team)
            .status(false)
            .isLeader(false)
            .build();
    teamUserRepository.save(teamUser);
}

다양한 예외 처리를 하였는데 이를 하나로 통일해서 관리하면 좋을 것 같다.

 

  • 리포지토리
/**
 * 유저가 팀에 속해있는지 여부
 * @param teamNo 팀번호
 * @param userNo 유저번호
 * @return Boolean
 */
@Query("SELECT COUNT(tu) > 0 " +
        "FROM TeamUser tu " +
        "WHERE tu.team.no = :teamNo AND tu.user.no = :userNo ")
Boolean findByUserNoEquals (@Param("teamNo") Long teamNo, @Param("userNo") Long userNo);

5. DELETE/team/joinRequestCancel팀원 가입 취소 메서드

더보기

팀원이 가입 취소 버튼을 누르면 중간 테이블에서 삭제하는 메서드이다

이를 활용하면 팀원 개인이 스스로 탈퇴할 수 있다.

  • 컨트롤러
/**
 * 팀원 가입 취소 메서드
 * @param teamNo 팀번호
 * @param userNo 유저번호
 * @return 없음
 * @throws Exception 신청하지 않았거나 존재하지 않는 팀입니다.
 */
@PDeleteMapping("/joinRequestCancel")
@Operation(summary = "팀원 가입 취소 메서드", description = "팀원 가입 취소  메서드입니다.")
@Parameters({
        @Parameter(name = "teamNo" , description = "팀 번호", example = "1"),
        @Parameter(name = "userNo" , description = "유저 번호")
})
public ResponseEntity<String> teamJoinRequestCancel (@RequestParam Long teamNo,
                                                     @RequestParam Long userNo) throws Exception {
    teamService.teamJoinRequestCancel(teamNo, userNo);

    return ResponseEntity.status(HttpStatus.OK).body("정상적으로 취소되었습니다.");
}

 

  • 서비스
/**
 * 팀원 취소
 * @param teamNo 팀번호
 * @param userNo 유저번호
 * @throws Exception 신청하지 않았거나 존재하지 않는 팀입니다.
 */
@Override
public void teamJoinRequestCancel(Long teamNo, Long userNo) throws Exception {
    Long teamUserNo = teamUserRepository.findByUserNoForTeamUserNo(teamNo, userNo);
    if (teamUserNo == null) {
        throw new IllegalArgumentException("신청하지 않았거나 존재하지 않는 팀입니다.");
    } else {
        teamUserRepository.deleteById(teamUserNo);
    }
}

 

6. GET/team/member/setting[팀장] 팀원 신청 목록 조회

더보기

팀장이 승인 여부가 ‘F’인 팀원들에 대한 목록을 조회하는 기능

  • 컨트롤러
/**
 * [팀장] 팀원 신청 목록 조회
 * @param teamNo 팀번호
 * @param userNo 유저번호
 * @return TeamMemberListDto
 * @throws Exception 권한이 없습니다!
 */
@GetMapping("/member/setting")
@Operation(summary = "[팀장] 팀원 신청 목록 조회", description = "팀원 신청 목록 조회 메서드입니다.")
@Parameters({
        @Parameter(name = "teamNo" , description = "팀 번호", example = "1"),
        @Parameter(name = "userNo" , description = "팀장 번호", example = "1")
})
public ResponseEntity<List<TeamMemberListDto>> getMemberRequest(
        @RequestParam Long teamNo,
        @RequestParam Long userNo) throws Exception {
    List<TeamMemberListDto> teamMemberRequest = teamService.getTeamMemberRequest(teamNo, userNo);

    return ResponseEntity.status(HttpStatus.OK).body(teamMemberRequest);
}

 

  •  서비스
/**
 * [팀장] 팀원 신청 목록 조회
 *
 * @param teamNo 팀번호
 * @param userNo 유저번호
 * @return TeamMemberListDto
 * @throws Exception 권한이 없습니다!
 */
@Override
public List<TeamMemberListDto> getTeamMemberRequest(Long teamNo, Long userNo) throws Exception {
    Boolean isLeader = teamUserRepository.isLeader(teamNo, userNo);
    if (isLeader != null && isLeader) {
        return teamUserRepository.findByTeamNoForMemberRequest(teamNo);
    } else {
        throw new IllegalArgumentException("권한이 없습니다!");
    }
}

 

7. PUT/team/member/setting/teamAccept[팀장] 팀원 가입 수락

더보기

팀장이 승인 여부가 ‘F’인 회원을 승인 여부 ‘T’로 저장 → 팀원 추가

  • 컨트롤러
/**
 * [팀장] 팀원 가입 수락 메서드
 * @param teamNo 팀번호
 * @param userNo 신청한 유저의 유저번호
 * @return 정상적으로 수락되었습니다.
 * @throws Exception 신청한 유저가 없습니다!
 * @throws Exception 이미 가입된 유저입니다!
 */
@PutMapping("/member/setting/teamAccept")
@Operation(summary = "[팀장] 팀원 가입 수락 메서드", description = "팀원 가입 수락 메서드입니다.")
@Parameters({
        @Parameter(name = "teamNo" , description = "팀 번호", example = "1"),
        @Parameter(name = "userNo" , description = "유저 번호")
})
public ResponseEntity<String> teamAccept (@RequestParam Long teamNo,
                                          @RequestParam Long userNo) throws Exception {
    teamService.teamAccept(teamNo, userNo);

    return ResponseEntity.status(HttpStatus.OK).body("정상적으로 수락 되었습니다.");
}

 

  •  서비스
/**
 * [팀장] 팀원 가입 수락
 * @param teamNo 팀번호
 * @param userNo 신청한 유저의 유저번호
 * @throws Exception 신청한 유저가 없습니다!
 * @throws Exception 이미 가입된 유저입니다!
 */
@Override
public void teamAccept(Long teamNo, Long userNo) throws Exception {
    Long teamUserNo = teamUserRepository.findByUserNoForTeamUserNo(teamNo, userNo);

    TeamUser teamUser = teamUserRepository.findById(teamUserNo)
            .orElseThrow(() -> new IllegalArgumentException("신청한 유저가 없습니다!"));

    boolean status = teamUser.isStatus();
    if (status) {
        throw new IllegalArgumentException("이미 가입된 유저입니다!");
    }else {
        teamUser.setStatus(true);
        teamUserRepository.save(teamUser);
    }
}

 

8. DELETE/team/member/setting/teamDelete[팀장] 팀원 가입 거부

더보기

팀장이 중간 테이블에서 팀원을 제거한다. 추방 기능으로 활용도 가능하지만 보류

  • 컨트롤러
/**
 * [팀장] 팀원 가입 거부 메서드
 * @param teamNo 팀번호
 * @param userNo 신청한 유저의 유저번호
 * @return 정상적으로 처리 되었습니다.
 * @throws Exception 신청한 유저가 없습니다!
 */
@DeleteMapping("/member/setting/teamDelete")
@Operation(summary = "[팀장] 팀원 가입 거부 메서드", description = "팀원 가입 거부 메서드입니다.")
@Parameters({
        @Parameter(name = "teamNo" , description = "팀 번호", example = "1"),
        @Parameter(name = "userNo" , description = "유저 번호")
})
public ResponseEntity<String> teamDelete(@RequestParam Long teamNo,
                                         @RequestParam Long userNo) throws Exception {
    teamService.teamDelete(teamNo, userNo);

    return ResponseEntity.status(HttpStatus.OK).body("정상적으로 처리 되었습니다.");
}

 

  •  서비스
/**
 * [팀장] 팀원 신청 거부
 * @param teamNo 팀번호
 * @param userNo 신청한 유저의 유저번호
 * @throws Exception 신청한 유저가 없습니다!
 */
@Override
public void teamDelete(Long teamNo, Long userNo) throws Exception {
    Long teamUserNo = teamUserRepository.findByUserNoForTeamUserNo(teamNo, userNo);

    TeamUser teamUser = teamUserRepository.findById(teamUserNo)
            .orElseThrow(() -> new IllegalArgumentException("신청한 유저가 없습니다!"));

    boolean status = teamUser.isStatus();
    if (!status) {
        teamUserRepository.deleteById(teamUserNo);
    }
    // 추방도 가능하지만 일방적인 추방 기능은 논의해봐야할듯
    // 현재는 유저 스스로 나가는것만 가능
}

이후에 URL 수정을 해주었다.

더보기

DELETE/team/{teamNo}/join-cancel**팀원 가입 취소 메서드

POST/team/{teamNo}/join-request**팀원 가입 신청 메서드

DELETE/team/{teamNo}/setting/delete**팀 삭제 메서드

GET/team/{teamNo}/setting/members[팀장] 팀원 신청 목록 조회

PUT/team/{teamNo}/setting/members/accept/{userNo}[팀장] 팀원 가입 수락 메서드

PUT/team/{teamNo}/setting/members/delete/{userNo}[팀장] 팀원 가입 거부 메서드

GET/team/{teamNo}/setting/members/find**팀원 목록 조회 메서드

PUT/team/{teamNo}/setting/update**팀 수정 메서드

POST/team/create**팀 생성 메서드

GET/team/find**해당 유저의 모든 팀 조회 메서드

하지만 RESTFul 하지 않기 때문에 다시 수정해야한다.