Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
import org.springframework.web.bind.annotation.RestController;

import com.Timo.Timo.domain.todo.docs.TodoControllerDocs;
import com.Timo.Timo.domain.todo.dto.request.SubtaskStatusUpdateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoCreateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoReorderRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoStatusUpdateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoUpdateRequest;
import com.Timo.Timo.domain.todo.dto.response.SubtaskStatusChangeResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoCreateResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoReorderResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoStatusChangeResponse;
Expand Down Expand Up @@ -81,6 +83,22 @@ public ResponseEntity<BaseResponse<TodoStatusChangeResponse>> changeTodoStatus(
.body(BaseResponse.onSuccess(TodoSuccessCode.STATUS_CHANGED, response));
}

@Override
@PatchMapping("/{todoId}/subtasks/{subtaskId}/status")
public ResponseEntity<BaseResponse<SubtaskStatusChangeResponse>> changeSubtaskStatus(
@AuthenticationPrincipal CustomUserDetails userDetails,
@PathVariable Long todoId,
@PathVariable Long subtaskId,
@Valid @RequestBody SubtaskStatusUpdateRequest request
) {
SubtaskStatusChangeResponse response =
todoService.changeSubtaskCompletion(userDetails.getUserId(), todoId, subtaskId, request);

return ResponseEntity
.status(TodoSuccessCode.SUBTASK_STATUS_CHANGED.getHttpStatus())
.body(BaseResponse.onSuccess(TodoSuccessCode.SUBTASK_STATUS_CHANGED, response));
}

@Override
@PatchMapping("/{todoId}/order")
public ResponseEntity<BaseResponse<TodoReorderResponse>> reorderTodo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import org.springframework.http.ResponseEntity;

import com.Timo.Timo.domain.todo.dto.request.SubtaskStatusUpdateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoCreateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoReorderRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoStatusUpdateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoUpdateRequest;
import com.Timo.Timo.domain.todo.dto.response.SubtaskStatusChangeResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoCreateResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoDetailResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoReorderResponse;
Expand Down Expand Up @@ -485,4 +487,76 @@ ResponseEntity<BaseResponse<TodoReorderResponse>> reorderTodo(
@Parameter(description = "이동할 TODO ID", example = "145") Long todoId,
TodoReorderRequest request
);

