diff --git a/src/main/java/com/mtvs/devlinkbackend/character/controller/UserCharacterController.java b/src/main/java/com/mtvs/devlinkbackend/character/controller/UserCharacterController.java new file mode 100644 index 0000000..204ae0c --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/character/controller/UserCharacterController.java @@ -0,0 +1,80 @@ +package com.mtvs.devlinkbackend.character.controller; + +import com.mtvs.devlinkbackend.character.dto.UserCharacterRegistRequestDTO; +import com.mtvs.devlinkbackend.character.dto.UserCharacterUpdateRequestDTO; +import com.mtvs.devlinkbackend.character.entity.UserCharacter; +import com.mtvs.devlinkbackend.character.service.UserCharacterService; +import com.mtvs.devlinkbackend.util.JwtUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/character") +public class UserCharacterController { + private final UserCharacterService userCharacterService; + private final JwtUtil jwtUtil; + + public UserCharacterController(UserCharacterService userCharacterService, JwtUtil jwtUtil) { + this.userCharacterService = userCharacterService; + this.jwtUtil = jwtUtil; + } + + @Operation(summary = "캐릭터 등록", description = "새로운 캐릭터를 등록합니다.") + @ApiResponse(responseCode = "201", description = "캐릭터가 성공적으로 등록되었습니다.") + @PostMapping + public ResponseEntity registerCharacter( + @RequestBody UserCharacterRegistRequestDTO userCharacterRegistRequestDTO, + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader); + UserCharacter userCharacter = userCharacterService.registCharacter(userCharacterRegistRequestDTO, accountId); + return new ResponseEntity<>(userCharacter, HttpStatus.CREATED); + } + + @Operation(summary = "캐릭터 조회", description = "계정 ID로 캐릭터를 조회합니다.") + @ApiResponse(responseCode = "200", description = "캐릭터가 성공적으로 조회되었습니다.") + @ApiResponse(responseCode = "404", description = "해당 계정 ID로 캐릭터를 찾을 수 없습니다.") + @GetMapping + public ResponseEntity getCharacter( + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader); + UserCharacter userCharacter = userCharacterService.findCharacterByAccountId(accountId); + if (userCharacter == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(userCharacter, HttpStatus.OK); + } + + @Operation(summary = "캐릭터 수정", description = "계정 ID로 캐릭터를 수정합니다.") + @ApiResponse(responseCode = "200", description = "캐릭터가 성공적으로 수정되었습니다.") + @ApiResponse(responseCode = "404", description = "해당 계정 ID로 캐릭터를 찾을 수 없습니다.") + @PatchMapping + public ResponseEntity updateCharacter( + @RequestBody UserCharacterUpdateRequestDTO userCharacterUpdateRequestDTO, + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader); + try { + UserCharacter updatedCharacter = userCharacterService.updateCharacter(userCharacterUpdateRequestDTO, accountId); + return new ResponseEntity<>(updatedCharacter, HttpStatus.OK); + } catch (IllegalArgumentException e) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } + + @Operation(summary = "캐릭터 삭제", description = "계정 ID로 캐릭터를 삭제합니다.") + @ApiResponse(responseCode = "204", description = "캐릭터가 성공적으로 삭제되었습니다.") + @ApiResponse(responseCode = "404", description = "해당 계정 ID로 캐릭터를 찾을 수 없습니다.") + @DeleteMapping + public ResponseEntity deleteCharacter( + @RequestHeader(name = "Authorization") String authorizationHeader) throws Exception { + + String accountId = jwtUtil.getSubjectFromAuthHeaderWithoutAuth(authorizationHeader); + userCharacterService.deleteCharacterByAccountId(accountId); + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/character/dto/UserCharacterRegistRequestDTO.java b/src/main/java/com/mtvs/devlinkbackend/character/dto/UserCharacterRegistRequestDTO.java new file mode 100644 index 0000000..0f5b0b8 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/character/dto/UserCharacterRegistRequestDTO.java @@ -0,0 +1,13 @@ +package com.mtvs.devlinkbackend.character.dto; + +import lombok.*; + +import java.util.List; + +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class UserCharacterRegistRequestDTO { + private List status; +} \ No newline at end of file diff --git a/src/main/java/com/mtvs/devlinkbackend/character/dto/UserCharacterUpdateRequestDTO.java b/src/main/java/com/mtvs/devlinkbackend/character/dto/UserCharacterUpdateRequestDTO.java new file mode 100644 index 0000000..d062c83 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/character/dto/UserCharacterUpdateRequestDTO.java @@ -0,0 +1,14 @@ +package com.mtvs.devlinkbackend.character.dto; + +import lombok.*; + +import java.util.List; + +@Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class UserCharacterUpdateRequestDTO { + private Long characterId; + private List status; +} \ No newline at end of file diff --git a/src/main/java/com/mtvs/devlinkbackend/character/entity/UserCharacter.java b/src/main/java/com/mtvs/devlinkbackend/character/entity/UserCharacter.java new file mode 100644 index 0000000..fb3a4d9 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/character/entity/UserCharacter.java @@ -0,0 +1,40 @@ +package com.mtvs.devlinkbackend.character.entity; + +import jakarta.persistence.*; +import lombok.Getter; + +import java.util.List; + +@Table(name = "USER_CHARACTER") +@Entity(name = "UserCharacter") +@Getter +public class UserCharacter { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "CHARACTER_ID") + private Long characterId; + + @Column(name = "ACCOUNT_ID", unique = true) + private String accountId; + + @ElementCollection + @CollectionTable(name = "STATUS_LIST", joinColumns = @JoinColumn(name = "CHARACTER_ID")) + @Column(name = "STATUS") + private List status; + + public UserCharacter() { + } + + public UserCharacter(String accountId, List status) { + this.accountId = accountId; + this.status = status; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public void setStatus(List status) { + this.status = status; + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/character/repository/UserCharacterRepository.java b/src/main/java/com/mtvs/devlinkbackend/character/repository/UserCharacterRepository.java new file mode 100644 index 0000000..05280e6 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/character/repository/UserCharacterRepository.java @@ -0,0 +1,11 @@ +package com.mtvs.devlinkbackend.character.repository; + +import com.mtvs.devlinkbackend.character.entity.UserCharacter; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserCharacterRepository extends JpaRepository { + UserCharacter findByAccountId(String accountId); + void deleteByAccountId(String accountId); +} diff --git a/src/main/java/com/mtvs/devlinkbackend/character/service/UserCharacterService.java b/src/main/java/com/mtvs/devlinkbackend/character/service/UserCharacterService.java new file mode 100644 index 0000000..618b07d --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/character/service/UserCharacterService.java @@ -0,0 +1,43 @@ +package com.mtvs.devlinkbackend.character.service; + +import com.mtvs.devlinkbackend.character.dto.UserCharacterRegistRequestDTO; +import com.mtvs.devlinkbackend.character.dto.UserCharacterUpdateRequestDTO; +import com.mtvs.devlinkbackend.character.entity.UserCharacter; +import com.mtvs.devlinkbackend.character.repository.UserCharacterRepository; +import jakarta.transaction.Transactional; +import org.springframework.stereotype.Service; + +@Service +public class UserCharacterService { + private final UserCharacterRepository userCharacterRepository; + + public UserCharacterService(UserCharacterRepository userCharacterRepository) { + this.userCharacterRepository = userCharacterRepository; + } + + @Transactional + public UserCharacter registCharacter(UserCharacterRegistRequestDTO userCharacterRegistRequestDTO, String accountId) { + return userCharacterRepository.save(new UserCharacter( + accountId, + userCharacterRegistRequestDTO.getStatus() + )); + } + + public UserCharacter findCharacterByAccountId(String accountId) { + return userCharacterRepository.findByAccountId(accountId); + } + + @Transactional + public UserCharacter updateCharacter(UserCharacterUpdateRequestDTO userCharacterUpdateRequestDTO, String accountId) { + UserCharacter userCharacter = userCharacterRepository.findByAccountId(accountId); + if(userCharacter == null) + throw new IllegalArgumentException("잘못된 계정으로 캐릭터 수정 시도"); + + userCharacter.setStatus(userCharacterUpdateRequestDTO.getStatus()); + return userCharacter; + } + + public void deleteCharacterByAccountId(String accountId) { + userCharacterRepository.deleteByAccountId(accountId); + } +} diff --git a/src/test/java/com/mtvs/devlinkbackend/crud/UserCharacterCRUDTest.java b/src/test/java/com/mtvs/devlinkbackend/crud/UserCharacterCRUDTest.java new file mode 100644 index 0000000..87c4ae9 --- /dev/null +++ b/src/test/java/com/mtvs/devlinkbackend/crud/UserCharacterCRUDTest.java @@ -0,0 +1,92 @@ +package com.mtvs.devlinkbackend.crud; + +import com.mtvs.devlinkbackend.character.dto.UserCharacterRegistRequestDTO; +import com.mtvs.devlinkbackend.character.dto.UserCharacterUpdateRequestDTO; +import com.mtvs.devlinkbackend.character.service.UserCharacterService; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +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.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 UserCharacterCRUDTest { + @Autowired + private UserCharacterService userCharacterService; + + private static Stream registUserCharacter() { + return Stream.of( + Arguments.of(new UserCharacterRegistRequestDTO( + List.of(5,6) + ), "계정3"), + Arguments.of(new UserCharacterRegistRequestDTO( + List.of(7,8) + ), "계정4") + ); + } + + private static Stream modifyUserCharacter() { + return Stream.of( + Arguments.of(new UserCharacterUpdateRequestDTO( + 1L, + List.of(13,2) + ), "계정1"), + Arguments.of(new UserCharacterUpdateRequestDTO( + 2L, + List.of(43,4) + ), "계정2") + ); + } + + @BeforeEach + public void setUp() { + userCharacterService.registCharacter(new UserCharacterRegistRequestDTO( + List.of(1,2) + ), "계정1"); + userCharacterService.registCharacter(new UserCharacterRegistRequestDTO( + List.of(3,4) + ), "계정2"); + } + + @Order(1) + @DisplayName("UserCharacter 등록") + @MethodSource("registUserCharacter") + @ParameterizedTest + public void testRegistUserCharacter(UserCharacterRegistRequestDTO userCharacterRegistRequestDTO, String accountId) { + Assertions.assertDoesNotThrow(() -> userCharacterService.registCharacter(userCharacterRegistRequestDTO, accountId)); + } + + @Order(2) + @DisplayName("계정 ID로 UserCharacter 조회") + @ValueSource(strings = {"계정1", "계정2"}) + @ParameterizedTest + public void testFindUserCharacterByAccountId(String accountId) { + Assertions.assertDoesNotThrow(() -> userCharacterService.findCharacterByAccountId(accountId)); + } + + @Order(3) + @DisplayName("UserCharacter 수정") + @MethodSource("modifyUserCharacter") + @ParameterizedTest + public void testUpdateUserCharacter(UserCharacterUpdateRequestDTO userCharacterUpdateRequestDTO, String accountId) { + Assertions.assertDoesNotThrow(() -> userCharacterService.updateCharacter(userCharacterUpdateRequestDTO, accountId)); + } + + @Order(4) + @DisplayName("UserCharacter 삭제") + @ValueSource(strings = {"계정1", "계정2"}) + @ParameterizedTest + public void testDeleteByAccountId(String accountId) { + Assertions.assertDoesNotThrow(() -> userCharacterService.deleteCharacterByAccountId(accountId)); + } +}