From 9af2fdbe83040179ac4d48b14a9679b5bed8c8cf Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Thu, 12 Feb 2026 15:25:39 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=B8=94=EB=A1=9D=20ap?= =?UTF-8?q?i=20=EA=B5=AC=ED=98=84=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/controller/BlockController.java | 75 ++++++++++++++++ .../com/umc/timeto/block/dto/BlockAddDTO.java | 21 +++++ .../timeto/block/dto/BlockResponseDTO.java | 19 ++++ .../timeto/block/dto/BlockResponseNumDTO.java | 18 ++++ .../com/umc/timeto/block/entity/Block.java | 41 +++++++++ .../block/repository/BlockRepository.java | 12 +++ .../timeto/block/service/BlockService.java | 19 ++++ .../block/service/BlockServiceImpl.java | 88 +++++++++++++++++++ .../global/apiPayload/code/ResponseCode.java | 8 ++ 9 files changed, 301 insertions(+) create mode 100644 src/main/java/com/umc/timeto/block/controller/BlockController.java create mode 100644 src/main/java/com/umc/timeto/block/dto/BlockAddDTO.java create mode 100644 src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java create mode 100644 src/main/java/com/umc/timeto/block/dto/BlockResponseNumDTO.java create mode 100644 src/main/java/com/umc/timeto/block/entity/Block.java create mode 100644 src/main/java/com/umc/timeto/block/repository/BlockRepository.java create mode 100644 src/main/java/com/umc/timeto/block/service/BlockService.java create mode 100644 src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java diff --git a/src/main/java/com/umc/timeto/block/controller/BlockController.java b/src/main/java/com/umc/timeto/block/controller/BlockController.java new file mode 100644 index 0000000..c58660b --- /dev/null +++ b/src/main/java/com/umc/timeto/block/controller/BlockController.java @@ -0,0 +1,75 @@ +package com.umc.timeto.block.controller; + +import com.umc.timeto.block.dto.BlockAddDTO; +import com.umc.timeto.block.dto.BlockResponseNumDTO; +import com.umc.timeto.block.service.BlockService; +import com.umc.timeto.global.apiPayload.code.ResponseCode; +import com.umc.timeto.global.apiPayload.dto.ResponseDTO; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/block") +public class BlockController { + + private final BlockService blockService; + + private Long getMemberId(Authentication authentication) { + return (Long) authentication.getPrincipal(); + } + + @Operation(summary = "타임블럭 저장", description = "할 일 시작 시간을 받아서 블록을 저장합니다") + @PatchMapping("/{todoId}") + public ResponseEntity createBlock( + @PathVariable Long todoId, + @RequestBody BlockAddDTO req, + Authentication authentication + ) { + var res= blockService.createBlock(todoId, req, getMemberId(authentication)); + + return ResponseEntity + .status(ResponseCode.SUCCESS_ADD_BLOCK.getStatus().value()) + .body(new ResponseDTO<>(ResponseCode.SUCCESS_ADD_BLOCK, res)); + } + + @Operation(summary = "날짜별 타임블럭 조회", description = "입력받은 날짜(YYYY-MM-DD)에 생성된 타임 블럭들을 조회합니다") + @GetMapping("/day") + public ResponseEntity getBlockByDay( + // 기본 format: yyyy-MM-DD + @RequestParam LocalDate date, + Authentication authentication + ) { + var res= blockService.getBlockByDay(date, getMemberId(authentication)); + + return ResponseEntity + .status(ResponseCode.SUCCESS_GET_BLOCKLIST.getStatus().value()) + .body(new ResponseDTO<>(ResponseCode.SUCCESS_GET_BLOCKLIST, res)); + } + + @Operation(summary = "한달 날짜별 타임블럭 수", + description = "입력받은 날짜(YYYY-MM)에 생성된 타임 블럭 수를 조회합니다") + @GetMapping("/month") + public ResponseEntity>> getBlockNumByMonth( + @RequestParam + @DateTimeFormat(pattern = "yyyy-MM") + YearMonth yearMonth, + Authentication authentication + ) { + + var res = blockService.getBlockNumByMonth(yearMonth, getMemberId(authentication)); + + return ResponseEntity + .status(ResponseCode.SUCCESS_GET_BLOCK_NUMBER.getStatus().value()) + .body(new ResponseDTO<>(ResponseCode.SUCCESS_GET_BLOCK_NUMBER, res)); + } + +} diff --git a/src/main/java/com/umc/timeto/block/dto/BlockAddDTO.java b/src/main/java/com/umc/timeto/block/dto/BlockAddDTO.java new file mode 100644 index 0000000..ee90040 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/dto/BlockAddDTO.java @@ -0,0 +1,21 @@ +package com.umc.timeto.block.dto; + + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BlockAddDTO { + @NotBlank(message = "startAt은 필수 입력 값입니다.") + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime startAt; +} diff --git a/src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java b/src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java new file mode 100644 index 0000000..1a1cac7 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java @@ -0,0 +1,19 @@ +package com.umc.timeto.block.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BlockResponseDTO { + private Long blockId; + private Long todoId; + private LocalDateTime startAt; + private LocalDateTime endAt; +} diff --git a/src/main/java/com/umc/timeto/block/dto/BlockResponseNumDTO.java b/src/main/java/com/umc/timeto/block/dto/BlockResponseNumDTO.java new file mode 100644 index 0000000..85e6384 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/dto/BlockResponseNumDTO.java @@ -0,0 +1,18 @@ +package com.umc.timeto.block.dto; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BlockResponseNumDTO { + private LocalDate date; + private Long count; +} diff --git a/src/main/java/com/umc/timeto/block/entity/Block.java b/src/main/java/com/umc/timeto/block/entity/Block.java new file mode 100644 index 0000000..754cd03 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/entity/Block.java @@ -0,0 +1,41 @@ +package com.umc.timeto.block.entity; + +import com.umc.timeto.todo.domain.Todo; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.time.LocalTime; + +@Entity +@Getter +@NoArgsConstructor +public class Block { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long blockId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "todo_id") + private Todo todo; + + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime startAt; + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime endAt; + // block에서는 startAt, endAt 입력받고 todoo 에 startAt 저장 및 duration update + //Duration: localtime + + public Block(Todo todo, LocalDateTime startAt) { + this.todo=todo; + this.startAt=startAt; + LocalTime duration = todo.getDuration(); + this.endAt = startAt + .plusHours(duration.getHour()) + .plusMinutes(duration.getMinute()) + .plusSeconds(duration.getSecond()); + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/timeto/block/repository/BlockRepository.java b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java new file mode 100644 index 0000000..50dab3c --- /dev/null +++ b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java @@ -0,0 +1,12 @@ +package com.umc.timeto.block.repository; + +import com.umc.timeto.block.entity.Block; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.time.LocalDateTime; +import java.util.List; + +public interface BlockRepository extends JpaRepository { + List findByStartAtBetween(LocalDateTime start, LocalDateTime end); + List findByStartAtGreaterThanEqualAndStartAtLessThan(LocalDateTime start, LocalDateTime nextMonthStart); +} diff --git a/src/main/java/com/umc/timeto/block/service/BlockService.java b/src/main/java/com/umc/timeto/block/service/BlockService.java new file mode 100644 index 0000000..fd2b8e2 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/service/BlockService.java @@ -0,0 +1,19 @@ +package com.umc.timeto.block.service; + +import com.umc.timeto.block.dto.BlockAddDTO; +import com.umc.timeto.block.dto.BlockResponseDTO; +import com.umc.timeto.block.dto.BlockResponseNumDTO; + +import java.time.LocalDate; +import java.time.YearMonth; +import java.util.List; + +public interface BlockService { + BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId); + + + List getBlockByDay(LocalDate date, Long memberId); + + List getBlockNumByMonth(YearMonth yearMonth, Long memberId); + +} diff --git a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java new file mode 100644 index 0000000..43552e0 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java @@ -0,0 +1,88 @@ +package com.umc.timeto.block.service; + +import com.umc.timeto.block.dto.BlockAddDTO; +import com.umc.timeto.block.dto.BlockResponseDTO; +import com.umc.timeto.block.dto.BlockResponseNumDTO; +import com.umc.timeto.block.entity.Block; +import com.umc.timeto.block.repository.BlockRepository; +import com.umc.timeto.global.apiPayload.code.ErrorCode; +import com.umc.timeto.global.apiPayload.exception.GlobalException; +import com.umc.timeto.todo.domain.Todo; +import com.umc.timeto.todo.repository.TodoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class BlockServiceImpl implements BlockService { + + private final TodoRepository todoRepository; + private final BlockRepository blockRepository; + + @Override + public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) { + + Todo todo = todoRepository.findById(todoId) + .orElseThrow(() -> new GlobalException(ErrorCode.TODO_NOT_FOUND)); + + Block block = new Block(todo, req.getStartAt()); + Block savedBlock = blockRepository.save(block); + + return BlockResponseDTO.builder() + .blockId(savedBlock.getBlockId()) + .todoId(todoId) + .startAt(savedBlock.getStartAt()) + .endAt(savedBlock.getEndAt()) + .build(); + } + + @Override + public List getBlockByDay(LocalDate date, Long memberId) { + + LocalDateTime start = date.atStartOfDay(); + LocalDateTime end = date.atTime(23, 59, 59); + + List blocks = blockRepository.findByStartAtBetween(start, end); + + return blocks.stream() + .map(block -> BlockResponseDTO.builder() + .blockId(block.getBlockId()) + .todoId(block.getTodo().getTodoId()) + .startAt(block.getStartAt()) + .endAt(block.getEndAt()) + .build()) + .collect(Collectors.toList()); + } + + @Override + public List getBlockNumByMonth(YearMonth yearMonth, Long memberId) { + + LocalDateTime start = yearMonth.atDay(1).atStartOfDay(); + LocalDateTime nextMonthStart = yearMonth.plusMonths(1) + .atDay(1) + .atStartOfDay(); + + List blocks = + blockRepository.findByStartAtGreaterThanEqualAndStartAtLessThan(start, nextMonthStart); + + return blocks.stream() + .collect(Collectors.groupingBy( + block -> block.getStartAt().toLocalDate(), + Collectors.counting() + )) + .entrySet() + .stream() + .map(entry -> new BlockResponseNumDTO( + entry.getKey(), + entry.getValue() + )) + .toList(); + } + +} diff --git a/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java b/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java index 2e1f0fb..5578c91 100644 --- a/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java +++ b/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java @@ -24,6 +24,14 @@ public enum ResponseCode { SUCCESS_UPDATE_FOLDER(HttpStatus.OK, "폴더를 성공적으로 수정했습니다."), SUCCESS_DELETE_FOLDER(HttpStatus.OK, "폴더를 성공적으로 삭제했습니다."), + + /** + * block + */ + SUCCESS_ADD_BLOCK(HttpStatus.CREATED, "블록을 성공적으로 등록했습니다."), + SUCCESS_GET_BLOCKLIST(HttpStatus.OK, "블록 리스트를 성공적으로 불러왔습니다."), + SUCCESS_GET_BLOCK_NUMBER(HttpStatus.OK, "블록 수를 성공적으로 불러왔습니다."), + // Common COMMON200(HttpStatus.OK, "요청에 성공하였습니다."), COMMON201(HttpStatus.CREATED, "회원 가입 및 로그인 성공"), From 78febd6dbd6f56ce4936131754c32d68fa19d091 Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Thu, 12 Feb 2026 16:56:51 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EA=B2=B9=EC=B9=A8=20=EC=98=A4=EB=A5=98=EC=B2=98=EB=A6=AC,=20to?= =?UTF-8?q?do=20duration=20=EC=88=98=EC=A0=95=20=EC=8B=9C=20block=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95=ED=95=98=EB=8F=84=EB=A1=9D,=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20api=20=EA=B5=AC=ED=98=84(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/controller/BlockController.java | 44 +++++ .../com/umc/timeto/block/entity/Block.java | 8 +- .../block/repository/BlockRepository.java | 32 +++- .../timeto/block/service/BlockService.java | 13 ++ .../block/service/BlockServiceImpl.java | 178 +++++++++++++++++- .../global/apiPayload/code/ErrorCode.java | 3 + .../global/apiPayload/code/ResponseCode.java | 1 + .../java/com/umc/timeto/todo/domain/Todo.java | 14 ++ .../todo/service/TodoCommandServiceImpl.java | 9 +- 9 files changed, 295 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/umc/timeto/block/controller/BlockController.java b/src/main/java/com/umc/timeto/block/controller/BlockController.java index c58660b..d5c38a2 100644 --- a/src/main/java/com/umc/timeto/block/controller/BlockController.java +++ b/src/main/java/com/umc/timeto/block/controller/BlockController.java @@ -13,6 +13,8 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.YearMonth; import java.util.List; @@ -72,4 +74,46 @@ public ResponseEntity>> getBlockNumByMonth .body(new ResponseDTO<>(ResponseCode.SUCCESS_GET_BLOCK_NUMBER, res)); } + @PatchMapping("/{blockId}/duration") + public ResponseEntity> updateDuration( + @PathVariable Long blockId, + @RequestParam LocalTime duration, + Authentication authentication + ) { + + blockService.updateBlockDuration( + blockId, + getMemberId(authentication), + duration + ); + + return ResponseEntity.ok( + new ResponseDTO<>(ResponseCode.SUCCESS_UPDATE_BLOCK, null) + ); + } + + + @PatchMapping("/{blockId}/move") + public ResponseEntity> moveBlock( + @PathVariable Long blockId, + @RequestParam + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + LocalDateTime startAt, + Authentication authentication + ) { + + blockService.moveBlock( + blockId, + getMemberId(authentication), + startAt + ); + + return ResponseEntity.ok( + new ResponseDTO<>(ResponseCode.SUCCESS_UPDATE_BLOCK, null) + ); + } + + + + } diff --git a/src/main/java/com/umc/timeto/block/entity/Block.java b/src/main/java/com/umc/timeto/block/entity/Block.java index 754cd03..08eb4dc 100644 --- a/src/main/java/com/umc/timeto/block/entity/Block.java +++ b/src/main/java/com/umc/timeto/block/entity/Block.java @@ -18,7 +18,7 @@ public class Block { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long blockId; - @ManyToOne(fetch = FetchType.LAZY) + @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "todo_id") private Todo todo; @@ -38,4 +38,10 @@ public Block(Todo todo, LocalDateTime startAt) { .plusMinutes(duration.getMinute()) .plusSeconds(duration.getSecond()); } + + public void updateTime(LocalDateTime startAt, LocalDateTime endAt) { + this.startAt = startAt; + this.endAt = endAt; + } + } \ No newline at end of file diff --git a/src/main/java/com/umc/timeto/block/repository/BlockRepository.java b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java index 50dab3c..3981072 100644 --- a/src/main/java/com/umc/timeto/block/repository/BlockRepository.java +++ b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java @@ -5,8 +5,36 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; public interface BlockRepository extends JpaRepository { - List findByStartAtBetween(LocalDateTime start, LocalDateTime end); - List findByStartAtGreaterThanEqualAndStartAtLessThan(LocalDateTime start, LocalDateTime nextMonthStart); + Optional findByTodo_TodoId(Long todoId); + + + List findByTodo_Folder_Member_MemberIdAndStartAtBetween( + Long memberId, + LocalDateTime start, + LocalDateTime end + ); + List findByTodo_Folder_Member_MemberIdAndStartAtGreaterThanEqualAndStartAtLessThan( + Long memberId, + LocalDateTime start, + LocalDateTime end + ); + + + List findByTodo_Folder_Member_MemberIdAndStartAtLessThanAndEndAtGreaterThan( + Long memberId, + LocalDateTime endAt, + LocalDateTime startAt + ); + + List findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + Long memberId, + Long blockId, + LocalDateTime endAt, + LocalDateTime startAt + ); + + Optional findByBlockIdAndTodo_Folder_Member_MemberId(Long blockId, Long memberId); } diff --git a/src/main/java/com/umc/timeto/block/service/BlockService.java b/src/main/java/com/umc/timeto/block/service/BlockService.java index fd2b8e2..1692dc8 100644 --- a/src/main/java/com/umc/timeto/block/service/BlockService.java +++ b/src/main/java/com/umc/timeto/block/service/BlockService.java @@ -5,6 +5,8 @@ import com.umc.timeto.block.dto.BlockResponseNumDTO; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.YearMonth; import java.util.List; @@ -16,4 +18,15 @@ public interface BlockService { List getBlockNumByMonth(YearMonth yearMonth, Long memberId); + BlockResponseDTO updateBlockDuration(Long blockId, Long memberId, LocalTime newDuration); + void updateBlockDurationByTodo(Long todoId, Long memberId, LocalTime newDuration); + + + BlockResponseDTO moveBlock(Long blockId, Long memberId, LocalDateTime newStart); + + + + + + } diff --git a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java index 43552e0..c67f1dc 100644 --- a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java +++ b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java @@ -11,15 +11,19 @@ import com.umc.timeto.todo.repository.TodoRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.YearMonth; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Service @RequiredArgsConstructor +@Transactional public class BlockServiceImpl implements BlockService { private final TodoRepository todoRepository; @@ -34,6 +38,27 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) Block block = new Block(todo, req.getStartAt()); Block savedBlock = blockRepository.save(block); + LocalDateTime startAt = req.getStartAt(); + LocalTime duration = todo.getDuration(); + + LocalDateTime endAt = startAt + .plusHours(duration.getHour()) + .plusMinutes(duration.getMinute()) + .plusSeconds(duration.getSecond()); + + List overlaps = + blockRepository + .findByTodo_Folder_Member_MemberIdAndStartAtLessThanAndEndAtGreaterThan( + memberId, + endAt, + startAt + ); + + if (!overlaps.isEmpty()) { + throw new GlobalException(ErrorCode.BLOCK_TIME_CONFLICT); + } + + return BlockResponseDTO.builder() .blockId(savedBlock.getBlockId()) .todoId(todoId) @@ -48,7 +73,13 @@ public List getBlockByDay(LocalDate date, Long memberId) { LocalDateTime start = date.atStartOfDay(); LocalDateTime end = date.atTime(23, 59, 59); - List blocks = blockRepository.findByStartAtBetween(start, end); + List blocks = + blockRepository + .findByTodo_Folder_Member_MemberIdAndStartAtBetween( + memberId, + start, + end + ); return blocks.stream() .map(block -> BlockResponseDTO.builder() @@ -69,7 +100,11 @@ public List getBlockNumByMonth(YearMonth yearMonth, Long me .atStartOfDay(); List blocks = - blockRepository.findByStartAtGreaterThanEqualAndStartAtLessThan(start, nextMonthStart); + blockRepository.findByTodo_Folder_Member_MemberIdAndStartAtGreaterThanEqualAndStartAtLessThan( + memberId, + start, + nextMonthStart + ); return blocks.stream() .collect(Collectors.groupingBy( @@ -85,4 +120,143 @@ public List getBlockNumByMonth(YearMonth yearMonth, Long me .toList(); } + @Override + public BlockResponseDTO updateBlockDuration(Long blockId, Long memberId, LocalTime newDuration) { + + Block block = blockRepository + .findByBlockIdAndTodo_Folder_Member_MemberId(blockId, memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.BLOCK_NOT_FOUND)); + + LocalDateTime newStart = block.getStartAt(); + LocalDateTime newEnd = newStart + .plusHours(newDuration.getHour()) + .plusMinutes(newDuration.getMinute()) + .plusSeconds(newDuration.getSecond()); + + List overlaps = + blockRepository + .findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + memberId, + blockId, + newEnd, + newStart + ); + + if (!overlaps.isEmpty()) { + throw new GlobalException(ErrorCode.BLOCK_TIME_CONFLICT); + } + + block.updateTime(newStart, newEnd); + + // Todo 동기화 + Todo todo = block.getTodo(); + todo.changeDuration(newDuration); + todo.updateStartAt(newStart); + + + return BlockResponseDTO.builder() + .blockId(block.getBlockId()) + .todoId(todo.getTodoId()) + .startAt(block.getStartAt()) + .endAt(block.getEndAt()) + .build(); + } + + + @Override + public void updateBlockDurationByTodo(Long todoId, + Long memberId, + LocalTime newDuration) { + + Todo todo = todoRepository + .findByTodoIdAndFolder_Goal_Member_MemberId(todoId, memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.TODO_NOT_FOUND)); + + Optional optionalBlock = + blockRepository.findByTodo_TodoId(todoId); + + // Block이 존재하는 경우 + if (optionalBlock.isPresent()) { + + Block block = optionalBlock.get(); + + LocalDateTime newStart = block.getStartAt(); + LocalDateTime newEnd = newStart + .plusHours(newDuration.getHour()) + .plusMinutes(newDuration.getMinute()) + .plusSeconds(newDuration.getSecond()); + + // 충돌 검사 + List overlaps = + blockRepository + .findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + memberId, + block.getBlockId(), + newEnd, + newStart + ); + + if (!overlaps.isEmpty()) { + throw new GlobalException(ErrorCode.BLOCK_TIME_CONFLICT); + } + + block.updateTime(newStart, newEnd); + todo.changeDuration(newDuration); + todo.updateStartAt(newStart); + + } + // Block이 없는 경우 + else { + todo.changeDuration(newDuration); + } + } + + + + + @Override + public BlockResponseDTO moveBlock(Long blockId, Long memberId, LocalDateTime newStart) { + + Block block = blockRepository + .findByBlockIdAndTodo_Folder_Member_MemberId(blockId, memberId) + .orElseThrow(() -> new GlobalException(ErrorCode.BLOCK_NOT_FOUND)); + + LocalTime duration = block.getTodo().getDuration(); + + LocalDateTime newEnd = newStart + .plusHours(duration.getHour()) + .plusMinutes(duration.getMinute()) + .plusSeconds(duration.getSecond()); + + List overlaps = + blockRepository + .findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + memberId, + blockId, + newEnd, + newStart + ); + + if (!overlaps.isEmpty()) { + throw new GlobalException(ErrorCode.BLOCK_TIME_CONFLICT); + } + + block.updateTime(newStart, newEnd); + + // Todo 동기화 + Todo todo = block.getTodo(); + todo.updateStartAt(newStart); + long seconds = java.time.Duration.between(newStart, newEnd).getSeconds(); + LocalTime newDuration = LocalTime.ofSecondOfDay(seconds); + todo.changeDuration(newDuration); + + return BlockResponseDTO.builder() + .blockId(block.getBlockId()) + .todoId(todo.getTodoId()) + .startAt(block.getStartAt()) + .endAt(block.getEndAt()) + .build(); + + } + } diff --git a/src/main/java/com/umc/timeto/global/apiPayload/code/ErrorCode.java b/src/main/java/com/umc/timeto/global/apiPayload/code/ErrorCode.java index 8bcec47..d3fabf5 100644 --- a/src/main/java/com/umc/timeto/global/apiPayload/code/ErrorCode.java +++ b/src/main/java/com/umc/timeto/global/apiPayload/code/ErrorCode.java @@ -12,6 +12,8 @@ public enum ErrorCode { * 400 BAD_REQUEST - 잘못된 요청 */ BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), + BLOCK_TIME_CONFLICT(HttpStatus.BAD_REQUEST, "이미 해당 시간에 블록이 존재합니다."), + /** * 401 UNAUTHORIZED - 인증 실패 @@ -30,6 +32,7 @@ public enum ErrorCode { GOAL_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 아이디를 가진 목표가 존재하지 않습니다."), FOLDER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 아이디를 가진 폴더가 존재하지 않습니다."), TODO_NOT_FOUND(HttpStatus.NOT_FOUND,"해당 아이디를 가진 할 일이 존재하지 않습니다."), + BLOCK_NOT_FOUND(HttpStatus.NOT_FOUND,"해당 아이디를 가진 블록이 존재하지 않습니다."), /** diff --git a/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java b/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java index 5578c91..4ef57da 100644 --- a/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java +++ b/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java @@ -31,6 +31,7 @@ public enum ResponseCode { SUCCESS_ADD_BLOCK(HttpStatus.CREATED, "블록을 성공적으로 등록했습니다."), SUCCESS_GET_BLOCKLIST(HttpStatus.OK, "블록 리스트를 성공적으로 불러왔습니다."), SUCCESS_GET_BLOCK_NUMBER(HttpStatus.OK, "블록 수를 성공적으로 불러왔습니다."), + SUCCESS_UPDATE_BLOCK(HttpStatus.OK, "블록을 성공적으로 업데이트했습니다"), // Common COMMON200(HttpStatus.OK, "요청에 성공하였습니다."), diff --git a/src/main/java/com/umc/timeto/todo/domain/Todo.java b/src/main/java/com/umc/timeto/todo/domain/Todo.java index 36665d0..0070d70 100644 --- a/src/main/java/com/umc/timeto/todo/domain/Todo.java +++ b/src/main/java/com/umc/timeto/todo/domain/Todo.java @@ -1,6 +1,7 @@ package com.umc.timeto.todo.domain; +import com.umc.timeto.block.entity.Block; import com.umc.timeto.folder.entity.Folder; import com.umc.timeto.todo.domain.enums.TodoPriority; import com.umc.timeto.todo.domain.enums.TodoState; @@ -49,6 +50,17 @@ public class Todo { private Folder folder; + //todo 삭제 시 연결된 block 자동 삭제 + @OneToOne( + mappedBy = "todo", + cascade = CascadeType.ALL, + orphanRemoval = true, + fetch = FetchType.LAZY + ) + private Block block; + + + public static Todo create(Folder folder, String name, TodoPriority priority, LocalTime duration) { Todo todo = new Todo(); todo.folder = folder; @@ -68,4 +80,6 @@ public void changeState(TodoState state) { public void changePriority(TodoPriority priority) { this.priority = priority; } public void changeDuration(LocalTime duration) { this.duration = duration; } + public void updateStartAt(LocalDateTime startAt) { this.startAt = startAt;} + } diff --git a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java index 0e4b223..adce381 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java @@ -1,5 +1,8 @@ package com.umc.timeto.todo.service; +import com.umc.timeto.block.entity.Block; +import com.umc.timeto.block.repository.BlockRepository; +import com.umc.timeto.block.service.BlockService; import com.umc.timeto.folder.repository.FolderRepository; import com.umc.timeto.global.apiPayload.code.ErrorCode; import com.umc.timeto.global.apiPayload.exception.GlobalException; @@ -16,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalTime; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -23,6 +27,7 @@ public class TodoCommandServiceImpl implements TodoCommandService{ private final TodoRepository todoRepository; private final FolderRepository folderRepository; + private final BlockService blockService; @Override public TodoStatusUpdateResponse updateStatus(Long memberId,Long todoId, TodoStatusUpdateRequest request) { @@ -51,10 +56,10 @@ public TodoGetResponse updateTodo(Long memberId, Long todoId, TodoUpdateRequest if (request.getPriority() != null) { todo.changePriority(request.getPriority()); } - + // duration 변경 시 Block이 처리 if (request.getDuration() != null && !request.getDuration().isBlank()) { LocalTime parsed = DurationParser.parseToLocalTime(request.getDuration()); - todo.changeDuration(parsed); + blockService.updateBlockDurationByTodo(todoId, memberId, parsed); } return new TodoGetResponse( From 77ebf8114d186f78acfbaf50e976e4008eafbb18 Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Thu, 12 Feb 2026 17:02:36 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9C=8F=EF=B8=8FTyping=20Error=20:=20?= =?UTF-8?q?=EC=98=A4=ED=83=80/=EA=B2=BD=EA=B3=A0=20=EB=93=B1=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timeto/block/repository/BlockRepository.java | 10 +++++----- .../timeto/block/service/BlockServiceImpl.java | 16 ++++++++-------- .../java/com/umc/timeto/todo/domain/Todo.java | 3 +-- .../todo/service/TodoCommandServiceImpl.java | 6 ------ 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/umc/timeto/block/repository/BlockRepository.java b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java index 3981072..b5e3fa9 100644 --- a/src/main/java/com/umc/timeto/block/repository/BlockRepository.java +++ b/src/main/java/com/umc/timeto/block/repository/BlockRepository.java @@ -11,30 +11,30 @@ public interface BlockRepository extends JpaRepository { Optional findByTodo_TodoId(Long todoId); - List findByTodo_Folder_Member_MemberIdAndStartAtBetween( + List findByTodo_Folder_Goal_Member_MemberIdAndStartAtBetween( Long memberId, LocalDateTime start, LocalDateTime end ); - List findByTodo_Folder_Member_MemberIdAndStartAtGreaterThanEqualAndStartAtLessThan( + List findByTodo_Folder_Goal_Member_MemberIdAndStartAtGreaterThanEqualAndStartAtLessThan( Long memberId, LocalDateTime start, LocalDateTime end ); - List findByTodo_Folder_Member_MemberIdAndStartAtLessThanAndEndAtGreaterThan( + List findByTodo_Folder_Goal_Member_MemberIdAndStartAtLessThanAndEndAtGreaterThan( Long memberId, LocalDateTime endAt, LocalDateTime startAt ); - List findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + List findByTodo_Folder_Goal_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( Long memberId, Long blockId, LocalDateTime endAt, LocalDateTime startAt ); - Optional findByBlockIdAndTodo_Folder_Member_MemberId(Long blockId, Long memberId); + Optional findByBlockIdAndTodo_Folder_Goal_Member_MemberId(Long blockId, Long memberId); } diff --git a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java index c67f1dc..39ea0fb 100644 --- a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java +++ b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java @@ -48,7 +48,7 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) List overlaps = blockRepository - .findByTodo_Folder_Member_MemberIdAndStartAtLessThanAndEndAtGreaterThan( + .findByTodo_Folder_Goal_Member_MemberIdAndStartAtLessThanAndEndAtGreaterThan( memberId, endAt, startAt @@ -75,7 +75,7 @@ public List getBlockByDay(LocalDate date, Long memberId) { List blocks = blockRepository - .findByTodo_Folder_Member_MemberIdAndStartAtBetween( + .findByTodo_Folder_Goal_Member_MemberIdAndStartAtBetween( memberId, start, end @@ -100,7 +100,7 @@ public List getBlockNumByMonth(YearMonth yearMonth, Long me .atStartOfDay(); List blocks = - blockRepository.findByTodo_Folder_Member_MemberIdAndStartAtGreaterThanEqualAndStartAtLessThan( + blockRepository.findByTodo_Folder_Goal_Member_MemberIdAndStartAtGreaterThanEqualAndStartAtLessThan( memberId, start, nextMonthStart @@ -124,7 +124,7 @@ public List getBlockNumByMonth(YearMonth yearMonth, Long me public BlockResponseDTO updateBlockDuration(Long blockId, Long memberId, LocalTime newDuration) { Block block = blockRepository - .findByBlockIdAndTodo_Folder_Member_MemberId(blockId, memberId) + .findByBlockIdAndTodo_Folder_Goal_Member_MemberId(blockId, memberId) .orElseThrow(() -> new GlobalException(ErrorCode.BLOCK_NOT_FOUND)); LocalDateTime newStart = block.getStartAt(); @@ -135,7 +135,7 @@ public BlockResponseDTO updateBlockDuration(Long blockId, Long memberId, LocalTi List overlaps = blockRepository - .findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + .findByTodo_Folder_Goal_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( memberId, blockId, newEnd, @@ -189,7 +189,7 @@ public void updateBlockDurationByTodo(Long todoId, // 충돌 검사 List overlaps = blockRepository - .findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + .findByTodo_Folder_Goal_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( memberId, block.getBlockId(), newEnd, @@ -218,7 +218,7 @@ public void updateBlockDurationByTodo(Long todoId, public BlockResponseDTO moveBlock(Long blockId, Long memberId, LocalDateTime newStart) { Block block = blockRepository - .findByBlockIdAndTodo_Folder_Member_MemberId(blockId, memberId) + .findByBlockIdAndTodo_Folder_Goal_Member_MemberId(blockId, memberId) .orElseThrow(() -> new GlobalException(ErrorCode.BLOCK_NOT_FOUND)); LocalTime duration = block.getTodo().getDuration(); @@ -230,7 +230,7 @@ public BlockResponseDTO moveBlock(Long blockId, Long memberId, LocalDateTime new List overlaps = blockRepository - .findByTodo_Folder_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( + .findByTodo_Folder_Goal_Member_MemberIdAndBlockIdNotAndStartAtLessThanAndEndAtGreaterThan( memberId, blockId, newEnd, diff --git a/src/main/java/com/umc/timeto/todo/domain/Todo.java b/src/main/java/com/umc/timeto/todo/domain/Todo.java index 0070d70..df2ca19 100644 --- a/src/main/java/com/umc/timeto/todo/domain/Todo.java +++ b/src/main/java/com/umc/timeto/todo/domain/Todo.java @@ -54,8 +54,7 @@ public class Todo { @OneToOne( mappedBy = "todo", cascade = CascadeType.ALL, - orphanRemoval = true, - fetch = FetchType.LAZY + orphanRemoval = true ) private Block block; diff --git a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java index adce381..c7a1f07 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java @@ -1,9 +1,5 @@ package com.umc.timeto.todo.service; - -import com.umc.timeto.block.entity.Block; -import com.umc.timeto.block.repository.BlockRepository; import com.umc.timeto.block.service.BlockService; -import com.umc.timeto.folder.repository.FolderRepository; import com.umc.timeto.global.apiPayload.code.ErrorCode; import com.umc.timeto.global.apiPayload.exception.GlobalException; import com.umc.timeto.todo.domain.Todo; @@ -19,14 +15,12 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalTime; -import java.util.Optional; @Service @RequiredArgsConstructor @Transactional public class TodoCommandServiceImpl implements TodoCommandService{ private final TodoRepository todoRepository; - private final FolderRepository folderRepository; private final BlockService blockService; @Override From 2d1f03ad5dcf6ce146fac3e9154ba6e77adfc72a Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Thu, 12 Feb 2026 20:39:07 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=B8=94=EB=A1=9D?= =?UTF-8?q?=EC=9D=B4=20=EB=93=B1=EB=A1=9D=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=ED=95=A0=EC=9D=BC=EB=A7=8C=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20api=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/apiPayload/code/ResponseCode.java | 2 ++ .../todo/controller/TodoController.java | 25 +++++++++++++++++++ .../todo/repository/TodoRepository.java | 3 +++ .../todo/service/TodoCommandService.java | 6 +++++ .../todo/service/TodoCommandServiceImpl.java | 22 ++++++++++++++++ 5 files changed, 58 insertions(+) diff --git a/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java b/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java index 4ef57da..d0e9495 100644 --- a/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java +++ b/src/main/java/com/umc/timeto/global/apiPayload/code/ResponseCode.java @@ -32,6 +32,8 @@ public enum ResponseCode { SUCCESS_GET_BLOCKLIST(HttpStatus.OK, "블록 리스트를 성공적으로 불러왔습니다."), SUCCESS_GET_BLOCK_NUMBER(HttpStatus.OK, "블록 수를 성공적으로 불러왔습니다."), SUCCESS_UPDATE_BLOCK(HttpStatus.OK, "블록을 성공적으로 업데이트했습니다"), + // 블록 생성 시 할일 조회 + SUCCESS_GET_UNBLOCKED_TODOS(HttpStatus.OK, "블록을 생성할 할 일을 성공적으로 불러왔습니다."), // Common COMMON200(HttpStatus.OK, "요청에 성공하였습니다."), diff --git a/src/main/java/com/umc/timeto/todo/controller/TodoController.java b/src/main/java/com/umc/timeto/todo/controller/TodoController.java index 6b5f522..3ac8740 100644 --- a/src/main/java/com/umc/timeto/todo/controller/TodoController.java +++ b/src/main/java/com/umc/timeto/todo/controller/TodoController.java @@ -15,8 +15,12 @@ import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; +import org.springframework.security.core.Authentication; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -28,6 +32,10 @@ public class TodoController { private final TodoQueryService todoQueryService; + private Long getMemberId(Authentication authentication) { + return (Long) authentication.getPrincipal(); + } + @Operation(summary = "할 일 정보 조회", description = "todoId로 할 일 상세 정보를 조회합니다.") @GetMapping("/{todoId}") public ResponseDTO getTodo(@PathVariable Long todoId) { @@ -109,4 +117,21 @@ public ResponseDTO deleteTodo(@PathVariable Long todoId) { todoCommandService.deleteTodo(memberId, todoId); return new ResponseDTO<>(ResponseCode.COMMON200, "성공"); } + + @Operation(summary = "블록 생성 후보 할일 조회", description = "블록이 등록되지 않은 할 일을 조회합니다") + @GetMapping("/{folderId}/todo/unblocked") + public ResponseEntity>> getUnblockedTodos( + Authentication authentication + ) { + + Long memberId = getMemberId(authentication); + + return ResponseEntity.ok( + new ResponseDTO<>( + ResponseCode.SUCCESS_GET_UNBLOCKED_TODOS, + todoCommandService.getUnblockedTodos(memberId) + ) + ); + } + } diff --git a/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java b/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java index 76fb326..0d2084c 100644 --- a/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java +++ b/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java @@ -20,4 +20,7 @@ List findAllByFolder_FolderIdAndFolder_Goal_Member_MemberIdAndState( void deleteByTodoIdAndFolder_Goal_Member_MemberId(Long todoId, Long memberId); boolean existsByTodoIdAndFolder_Goal_Member_MemberId(Long todoId, Long memberId); + + List findByFolder_Goal_Member_MemberIdAndBlockIsNull(Long memberId); + } diff --git a/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java b/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java index 6fff9fd..3e145a2 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java @@ -4,10 +4,16 @@ import com.umc.timeto.todo.dto.request.TodoUpdateRequest; import com.umc.timeto.todo.dto.response.TodoGetResponse; import com.umc.timeto.todo.dto.response.TodoStatusUpdateResponse; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; public interface TodoCommandService { TodoStatusUpdateResponse updateStatus(Long memberId, Long todoId, TodoStatusUpdateRequest request); TodoGetResponse updateTodo(Long memberId, Long todoId, TodoUpdateRequest request); void deleteTodo(Long memberId, Long todoId); + + @Transactional(readOnly = true) + List getUnblockedTodos(Long memberId); } diff --git a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java index c7a1f07..2ab945c 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java @@ -15,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalTime; +import java.util.List; @Service @RequiredArgsConstructor @@ -73,4 +74,25 @@ public void deleteTodo(Long memberId, Long todoId) { } todoRepository.deleteByTodoIdAndFolder_Goal_Member_MemberId(todoId, memberId); } + + + @Transactional(readOnly = true) + @Override + public List getUnblockedTodos(Long memberId) { + + List todos = + todoRepository + .findByFolder_Goal_Member_MemberIdAndBlockIsNull(memberId); + + return todos.stream() + .map(todo -> new TodoGetResponse( + todo.getTodoId(), + todo.getName(), + DurationFormatter.format(todo.getDuration()), + todo.getPriority(), + todo.getState() + )) + .toList(); + } + } From b29e7dc4c8170cc90eecef6e3b182399d54552c1 Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Sat, 14 Feb 2026 12:33:08 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=84=B8=EB=B6=80?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=94=94?= =?UTF-8?q?=EB=B2=84=EA=B9=85=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../block/controller/BlockController.java | 10 ++++-- .../timeto/block/dto/BlockResponseDTO.java | 1 - .../block/dto/BlockResponseDetailDTO.java | 28 ++++++++++++++++ .../com/umc/timeto/block/entity/Block.java | 33 +++++++++++++++---- .../timeto/block/service/BlockService.java | 3 +- .../block/service/BlockServiceImpl.java | 20 ++++++++--- .../todo/controller/TodoController.java | 3 +- .../todo/repository/TodoRepository.java | 7 +++- .../todo/service/TodoCommandService.java | 2 +- .../todo/service/TodoCommandServiceImpl.java | 7 ++-- 10 files changed, 95 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/umc/timeto/block/dto/BlockResponseDetailDTO.java diff --git a/src/main/java/com/umc/timeto/block/controller/BlockController.java b/src/main/java/com/umc/timeto/block/controller/BlockController.java index d5c38a2..3a192a8 100644 --- a/src/main/java/com/umc/timeto/block/controller/BlockController.java +++ b/src/main/java/com/umc/timeto/block/controller/BlockController.java @@ -29,7 +29,8 @@ private Long getMemberId(Authentication authentication) { return (Long) authentication.getPrincipal(); } - @Operation(summary = "타임블럭 저장", description = "할 일 시작 시간을 받아서 블록을 저장합니다") + @Operation(summary = "타임블럭 저장", description = "할 일 시작 시간을 받아서 블록을 저장합니다. 블록에는 일정 겹침 검사가 존재합니다." + + "(ex: 일정 1이 7:30~8:30 일떄, 일정2의 생성/이동은 8:30분 이상부터 가능)") @PatchMapping("/{todoId}") public ResponseEntity createBlock( @PathVariable Long todoId, @@ -43,7 +44,7 @@ public ResponseEntity createBlock( .body(new ResponseDTO<>(ResponseCode.SUCCESS_ADD_BLOCK, res)); } - @Operation(summary = "날짜별 타임블럭 조회", description = "입력받은 날짜(YYYY-MM-DD)에 생성된 타임 블럭들을 조회합니다") + @Operation(summary = "날짜별 타임블럭 조회", description = "입력받은 날짜(yyyy-MM-DD)에 생성된 타임 블럭들을 조회합니다 블록 조회 기본(메인) 화면에 사용합니다. ") @GetMapping("/day") public ResponseEntity getBlockByDay( // 기본 format: yyyy-MM-DD @@ -74,6 +75,8 @@ public ResponseEntity>> getBlockNumByMonth .body(new ResponseDTO<>(ResponseCode.SUCCESS_GET_BLOCK_NUMBER, res)); } + @Operation(summary = "블록 소요시간 변경", + description = "블록 소요시간 변경 시 사용합니다. 할 일 내부에서만 소요시간 변경이 가능하다면 사용하지 않아도 됩니다. ") @PatchMapping("/{blockId}/duration") public ResponseEntity> updateDuration( @PathVariable Long blockId, @@ -93,6 +96,9 @@ public ResponseEntity> updateDuration( } + @Operation(summary = "블록 이동", + description = "블록을 드래그&드롭으로 이동했을 때 정보를 갱신합니다. 변경된 시작 시간을 입력으로 받습니다. " + + "이동된 시간이 다른 일정과 겹칠 경우 갱신되지 않습니다. startAt format: yyyy-MM-dd'T'HH:mm") @PatchMapping("/{blockId}/move") public ResponseEntity> moveBlock( @PathVariable Long blockId, diff --git a/src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java b/src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java index 1a1cac7..e2ed89b 100644 --- a/src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java +++ b/src/main/java/com/umc/timeto/block/dto/BlockResponseDTO.java @@ -1,5 +1,4 @@ package com.umc.timeto.block.dto; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/umc/timeto/block/dto/BlockResponseDetailDTO.java b/src/main/java/com/umc/timeto/block/dto/BlockResponseDetailDTO.java new file mode 100644 index 0000000..0c77669 --- /dev/null +++ b/src/main/java/com/umc/timeto/block/dto/BlockResponseDetailDTO.java @@ -0,0 +1,28 @@ +package com.umc.timeto.block.dto; + +import com.umc.timeto.todo.domain.enums.TodoPriority; +import com.umc.timeto.todo.domain.enums.TodoState; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BlockResponseDetailDTO { + private Long blockId; + private Long todoId; + private LocalDateTime startAt; + private LocalDateTime endAt; + private String todoName; + private TodoPriority priority; + private TodoState state; + private String goalName; + private String color; + + +} diff --git a/src/main/java/com/umc/timeto/block/entity/Block.java b/src/main/java/com/umc/timeto/block/entity/Block.java index 08eb4dc..9c27dc9 100644 --- a/src/main/java/com/umc/timeto/block/entity/Block.java +++ b/src/main/java/com/umc/timeto/block/entity/Block.java @@ -31,17 +31,38 @@ public class Block { public Block(Todo todo, LocalDateTime startAt) { this.todo=todo; - this.startAt=startAt; + + LocalDateTime normalizedStart = normalize(startAt); + this.startAt = normalizedStart; LocalTime duration = todo.getDuration(); - this.endAt = startAt + + LocalDateTime calculatedEnd = normalizedStart .plusHours(duration.getHour()) - .plusMinutes(duration.getMinute()) - .plusSeconds(duration.getSecond()); + .plusMinutes(duration.getMinute()); + + this.endAt = normalize(calculatedEnd); } public void updateTime(LocalDateTime startAt, LocalDateTime endAt) { - this.startAt = startAt; - this.endAt = endAt; + this.startAt = normalize(startAt); + this.endAt = normalize(endAt); + } + + private LocalDateTime normalize(LocalDateTime time) { + return time.withSecond(0).withNano(0); } + @PrePersist + @PreUpdate + private void trimSeconds() { + if (startAt != null) { + startAt = startAt.withSecond(0).withNano(0); + } + if (endAt != null) { + endAt = endAt.withSecond(0).withNano(0); + } + } + + + } \ No newline at end of file diff --git a/src/main/java/com/umc/timeto/block/service/BlockService.java b/src/main/java/com/umc/timeto/block/service/BlockService.java index 1692dc8..3715d0a 100644 --- a/src/main/java/com/umc/timeto/block/service/BlockService.java +++ b/src/main/java/com/umc/timeto/block/service/BlockService.java @@ -2,6 +2,7 @@ import com.umc.timeto.block.dto.BlockAddDTO; import com.umc.timeto.block.dto.BlockResponseDTO; +import com.umc.timeto.block.dto.BlockResponseDetailDTO; import com.umc.timeto.block.dto.BlockResponseNumDTO; import java.time.LocalDate; @@ -14,7 +15,7 @@ public interface BlockService { BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId); - List getBlockByDay(LocalDate date, Long memberId); + List getBlockByDay(LocalDate date, Long memberId); List getBlockNumByMonth(YearMonth yearMonth, Long memberId); diff --git a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java index 39ea0fb..40474f8 100644 --- a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java +++ b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java @@ -2,6 +2,7 @@ import com.umc.timeto.block.dto.BlockAddDTO; import com.umc.timeto.block.dto.BlockResponseDTO; +import com.umc.timeto.block.dto.BlockResponseDetailDTO; import com.umc.timeto.block.dto.BlockResponseNumDTO; import com.umc.timeto.block.entity.Block; import com.umc.timeto.block.repository.BlockRepository; @@ -35,9 +36,8 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) Todo todo = todoRepository.findById(todoId) .orElseThrow(() -> new GlobalException(ErrorCode.TODO_NOT_FOUND)); - Block block = new Block(todo, req.getStartAt()); - Block savedBlock = blockRepository.save(block); + // 블록 겹침 조회 LocalDateTime startAt = req.getStartAt(); LocalTime duration = todo.getDuration(); @@ -45,6 +45,7 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) .plusHours(duration.getHour()) .plusMinutes(duration.getMinute()) .plusSeconds(duration.getSecond()); + System.out.println("시작시간" +startAt + "끝나는시간" +endAt); List overlaps = blockRepository @@ -59,6 +60,11 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) } + //블록 저장 + Block block = new Block(todo, startAt); + Block savedBlock = blockRepository.save(block); + + return BlockResponseDTO.builder() .blockId(savedBlock.getBlockId()) .todoId(todoId) @@ -68,7 +74,7 @@ public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) } @Override - public List getBlockByDay(LocalDate date, Long memberId) { + public List getBlockByDay(LocalDate date, Long memberId) { LocalDateTime start = date.atStartOfDay(); LocalDateTime end = date.atTime(23, 59, 59); @@ -81,12 +87,18 @@ public List getBlockByDay(LocalDate date, Long memberId) { end ); + return blocks.stream() - .map(block -> BlockResponseDTO.builder() + .map(block -> BlockResponseDetailDTO.builder() .blockId(block.getBlockId()) .todoId(block.getTodo().getTodoId()) .startAt(block.getStartAt()) .endAt(block.getEndAt()) + .todoName(block.getTodo().getName()) + .priority(block.getTodo().getPriority()) + .state(block.getTodo().getState()) + .goalName(block.getTodo().getFolder().getGoal().getName()) + .color(block.getTodo().getFolder().getGoal().getColor()) .build()) .collect(Collectors.toList()); } diff --git a/src/main/java/com/umc/timeto/todo/controller/TodoController.java b/src/main/java/com/umc/timeto/todo/controller/TodoController.java index 3ac8740..c45fefb 100644 --- a/src/main/java/com/umc/timeto/todo/controller/TodoController.java +++ b/src/main/java/com/umc/timeto/todo/controller/TodoController.java @@ -121,6 +121,7 @@ public ResponseDTO deleteTodo(@PathVariable Long todoId) { @Operation(summary = "블록 생성 후보 할일 조회", description = "블록이 등록되지 않은 할 일을 조회합니다") @GetMapping("/{folderId}/todo/unblocked") public ResponseEntity>> getUnblockedTodos( + @PathVariable Long folderId, Authentication authentication ) { @@ -129,7 +130,7 @@ public ResponseEntity>> getUnblockedTodos( return ResponseEntity.ok( new ResponseDTO<>( ResponseCode.SUCCESS_GET_UNBLOCKED_TODOS, - todoCommandService.getUnblockedTodos(memberId) + todoCommandService.getUnblockedTodos(memberId,folderId) ) ); } diff --git a/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java b/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java index 0d2084c..789518c 100644 --- a/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java +++ b/src/main/java/com/umc/timeto/todo/repository/TodoRepository.java @@ -21,6 +21,11 @@ List findAllByFolder_FolderIdAndFolder_Goal_Member_MemberIdAndState( boolean existsByTodoIdAndFolder_Goal_Member_MemberId(Long todoId, Long memberId); - List findByFolder_Goal_Member_MemberIdAndBlockIsNull(Long memberId); + // block 후보 조회에 사용 + List findByFolder_FolderIdAndFolder_Goal_Member_MemberIdAndBlockIsNull( + Long folderId, + Long memberId + ); + } diff --git a/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java b/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java index 3e145a2..ae3dcea 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoCommandService.java @@ -14,6 +14,6 @@ public interface TodoCommandService { void deleteTodo(Long memberId, Long todoId); @Transactional(readOnly = true) - List getUnblockedTodos(Long memberId); + List getUnblockedTodos(Long memberId,Long folderId); } diff --git a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java index 2ab945c..b276594 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java @@ -78,11 +78,14 @@ public void deleteTodo(Long memberId, Long todoId) { @Transactional(readOnly = true) @Override - public List getUnblockedTodos(Long memberId) { + public List getUnblockedTodos(Long memberId,Long folderId) { List todos = todoRepository - .findByFolder_Goal_Member_MemberIdAndBlockIsNull(memberId); + .findByFolder_FolderIdAndFolder_Goal_Member_MemberIdAndBlockIsNull( + folderId, + memberId + ); return todos.stream() .map(todo -> new TodoGetResponse( From b053fb8527f067b1d520f4a4432b0c8cb294a738 Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Sat, 14 Feb 2026 12:51:39 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20:=20CI=20build=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/umc/timeto/todo/service/TodoCommandServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java index cfedf89..63fdef8 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoCommandServiceImpl.java @@ -94,7 +94,8 @@ public List getUnblockedTodos(Long memberId,Long folderId) { todo.getName(), DurationFormatter.format(todo.getDuration()), todo.getPriority(), - todo.getState() + todo.getState(), + todo.getStartAt() )) .toList(); } From 39d2d48cc624496eb710f7c9fbc9e386ed8d3ef3 Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Sat, 14 Feb 2026 12:52:31 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20:=20CI=20build=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/umc/timeto/todo/service/TodoQueryServiceImpl.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/umc/timeto/todo/service/TodoQueryServiceImpl.java b/src/main/java/com/umc/timeto/todo/service/TodoQueryServiceImpl.java index 45093e4..b7b49cd 100644 --- a/src/main/java/com/umc/timeto/todo/service/TodoQueryServiceImpl.java +++ b/src/main/java/com/umc/timeto/todo/service/TodoQueryServiceImpl.java @@ -1,6 +1,4 @@ package com.umc.timeto.todo.service; - -import com.umc.timeto.folder.repository.FolderRepository; import com.umc.timeto.global.apiPayload.code.ErrorCode; import com.umc.timeto.global.apiPayload.exception.GlobalException; import com.umc.timeto.todo.domain.Todo; From 2c3784fb646b1002d92ea6525a27df852997ad4b Mon Sep 17 00:00:00 2001 From: cccyyy333 Date: Sun, 15 Feb 2026 20:37:18 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20:=20=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EB=A7=9E=EC=B6=B0=EC=84=9C=20=EC=88=98=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/umc/timeto/block/service/BlockServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java index 40474f8..35d5bc1 100644 --- a/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java +++ b/src/main/java/com/umc/timeto/block/service/BlockServiceImpl.java @@ -33,7 +33,7 @@ public class BlockServiceImpl implements BlockService { @Override public BlockResponseDTO createBlock(Long todoId, BlockAddDTO req, Long memberId) { - Todo todo = todoRepository.findById(todoId) + Todo todo = todoRepository.findByTodoIdAndFolder_Goal_Member_MemberId(todoId,memberId) .orElseThrow(() -> new GlobalException(ErrorCode.TODO_NOT_FOUND));