@Operation(
summary = "하위 태스크 완료 상태 변경",
description = """
하위 태스크의 완료 여부를 변경합니다.

todoId로 소유한 TODO를 확인한 뒤, 해당 TODO에 속한 subtaskId의 완료 상태를 변경합니다.

Swagger UI 오른쪽 위의 Authorize 버튼을 눌러 유효한 Access Token을 입력해야 합니다.
"""
)
@RequestBody(
required = true,
description = "하위 태스크 완료 상태 변경 요청",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = SubtaskStatusUpdateRequest.class),
examples = @ExampleObject(
name = "하위 태스크 완료 상태 변경 요청 예시",
value = """
{
"isCompleted": true
}
"""
)
)
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "하위 태스크 완료 상태 변경 성공",
useReturnTypeSchema = true
),
@ApiResponse(
responseCode = "400",
description = "isCompleted가 누락된 경우",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorDto.class)
)
),
@ApiResponse(
responseCode = "401",
description = "Access Token이 없거나 만료되었거나 유효하지 않은 경우",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorDto.class)
)
),
@ApiResponse(
responseCode = "404",
description = "존재하지 않는 TODO이거나 해당 TODO에 속하지 않는 하위 태스크인 경우",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorDto.class)
)
),
@ApiResponse(
responseCode = "500",
description = "서버 내부 오류",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ErrorDto.class)
)
)
})
ResponseEntity<BaseResponse<SubtaskStatusChangeResponse>> changeSubtaskStatus(
@Parameter(hidden = true) CustomUserDetails userDetails,
@Parameter(description = "대상 TODO ID", example = "5") Long todoId,
@Parameter(description = "대상 하위 태스크 ID", example = "2") Long subtaskId,
SubtaskStatusUpdateRequest request
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.Timo.Timo.domain.todo.dto.request;

import com.fasterxml.jackson.annotation.JsonProperty;

import jakarta.validation.constraints.NotNull;

public record SubtaskStatusUpdateRequest(
@NotNull
@JsonProperty("isCompleted")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@NotNull, @Valid를 사용하면 컨트롤러 진입 전에 걸러질 수 있을 것 같은데 Service 레이어에서 isCompleted가 null인 경우를 체크하는 이유가 따로 있을까용

Boolean isCompleted
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

import com.fasterxml.jackson.annotation.JsonProperty;

import jakarta.validation.constraints.NotNull;

public record TodoStatusUpdateRequest(
@NotNull
@JsonProperty("isCompleted")
Boolean isCompleted,

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.Timo.Timo.domain.todo.dto.response;

import com.Timo.Timo.domain.todo.entity.Subtask;

public record SubtaskStatusChangeResponse(
Long subtaskId,
Boolean completed
) {
public static SubtaskStatusChangeResponse from(Subtask subtask) {
return new SubtaskStatusChangeResponse(
subtask.getId(),
subtask.isCompleted()
);
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/Timo/Timo/domain/todo/entity/Subtask.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,8 @@ public void update(String content, boolean completed, int sortOrder) {
void assignTodo(Todo todo) {
this.todo = todo;
}

public void updateCompleted(boolean completed) {
this.completed = completed;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public enum TodoErrorCode implements BaseErrorCode {
INVALID_REQUEST(HttpStatus.BAD_REQUEST, "TODO_400", "필수 필드가 누락되었거나 형식이 올바르지 않습니다."),
INVALID_TITLE(HttpStatus.BAD_REQUEST, "TODO_400", "투두명은 한국어 20자/영어 30자를 초과할 수 없습니다."),
NO_UPDATE_FIELDS(HttpStatus.BAD_REQUEST, "TODO_400", "수정할 필드가 없습니다."),
IS_COMPLETED_REQUIRED(HttpStatus.BAD_REQUEST, "TODO_400", "isCompleted는 필수입니다."),
INVALID_INDEX(HttpStatus.BAD_REQUEST, "TODO_400", "유효하지 않은 인덱스 값입니다."),
TODO_NOT_FOUND(HttpStatus.NOT_FOUND, "TODO_404", "존재하지 않는 TODO입니다."),
TODO_NOT_FOUND_ON_DATE(HttpStatus.NOT_FOUND, "TODO_404", "해당 날짜에 존재하지 않는 투두입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum TodoSuccessCode implements BaseSuccessCode {
CREATED(HttpStatus.CREATED, "TODO가 생성되었습니다."),
GET_DETAIL(HttpStatus.OK, "TODO 상세 조회 성공"),
STATUS_CHANGED(HttpStatus.OK, "TODO 상태가 변경되었습니다."),
SUBTASK_STATUS_CHANGED(HttpStatus.OK, "하위 태스크 상태가 변경되었습니다."),
UPDATED(HttpStatus.OK, "TODO가 수정되었습니다."),
DELETED(HttpStatus.OK, "TODO가 삭제되었습니다."),
REORDERED(HttpStatus.OK, "TODO 순서가 변경되었습니다."),
Expand Down
22 changes: 18 additions & 4 deletions src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
import com.Timo.Timo.domain.tag.exception.TagErrorCode;
import com.Timo.Timo.domain.tag.repository.TagRepository;
import com.Timo.Timo.domain.timer.service.TimerService;
import com.Timo.Timo.domain.todo.dto.request.SubtaskStatusUpdateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoCreateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoReorderRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoStatusUpdateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoSubtaskUpdateRequest;
import com.Timo.Timo.domain.todo.dto.request.TodoUpdateRequest;
import com.Timo.Timo.domain.todo.dto.response.SubtaskStatusChangeResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoCreateResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoDetailResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoReorderResponse;
import com.Timo.Timo.domain.todo.dto.response.TodoStatusChangeResponse;
import com.Timo.Timo.domain.todo.entity.Subtask;
import com.Timo.Timo.domain.todo.entity.Todo;
import com.Timo.Timo.domain.todo.entity.TodoInstance;
import com.Timo.Timo.domain.todo.enums.RepeatType;
Expand Down Expand Up @@ -106,10 +109,6 @@ public TodoDetailResponse getTodoDetail(Long userId, Long todoId, String dateVal

@Transactional
public TodoStatusChangeResponse changeCompletion(Long userId, Long todoId, TodoStatusUpdateRequest request) {
if (request.isCompleted() == null) {
throw new CustomException(TodoErrorCode.IS_COMPLETED_REQUIRED);
}

Todo todo = todoRepository.findByIdAndUser_Id(todoId, userId)
.orElseThrow(() -> new CustomException(TodoErrorCode.TODO_NOT_FOUND));

Expand All @@ -126,6 +125,21 @@ public TodoStatusChangeResponse changeCompletion(Long userId, Long todoId, TodoS
return TodoStatusChangeResponse.from(todoId, instance);
}

@Transactional
public SubtaskStatusChangeResponse changeSubtaskCompletion(
Long userId, Long todoId, Long subtaskId, SubtaskStatusUpdateRequest request) {
Todo todo = todoRepository.findByIdAndUser_Id(todoId, userId)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

소유한 TODO를 먼저 조회한 뒤 그 안에서 하위 태스크를 찾는 흐름이라 권한 확인과 소속 검증이 같이 처리되네용 짱짱쓴

.orElseThrow(() -> new CustomException(TodoErrorCode.TODO_NOT_FOUND));

Subtask subtask = todo.getSubtasks().stream()
.filter(it -> it.getId().equals(subtaskId))
.findFirst()
.orElseThrow(() -> new CustomException(TodoErrorCode.SUBTASK_NOT_FOUND));

subtask.updateCompleted(request.isCompleted());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3) 현재 Subtask 엔티티의 completed 값을 직접 변경하고 있어서, daily TODO의 오늘 하위 태스크를 완료하면 다른 날짜에서도 같은 하위 태스크가 완료 상태로 보일 수 있을 것 같아요. 하위 태스크 완료 상태도 날짜별로 관리되어야 하지 않을까용

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

입코리해드렸습니다

return SubtaskStatusChangeResponse.from(subtask);
}

@Transactional
public TodoReorderResponse reorderTodo(Long userId, Long todoId, TodoReorderRequest request) {
Todo todo = todoRepository.findByIdAndUser_Id(todoId, userId)
Expand Down