From 0d46958d4ff5d9d8125f36f2ecdece031db47ce0 Mon Sep 17 00:00:00 2001 From: m1lotic Date: Fri, 20 Jun 2025 09:02:09 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8C=80=EC=8B=9C=EB=B3=B4=EB=93=9C=20?= =?UTF-8?q?=ED=86=B5=EA=B3=84=EB=8D=B0=EC=9D=B4=ED=84=B0,=20=ED=83=9C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9A=94=EC=95=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 38 +++++ .../controller/DashboardController.java | 38 +++++ .../dashboard/dto/TaskSimpleResponse.java | 18 +++ .../dashboard/dto/TaskStatisticsResponse.java | 10 ++ .../dto/TodayTaskSummaryResponse.java | 8 + .../repository/TaskRepositoryCustom.java | 14 ++ .../repository/TaskRepositoryImpl.java | 78 +++++++++ .../dashboard/service/DashboardService.java | 45 ++++++ .../taskflow/domain/member/entity/Member.java | 1 + .../com/taskflow/domain/task/entity/Task.java | 152 ++++++++++++++++++ .../domain/task/enums/TaskPriority.java | 6 + .../domain/task/enums/TaskStatus.java | 6 + .../task/repository/TaskRepository.java | 63 ++++++++ .../global/config/QuerydslConfig.java | 20 +++ .../exception/GlobalExceptionHandler.java | 10 ++ .../dashboard/StastisticException.java | 14 ++ .../response/success/StatisticSuccess.java | 23 +++ .../java/com/taskflow/TimeComparisonTest.java | 23 +++ 18 files changed, 567 insertions(+) create mode 100644 src/main/java/com/taskflow/domain/dashboard/controller/DashboardController.java create mode 100644 src/main/java/com/taskflow/domain/dashboard/dto/TaskSimpleResponse.java create mode 100644 src/main/java/com/taskflow/domain/dashboard/dto/TaskStatisticsResponse.java create mode 100644 src/main/java/com/taskflow/domain/dashboard/dto/TodayTaskSummaryResponse.java create mode 100644 src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryCustom.java create mode 100644 src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryImpl.java create mode 100644 src/main/java/com/taskflow/domain/dashboard/service/DashboardService.java create mode 100644 src/main/java/com/taskflow/domain/task/entity/Task.java create mode 100644 src/main/java/com/taskflow/domain/task/enums/TaskPriority.java create mode 100644 src/main/java/com/taskflow/domain/task/enums/TaskStatus.java create mode 100644 src/main/java/com/taskflow/domain/task/repository/TaskRepository.java create mode 100644 src/main/java/com/taskflow/global/config/QuerydslConfig.java create mode 100644 src/main/java/com/taskflow/global/exception/dashboard/StastisticException.java create mode 100644 src/main/java/com/taskflow/global/response/success/StatisticSuccess.java create mode 100644 src/test/java/com/taskflow/TimeComparisonTest.java diff --git a/build.gradle b/build.gradle index 44e2a8f..e7e3dd8 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.5.0' id 'io.spring.dependency-management' version '1.1.7' + //id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' } group = 'com' @@ -28,6 +29,13 @@ dependencies { // implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + + // queryDSL + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' @@ -36,6 +44,36 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } +/* + * queryDSL 설정 추가 + */ +// querydsl에서 사용할 경로 설정 +//def querydslDir = "$buildDir/generated/querydsl" +// +// +//// JPA 사용 여부와 사용할 경로를 설정 +////querydsl { +//// jpa = true +//// querydslSourcesDir = querydslDir +////} +// +//// build 시 사용할 sourceSet 추가 +//sourceSets { +// main.java.srcDir querydslDir +//} +// +//tasks.withType(JavaCompile).configureEach { +// options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir) +//} +// +//// querydsl 이 compileClassPath 를 상속하도록 설정 +//configurations { +// compileOnly { +// extendsFrom annotationProcessor +// } +// querydsl.extendsFrom compileClasspath +//} + tasks.named('test') { useJUnitPlatform() } diff --git a/src/main/java/com/taskflow/domain/dashboard/controller/DashboardController.java b/src/main/java/com/taskflow/domain/dashboard/controller/DashboardController.java new file mode 100644 index 0000000..2fb9f37 --- /dev/null +++ b/src/main/java/com/taskflow/domain/dashboard/controller/DashboardController.java @@ -0,0 +1,38 @@ +package com.taskflow.domain.dashboard.controller; + +import com.taskflow.domain.dashboard.dto.TaskStatisticsResponse; +import com.taskflow.domain.dashboard.dto.TodayTaskSummaryResponse; +import com.taskflow.domain.dashboard.service.DashboardService; +import com.taskflow.global.common.ApiResponse; +import com.taskflow.global.response.success.StatisticSuccess; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DashboardController { + + private final DashboardService dashboardService; + + + public DashboardController(DashboardService dashboardService) { + this.dashboardService = dashboardService; + } + + //통계값 조회 API + @GetMapping("/dashboard/statistics") + public ResponseEntity> getStatistics() { + return ResponseEntity.status(HttpStatus.OK). + body(ApiResponse.success(StatisticSuccess.STATISTIC_SUCCESS.getMessage(),dashboardService.getTaskStatistics())); + } + + //오늘의 태스크 요약 + @GetMapping("/dashboard/my-tasks/today") + public ResponseEntity> getMyTodayTasks(@RequestParam Long assigneeId) { + return ResponseEntity.status(HttpStatus.OK). +// body(ApiResponse.success(TaskSuccess.TASK_READ_SUCCESS.getMessage(), response)); + body(ApiResponse.success("태스크요약 성공", dashboardService.getTodayTaskSummary(assigneeId))); + } +} diff --git a/src/main/java/com/taskflow/domain/dashboard/dto/TaskSimpleResponse.java b/src/main/java/com/taskflow/domain/dashboard/dto/TaskSimpleResponse.java new file mode 100644 index 0000000..a6f8ff0 --- /dev/null +++ b/src/main/java/com/taskflow/domain/dashboard/dto/TaskSimpleResponse.java @@ -0,0 +1,18 @@ +package com.taskflow.domain.dashboard.dto; + +import com.taskflow.domain.task.entity.Task; + +import java.time.LocalDateTime; + +public record TaskSimpleResponse(Long id, String title, String priority, LocalDateTime dueDate) { + + public static TaskSimpleResponse from(Task task) { + return new TaskSimpleResponse( + task.getId(), + task.getTitle(), + task.getPriority().name(), + task.getDueDate() + ); + } + +} diff --git a/src/main/java/com/taskflow/domain/dashboard/dto/TaskStatisticsResponse.java b/src/main/java/com/taskflow/domain/dashboard/dto/TaskStatisticsResponse.java new file mode 100644 index 0000000..49933b1 --- /dev/null +++ b/src/main/java/com/taskflow/domain/dashboard/dto/TaskStatisticsResponse.java @@ -0,0 +1,10 @@ +package com.taskflow.domain.dashboard.dto; + +public record TaskStatisticsResponse( + long totalCount, + long todoCount, + long inProgressCount, + long doneCount, + double completionRate, + long overdueCount +) {} diff --git a/src/main/java/com/taskflow/domain/dashboard/dto/TodayTaskSummaryResponse.java b/src/main/java/com/taskflow/domain/dashboard/dto/TodayTaskSummaryResponse.java new file mode 100644 index 0000000..37a1ab3 --- /dev/null +++ b/src/main/java/com/taskflow/domain/dashboard/dto/TodayTaskSummaryResponse.java @@ -0,0 +1,8 @@ +package com.taskflow.domain.dashboard.dto; + +import java.util.List; + +public record TodayTaskSummaryResponse( + List todoTasks, + List inProgressTasks +) {} diff --git a/src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryCustom.java b/src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryCustom.java new file mode 100644 index 0000000..2d822c2 --- /dev/null +++ b/src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryCustom.java @@ -0,0 +1,14 @@ +package com.taskflow.domain.dashboard.repository; + + +import com.taskflow.domain.dashboard.dto.TaskStatisticsResponse; +import com.taskflow.domain.task.entity.Task; +import com.taskflow.domain.task.enums.TaskStatus; + +import java.util.List; + +public interface TaskRepositoryCustom { + TaskStatisticsResponse fetchTaskStatistics(); + List findTodayTasksByStatusAndAssignee(TaskStatus status, Long assigneeId); + +} diff --git a/src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryImpl.java b/src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryImpl.java new file mode 100644 index 0000000..ee8934d --- /dev/null +++ b/src/main/java/com/taskflow/domain/dashboard/repository/TaskRepositoryImpl.java @@ -0,0 +1,78 @@ +package com.taskflow.domain.dashboard.repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.taskflow.domain.dashboard.dto.TaskStatisticsResponse; + +import com.taskflow.domain.task.entity.QTask; +import com.taskflow.domain.task.entity.Task; +import com.taskflow.domain.task.enums.TaskStatus; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@RequiredArgsConstructor +@Repository +public class TaskRepositoryImpl implements TaskRepositoryCustom { + private final JPAQueryFactory queryFactory; + + + @Override + public TaskStatisticsResponse fetchTaskStatistics() { + QTask task = QTask.task; + LocalDateTime now = LocalDateTime.now(); + + long total = queryFactory.select(task.count()) + .from(task) + .where(task.isDeleted.isFalse()) + .fetchOne(); + + long todo = queryFactory.select(task.count()) + .from(task) + .where(task.isDeleted.isFalse() + .and(task.status.eq(TaskStatus.valueOf("TODO")))) + .fetchOne(); + + long inProgress = queryFactory.select(task.count()) + .from(task) + .where(task.isDeleted.isFalse() + .and(task.status.eq(TaskStatus.valueOf("IN_PROGRESS")))) + .fetchOne(); + + long done = queryFactory.select(task.count()) + .from(task) + .where(task.isDeleted.isFalse() + .and(task.status.eq(TaskStatus.valueOf("DONE")))) + .fetchOne(); + + long overdue = queryFactory.select(task.count()) + .from(task) + .where(task.isDeleted.isFalse() + .and(task.status.in(TaskStatus.TODO, TaskStatus.IN_PROGRESS)) + .and(task.dueDate.before(now))) + .fetchOne(); + + double completionRate = total == 0 ? 0.0 : Math.round((done * 10000.0 / total)) / 100.0; + + return new TaskStatisticsResponse(total, todo, inProgress, done, completionRate, overdue); + } + + @Override + public List findTodayTasksByStatusAndAssignee(TaskStatus status, Long assigneeId) { + QTask task = QTask.task; + + return queryFactory + .selectFrom(task) + .where( + task.assignee.id.eq(assigneeId), + task.status.eq(status), + task.isDeleted.isFalse(), + task.createdAt.between(LocalDate.now().atStartOfDay(), LocalDateTime.now()) + ) + .orderBy(task.priority.desc()) // HIGH > MEDIUM > LOW + .fetch(); + } +} + diff --git a/src/main/java/com/taskflow/domain/dashboard/service/DashboardService.java b/src/main/java/com/taskflow/domain/dashboard/service/DashboardService.java new file mode 100644 index 0000000..473dc5e --- /dev/null +++ b/src/main/java/com/taskflow/domain/dashboard/service/DashboardService.java @@ -0,0 +1,45 @@ +package com.taskflow.domain.dashboard.service; + +import com.taskflow.domain.dashboard.dto.TaskSimpleResponse; +import com.taskflow.domain.dashboard.dto.TaskStatisticsResponse; +import com.taskflow.domain.dashboard.dto.TodayTaskSummaryResponse; +import com.taskflow.domain.task.entity.Task; +import com.taskflow.domain.task.enums.TaskStatus; +import com.taskflow.domain.task.repository.TaskRepository; +import com.taskflow.global.exception.dashboard.StastisticException; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class DashboardService { + + private final TaskRepository taskRepository; + + public DashboardService(TaskRepository taskRepository) { + this.taskRepository = taskRepository; + } + + public TaskStatisticsResponse getTaskStatistics() { + + TaskStatisticsResponse dto = taskRepository.fetchTaskStatistics(); + + if (dto.totalCount() == 0) { + throw new StastisticException("태스크 통계를 불러오는 중 오류 발생", HttpStatus.INTERNAL_SERVER_ERROR); + } + + return dto; + } + + public TodayTaskSummaryResponse getTodayTaskSummary(Long assigneeId) { + List todos = taskRepository.findTodayTasksByStatusAndAssignee(TaskStatus.TODO, assigneeId); + List inProgress = taskRepository.findTodayTasksByStatusAndAssignee(TaskStatus.IN_PROGRESS, assigneeId); + + return new TodayTaskSummaryResponse( + todos.stream().map(TaskSimpleResponse::from).toList(), + inProgress.stream().map(TaskSimpleResponse::from).toList() + ); + } + +} diff --git a/src/main/java/com/taskflow/domain/member/entity/Member.java b/src/main/java/com/taskflow/domain/member/entity/Member.java index 91f2224..404e7b6 100644 --- a/src/main/java/com/taskflow/domain/member/entity/Member.java +++ b/src/main/java/com/taskflow/domain/member/entity/Member.java @@ -36,6 +36,7 @@ public class Member extends BaseEntity { private UserRole userRole; @Column(nullable = false, columnDefinition = "BOOLEAN DEFAULT false") + @Builder.Default private Boolean is_deleted = false; public void softDelete() { diff --git a/src/main/java/com/taskflow/domain/task/entity/Task.java b/src/main/java/com/taskflow/domain/task/entity/Task.java new file mode 100644 index 0000000..0f95c06 --- /dev/null +++ b/src/main/java/com/taskflow/domain/task/entity/Task.java @@ -0,0 +1,152 @@ +package com.taskflow.domain.task.entity; + +import com.taskflow.domain.member.entity.Member; +import com.taskflow.domain.task.enums.TaskPriority; +import com.taskflow.domain.task.enums.TaskStatus; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 일정(Task) 도메인 엔티티 + * 업무 제목, 설명, 상태, 우선순위, 생성자 및 담당자 등 핵심 정보를 포함 + */ +@Entity +@Getter +@NoArgsConstructor +public class Task { + + /** + * 일정 고유 ID + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** + * 일정 제목 + */ + private String title; + + /** + * 일정 설명 + */ + private String description; + + /** + * 우선순위 (LOW, MEDIUM, HIGH) + */ + @Enumerated(EnumType.STRING) + private TaskPriority priority; + + /** + * 상태 (TODO, IN_PROGRESS, DONE 등) + */ + @Enumerated(EnumType.STRING) + private TaskStatus status; + + /** + * 시작일 (선택값) + */ + private LocalDateTime startDate; + + /** + * 마감일 + */ + private LocalDateTime dueDate; + + /** + * 생성일 + */ + private LocalDateTime createdAt; + + /** + * 수정일 + */ + private LocalDateTime updatedAt; + + /** + * 삭제 여부 플래그 + */ + private boolean isDeleted; + + /** + * 삭제일시 + */ + private LocalDateTime deletedAt; + + /** + * 일정 생성자 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "creator_id") + private Member creator; + + /** + * 일정 담당자 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "assignee_id") + private Member assignee; + + /** + * 일정 생성자 + */ + public Task(String title, String description, TaskPriority priority, TaskStatus status, + LocalDateTime dueDate, Member creator, Member assignee) { + this.title = title; + this.description = description; + this.priority = priority; + this.status = status; + this.dueDate = dueDate; + this.creator = creator; + this.assignee = assignee; + this.isDeleted = false; + this.startDate = null; + this.deletedAt = null; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + /** + * 일정 정보 수정 + */ + public void update(String title, String description, TaskPriority priority, TaskStatus status, + LocalDateTime dueDate, Member assignee) { + this.title = title; + this.description = description; + this.priority = priority; + this.status = status; + this.dueDate = dueDate; + this.assignee = assignee; + this.updatedAt = LocalDateTime.now(); + } + + /** + * 일정 상태 변경 + */ + public void changeStatus(TaskStatus status) { + this.status = status; + this.updatedAt = LocalDateTime.now(); + } + + /** + * 태스크의 시작일(startDate)을 설정하는 메서드 + * 상태가 IN_PROGRESS로 변경될 때 호출됨 + * + * @param startDate 작업 시작일 + */ + public void setStartDate(LocalDateTime startDate) { + this.startDate = startDate; + } + + /** + * 일정 삭제 처리 + */ + public void delete() { + this.isDeleted = true; + this.deletedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/taskflow/domain/task/enums/TaskPriority.java b/src/main/java/com/taskflow/domain/task/enums/TaskPriority.java new file mode 100644 index 0000000..a5297d4 --- /dev/null +++ b/src/main/java/com/taskflow/domain/task/enums/TaskPriority.java @@ -0,0 +1,6 @@ +package com.taskflow.domain.task.enums; + + +public enum TaskPriority { + LOW, MEDIUM, HIGH +} \ No newline at end of file diff --git a/src/main/java/com/taskflow/domain/task/enums/TaskStatus.java b/src/main/java/com/taskflow/domain/task/enums/TaskStatus.java new file mode 100644 index 0000000..1c51dee --- /dev/null +++ b/src/main/java/com/taskflow/domain/task/enums/TaskStatus.java @@ -0,0 +1,6 @@ +package com.taskflow.domain.task.enums; + +// 일정 상태 정의 +public enum TaskStatus { + TODO, IN_PROGRESS, DONE +} \ No newline at end of file diff --git a/src/main/java/com/taskflow/domain/task/repository/TaskRepository.java b/src/main/java/com/taskflow/domain/task/repository/TaskRepository.java new file mode 100644 index 0000000..0e9d1c6 --- /dev/null +++ b/src/main/java/com/taskflow/domain/task/repository/TaskRepository.java @@ -0,0 +1,63 @@ +package com.taskflow.domain.task.repository; + +import com.taskflow.domain.dashboard.repository.TaskRepositoryCustom; +import com.taskflow.domain.member.entity.Member; +import com.taskflow.domain.task.entity.Task; +import com.taskflow.domain.task.enums.TaskStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +/** + * Task 엔티티에 대한 데이터베이스 접근을 담당하는 JPA 레포지토리 인터페이스 + */ +public interface TaskRepository extends JpaRepository, TaskRepositoryCustom { + + /** + * 삭제되지 않은 전체 일정 조회 + */ + List findAllByIsDeletedFalse(); + + /** + * 담당자 기준으로 삭제되지 않은 일정 조회 + */ + List findAllByAssigneeAndIsDeletedFalse(Member assignee); + + /** + * 작성자 기준으로 삭제되지 않은 일정 조회 + */ + List findAllByCreatorAndIsDeletedFalse(Member creator); + + /** + * 상태 기준으로 삭제되지 않은 일정 조회 + */ + List findAllByStatusAndIsDeletedFalse(TaskStatus status); + + /** + * 제목 키워드로 삭제되지 않은 일정 검색 (페이징 처리) + */ + Page findByTitleContainingAndIsDeletedFalse(String title, Pageable pageable); + + /** + * 설명 키워드로 삭제되지 않은 일정 검색 (페이징 처리) + */ + Page findByDescriptionContainingAndIsDeletedFalse(String description, Pageable pageable); + + /** + * 제목과 상태 기준으로 삭제되지 않은 일정 조회 + */ + List findByTitleContainingAndStatusAndIsDeletedFalse(String title, TaskStatus status); + + /** + * 삭제되지 않은 일정 단건 조회 + */ + Optional findByIdAndIsDeletedFalse(Long taskId); + + /** + * 상태, 담당자, 제목 키워드 기준으로 삭제되지 않은 일정 조회 + */ + List findAllByStatusAndAssigneeAndTitleContainingAndIsDeletedFalse(TaskStatus status, Member assignee, String title); +} \ No newline at end of file diff --git a/src/main/java/com/taskflow/global/config/QuerydslConfig.java b/src/main/java/com/taskflow/global/config/QuerydslConfig.java new file mode 100644 index 0000000..d4ab956 --- /dev/null +++ b/src/main/java/com/taskflow/global/config/QuerydslConfig.java @@ -0,0 +1,20 @@ +package com.taskflow.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QuerydslConfig { + + //영속성을 트랜잭션 범위에서 생성하여 관리하는 어노테이션 + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/taskflow/global/exception/GlobalExceptionHandler.java b/src/main/java/com/taskflow/global/exception/GlobalExceptionHandler.java index a85bc3d..0a02184 100644 --- a/src/main/java/com/taskflow/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/taskflow/global/exception/GlobalExceptionHandler.java @@ -1,7 +1,9 @@ package com.taskflow.global.exception; import com.taskflow.global.common.ApiResponse; +import com.taskflow.global.exception.dashboard.StastisticException; import org.springframework.http.ResponseEntity; +import org.springframework.web.ErrorResponse; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -17,4 +19,12 @@ public ResponseEntity> handleCustomException(CustomException .status(e.getStatus()) .body(ApiResponse.fail(e.getErrorMessage())); } + + //통계 예외처리 + @ExceptionHandler(StastisticException.class) + public ResponseEntity> handleStastisticException(StastisticException e) { + return ResponseEntity + .status(e.getStatus().value()) + .body(ApiResponse.fail(e.getMessage())); + } } diff --git a/src/main/java/com/taskflow/global/exception/dashboard/StastisticException.java b/src/main/java/com/taskflow/global/exception/dashboard/StastisticException.java new file mode 100644 index 0000000..0583b00 --- /dev/null +++ b/src/main/java/com/taskflow/global/exception/dashboard/StastisticException.java @@ -0,0 +1,14 @@ +package com.taskflow.global.exception.dashboard; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public class StastisticException extends RuntimeException { + private final HttpStatus status; + + public StastisticException(String message, HttpStatus status) { + super(message); + this.status = status; + } +} diff --git a/src/main/java/com/taskflow/global/response/success/StatisticSuccess.java b/src/main/java/com/taskflow/global/response/success/StatisticSuccess.java new file mode 100644 index 0000000..312892e --- /dev/null +++ b/src/main/java/com/taskflow/global/response/success/StatisticSuccess.java @@ -0,0 +1,23 @@ +package com.taskflow.global.response.success; + +import org.springframework.http.HttpStatus; + +public enum StatisticSuccess { + STATISTIC_SUCCESS(HttpStatus.OK, "통계정보 전달 성공"); + + private final HttpStatus status; + private final String message; + + StatisticSuccess(HttpStatus status, String message) { + this.status = status; + this.message = message; + } + + public HttpStatus getStatus() { + return status; + } + + public String getMessage() { + return message; + } +} diff --git a/src/test/java/com/taskflow/TimeComparisonTest.java b/src/test/java/com/taskflow/TimeComparisonTest.java new file mode 100644 index 0000000..ddeced5 --- /dev/null +++ b/src/test/java/com/taskflow/TimeComparisonTest.java @@ -0,0 +1,23 @@ +package com.taskflow; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import java.time.LocalDateTime; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +class TimeComparisonTest { + + @Test + void 기한_초과_비교_테스트() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime dueDatePast = now.minusDays(1); + LocalDateTime dueDateFuture = now.plusDays(1); + + assertTrue(dueDatePast.isBefore(now)); + assertFalse(dueDateFuture.isBefore(now)); + } +}