diff --git a/src/main/java/kwonyonghoon/todogo/dto/AddTaskRequest.java b/src/main/java/kwonyonghoon/todogo/dto/AddTaskRequest.java index 962255d..9303074 100644 --- a/src/main/java/kwonyonghoon/todogo/dto/AddTaskRequest.java +++ b/src/main/java/kwonyonghoon/todogo/dto/AddTaskRequest.java @@ -13,11 +13,11 @@ @Getter public class AddTaskRequest { - public String title; - public String description; - public LocalDateTime deadline; - public Boolean status; - public Long userId; + private String title; + private String description; + private LocalDateTime deadline; + private Boolean status; + private Long userId; public Task toEntity(User user){ return Task.builder() diff --git a/src/main/java/kwonyonghoon/todogo/dto/AddUserRequest.java b/src/main/java/kwonyonghoon/todogo/dto/AddUserRequest.java index 0b4b481..7a9eb65 100644 --- a/src/main/java/kwonyonghoon/todogo/dto/AddUserRequest.java +++ b/src/main/java/kwonyonghoon/todogo/dto/AddUserRequest.java @@ -1,10 +1,12 @@ package kwonyonghoon.todogo.dto; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @Getter @NoArgsConstructor +@AllArgsConstructor public class AddUserRequest { private String phoneNumber; diff --git a/src/main/java/kwonyonghoon/todogo/dto/TaskResponse.java b/src/main/java/kwonyonghoon/todogo/dto/TaskResponse.java index 689636f..8e0727e 100644 --- a/src/main/java/kwonyonghoon/todogo/dto/TaskResponse.java +++ b/src/main/java/kwonyonghoon/todogo/dto/TaskResponse.java @@ -9,6 +9,8 @@ @Getter public class TaskResponse { + private final Long userId; + private final Long taskNumber; private final String title; private final String description; private final LocalDateTime deadline; @@ -16,6 +18,8 @@ public class TaskResponse { private final UserResponse user; public TaskResponse(Task task) { + this.userId = task.getId().getUserId(); + this.taskNumber = task.getId().getTaskNumber(); this.title = task.getTitle(); this.description = task.getDescription(); this.deadline = task.getDeadline(); diff --git a/src/main/java/kwonyonghoon/todogo/dto/UserResponse.java b/src/main/java/kwonyonghoon/todogo/dto/UserResponse.java index d52031e..528ca9b 100644 --- a/src/main/java/kwonyonghoon/todogo/dto/UserResponse.java +++ b/src/main/java/kwonyonghoon/todogo/dto/UserResponse.java @@ -1,9 +1,14 @@ package kwonyonghoon.todogo.dto; -import kwonyonghoon.todogo.user.User; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import kwonyonghoon.todogo.user.User; @Getter +@NoArgsConstructor(force = true) +@AllArgsConstructor public class UserResponse { private final Long id; @@ -17,5 +22,4 @@ public UserResponse(User user) { this.phoneNumber = user.getPhoneNumber(); this.name = user.getName(); } - } diff --git a/src/main/java/kwonyonghoon/todogo/task/Task.java b/src/main/java/kwonyonghoon/todogo/task/Task.java index bebcbb0..df3bb2a 100644 --- a/src/main/java/kwonyonghoon/todogo/task/Task.java +++ b/src/main/java/kwonyonghoon/todogo/task/Task.java @@ -16,10 +16,8 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Task { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", updatable = false) - private Long id; + @EmbeddedId + private TaskId id; @CreatedDate @Column(name = "created_at") @@ -42,16 +40,18 @@ public class Task { private Boolean status; @ManyToOne(fetch = FetchType.LAZY) + @MapsId("userId") @JoinColumn(name = "user_id", nullable = false) private User user; @Builder - public Task(String title, String description, LocalDateTime deadline, Boolean status, User user) { + public Task(User user, Long taskNumber, String title, String description, LocalDateTime deadline, Boolean status) { + this.id = new TaskId(user.getId(), taskNumber); + this.user = user; this.title = title; this.description = description; this.deadline = deadline; this.status = status; - this.user = user; } public void update(String title, String description, LocalDateTime deadline, Boolean status) { diff --git a/src/main/java/kwonyonghoon/todogo/task/TaskApiController.java b/src/main/java/kwonyonghoon/todogo/task/TaskApiController.java index f47ddfb..81456d4 100644 --- a/src/main/java/kwonyonghoon/todogo/task/TaskApiController.java +++ b/src/main/java/kwonyonghoon/todogo/task/TaskApiController.java @@ -21,9 +21,9 @@ public class TaskApiController { @PostMapping("/api/tasks") public ResponseEntity addTask(@RequestBody AddTaskRequest request){ - User user = userRepository.findById(request.getUserId()) - .orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다.")); - Task task = request.toEntity(user); +// User user = userRepository.findById(request.getUserId()) +// .orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다.")); +// Task task = request.toEntity(user); Task savedTask = taskService.save(request); return ResponseEntity.status(HttpStatus.CREATED) @@ -41,28 +41,39 @@ public ResponseEntity> findAllTasks(){ .body(tasks); } - @GetMapping("/api/tasks/{id}") - public ResponseEntity findTaskById(@PathVariable Long id){ - Task task = taskService.findById(id); + @GetMapping("/api/users/{userId}/tasks/{taskNumber}") + public ResponseEntity findTask( + @PathVariable Long userId, + @PathVariable Long taskNumber){ + + TaskId taskId = new TaskId(userId, taskNumber); + Task task = taskService.findById(taskId); return ResponseEntity.ok() .body(new TaskResponse(task)); } - @DeleteMapping("/api/tasks/{id}") - public ResponseEntity deleteTask(@PathVariable Long id){ - taskService.delete(id); + @DeleteMapping("/api/users/{userId}/tasks/{taskNumber}") + public ResponseEntity deleteTask( + @PathVariable Long userId, + @PathVariable Long taskNumber){ + + TaskId taskId = new TaskId(userId, taskNumber); + taskService.delete(taskId); return ResponseEntity.ok() .build(); } - @PutMapping("/api/tasks/{id}") - public ResponseEntity updateTask(@PathVariable Long id, @RequestBody UpdateTaskRequest request){ - Task updatedTask = taskService.update(id, request); + @PutMapping("/api/users/{userId}/tasks/{taskNumber}") + public ResponseEntity updateTask( + @PathVariable Long userId, + @PathVariable Long taskNumber, + @RequestBody UpdateTaskRequest request){ - return ResponseEntity.ok() - .body(new TaskResponse(updatedTask)); + TaskId taskId = new TaskId(userId, taskNumber); + Task updatedTask = taskService.update(taskId, request); + return ResponseEntity.ok().body(new TaskResponse(updatedTask)); } @GetMapping("/api/tasks/user/{uuid}") diff --git a/src/main/java/kwonyonghoon/todogo/task/TaskId.java b/src/main/java/kwonyonghoon/todogo/task/TaskId.java new file mode 100644 index 0000000..ec37f97 --- /dev/null +++ b/src/main/java/kwonyonghoon/todogo/task/TaskId.java @@ -0,0 +1,21 @@ +package kwonyonghoon.todogo.task; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Embeddable +@Getter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +public class TaskId implements Serializable { + + private Long userId; + private Long taskNumber; + +} diff --git a/src/main/java/kwonyonghoon/todogo/task/TaskRepository.java b/src/main/java/kwonyonghoon/todogo/task/TaskRepository.java index 5bc0805..eb7ab4c 100644 --- a/src/main/java/kwonyonghoon/todogo/task/TaskRepository.java +++ b/src/main/java/kwonyonghoon/todogo/task/TaskRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -public interface TaskRepository extends JpaRepository { +public interface TaskRepository extends JpaRepository { List findAllByUser(User user); + Long countByUser(User user); } \ No newline at end of file diff --git a/src/main/java/kwonyonghoon/todogo/task/TaskService.java b/src/main/java/kwonyonghoon/todogo/task/TaskService.java index 8f9ac46..812be94 100644 --- a/src/main/java/kwonyonghoon/todogo/task/TaskService.java +++ b/src/main/java/kwonyonghoon/todogo/task/TaskService.java @@ -21,24 +21,49 @@ public Task save(AddTaskRequest request){ User user = userRepository.findById(request.getUserId()) .orElseThrow(()-> new IllegalArgumentException("User not found")); - return taskRepository.save(request.toEntity(user)); + Long nextTaskNumber = getNextTaskNumber(user); + + if(nextTaskNumber == null){ + throw new IllegalArgumentException("TaskNumber는 Null이 될 수 없습니다"); + } + + Task task = Task.builder(). + title(request.getTitle()) + .description(request.getDescription()) + .deadline(request.getDeadline()) + .status(request.getStatus()) + .user(user) + .taskNumber(nextTaskNumber) + .build(); + + return taskRepository.save(task); + } + + public Long getNextTaskNumber(User user){ + Long nextTaskNumber = taskRepository.countByUser(user) + 1; + + if(nextTaskNumber == null){ + throw new IllegalArgumentException("TaskNumber는 Null이 될 수 없습니다"); + } + + return nextTaskNumber; } public List findAll(){ return taskRepository.findAll(); } - public Task findById(Long id){ + public Task findById(TaskId id){ return taskRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("not found: " + id)); } - public void delete(Long id){ + public void delete(TaskId id){ taskRepository.deleteById(id); } @Transactional - public Task update(Long id, UpdateTaskRequest request){ + public Task update(TaskId id, UpdateTaskRequest request){ Task task = taskRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("not found: " + id)); diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 37dbd4d..9a6c1dd 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,4 +1,4 @@ INSERT INTO USER_TABLE (uuid, phone_number, name) VALUES('123e4567-e89b-12d3-a456-426614174000', '010-1234-5678', '홍길동') -INSERT INTO task (title, description, deadline, status, user_id) VALUES ('제목 1', '내용 1', '2025-4-14 17:30:30', false, 1) -INSERT INTO task (title, description, deadline, status, user_id) VALUES ('제목 2', '내용 2', '2025-4-15 17:30:30', true, 1) -INSERT INTO task (title, description, deadline, status, user_id) VALUES ('제목 3', '내용 3', '2025-4-16 17:30:30', false, 1) \ No newline at end of file +INSERT INTO task (title, description, deadline, status, user_id, task_number) VALUES ('제목 1', '내용 1', '2025-4-14 17:30:30', false, 1, 1) +INSERT INTO task (title, description, deadline, status, user_id, task_number) VALUES ('제목 2', '내용 2', '2025-4-15 17:30:30', true, 1, 2) +INSERT INTO task (title, description, deadline, status, user_id, task_number) VALUES ('제목 3', '내용 3', '2025-4-16 17:30:30', false, 1, 3) \ No newline at end of file diff --git a/src/test/java/kwonyonghoon/todogo/task/TaskApiControllerTest.java b/src/test/java/kwonyonghoon/todogo/task/TaskApiControllerTest.java index a134791..45f4114 100644 --- a/src/test/java/kwonyonghoon/todogo/task/TaskApiControllerTest.java +++ b/src/test/java/kwonyonghoon/todogo/task/TaskApiControllerTest.java @@ -46,6 +46,9 @@ class TaskApiControllerTest { @Autowired UserRepository userRepository; + @Autowired + TaskService taskService; + @BeforeEach public void mockMvcSetUp(){ this.mockMvc = MockMvcBuilders.webAppContextSetup(context) @@ -87,6 +90,7 @@ public void addTask() throws Exception { assertThat(tasks.get(0).getDescription()).isEqualTo(description); assertThat(tasks.get(0).getStatus()).isEqualTo(status); assertThat(tasks.get(0).getUser().getId()).isEqualTo(user.getId()); + assertThat(tasks.get(0).getId().getTaskNumber()).isEqualTo(1); } @DisplayName("findAllTasks: 데이터 목록 조회에 성공한다.") @@ -109,6 +113,7 @@ public void findAllTasks() throws Exception { .deadline(deadline) .status(status) .user(user) + .taskNumber(taskService.getNextTaskNumber(user)) .build()); // when @@ -126,13 +131,13 @@ public void findAllTasks() throws Exception { @Test public void findTask() throws Exception { // given - final String url = "/api/tasks/{id}"; + final String url = "/api/users/{userId}/tasks/{taskNumber}"; final String title = "title"; final String description = "description"; final LocalDateTime deadline = LocalDateTime.now(); final Boolean status = false; final User user = userRepository.save(User.builder() - .phoneNumber("010-4072-7941") + .phoneNumber("010-4072-7942") .name("스포는범죄다") .build()); @@ -142,10 +147,11 @@ public void findTask() throws Exception { .deadline(deadline) .status(status) .user(user) + .taskNumber(taskService.getNextTaskNumber(user)) .build()); // when - final ResultActions resultActions = mockMvc.perform(get(url, savedTask.getId())); + final ResultActions resultActions = mockMvc.perform(get(url, user.getId(), savedTask.getId().getTaskNumber())); // then resultActions @@ -159,7 +165,7 @@ public void findTask() throws Exception { @Test public void deleteTask() throws Exception { // given - final String url = "/api/tasks/{id}"; + final String url = "/api/users/{userId}/tasks/{taskNumber}"; final String title = "title"; final String description = "description"; final LocalDateTime deadline = LocalDateTime.now(); @@ -175,10 +181,11 @@ public void deleteTask() throws Exception { .deadline(deadline) .status(status) .user(user) + .taskNumber(taskService.getNextTaskNumber(user)) .build()); // when - mockMvc.perform(delete(url, savedTask.getId())) + mockMvc.perform(delete(url, user.getId(), savedTask.getId().getTaskNumber())) .andExpect(status().isOk()); // then @@ -191,7 +198,7 @@ public void deleteTask() throws Exception { @Test public void updateTask() throws Exception { // given - final String url = "/api/tasks/{id}"; + final String url = "/api/users/{userId}/tasks/{taskNumber}"; final String title = "title"; final String description = "description"; final LocalDateTime deadline = LocalDateTime.now(); @@ -207,6 +214,7 @@ public void updateTask() throws Exception { .deadline(deadline) .status(status) .user(user) + .taskNumber(taskService.getNextTaskNumber(user)) .build()); final String newTitle = "new title"; @@ -217,7 +225,7 @@ public void updateTask() throws Exception { UpdateTaskRequest request = new UpdateTaskRequest(newTitle, newDescription, newDeadline, newStatus, user.getId()); // when - ResultActions result = mockMvc.perform(put(url, savedTask.getId()) + ResultActions result = mockMvc.perform(put(url, user.getId(), savedTask.getId().getTaskNumber()) .contentType(MediaType.APPLICATION_JSON_VALUE) .content(objectMapper.writeValueAsString(request))); @@ -230,4 +238,110 @@ public void updateTask() throws Exception { assertThat(task.getDescription()).isEqualTo(newDescription); assertThat(task.getStatus()).isEqualTo(newStatus); } + + @DisplayName("findTasksByUser: 특정 유저의 Task 목록 조회에 성공한다.") + @Test + public void findTasksByUser() throws Exception { + // given + final String url = "/api/tasks/user/{userId}"; + final User user1 = userRepository.save(User.builder() + .phoneNumber("010-0000-0001") + .name("사용자1") + .build()); + + final User user2 = userRepository.save(User.builder() + .phoneNumber("010-0000-0002") + .name("사용자2") + .build()); + + taskRepository.save(Task.builder() + .title("User1 Task1") + .description("description") + .deadline(LocalDateTime.now()) + .status(false) + .user(user1) + .taskNumber(taskService.getNextTaskNumber(user1)) + .build()); + + taskRepository.save(Task.builder() + .title("User2 Task1") + .description("description") + .deadline(LocalDateTime.now()) + .status(false) + .user(user2) + .taskNumber(taskService.getNextTaskNumber(user2)) + .build()); + + // when + final ResultActions resultActions = mockMvc.perform(get(url, user1.getUuid()) + .accept(MediaType.APPLICATION_JSON)); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(1)) + .andExpect(jsonPath("$[0].title").value("User1 Task1")); + } + + @DisplayName("addTask: 사용자별로 ID가 1부터 시작한다.") + @Test + public void addTaskForDifferentUsers() throws Exception { + // 사용자 1 생성 + final User user1 = userRepository.save(User.builder() + .phoneNumber("010-0000-0001") + .name("사용자1") + .build()); + + // 사용자 2 생성 + final User user2 = userRepository.save(User.builder() + .phoneNumber("010-0000-0002") + .name("사용자2") + .build()); + + // 사용자 1의 첫 번째 Task 추가 + final AddTaskRequest taskRequest1 = new AddTaskRequest("Task1", "Description1", LocalDateTime.now(), false, user1.getId()); + final String requestBody1 = objectMapper.writeValueAsString(taskRequest1); + mockMvc.perform(post("/api/tasks") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(requestBody1)) + .andExpect(status().isCreated()); + + // 사용자 2의 첫 번째 Task 추가 + final AddTaskRequest taskRequest2 = new AddTaskRequest("Task2", "Description2", LocalDateTime.now(), false, user2.getId()); + final String requestBody2 = objectMapper.writeValueAsString(taskRequest2); + mockMvc.perform(post("/api/tasks") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(requestBody2)) + .andExpect(status().isCreated()); + + // 사용자 1의 두 번째 Task 추가 + final AddTaskRequest taskRequest3 = new AddTaskRequest("Task3", "Description3", LocalDateTime.now(), false, user1.getId()); + final String requestBody3 = objectMapper.writeValueAsString(taskRequest3); + mockMvc.perform(post("/api/tasks") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(requestBody3)) + .andExpect(status().isCreated()); + + // 사용자 2의 두 번째 Task 추가 + final AddTaskRequest taskRequest4 = new AddTaskRequest("Task4", "Description4", LocalDateTime.now(), false, user2.getId()); + final String requestBody4 = objectMapper.writeValueAsString(taskRequest4); + mockMvc.perform(post("/api/tasks") + .contentType(MediaType.APPLICATION_JSON_VALUE) + .content(requestBody4)) + .andExpect(status().isCreated()); + + // 사용자 1의 Task 목록 조회 + final List tasksByUser1 = taskRepository.findAllByUser(user1); + // 사용자 2의 Task 목록 조회 + final List tasksByUser2 = taskRepository.findAllByUser(user2); + + // 사용자 1의 Task ID가 1부터 시작하는지 확인 + assertThat(tasksByUser1.get(0).getId().getTaskNumber()).isEqualTo(1); + assertThat(tasksByUser1.get(1).getId().getTaskNumber()).isEqualTo(2); + + // 사용자 2의 Task ID가 1부터 시작하는지 확인 + assertThat(tasksByUser2.get(0).getId().getTaskNumber()).isEqualTo(1); + assertThat(tasksByUser2.get(1).getId().getTaskNumber()).isEqualTo(2); + } + } \ No newline at end of file diff --git a/src/test/java/kwonyonghoon/todogo/user/UserServiceTest.java b/src/test/java/kwonyonghoon/todogo/user/UserServiceTest.java index 50edbc3..d50171e 100644 --- a/src/test/java/kwonyonghoon/todogo/user/UserServiceTest.java +++ b/src/test/java/kwonyonghoon/todogo/user/UserServiceTest.java @@ -22,7 +22,7 @@ class UserServiceTest { @Test void signup(){ // given - String phoneNumber = "010-1234-5679"; + String phoneNumber = "010-1234-5680"; String name = "홍길동"; // when