diff --git a/README.md b/README.md new file mode 100644 index 00000000..7c4e9fe4 --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +## ⏰ Timo ️ +TimoLanding + + +## βš™οΈ Features +Timo-Flow1 + +**πŸ“ νˆ¬λ‘μ™€ μ‹œκ°„μ„ μž…λ ₯ν•΄ νƒ€μž„λ°•μŠ€ 생성** + +ν•΄μ•Ό ν•  일과 μ˜ˆμƒ μ†Œμš” μ‹œκ°„μ„ μž…λ ₯ν•˜λ©΄ ν•˜λ£¨ κ³„νšμ΄ νƒ€μž„λ°•μŠ€ ν˜•νƒœλ‘œ μžλ™ μƒμ„±λ©λ‹ˆλ‹€. + +Timo-Flow2 + +**⏱️ 타이머λ₯Ό μ‹€ν–‰ν•΄ κ³„νš μˆ˜ν–‰** + +각 νƒ€μž„λ°•μŠ€λ₯Ό μ‹€ν–‰ν•˜λ©΄ μ„€μ •ν•œ μ‹œκ°„λ§ŒνΌ 타이머가 μ‹œμž‘λ˜κ³  μ‹€μ œ μˆ˜ν–‰ μ‹œκ°„μ΄ μΈ‘μ •λ©λ‹ˆλ‹€. + +Timo-Flow3 + +**πŸ“Š νƒ€μž„λΌμΈμ„ ν†΅ν•΄μ„œ 기둝** + +νƒ€μ΄λ¨Έλ‘œ μˆ˜ν–‰ν•œ μ‹œκ°„λ§ŒνΌ νƒ€μž„λΌμΈμ— μ‹€μ‹œκ°„μœΌλ‘œ κΈ°λ‘λ˜μ–΄ ν•˜λ£¨ λ™μ•ˆ 무엇을 ν–ˆλŠ”μ§€ ν•œλˆˆμ— νŒŒμ•…ν•  수 μžˆμŠ΅λ‹ˆλ‹€. + +Timo-Flow4 + +**πŸ€– AI ν”Όλ“œλ°±** + +κ³„νšν•œ μ†Œμš” μ‹œκ°„κ³Ό μ‹€μ œ μ†Œμš” μ‹œκ°„μ„ 비ꡐ해 AIκ°€ μ‚¬μš©μžμ—κ²Œ λ§žλŠ” μ‹œκ°„ μΆ”μ²œκ³Ό ν”Όλ“œλ°±μ„ μ œκ³΅ν•©λ‹ˆλ‹€. + +Timo-Flow5 + +**▢️ μ™„λ£Œν•˜λ©΄ λ‹€μŒ νƒ€μž„λ°•μŠ€λ₯Ό μ‹œμž‘** + +ν•˜λ‚˜μ˜ νˆ¬λ‘λ₯Ό μ™„λ£Œν•˜λ©΄ μ΄μ–΄μ„œ λ‹€μŒ νƒ€μž„λ°•μŠ€λ‘œ μžμ—°μŠ€λŸ½κ²Œ λ„˜μ–΄κ°€ κ³„νšν•œ ν•˜λ£¨λ₯Ό λŠκΉ€ 없이 μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. + +Timo-Flow6 + +**πŸ“ˆ 톡계** + +ν•˜λ£¨ λ™μ•ˆ κ³„νšν•œ μ‹œκ°„κ³Ό μ‹€μ œ μˆ˜ν–‰ μ‹œκ°„μ˜ 차이λ₯Ό μΊ˜λ¦°λ” λ·°μ—μ„œ ν•œλˆˆμ— 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. + +Timo-Flow7 + +**🎯 집쀑 λͺ¨λ“œ** + +ν•˜λ‚˜μ˜ νˆ¬λ‘μ—λ§Œ μ§‘μ€‘ν•΄μ„œ μˆ˜ν–‰ν•  수 μžˆλŠ” λͺ°μž… μ „μš© 화면을 μ œκ³΅ν•©λ‹ˆλ‹€. + +#### ν•  일을 μ‹œκ°„ λ‹¨μœ„λ‘œ κ΄€λ¦¬ν•˜λŠ” νƒ€μž„λ°•μ‹± νˆ¬λ‘ + +TimoλŠ” μž‘μ—…μ„ μ‹€ν–‰ κ°€λŠ₯ν•œ μ‹œκ°„ λΈ”λ‘μœΌλ‘œ λ³€ν™˜ν•΄ +κ³„νšν•œ 일을 μ‹€μ œλ‘œ μ™„λ£Œν•  수 μžˆλ„λ‘ λ•λŠ” νƒ€μž„λ°•μŠ€ 기반 ν•  일 관리 μ„œλΉ„μŠ€μž…λ‹ˆλ‹€. + +기쑴의 νˆ¬λ‘ μ„œλΉ„μŠ€μ™€ 달리, TimoλŠ” 각 μž‘μ—…μ„ μΆ”κ°€ν•  λ•Œ μ˜ˆμƒ μ†Œμš” μ‹œκ°„μ„ μž…λ ₯ν•˜λ„λ‘ ν•˜λ©° +이λ₯Ό λ°”νƒ•μœΌλ‘œ ν•˜λ£¨ 일정을 μ‹œκ°„ 블둝 μ€‘μ‹¬μœΌλ‘œ κ΅¬μ„±ν•©λ‹ˆλ‹€. + +λ˜ν•œ μ‚¬μš©μžμ˜ μž‘μ—… 및 μ‹œκ°„ μ‚¬μš© νŒ¨ν„΄μ„ μ§€μ†μ μœΌλ‘œ ν•™μŠ΅ν•˜μ—¬ AI 기반 μ†Œμš” μ‹œκ°„ μΆ”μ²œμ„ μ œκ³΅ν•©λ‹ˆλ‹€. +이λ₯Ό 톡해 κ³Όλ„ν•œ κ³„νšμ„ 쀄이고 보닀 ν˜„μ‹€μ μ΄κ³  μ‹€ν–‰ κ°€λŠ₯ν•œ 일정을 μ„ΈμšΈ 수 μžˆλ„λ‘ μ§€μ›ν•©λ‹ˆλ‹€. + +  + +## ✨ Backend Developers + + + + + + + + + + + + + + + + + + + + + +
μ •μœ€μ•„λ°•μ˜ˆλ‚˜μž₯자윀
+ Timo-yuna + + Timo-yena + + Timo-jayoon +
Backend LeaderBackend DevBackend Dev
@laura-jung@anykrap@Jy000n
+ +  + +## πŸ› οΈ Tech Stack +#### Backend & Auth +SPRING BOOT SPRING SECURITY JWT + +#### Database & Cache +MYSQL REDIS + +#### Infra & Deploy +DOCKER Nginx AWS EC2 AWS RDS GHCR + +#### Monitoring +Prometheus Grafana Sentry + +#### AI +Google Gemini + +  + +## πŸ–₯️ System Architecture +Timo-system_architecture + +  + +## πŸ“Š ERD +Timo-erd + + +  + +## πŸ“¦οΈ Directory +``` +πŸ“‚com.Timo.Timo +β”œβ”€β”€πŸ“‚domain +β”‚ β”œβ”€β”€πŸ“‚timer +β”‚ β”‚ β”œβ”€β”€πŸ“‚controller # HTTP μš”μ²­/응닡 처리 +β”‚ β”‚ β”œβ”€β”€πŸ“‚docs # Swagger λ¬Έμ„œ (Controller와 뢄리) +β”‚ β”‚ β”œβ”€β”€πŸ“‚dto # μš”μ²­/응닡 DTO +β”‚ β”‚ β”œβ”€β”€πŸ“‚entity # JPA μ—”ν‹°ν‹° +β”‚ β”‚ β”œβ”€β”€πŸ“‚enums # 도메인 μ „μš© enum +β”‚ β”‚ β”œβ”€β”€πŸ“‚exception # μ—λŸ¬/성곡 μ½”λ“œ +β”‚ β”‚ β”œβ”€β”€πŸ“‚repository # DB μ ‘κ·Ό +β”‚ β”‚ β””β”€β”€πŸ“‚service # λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 +β”‚ β”œβ”€β”€πŸ“‚todo +β”‚ β”œβ”€β”€πŸ“‚user +β”‚ └── ... +β””β”€β”€πŸ“‚global + β”œβ”€β”€πŸ“‚auth # 인증/인가 곡톡 둜직 + β”œβ”€β”€πŸ“‚common # 곡톡 베이슀 클래슀 (BaseTimeEntity λ“±) + β”œβ”€β”€πŸ“‚config # Swagger, Security λ“± μ„€μ • + β”œβ”€β”€πŸ“‚exception # 곡톡 μ˜ˆμ™Έ 처리 + β”œβ”€β”€πŸ“‚jwt # JWT μœ ν‹Έ (λ°œκΈ‰/검증) + β”œβ”€β”€πŸ“‚logging # μš”μ²­/응닡 및 μ˜ˆμ™Έ λ‘œκΉ… + β””β”€β”€πŸ“‚response # 곡톡 응닡 포맷 (BaseResponse) +``` + +  + +## πŸ“‹ Convention +[🌟 κΉƒ μ»¨λ²€μ…˜ λ³΄λŸ¬κ°€κΈ°](https://app.notion.com/p/Timo-Git-Convention-3877196f19708068b15fe97386a80e01)
+[🌟 μ½”λ“œ μ»¨λ²€μ…˜ λ³΄λŸ¬κ°€κΈ°](https://app.notion.com/p/Timo-spring-code-convention-3877196f19708031b1bcd531a93119fd) diff --git a/src/main/java/com/Timo/Timo/domain/home/dto/response/HomeResponse.java b/src/main/java/com/Timo/Timo/domain/home/dto/response/HomeResponse.java index 8db3d142..773b726f 100644 --- a/src/main/java/com/Timo/Timo/domain/home/dto/response/HomeResponse.java +++ b/src/main/java/com/Timo/Timo/domain/home/dto/response/HomeResponse.java @@ -74,7 +74,7 @@ public record TodoResponse( String priority, TagResponse tag, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - boolean hasSubtask, + boolean hasMemo, @JsonProperty("isRepeated") @Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean isRepeated, diff --git a/src/main/java/com/Timo/Timo/domain/home/dto/response/TodayResponse.java b/src/main/java/com/Timo/Timo/domain/home/dto/response/TodayResponse.java index 6668220d..32a95529 100644 --- a/src/main/java/com/Timo/Timo/domain/home/dto/response/TodayResponse.java +++ b/src/main/java/com/Timo/Timo/domain/home/dto/response/TodayResponse.java @@ -60,7 +60,7 @@ public record TodayTodoResponse( @Schema(requiredMode = Schema.RequiredMode.REQUIRED) boolean isRepeated, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - boolean hasSubtask, + boolean hasMemo, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) TodoTimerStatus timerStatus, Integer sortOrder, @@ -78,7 +78,7 @@ public static TodayTodoResponse from(LocalDate date, TodoResponse todo) { todo.priority(), todo.tag(), todo.isRepeated(), - todo.hasSubtask(), + todo.hasMemo(), todo.timerStatus(), todo.sortOrder(), todo.subtasks() diff --git a/src/main/java/com/Timo/Timo/domain/home/mapper/HomeTodoMapper.java b/src/main/java/com/Timo/Timo/domain/home/mapper/HomeTodoMapper.java index 2260b82c..a647a0d1 100644 --- a/src/main/java/com/Timo/Timo/domain/home/mapper/HomeTodoMapper.java +++ b/src/main/java/com/Timo/Timo/domain/home/mapper/HomeTodoMapper.java @@ -30,7 +30,7 @@ public TodoResponse toResponse(TodoContext context) { todo.getDurationSeconds(), todo.getPriority() != null ? todo.getPriority().name() : null, mapTag(context.tag()), - !todo.getSubtasks().isEmpty(), + hasMemo(todo, instance), todo.getRepeatType() != RepeatType.NONE, resolveTimerStatus(instance), instance != null ? instance.getSortOrder() : context.defaultSortOrder(), @@ -38,6 +38,11 @@ public TodoResponse toResponse(TodoContext context) { ); } + private boolean hasMemo(Todo todo, TodoInstance instance) { + String memo = instance != null ? instance.resolveMemo() : todo.getMemo(); + return memo != null && !memo.isBlank(); + } + private TagResponse mapTag(Tag tag) { return tag != null ? TagResponse.from(tag) : null; } diff --git a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java index f2959c24..dcb13d4f 100644 --- a/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java +++ b/src/main/java/com/Timo/Timo/domain/timer/repository/TimerRecordRepository.java @@ -3,6 +3,7 @@ import com.Timo.Timo.domain.timer.entity.TimerRecord; import com.Timo.Timo.domain.timer.enums.TimerStatus; import jakarta.persistence.LockModeType; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -18,6 +19,9 @@ public interface TimerRecordRepository extends JpaRepository boolean existsByTodo_IdAndStatusIn(Long todoId, List statuses); + boolean existsByTodo_IdAndTargetDateAndStatusIn( + Long todoId, LocalDate targetDate, List statuses); + @Modifying(clearAutomatically = true) @Query("delete from TimerRecord r where r.todo.id = :todoId") void deleteByTodoId(@Param("todoId") Long todoId); diff --git a/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java b/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java index 8eea3fb4..778984a6 100644 --- a/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java +++ b/src/main/java/com/Timo/Timo/domain/timer/service/TimerService.java @@ -184,6 +184,10 @@ public boolean hasActiveTimer(Long todoId) { return timerRecordRepository.existsByTodo_IdAndStatusIn(todoId, ACTIVE_STATUS); } + public boolean hasActiveTimerOn(Long todoId, LocalDate date) { + return timerRecordRepository.existsByTodo_IdAndTargetDateAndStatusIn(todoId, date, ACTIVE_STATUS); + } + @Transactional public void deleteTimersByTodo(Long todoId) { timerSessionRepository.deleteByTodoId(todoId); diff --git a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java index c9cad042..f3446eed 100644 --- a/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java +++ b/src/main/java/com/Timo/Timo/domain/todo/service/TodoService.java @@ -137,7 +137,7 @@ public TodoStatusChangeResponse changeCompletion(Long userId, Long todoId, TodoS throw new CustomException(TodoErrorCode.TODO_NOT_FOUND); } - if (timerService.hasActiveTimer(todoId)) { + if (timerService.hasActiveTimerOn(todoId, date)) { throw new CustomException(TodoErrorCode.TIMER_RUNNING); }