diff --git a/src/main/java/com/mtvs/devlinkbackend/team/controller/TeamController.java b/src/main/java/com/mtvs/devlinkbackend/team/controller/TeamController.java new file mode 100644 index 0000000..5e2d407 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/team/controller/TeamController.java @@ -0,0 +1,125 @@ +package com.mtvs.devlinkbackend.team.controller; + +import com.mtvs.devlinkbackend.config.JwtUtil; +import com.mtvs.devlinkbackend.team.dto.TeamRegistRequestDTO; +import com.mtvs.devlinkbackend.team.dto.TeamUpdateRequestDTO; +import com.mtvs.devlinkbackend.team.entity.Team; +import com.mtvs.devlinkbackend.team.service.TeamService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/team") +public class TeamController { + + private final TeamService teamService; + private final JwtUtil jwtUtil; + + public TeamController(TeamService teamService, JwtUtil jwtUtil) { + this.teamService = teamService; + this.jwtUtil = jwtUtil; + } + + @Operation(summary = "팀 등록", description = "새로운 팀을 등록하고 등록된 팀 정보를 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "팀이 성공적으로 등록되었습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 입력 데이터입니다.") + }) + @PostMapping + public ResponseEntity registTeam(@RequestBody TeamRegistRequestDTO teamRegistRequestDTO, @RequestParam String accountId) { + Team team = teamService.registTeam(teamRegistRequestDTO, accountId); + return new ResponseEntity<>(team, HttpStatus.CREATED); + } + + @Operation(summary = "ID로 팀 조회", description = "ID를 기반으로 팀 정보를 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "팀이 성공적으로 조회되었습니다."), + @ApiResponse(responseCode = "404", description = "팀을 찾을 수 없습니다.") + }) + @GetMapping("/{teamId}") + public ResponseEntity getTeamById( + @PathVariable Long teamId) { + + Team team = teamService.findTeamByTeamId(teamId); + if (team != null) { + return ResponseEntity.ok(team); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + } + + @Operation(summary = "사용자가 프로젝트 매니저인 팀 조회", description = "사용자가 프로젝트 매니저인 팀 목록을 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "팀이 성공적으로 조회되었습니다."), + @ApiResponse(responseCode = "404", description = "주어진 프로젝트 매니저 ID에 대한 팀이 없습니다.") + }) + @GetMapping("/manager") + public ResponseEntity> getTeamsByPmId( + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String pmId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader); + List teams = teamService.findTeamsByPmId(pmId); + return ResponseEntity.ok(teams); + } + + @Operation(summary = "팀 이름으로 팀 조회", description = "지정된 문자열이 포함된 팀 이름을 가진 팀 목록을 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "팀이 성공적으로 조회되었습니다.") + }) + @GetMapping("/search") + public ResponseEntity> getTeamsByTeamNameContaining( + @RequestParam String teamName) { + + List teams = teamService.findTeamsByTeamNameContaining(teamName); + return ResponseEntity.ok(teams); + } + + @Operation(summary = "사용자가 멤버인 팀 조회", description = "사용자가 멤버인 팀 목록을 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "팀이 성공적으로 조회되었습니다.") + }) + @GetMapping("/members/search") + public ResponseEntity> getTeamsByMemberIdContaining( + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String memberId = jwtUtil.getSubjectFromTokenWithoutAuth(authorizationHeader); + List teams = teamService.findTeamsByMemberIdContaining(memberId); + return ResponseEntity.ok(teams); + } + + @Operation(summary = "팀 업데이트", description = "기존 팀을 업데이트하고 업데이트된 팀 정보를 반환합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "팀이 성공적으로 업데이트되었습니다."), + @ApiResponse(responseCode = "400", description = "잘못된 입력 데이터 또는 권한이 없는 접근입니다."), + @ApiResponse(responseCode = "404", description = "팀을 찾을 수 없습니다.") + }) + @PatchMapping + public ResponseEntity updateTeam( + @RequestBody TeamUpdateRequestDTO teamUpdateRequestDTO, + @RequestParam String accountId) { + + try { + Team updatedTeam = teamService.updateTeam(teamUpdateRequestDTO, accountId); + return ResponseEntity.ok(updatedTeam); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + } + + @Operation(summary = "팀 삭제", description = "ID를 통해 팀을 삭제합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "팀이 성공적으로 삭제되었습니다."), + @ApiResponse(responseCode = "404", description = "팀을 찾을 수 없습니다.") + }) + @DeleteMapping("/{teamId}") + public ResponseEntity deleteTeam(@PathVariable Long teamId) { + teamService.deleteTeam(teamId); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/team/dto/TeamRegistRequestDTO.java b/src/main/java/com/mtvs/devlinkbackend/team/dto/TeamRegistRequestDTO.java new file mode 100644 index 0000000..c2fd161 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/team/dto/TeamRegistRequestDTO.java @@ -0,0 +1,16 @@ +package com.mtvs.devlinkbackend.team.dto; + +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class TeamRegistRequestDTO { + private String teamName; + private String introduction; + private List memberList; +} diff --git a/src/main/java/com/mtvs/devlinkbackend/team/dto/TeamUpdateRequestDTO.java b/src/main/java/com/mtvs/devlinkbackend/team/dto/TeamUpdateRequestDTO.java new file mode 100644 index 0000000..73162cb --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/team/dto/TeamUpdateRequestDTO.java @@ -0,0 +1,17 @@ +package com.mtvs.devlinkbackend.team.dto; + +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class TeamUpdateRequestDTO { + private Long teamId; + private String teamName; + private String introduction; + private List memberList; +} diff --git a/src/main/java/com/mtvs/devlinkbackend/team/entity/Team.java b/src/main/java/com/mtvs/devlinkbackend/team/entity/Team.java new file mode 100644 index 0000000..08b05ab --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/team/entity/Team.java @@ -0,0 +1,66 @@ +package com.mtvs.devlinkbackend.team.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.List; + +@Table(name = "TEAM") +@Entity(name = "Team") +@NoArgsConstructor +@ToString +@Getter +public class Team { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "TEAM_ID") + private Long teamId; + + @Column(name = "PM_ID") + private String pmId; + + @Column(name = "TEAM_NAME", unique = true) + private String teamName; + + @Column(name = "INTRODUCTION") + private String introduction; + + @ElementCollection + @CollectionTable(name = "TEAM_MEMBER", joinColumns = @JoinColumn(name = "TEAM_ID")) + @Column(name = "MEMBER_LIST") + private List memberList = new ArrayList<>(); + + public Team(String pmId, String teamName, String introduction, List memberList) { + this.pmId = pmId; + this.teamName = teamName; + this.introduction = introduction; + this.memberList = memberList; + } + + public void setPmId(String pmId) { + this.pmId = pmId; + } + + public void setTeamName(String teamName) { + this.teamName = teamName; + } + + public void setIntroduction(String introduction) { + this.introduction = introduction; + } + + public void setMemberList(List memberList) { + this.memberList = memberList; + } + + public void addMember(String memberId) { + this.memberList.add(memberId); + } + + public void removeMember(String memberId) { + this.memberList.remove(memberId); + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/team/repository/TeamRepository.java b/src/main/java/com/mtvs/devlinkbackend/team/repository/TeamRepository.java new file mode 100644 index 0000000..c11bb4a --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/team/repository/TeamRepository.java @@ -0,0 +1,19 @@ +package com.mtvs.devlinkbackend.team.repository; + +import com.mtvs.devlinkbackend.team.entity.Team; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface TeamRepository extends JpaRepository { + List findTeamsByTeamNameContaining(String teamName); + + List findTeamByPmId(String pmId); + + @Query("SELECT t FROM Team t JOIN t.memberList m WHERE m LIKE :memberId") + List findTeamsByMemberIdContaining(@Param("memberId") String accountId); +} diff --git a/src/main/java/com/mtvs/devlinkbackend/team/service/TeamService.java b/src/main/java/com/mtvs/devlinkbackend/team/service/TeamService.java new file mode 100644 index 0000000..cc1d0ae --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/team/service/TeamService.java @@ -0,0 +1,64 @@ +package com.mtvs.devlinkbackend.team.service; + +import com.mtvs.devlinkbackend.team.dto.TeamRegistRequestDTO; +import com.mtvs.devlinkbackend.team.dto.TeamUpdateRequestDTO; +import com.mtvs.devlinkbackend.team.entity.Team; +import com.mtvs.devlinkbackend.team.repository.TeamRepository; +import jakarta.transaction.Transactional; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public class TeamService { + private final TeamRepository teamRepository; + + public TeamService(TeamRepository teamRepository) { + this.teamRepository = teamRepository; + } + + @Transactional + public Team registTeam(TeamRegistRequestDTO teamRegistRequestDTO, String accountId) { + return teamRepository.save(new Team( + accountId, + teamRegistRequestDTO.getTeamName(), + teamRegistRequestDTO.getIntroduction(), + teamRegistRequestDTO.getMemberList() + )); + } + + public Team findTeamByTeamId(Long teamId) { + return teamRepository.findById(teamId).orElse(null); + } + + public List findTeamsByPmId(String pmId) { + return teamRepository.findTeamByPmId(pmId); + } + + public List findTeamsByTeamNameContaining(String teamName) { + return teamRepository.findTeamsByTeamNameContaining(teamName); + } + + public List findTeamsByMemberIdContaining(String memberId) { + return teamRepository.findTeamsByMemberIdContaining(memberId); + } + + @Transactional + public Team updateTeam(TeamUpdateRequestDTO teamUpdateRequestDTO, String accountId) { + Optional team = teamRepository.findById(teamUpdateRequestDTO.getTeamId()); + if (team.isPresent()) { + Team foundTeam = team.get(); + if(foundTeam.getPmId().equals(accountId)) { + foundTeam.setTeamName(teamUpdateRequestDTO.getTeamName()); + foundTeam.setIntroduction(teamUpdateRequestDTO.getIntroduction()); + foundTeam.setMemberList(teamUpdateRequestDTO.getMemberList()); + return foundTeam; + } else throw new IllegalArgumentException("pm이 아닌 계정으로 team 수정 시도"); + } else throw new IllegalArgumentException("해당 Team은 존재하지 않음"); + } + + public void deleteTeam(Long teamId) { + teamRepository.deleteById(teamId); + } +} diff --git a/src/test/java/com/mtvs/devlinkbackend/team/TeamCRUDTest.java b/src/test/java/com/mtvs/devlinkbackend/team/TeamCRUDTest.java new file mode 100644 index 0000000..3778a24 --- /dev/null +++ b/src/test/java/com/mtvs/devlinkbackend/team/TeamCRUDTest.java @@ -0,0 +1,103 @@ +package com.mtvs.devlinkbackend.team; + +import com.mtvs.devlinkbackend.team.dto.TeamRegistRequestDTO; +import com.mtvs.devlinkbackend.team.dto.TeamUpdateRequestDTO; +import com.mtvs.devlinkbackend.team.service.TeamService; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; +import java.util.stream.Stream; + +@SpringBootTest +@Transactional +public class TeamCRUDTest { + + @Autowired + private TeamService teamService; + + private static Stream newTeam() { + return Stream.of( + Arguments.of(new TeamRegistRequestDTO("팀 이름00", "소개00", List.of("계정1","계정3")), "계정2"), + Arguments.of(new TeamRegistRequestDTO("팀 이름00", "소개00", List.of("계정1","계정3")), "계정2") + ); + } + + private static Stream modifiedTeam() { + return Stream.of( + Arguments.of(new TeamUpdateRequestDTO(1L,"팀 이름0", "소개0", List.of("계정2","계정3")), "계정1"), + Arguments.of(new TeamUpdateRequestDTO(2L,"팀 이름00", "소개00", List.of("계정1","계정3")), "계정2") + ); + } + + @DisplayName("팀 추가 테스트") + @ParameterizedTest + @MethodSource("newTeam") + @Order(0) + public void testCreateTeam(TeamRegistRequestDTO questionRegistRequestDTO, String accountId) { + Assertions.assertDoesNotThrow(() -> teamService.registTeam(questionRegistRequestDTO, accountId)); + } + + @DisplayName("PK로 팀 조회 테스트") + @ValueSource(longs = {1,2}) + @ParameterizedTest + @Order(1) + public void testFindTeamByTeamId(long teamId) { + Assertions.assertDoesNotThrow(() -> + System.out.println("Team = " + teamService.findTeamByTeamId(teamId))); + } + + @DisplayName("계정 ID가 PM인 팀 조회 테스트") + @ValueSource(strings = {"계정1","계정2"}) + @ParameterizedTest + @Order(2) + public void testFindTeamByPmId(String pmId) { + Assertions.assertDoesNotThrow(() -> + System.out.println("Team = " + teamService.findTeamsByPmId(pmId))); + } + + @DisplayName("계정 ID가 멤버인 팀 조회 테스트") + @ValueSource(strings = {"계정1", "계정2"}) + @ParameterizedTest + @Order(3) + public void testFindTeamsByMemberId(String memberId) { + Assertions.assertDoesNotThrow(() -> + System.out.println("Team = " + teamService.findTeamsByMemberIdContaining(memberId))); + } + + @DisplayName("팀 이름으로 팀 조회 테스트") + @ValueSource(strings = {"팀 이름0", "팀 이름1"}) + @ParameterizedTest + @Order(4) + public void testFindTeamsByTeamNameContaining(String teamName) { + Assertions.assertDoesNotThrow(() -> + System.out.println("Team = " + teamService.findTeamsByTeamNameContaining(teamName))); + } + + @DisplayName("팀 수정 테스트") + @MethodSource("modifiedTeam") + @ParameterizedTest + @Order(5) + public void testUpdateTeam(TeamUpdateRequestDTO questionUpdateRequestDTO, String accountId) { + Assertions.assertDoesNotThrow(() -> + System.out.println(teamService.updateTeam(questionUpdateRequestDTO, accountId))); + } + + @DisplayName("팀 삭제 테스트") + @ValueSource(longs = {0,1}) + @ParameterizedTest + @Order(6) + public void testDeleteTeam(long teamId) { + Assertions.assertDoesNotThrow(() -> + teamService.deleteTeam(teamId)); + } +}