Skip to content

Commit 5cab4a9

Browse files
committed
feat: Todo 생성 구현
1 parent 61d8b1a commit 5cab4a9

File tree

7 files changed

+108
-5
lines changed

7 files changed

+108
-5
lines changed

src/main/java/com/back/domain/study/plan/service/StudyPlanService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -582,16 +582,16 @@ private void validateTimeConflict(Long userId, Long planIdToExclude, LocalDateTi
582582

583583
for (StudyPlan plan : conflictingOriginalPlans) {
584584
if (plan.getRepeatRule() == null) {
585-
// 2-1. 단발성 계획: 쿼리에서 이미 시간 범위가 겹친다고 걸러졌지만, 최종 확인
585+
// 2-1. 단발성 계획 -> 쿼리에서 이미 시간 범위가 겹친다고 걸러졌지만 재확인
586586
if (isOverlapping(plan.getStartDate(), plan.getEndDate(), newStart, newEnd)) {
587587
throw new CustomException(ErrorCode.PLAN_TIME_CONFLICT);
588588
}
589589
} else {
590-
// 2-2. 반복 계획: 기존 헬퍼를 사용해 요청 날짜의 가상 인스턴스를 생성하고 검사
590+
// 2-2. 반복 계획 -> 기존 메서드를 사용해 요청 날짜의 가상 인스턴스를 생성하고 검사
591591
StudyPlanResponse virtualPlan = createVirtualPlanForDate(plan, newPlanDate);
592592

593593
if (virtualPlan != null) {
594-
// 가상 인스턴스가 존재하고 (삭제되지 않았고)
594+
// 가상 인스턴스가 존재하고
595595
// 해당 인스턴스의 확정된 시간이 새 계획과 겹치는지 최종 확인
596596
if (isOverlapping(virtualPlan.getStartDate(), virtualPlan.getEndDate(), newStart, newEnd)) {
597597
throw new CustomException(ErrorCode.PLAN_TIME_CONFLICT);

src/main/java/com/back/domain/study/todo/controller/TodoController.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
package com.back.domain.study.todo.controller;
22

3+
import com.back.domain.study.todo.dto.TodoRequestDto;
4+
import com.back.domain.study.todo.dto.TodoResponseDto;
5+
import com.back.domain.study.todo.service.TodoService;
6+
import com.back.global.common.dto.RsData;
7+
import com.back.global.security.user.CustomUserDetails;
8+
import io.swagger.v3.oas.annotations.Operation;
39
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import jakarta.validation.Valid;
411
import lombok.RequiredArgsConstructor;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
14+
import org.springframework.web.bind.annotation.PostMapping;
15+
import org.springframework.web.bind.annotation.RequestBody;
516
import org.springframework.web.bind.annotation.RequestMapping;
617
import org.springframework.web.bind.annotation.RestController;
718

@@ -10,5 +21,16 @@
1021
@RequestMapping("/api/todos")
1122
@Tag(name = "Todo", description = "할 일 관련 API")
1223
public class TodoController {
24+
private final TodoService todoService;
25+
26+
@PostMapping
27+
@Operation(summary = "할 일 생성", description = "새로운 할 일을 생성합니다.")
28+
public ResponseEntity<RsData<TodoResponseDto>> createTodo(
29+
@AuthenticationPrincipal CustomUserDetails userDetails,
30+
@Valid @RequestBody TodoRequestDto requestDto
31+
) {
32+
TodoResponseDto response = todoService.createTodo(userDetails.getUserId(), requestDto);
33+
return ResponseEntity.ok(RsData.success("할 일이 생성되었습니다.", response));
34+
}
1335

1436
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.back.domain.study.todo.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotNull;
6+
7+
import java.time.LocalDate;
8+
9+
public record TodoRequestDto(
10+
@NotBlank(message = "할 일 설명은 필수입니다.")
11+
String description,
12+
@NotNull(message = "날짜는 필수입니다.")
13+
LocalDate date
14+
) {
15+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.back.domain.study.todo.dto;
2+
3+
import com.back.domain.study.todo.entity.Todo;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
import java.time.LocalDate;
7+
8+
public record TodoResponseDto(
9+
Long id,
10+
String description,
11+
boolean isComplete,
12+
LocalDate date
13+
) {
14+
// entity -> DTO
15+
public static TodoResponseDto from(Todo todo) {
16+
return new TodoResponseDto(
17+
todo.getId(),
18+
todo.getDescription(),
19+
todo.isComplete(),
20+
todo.getDate()
21+
);
22+
}
23+
}

src/main/java/com/back/domain/study/todo/entity/Todo.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
@Getter
1616
@NoArgsConstructor
1717
public class Todo extends BaseEntity {
18-
@ManyToOne(fetch = FetchType.LAZY)
18+
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
1919
@JoinColumn(name = "user_id")
2020
private User user;
2121

@@ -25,4 +25,11 @@ public class Todo extends BaseEntity {
2525

2626
private LocalDate date;
2727

28+
public Todo(User user, String description, LocalDate date) {
29+
this.user = user;
30+
this.description = description;
31+
this.date = date;
32+
this.isComplete = false;
33+
}
34+
2835
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,43 @@
11
package com.back.domain.study.todo.service;
22

3+
import com.back.domain.study.todo.dto.TodoRequestDto;
4+
import com.back.domain.study.todo.dto.TodoResponseDto;
5+
import com.back.domain.study.todo.entity.Todo;
6+
import com.back.domain.study.todo.repository.TodoRepository;
7+
import com.back.domain.user.entity.User;
8+
import com.back.domain.user.repository.UserRepository;
9+
import com.back.global.exception.CustomException;
10+
import com.back.global.exception.ErrorCode;
11+
import com.back.global.security.user.CustomUserDetails;
12+
import jakarta.validation.Valid;
13+
import lombok.RequiredArgsConstructor;
314
import org.springframework.stereotype.Service;
15+
import org.springframework.transaction.annotation.Transactional;
416

517
@Service
18+
@RequiredArgsConstructor
19+
@Transactional(readOnly = true)
620
public class TodoService {
21+
private final TodoRepository todoRepository;
22+
private final UserRepository userRepository;
23+
24+
// ==================== 생성 ===================
25+
public TodoResponseDto createTodo(Long userId, TodoRequestDto request) {
26+
User user = userRepository.findById(userId)
27+
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
28+
Todo todo = new Todo(
29+
user,
30+
request.description(),
31+
request.date()
32+
);
33+
34+
Todo savedTodo = todoRepository.save(todo);
35+
return TodoResponseDto.from(savedTodo);
36+
}
37+
38+
// ==================== 조회 ===================
39+
40+
// ==================== 수정 ===================
41+
42+
// ==================== 삭제 ===================
743
}

src/main/java/com/back/global/exception/ErrorCode.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public enum ErrorCode {
4343
PLAN_ORIGINAL_REPEAT_NOT_FOUND(HttpStatus.NOT_FOUND, "PLAN_004", "해당 날짜에 원본 반복 계획을 찾을 수 없습니다."),
4444
INVALID_DATE_FORMAT(HttpStatus.BAD_REQUEST, "PLAN_005", "날짜 형식이 올바르지 않습니다. (YYYY-MM-DD 형식을 사용해주세요)"),
4545
PLAN_INVALID_TIME_RANGE(HttpStatus.BAD_REQUEST, "PLAN_006", "시작 시간은 종료 시간보다 빨라야 합니다."),
46-
PLAN_TIME_CONFLICT(HttpStatus.CONFLICT, "PLAN_007", "이미 존재하는 학습 계획과 시간이 겹칩니다."),
46+
PLAN_TIME_CONFLICT(HttpStatus.CONFLICT, "PLAN_007", "이미 존재하는 학습 계획과 시간이 겹칩니다. 기존 종료 시간과 겹치는 경우는 제외됩니다."),
4747
PLAN_CANNOT_UPDATE(HttpStatus.BAD_REQUEST, "PLAN_008", "수정 스위치 로직 탈출. 어떤 경우인지 파악이 필요합니다."),
4848
REPEAT_INVALID_UNTIL_DATE(HttpStatus.BAD_REQUEST, "REPEAT_001", "반복 계획의 종료 날짜는 시작 날짜 이전일 수 없습니다."),
4949
REPEAT_BYDAY_REQUIRED(HttpStatus.BAD_REQUEST, "REPEAT_002", "주간 반복 계획의 경우 요일(byDay) 정보가 필요합니다."),

0 commit comments

Comments
 (0)