From 4eb1826e539b034480f82c1c521c9bce9d930bab Mon Sep 17 00:00:00 2001 From: YG Date: Sat, 26 Apr 2025 20:16:59 +0300 Subject: [PATCH 1/9] feat: create CRUD service task manager --- .idea/amplicode-jpa.xml | 4 +- .idea/amplicode-settings.xml | 15 ++ .idea/compiler.xml | 28 +++ .idea/dataSources.local.xml | 20 ++ .idea/dataSources.xml | 17 ++ .../f04243a9-2346-4975-b5d1-b9ee4bd49928.xml | 178 ++++++++++++++++++ .../_src_/database/TESTDB.kGhKkw.meta | 1 + .../TESTDB.kGhKkw/schema/PUBLIC.aaZQjQ.meta | 2 + .idea/encodings.xml | 7 + .idea/jarRepositories.xml | 20 ++ .idea/misc.xml | 8 + resources/task.csv | 4 - service/Dockerfile | 4 + service/pom.xml | 107 +++++++++++ .../task/manager/TaskManagerApplication.java | 11 ++ .../manager/controller/EpicController.java | 37 ++++ .../manager/controller/SubtaskController.java | 37 ++++ .../manager/controller/TaskController.java | 37 ++++ .../dto/epic/EpicRequestCreatedDto.java | 22 +++ .../manager/dto/epic/EpicResponseDto.java | 20 ++ .../dto/subtask/SubtaskRequestCreatedDto.java | 20 ++ .../dto/subtask/SubtaskResponseDto.java | 14 ++ .../dto/task/TaskRequestCreatedDto.java | 16 ++ .../manager/dto/task/TaskResponseDto.java | 14 ++ .../task/manager/error/ErrorHandler.java | 32 ++++ .../error/InvalidTaskDataException.java | 7 + .../task/manager/error/NotFoundException.java | 7 + .../TaskConstraintViolationException.java | 7 + .../task/manager/mapper/EpicMapper.java | 23 +++ .../task/manager/mapper/SubtaskMapper.java | 17 ++ .../task/manager/mapper/TaskMapper.java | 19 ++ .../java/service/task/manager/model/Epic.java | 70 +++++++ .../service/task/manager/model/Subtask.java | 67 +++++++ .../java/service/task/manager/model/Task.java | 65 +++++++ .../task/manager/model/enums}/Status.java | 2 +- .../task/manager/model/enums}/TaskType.java | 2 +- .../manager/repository/EpicRepository.java | 9 + .../manager/repository/SubtaskRepository.java | 9 + .../manager/repository/TaskRepository.java | 9 + .../task/manager/service/EpicService.java | 14 ++ .../task/manager/service/SubtaskService.java | 15 ++ .../task/manager/service/TaskService.java | 14 ++ .../manager/service/impl/EpicServiceImpl.java | 46 +++++ .../service/impl/SubtaskServiceImpl.java | 47 +++++ .../manager/service/impl/TaskServiceImpl.java | 45 +++++ service/src/main/resources/application.yml | 13 ++ service/src/main/resources/schema.sql | 45 +++++ service/target/classes/application.yml | 13 ++ service/target/classes/schema.sql | 45 +++++ .../task/manager/mapper/EpicMapperImpl.java | 104 ++++++++++ .../manager/mapper/SubtaskMapperImpl.java | 65 +++++++ .../task/manager/mapper/TaskMapperImpl.java | 65 +++++++ service/target/maven-archiver/pom.properties | 3 + .../compile/default-compile/createdFiles.lst | 38 ++++ .../compile/default-compile/inputFiles.lst | 31 +++ .../default-testCompile/createdFiles.lst | 0 .../default-testCompile/inputFiles.lst | 0 .../target/service-1.0-SNAPSHOT.jar.original | Bin 0 -> 42560 bytes src/task/manager/schedule/model/Epic.java | 52 ----- src/task/manager/schedule/model/Subtask.java | 41 ---- src/task/manager/schedule/model/Task.java | 118 ------------ .../service/FileBackedTaskManager.java | 1 - 62 files changed, 1584 insertions(+), 219 deletions(-) create mode 100644 .idea/amplicode-settings.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/dataSources.local.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml create mode 100644 .idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw.meta create mode 100644 .idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw/schema/PUBLIC.aaZQjQ.meta create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml delete mode 100644 resources/task.csv create mode 100644 service/Dockerfile create mode 100644 service/pom.xml create mode 100644 service/src/main/java/service/task/manager/TaskManagerApplication.java create mode 100644 service/src/main/java/service/task/manager/controller/EpicController.java create mode 100644 service/src/main/java/service/task/manager/controller/SubtaskController.java create mode 100644 service/src/main/java/service/task/manager/controller/TaskController.java create mode 100644 service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java create mode 100644 service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java create mode 100644 service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java create mode 100644 service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java create mode 100644 service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java create mode 100644 service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java create mode 100644 service/src/main/java/service/task/manager/error/ErrorHandler.java create mode 100644 service/src/main/java/service/task/manager/error/InvalidTaskDataException.java create mode 100644 service/src/main/java/service/task/manager/error/NotFoundException.java create mode 100644 service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java create mode 100644 service/src/main/java/service/task/manager/mapper/EpicMapper.java create mode 100644 service/src/main/java/service/task/manager/mapper/SubtaskMapper.java create mode 100644 service/src/main/java/service/task/manager/mapper/TaskMapper.java create mode 100644 service/src/main/java/service/task/manager/model/Epic.java create mode 100644 service/src/main/java/service/task/manager/model/Subtask.java create mode 100644 service/src/main/java/service/task/manager/model/Task.java rename {src/task/manager/schedule/model => service/src/main/java/service/task/manager/model/enums}/Status.java (58%) rename {src/task/manager/schedule/model => service/src/main/java/service/task/manager/model/enums}/TaskType.java (58%) create mode 100644 service/src/main/java/service/task/manager/repository/EpicRepository.java create mode 100644 service/src/main/java/service/task/manager/repository/SubtaskRepository.java create mode 100644 service/src/main/java/service/task/manager/repository/TaskRepository.java create mode 100644 service/src/main/java/service/task/manager/service/EpicService.java create mode 100644 service/src/main/java/service/task/manager/service/SubtaskService.java create mode 100644 service/src/main/java/service/task/manager/service/TaskService.java create mode 100644 service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java create mode 100644 service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java create mode 100644 service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java create mode 100644 service/src/main/resources/application.yml create mode 100644 service/src/main/resources/schema.sql create mode 100644 service/target/classes/application.yml create mode 100644 service/target/classes/schema.sql create mode 100644 service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java create mode 100644 service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java create mode 100644 service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java create mode 100644 service/target/maven-archiver/pom.properties create mode 100644 service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst create mode 100644 service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst create mode 100644 service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst create mode 100644 service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst create mode 100644 service/target/service-1.0-SNAPSHOT.jar.original delete mode 100644 src/task/manager/schedule/model/Epic.java delete mode 100644 src/task/manager/schedule/model/Subtask.java delete mode 100644 src/task/manager/schedule/model/Task.java diff --git a/.idea/amplicode-jpa.xml b/.idea/amplicode-jpa.xml index 98351bf..f6a56e2 100644 --- a/.idea/amplicode-jpa.xml +++ b/.idea/amplicode-jpa.xml @@ -1,6 +1,8 @@ - + + \ No newline at end of file diff --git a/.idea/amplicode-settings.xml b/.idea/amplicode-settings.xml new file mode 100644 index 0000000..b965efa --- /dev/null +++ b/.idea/amplicode-settings.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..795ca79 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.local.xml b/.idea/dataSources.local.xml new file mode 100644 index 0000000..10a6f99 --- /dev/null +++ b/.idea/dataSources.local.xml @@ -0,0 +1,20 @@ + + + + + + " + + + master_key + sa + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..ab41c1c --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + h2.unified + true + org.h2.Driver + jdbc:h2:mem:testdb + + + + + + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml new file mode 100644 index 0000000..3ab49a9 --- /dev/null +++ b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml @@ -0,0 +1,178 @@ + + + + + 2.2.220 + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 2025-04-26.07:46:18 + + + 1 + + + \ No newline at end of file diff --git a/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw.meta b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw.meta new file mode 100644 index 0000000..5d0718d --- /dev/null +++ b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw.meta @@ -0,0 +1 @@ +#n:TESTDB \ No newline at end of file diff --git a/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw/schema/PUBLIC.aaZQjQ.meta b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw/schema/PUBLIC.aaZQjQ.meta new file mode 100644 index 0000000..3fdc81a --- /dev/null +++ b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928/storage_v2/_src_/database/TESTDB.kGhKkw/schema/PUBLIC.aaZQjQ.meta @@ -0,0 +1,2 @@ +#n:PUBLIC +! [0, 0, null, null, -2147483648, -2147483648] diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..1926ee2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..712ab9d --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 6a61e88..1ceb38f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,13 @@ + + + + diff --git a/resources/task.csv b/resources/task.csv deleted file mode 100644 index f86272e..0000000 --- a/resources/task.csv +++ /dev/null @@ -1,4 +0,0 @@ -id,type,name,status,description,epic -1,TASK,Дом,NEW,Убраться в кухни и ванной,2024-06-19T20:18:39.706859,2024-06-19T20:58:39.706859 -2,TASK,Работа,IN_PROGRESS,Сделать куча рутины и пойти домой:),2024-06-20T20:18:39.719817,2024-06-20T21:08:39.719817 - diff --git a/service/Dockerfile b/service/Dockerfile new file mode 100644 index 0000000..9fd6682 --- /dev/null +++ b/service/Dockerfile @@ -0,0 +1,4 @@ +FROM ubuntu:latest +LABEL authors="admin" + +ENTRYPOINT ["top", "-b"] \ No newline at end of file diff --git a/service/pom.xml b/service/pom.xml new file mode 100644 index 0000000..b88716d --- /dev/null +++ b/service/pom.xml @@ -0,0 +1,107 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.4.4 + + + service.task.manager + service + 1.0-SNAPSHOT + + + 21 + 21 + UTF-8 + 2.6.0 + 1.6.0 + 1.18.34 + + + + io.swagger.core.v3 + swagger-annotations + 2.1.10 + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + + + + org.projectlombok + lombok + 1.18.34 + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-validation + + + com.h2database + h2 + runtime + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 21 + 21 + + + + org.projectlombok + lombok + ${lombok.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + + + + + + \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/TaskManagerApplication.java b/service/src/main/java/service/task/manager/TaskManagerApplication.java new file mode 100644 index 0000000..acb9e68 --- /dev/null +++ b/service/src/main/java/service/task/manager/TaskManagerApplication.java @@ -0,0 +1,11 @@ +package service.task.manager; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TaskManagerApplication { + public static void main(String[] args) { + SpringApplication.run(TaskManagerApplication.class, args); + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/EpicController.java b/service/src/main/java/service/task/manager/controller/EpicController.java new file mode 100644 index 0000000..f100995 --- /dev/null +++ b/service/src/main/java/service/task/manager/controller/EpicController.java @@ -0,0 +1,37 @@ +package service.task.manager.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.service.EpicService; + +import java.util.List; + +@Slf4j +@CrossOrigin +@RestController +@RequestMapping("/epic") +public class EpicController { + private final EpicService service; + + public EpicController(EpicService service) { + this.service = service; + } + + @PostMapping + public void create(@RequestBody EpicRequestCreatedDto dto) { + service.create(dto); + } + + @GetMapping("/{id}") + public EpicResponseDto findById(@PathVariable Long id) { + return service.findById(id); + } + + @GetMapping + public List findAll() { + return service.findAll(); + } +} diff --git a/service/src/main/java/service/task/manager/controller/SubtaskController.java b/service/src/main/java/service/task/manager/controller/SubtaskController.java new file mode 100644 index 0000000..222ef79 --- /dev/null +++ b/service/src/main/java/service/task/manager/controller/SubtaskController.java @@ -0,0 +1,37 @@ +package service.task.manager.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.service.SubtaskService; + +import java.util.List; + +@Slf4j +@CrossOrigin +@RestController +@RequestMapping("/subtask") +public class SubtaskController { + private final SubtaskService service; + + public SubtaskController(SubtaskService service) { + this.service = service; + } + + @PostMapping + public void create(@RequestBody SubtaskRequestCreatedDto dto) { + service.create(dto); + } + + @GetMapping("/{id}") + public SubtaskResponseDto findById(@PathVariable Long id) { + return service.findById(id); + } + + @GetMapping + public List findAll() { + return service.findAll(); + } +} diff --git a/service/src/main/java/service/task/manager/controller/TaskController.java b/service/src/main/java/service/task/manager/controller/TaskController.java new file mode 100644 index 0000000..9b65249 --- /dev/null +++ b/service/src/main/java/service/task/manager/controller/TaskController.java @@ -0,0 +1,37 @@ +package service.task.manager.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskResponseDto; +import service.task.manager.service.TaskService; + +import java.util.List; + +@Slf4j +@CrossOrigin +@RestController +@RequestMapping("/task") +public class TaskController { + private final TaskService service; + + public TaskController(TaskService service) { + this.service = service; + } + + @PostMapping + public void create(@RequestBody TaskRequestCreatedDto dto){ + service.create(dto); + } + + @GetMapping("/{id}") + public TaskResponseDto get(@PathVariable Long id){ + return service.findById(id); + } + + @GetMapping + public List getAll(){ + return service.findAll(); + } +} diff --git a/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java b/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java new file mode 100644 index 0000000..095df43 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java @@ -0,0 +1,22 @@ +package service.task.manager.dto.epic; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * DTO for {@link service.task.manager.model.Epic} + */ +@Builder +public record EpicRequestCreatedDto(@NotBlank + String name, + @NotBlank(message = "blank description") + String description, + @NotNull(message = "null start time") + LocalDateTime startTime, + @NotNull(message = "null duration") + Duration duration) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java b/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java new file mode 100644 index 0000000..9f9d053 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java @@ -0,0 +1,20 @@ +package service.task.manager.dto.epic; + +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; + +/** + * DTO for {@link service.task.manager.model.Epic} + */ +public record EpicResponseDto(Long id, List subtasks, String name, String description, Status status, + LocalDateTime startTime, Duration duration, LocalDateTime endTime, TaskType type) { + /** + * DTO for {@link service.task.manager.model.Subtask} + */ + public record SubtaskDto(Long id, String name, String description, Status status) { + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java new file mode 100644 index 0000000..810cad5 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java @@ -0,0 +1,20 @@ +package service.task.manager.dto.subtask; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import service.task.manager.model.Subtask; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * DTO for {@link Subtask} + */ +@Builder +public record SubtaskRequestCreatedDto(Long epicId, + @NotBlank(message = "blank name") String name, + @NotBlank(message = "blank description") String description, + @NotNull(message = "null start time") LocalDateTime startTime, + @NotNull Duration duration) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java b/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java new file mode 100644 index 0000000..eeaae9a --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java @@ -0,0 +1,14 @@ +package service.task.manager.dto.subtask; + +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * DTO for {@link service.task.manager.model.Subtask} + */ +public record SubtaskResponseDto(Long id, String name, String description, Status status, LocalDateTime startTime, + LocalDateTime endTime, Duration duration, TaskType type) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java b/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java new file mode 100644 index 0000000..a315852 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java @@ -0,0 +1,16 @@ +package service.task.manager.dto.task; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * DTO for {@link service.task.manager.model.Task} + */ +public record TaskRequestCreatedDto(@NotBlank(message = "blank name") String name, + @NotBlank(message = "blank description") String description, + @NotNull(message = "null start time ") LocalDateTime startTime, + @NotNull Duration duration) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java b/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java new file mode 100644 index 0000000..467e687 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java @@ -0,0 +1,14 @@ +package service.task.manager.dto.task; + +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * DTO for {@link service.task.manager.model.Task} + */ +public record TaskResponseDto(Long id, String name, String description, Status status, LocalDateTime startTime, + LocalDateTime endTime, Duration duration, TaskType type) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/error/ErrorHandler.java b/service/src/main/java/service/task/manager/error/ErrorHandler.java new file mode 100644 index 0000000..1466790 --- /dev/null +++ b/service/src/main/java/service/task/manager/error/ErrorHandler.java @@ -0,0 +1,32 @@ +package service.task.manager.error; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(NotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ErrorResponse handleNotFoundException(NotFoundException ex) { + return new ErrorResponse(ex.getMessage()); + } + + @ExceptionHandler(InvalidTaskDataException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleInvalidTaskDataException(InvalidTaskDataException ex) { + return new ErrorResponse(ex.getMessage()); + } + + @ExceptionHandler(TaskConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ErrorResponse handleTaskConstraintViolationException(TaskConstraintViolationException ex) { + return new ErrorResponse(ex.getMessage()); + } + + record ErrorResponse(String message) { + } + +} diff --git a/service/src/main/java/service/task/manager/error/InvalidTaskDataException.java b/service/src/main/java/service/task/manager/error/InvalidTaskDataException.java new file mode 100644 index 0000000..a74186e --- /dev/null +++ b/service/src/main/java/service/task/manager/error/InvalidTaskDataException.java @@ -0,0 +1,7 @@ +package service.task.manager.error; + +public class InvalidTaskDataException extends RuntimeException { + public InvalidTaskDataException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/error/NotFoundException.java b/service/src/main/java/service/task/manager/error/NotFoundException.java new file mode 100644 index 0000000..f4f56c6 --- /dev/null +++ b/service/src/main/java/service/task/manager/error/NotFoundException.java @@ -0,0 +1,7 @@ +package service.task.manager.error; + +public class NotFoundException extends RuntimeException { + public NotFoundException(String message) { + super(message); + } +} diff --git a/service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java b/service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java new file mode 100644 index 0000000..3a5b163 --- /dev/null +++ b/service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java @@ -0,0 +1,7 @@ +package service.task.manager.error; + +public class TaskConstraintViolationException extends RuntimeException { + public TaskConstraintViolationException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/mapper/EpicMapper.java b/service/src/main/java/service/task/manager/mapper/EpicMapper.java new file mode 100644 index 0000000..a683c61 --- /dev/null +++ b/service/src/main/java/service/task/manager/mapper/EpicMapper.java @@ -0,0 +1,23 @@ +package service.task.manager.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.model.Epic; +import service.task.manager.model.Subtask; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public interface EpicMapper { + + // Маппинг из DTO в сущность Epic + Epic toEntity(EpicRequestCreatedDto epicRequestCreatedDto); + + // Маппинг из сущности Epic в DTO ответа + EpicResponseDto toResponseDto(Epic epic); + + // Маппинг для подзадач (Subtask -> SubtaskDto) + EpicResponseDto.SubtaskDto toSubtaskDto(Subtask subtask); +} diff --git a/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java b/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java new file mode 100644 index 0000000..1db910a --- /dev/null +++ b/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java @@ -0,0 +1,17 @@ +package service.task.manager.mapper; + +import org.mapstruct.*; +import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.model.Epic; +import service.task.manager.model.Subtask; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public interface SubtaskMapper { + + // Маппинг из DTO в сущность Subtask + Subtask toEntity(SubtaskRequestCreatedDto subtaskRequestCreatedDto); + + // Маппинг из сущности Subtask в DTO ответа + SubtaskResponseDto toResponseDto(Subtask subtask); +} diff --git a/service/src/main/java/service/task/manager/mapper/TaskMapper.java b/service/src/main/java/service/task/manager/mapper/TaskMapper.java new file mode 100644 index 0000000..5a69a8a --- /dev/null +++ b/service/src/main/java/service/task/manager/mapper/TaskMapper.java @@ -0,0 +1,19 @@ +package service.task.manager.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; +import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskResponseDto; +import service.task.manager.model.Task; + +@Mapper(componentModel = "spring") +public interface TaskMapper { + + // Маппинг из DTO в сущность Task + Task toEntity(TaskRequestCreatedDto taskRequestCreatedDto); + + // Маппинг из сущности Task в DTO ответа + TaskResponseDto toResponseDto(Task task); +} diff --git a/service/src/main/java/service/task/manager/model/Epic.java b/service/src/main/java/service/task/manager/model/Epic.java new file mode 100644 index 0000000..33aac29 --- /dev/null +++ b/service/src/main/java/service/task/manager/model/Epic.java @@ -0,0 +1,70 @@ +package service.task.manager.model; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.proxy.HibernateProxy; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +@Getter +@ToString(exclude = "subtasks") +@Setter +@Entity +@Table(name = "epic") +public class Epic { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToMany(mappedBy = "epic", cascade = CascadeType.ALL, orphanRemoval = true) + private List subtasks = new ArrayList<>(); + + @Column(nullable = false) + private String name; + + private String description; + + @Enumerated(EnumType.STRING) + private Status status; + + @Column(name = "start_time") + private LocalDateTime startTime; + + private Duration duration; + + @Column(name = "end_time") + private LocalDateTime endTime; + + @Setter(AccessLevel.NONE) + @Enumerated(EnumType.STRING) + private TaskType type = TaskType.EPIC; + + @PrePersist + @PreUpdate + public void calculateEndTime() { + if (startTime != null && duration != null) { + endTime = startTime.plus(duration); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Epic epic)) return false; + return Objects.equals(id, epic.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/model/Subtask.java b/service/src/main/java/service/task/manager/model/Subtask.java new file mode 100644 index 0000000..b202776 --- /dev/null +++ b/service/src/main/java/service/task/manager/model/Subtask.java @@ -0,0 +1,67 @@ +package service.task.manager.model; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.proxy.HibernateProxy; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Objects; + +@Getter +@Setter +@ToString +@RequiredArgsConstructor +@Entity +@Table(name = "subtask") +public class Subtask { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "epic_id") + private Epic epic; + + @Column(nullable = false) + private String name; + + private String description; + + @Enumerated(EnumType.STRING) + private Status status; + + @Column(name = "start_time") + private LocalDateTime startTime; + + @Column(name = "end_time") + private LocalDateTime endTime; + + private Duration duration; + + @Setter(AccessLevel.NONE) + @Enumerated(EnumType.STRING) + private TaskType type = TaskType.SUBTASK; + + @PrePersist + @PreUpdate + public void calculateEndTime() { + if (startTime != null && duration != null) { + endTime = startTime.plus(duration); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Subtask subtask)) return false; + return Objects.equals(id, subtask.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/model/Task.java b/service/src/main/java/service/task/manager/model/Task.java new file mode 100644 index 0000000..f133ea3 --- /dev/null +++ b/service/src/main/java/service/task/manager/model/Task.java @@ -0,0 +1,65 @@ +package service.task.manager.model; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.proxy.HibernateProxy; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Objects; + +@Getter +@Setter +@ToString +@Entity +@Table(name = "task") +public class Task { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String name; + + private String description; + + @Enumerated(EnumType.STRING) + private Status status; + + @Column(name = "start_time") + private LocalDateTime startTime; + + @Column(name = "end_time") + private LocalDateTime endTime; + + private Duration duration; + + @Setter(AccessLevel.NONE) + @Enumerated(EnumType.STRING) + private TaskType type = TaskType.TASK; + + @PrePersist + @PreUpdate + public void calculateEndTime() { + if (startTime != null && duration != null) { + endTime = startTime.plus(duration); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Task task)) return false; + return Objects.equals(id, task.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } +} \ No newline at end of file diff --git a/src/task/manager/schedule/model/Status.java b/service/src/main/java/service/task/manager/model/enums/Status.java similarity index 58% rename from src/task/manager/schedule/model/Status.java rename to service/src/main/java/service/task/manager/model/enums/Status.java index daf6206..19818b4 100644 --- a/src/task/manager/schedule/model/Status.java +++ b/service/src/main/java/service/task/manager/model/enums/Status.java @@ -1,4 +1,4 @@ -package task.manager.schedule.model; +package service.task.manager.model.enums; public enum Status { NEW, diff --git a/src/task/manager/schedule/model/TaskType.java b/service/src/main/java/service/task/manager/model/enums/TaskType.java similarity index 58% rename from src/task/manager/schedule/model/TaskType.java rename to service/src/main/java/service/task/manager/model/enums/TaskType.java index 8b745a0..3866070 100644 --- a/src/task/manager/schedule/model/TaskType.java +++ b/service/src/main/java/service/task/manager/model/enums/TaskType.java @@ -1,4 +1,4 @@ -package task.manager.schedule.model; +package service.task.manager.model.enums; public enum TaskType { TASK, diff --git a/service/src/main/java/service/task/manager/repository/EpicRepository.java b/service/src/main/java/service/task/manager/repository/EpicRepository.java new file mode 100644 index 0000000..833effa --- /dev/null +++ b/service/src/main/java/service/task/manager/repository/EpicRepository.java @@ -0,0 +1,9 @@ +package service.task.manager.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; +import service.task.manager.model.Epic; + +@Repository +public interface EpicRepository extends CrudRepository { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/repository/SubtaskRepository.java b/service/src/main/java/service/task/manager/repository/SubtaskRepository.java new file mode 100644 index 0000000..f74fbed --- /dev/null +++ b/service/src/main/java/service/task/manager/repository/SubtaskRepository.java @@ -0,0 +1,9 @@ +package service.task.manager.repository; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; +import service.task.manager.model.Subtask; + +@Repository +public interface SubtaskRepository extends CrudRepository { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/repository/TaskRepository.java b/service/src/main/java/service/task/manager/repository/TaskRepository.java new file mode 100644 index 0000000..13cfa45 --- /dev/null +++ b/service/src/main/java/service/task/manager/repository/TaskRepository.java @@ -0,0 +1,9 @@ +package service.task.manager.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import service.task.manager.model.Task; + +@Repository +public interface TaskRepository extends JpaRepository { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/service/EpicService.java b/service/src/main/java/service/task/manager/service/EpicService.java new file mode 100644 index 0000000..2814022 --- /dev/null +++ b/service/src/main/java/service/task/manager/service/EpicService.java @@ -0,0 +1,14 @@ +package service.task.manager.service; + +import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicResponseDto; + +import java.util.List; + +public interface EpicService { + void create(EpicRequestCreatedDto dto); + + EpicResponseDto findById(Long id); + + List findAll(); +} diff --git a/service/src/main/java/service/task/manager/service/SubtaskService.java b/service/src/main/java/service/task/manager/service/SubtaskService.java new file mode 100644 index 0000000..401e0cb --- /dev/null +++ b/service/src/main/java/service/task/manager/service/SubtaskService.java @@ -0,0 +1,15 @@ +package service.task.manager.service; + +import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.model.Subtask; + +import java.util.List; + +public interface SubtaskService { + void create(SubtaskRequestCreatedDto dto); + + SubtaskResponseDto findById(Long id); + + List findAll(); +} diff --git a/service/src/main/java/service/task/manager/service/TaskService.java b/service/src/main/java/service/task/manager/service/TaskService.java new file mode 100644 index 0000000..d442afe --- /dev/null +++ b/service/src/main/java/service/task/manager/service/TaskService.java @@ -0,0 +1,14 @@ +package service.task.manager.service; + +import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskResponseDto; + +import java.util.List; + +public interface TaskService { + void create(TaskRequestCreatedDto dto); + + TaskResponseDto findById(Long id); + + List findAll(); +} diff --git a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java new file mode 100644 index 0000000..6b55f90 --- /dev/null +++ b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java @@ -0,0 +1,46 @@ +package service.task.manager.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.mapper.EpicMapper; +import service.task.manager.repository.EpicRepository; +import service.task.manager.service.EpicService; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class EpicServiceImpl implements EpicService { + private final EpicRepository repository; + private final EpicMapper mapper; + + + /** + * @param dto + */ + @Override + public void create(EpicRequestCreatedDto dto) { + + } + + /** + * @param id + * @return + */ + @Override + public EpicResponseDto findById(Long id) { + return null; + } + + /** + * @return + */ + @Override + public List findAll() { + return List.of(); + } +} diff --git a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java new file mode 100644 index 0000000..dbc5d11 --- /dev/null +++ b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java @@ -0,0 +1,47 @@ +package service.task.manager.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.mapper.SubtaskMapper; +import service.task.manager.repository.SubtaskRepository; +import service.task.manager.service.SubtaskService; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SubtaskServiceImpl implements SubtaskService { + private final SubtaskRepository repository; + private final SubtaskMapper mapper; + + + + /** + * @param dto + */ + @Override + public void create(SubtaskRequestCreatedDto dto) { + + } + + /** + * @param id + * @return + */ + @Override + public SubtaskResponseDto findById(Long id) { + return null; + } + + /** + * @return + */ + @Override + public List findAll() { + return List.of(); + } +} diff --git a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java new file mode 100644 index 0000000..2d52f57 --- /dev/null +++ b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java @@ -0,0 +1,45 @@ +package service.task.manager.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskResponseDto; +import service.task.manager.mapper.TaskMapper; +import service.task.manager.repository.TaskRepository; +import service.task.manager.service.TaskService; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class TaskServiceImpl implements TaskService { + private final TaskRepository repository; + private final TaskMapper mapper; + + /** + * @param dto + */ + @Override + public void create(TaskRequestCreatedDto dto) { + //repository.save(mapper.toEntity(dto)); + } + + /** + * @param id + * @return + */ + @Override + public TaskResponseDto findById(Long id) { + return null; + } + + /** + * @return + */ + @Override + public List findAll() { + return List.of(); + } +} diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml new file mode 100644 index 0000000..04967c9 --- /dev/null +++ b/service/src/main/resources/application.yml @@ -0,0 +1,13 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + username: sa + password: + driver-class-name: org.h2.Driver + sql: + init: + mode: always + schema-locations: classpath:schema.sql + jpa: + properties: + hibernate.dialect: org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/service/src/main/resources/schema.sql b/service/src/main/resources/schema.sql new file mode 100644 index 0000000..4135a0a --- /dev/null +++ b/service/src/main/resources/schema.sql @@ -0,0 +1,45 @@ +-- Таблица для Epic +CREATE TABLE epic +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + status VARCHAR(50), + start_time TIMESTAMP, + duration BIGINT, -- Хранится в секундах + end_time TIMESTAMP, + type VARCHAR(50) NOT NULL +); + +-- Таблица для Subtask +CREATE TABLE subtask +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + epic_id BIGINT, + name VARCHAR(255) NOT NULL, + description TEXT, + status VARCHAR(50), + start_time TIMESTAMP, + duration BIGINT, -- Хранится в секундах + end_time TIMESTAMP, + type VARCHAR(50) NOT NULL, + FOREIGN KEY (epic_id) REFERENCES epic (id) ON DELETE CASCADE +); + +-- Таблица для Task +CREATE TABLE task +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + status VARCHAR(50), + start_time TIMESTAMP, + duration BIGINT, -- Хранится в секундах + end_time TIMESTAMP, + type VARCHAR(50) NOT NULL +); + +-- Опционально: Добавление индексов для оптимизации +CREATE INDEX idx_epic_status ON epic (status); +CREATE INDEX idx_subtask_epic_id ON subtask (epic_id); +CREATE INDEX idx_task_status ON task (status); \ No newline at end of file diff --git a/service/target/classes/application.yml b/service/target/classes/application.yml new file mode 100644 index 0000000..04967c9 --- /dev/null +++ b/service/target/classes/application.yml @@ -0,0 +1,13 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + username: sa + password: + driver-class-name: org.h2.Driver + sql: + init: + mode: always + schema-locations: classpath:schema.sql + jpa: + properties: + hibernate.dialect: org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/service/target/classes/schema.sql b/service/target/classes/schema.sql new file mode 100644 index 0000000..4135a0a --- /dev/null +++ b/service/target/classes/schema.sql @@ -0,0 +1,45 @@ +-- Таблица для Epic +CREATE TABLE epic +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + status VARCHAR(50), + start_time TIMESTAMP, + duration BIGINT, -- Хранится в секундах + end_time TIMESTAMP, + type VARCHAR(50) NOT NULL +); + +-- Таблица для Subtask +CREATE TABLE subtask +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + epic_id BIGINT, + name VARCHAR(255) NOT NULL, + description TEXT, + status VARCHAR(50), + start_time TIMESTAMP, + duration BIGINT, -- Хранится в секундах + end_time TIMESTAMP, + type VARCHAR(50) NOT NULL, + FOREIGN KEY (epic_id) REFERENCES epic (id) ON DELETE CASCADE +); + +-- Таблица для Task +CREATE TABLE task +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, + status VARCHAR(50), + start_time TIMESTAMP, + duration BIGINT, -- Хранится в секундах + end_time TIMESTAMP, + type VARCHAR(50) NOT NULL +); + +-- Опционально: Добавление индексов для оптимизации +CREATE INDEX idx_epic_status ON epic (status); +CREATE INDEX idx_subtask_epic_id ON subtask (epic_id); +CREATE INDEX idx_task_status ON task (status); \ No newline at end of file diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java new file mode 100644 index 0000000..34204e8 --- /dev/null +++ b/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java @@ -0,0 +1,104 @@ +package service.task.manager.mapper; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; +import org.springframework.stereotype.Component; +import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.model.Epic; +import service.task.manager.model.Subtask; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2025-04-26T20:14:42+0300", + comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +@Component +public class EpicMapperImpl implements EpicMapper { + + @Override + public Epic toEntity(EpicRequestCreatedDto epicRequestCreatedDto) { + if ( epicRequestCreatedDto == null ) { + return null; + } + + Epic epic = new Epic(); + + epic.setName( epicRequestCreatedDto.name() ); + epic.setDescription( epicRequestCreatedDto.description() ); + epic.setStartTime( epicRequestCreatedDto.startTime() ); + epic.setDuration( epicRequestCreatedDto.duration() ); + + return epic; + } + + @Override + public EpicResponseDto toResponseDto(Epic epic) { + if ( epic == null ) { + return null; + } + + Long id = null; + List subtasks = null; + String name = null; + String description = null; + Status status = null; + LocalDateTime startTime = null; + Duration duration = null; + LocalDateTime endTime = null; + TaskType type = null; + + id = epic.getId(); + subtasks = subtaskListToSubtaskDtoList( epic.getSubtasks() ); + name = epic.getName(); + description = epic.getDescription(); + status = epic.getStatus(); + startTime = epic.getStartTime(); + duration = epic.getDuration(); + endTime = epic.getEndTime(); + type = epic.getType(); + + EpicResponseDto epicResponseDto = new EpicResponseDto( id, subtasks, name, description, status, startTime, duration, endTime, type ); + + return epicResponseDto; + } + + @Override + public EpicResponseDto.SubtaskDto toSubtaskDto(Subtask subtask) { + if ( subtask == null ) { + return null; + } + + Long id = null; + String name = null; + String description = null; + Status status = null; + + id = subtask.getId(); + name = subtask.getName(); + description = subtask.getDescription(); + status = subtask.getStatus(); + + EpicResponseDto.SubtaskDto subtaskDto = new EpicResponseDto.SubtaskDto( id, name, description, status ); + + return subtaskDto; + } + + protected List subtaskListToSubtaskDtoList(List list) { + if ( list == null ) { + return null; + } + + List list1 = new ArrayList( list.size() ); + for ( Subtask subtask : list ) { + list1.add( toSubtaskDto( subtask ) ); + } + + return list1; + } +} diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java new file mode 100644 index 0000000..cf59bb0 --- /dev/null +++ b/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java @@ -0,0 +1,65 @@ +package service.task.manager.mapper; + +import java.time.Duration; +import java.time.LocalDateTime; +import javax.annotation.processing.Generated; +import org.springframework.stereotype.Component; +import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.model.Subtask; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2025-04-26T20:14:42+0300", + comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +@Component +public class SubtaskMapperImpl implements SubtaskMapper { + + @Override + public Subtask toEntity(SubtaskRequestCreatedDto subtaskRequestCreatedDto) { + if ( subtaskRequestCreatedDto == null ) { + return null; + } + + Subtask subtask = new Subtask(); + + subtask.setName( subtaskRequestCreatedDto.name() ); + subtask.setDescription( subtaskRequestCreatedDto.description() ); + subtask.setStartTime( subtaskRequestCreatedDto.startTime() ); + subtask.setDuration( subtaskRequestCreatedDto.duration() ); + + return subtask; + } + + @Override + public SubtaskResponseDto toResponseDto(Subtask subtask) { + if ( subtask == null ) { + return null; + } + + Long id = null; + String name = null; + String description = null; + Status status = null; + LocalDateTime startTime = null; + LocalDateTime endTime = null; + Duration duration = null; + TaskType type = null; + + id = subtask.getId(); + name = subtask.getName(); + description = subtask.getDescription(); + status = subtask.getStatus(); + startTime = subtask.getStartTime(); + endTime = subtask.getEndTime(); + duration = subtask.getDuration(); + type = subtask.getType(); + + SubtaskResponseDto subtaskResponseDto = new SubtaskResponseDto( id, name, description, status, startTime, endTime, duration, type ); + + return subtaskResponseDto; + } +} diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java new file mode 100644 index 0000000..3653f65 --- /dev/null +++ b/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java @@ -0,0 +1,65 @@ +package service.task.manager.mapper; + +import java.time.Duration; +import java.time.LocalDateTime; +import javax.annotation.processing.Generated; +import org.springframework.stereotype.Component; +import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskResponseDto; +import service.task.manager.model.Task; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2025-04-26T20:14:42+0300", + comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" +) +@Component +public class TaskMapperImpl implements TaskMapper { + + @Override + public Task toEntity(TaskRequestCreatedDto taskRequestCreatedDto) { + if ( taskRequestCreatedDto == null ) { + return null; + } + + Task task = new Task(); + + task.setName( taskRequestCreatedDto.name() ); + task.setDescription( taskRequestCreatedDto.description() ); + task.setStartTime( taskRequestCreatedDto.startTime() ); + task.setDuration( taskRequestCreatedDto.duration() ); + + return task; + } + + @Override + public TaskResponseDto toResponseDto(Task task) { + if ( task == null ) { + return null; + } + + Long id = null; + String name = null; + String description = null; + Status status = null; + LocalDateTime startTime = null; + LocalDateTime endTime = null; + Duration duration = null; + TaskType type = null; + + id = task.getId(); + name = task.getName(); + description = task.getDescription(); + status = task.getStatus(); + startTime = task.getStartTime(); + endTime = task.getEndTime(); + duration = task.getDuration(); + type = task.getType(); + + TaskResponseDto taskResponseDto = new TaskResponseDto( id, name, description, status, startTime, endTime, duration, type ); + + return taskResponseDto; + } +} diff --git a/service/target/maven-archiver/pom.properties b/service/target/maven-archiver/pom.properties new file mode 100644 index 0000000..bacc968 --- /dev/null +++ b/service/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=service +groupId=service.task.manager +version=1.0-SNAPSHOT diff --git a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..167b84a --- /dev/null +++ b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,38 @@ +service/task/manager/dto/task/TaskRequestCreatedDto.class +service/task/manager/model/Subtask.class +service/task/manager/service/EpicService.class +service/task/manager/model/enums/Status.class +service/task/manager/dto/epic/EpicResponseDto$SubtaskDto.class +service/task/manager/dto/subtask/SubtaskRequestCreatedDto.class +service/task/manager/dto/epic/EpicRequestCreatedDto$EpicRequestCreatedDtoBuilder.class +service/task/manager/error/ErrorHandler.class +service/task/manager/dto/subtask/SubtaskResponseDto.class +service/task/manager/mapper/EpicMapper.class +service/task/manager/error/NotFoundException.class +service/task/manager/repository/SubtaskRepository.class +service/task/manager/dto/epic/EpicResponseDto.class +service/task/manager/model/enums/TaskType.class +service/task/manager/error/ErrorHandler$ErrorResponse.class +service/task/manager/service/impl/SubtaskServiceImpl.class +service/task/manager/mapper/SubtaskMapper.class +service/task/manager/repository/EpicRepository.class +service/task/manager/repository/TaskRepository.class +service/task/manager/error/TaskConstraintViolationException.class +service/task/manager/dto/epic/EpicRequestCreatedDto.class +service/task/manager/dto/task/TaskResponseDto.class +service/task/manager/service/SubtaskService.class +service/task/manager/controller/TaskController.class +service/task/manager/service/impl/EpicServiceImpl.class +service/task/manager/controller/EpicController.class +service/task/manager/model/Epic.class +service/task/manager/TaskManagerApplication.class +service/task/manager/mapper/EpicMapperImpl.class +service/task/manager/controller/SubtaskController.class +service/task/manager/mapper/TaskMapperImpl.class +service/task/manager/dto/subtask/SubtaskRequestCreatedDto$SubtaskRequestCreatedDtoBuilder.class +service/task/manager/model/Task.class +service/task/manager/service/impl/TaskServiceImpl.class +service/task/manager/mapper/TaskMapper.class +service/task/manager/service/TaskService.class +service/task/manager/mapper/SubtaskMapperImpl.class +service/task/manager/error/InvalidTaskDataException.class diff --git a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..dd2d2ab --- /dev/null +++ b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,31 @@ +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/SubtaskService.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/controller/EpicController.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/mapper/EpicMapper.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/InvalidTaskDataException.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/controller/SubtaskController.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/TaskRepository.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/mapper/TaskMapper.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/TaskManagerApplication.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/ErrorHandler.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/enums/Status.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Epic.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/SubtaskRepository.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/controller/TaskController.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Task.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Subtask.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/EpicRepository.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/TaskService.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/EpicService.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/NotFoundException.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/enums/TaskType.java diff --git a/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/service/target/service-1.0-SNAPSHOT.jar.original b/service/target/service-1.0-SNAPSHOT.jar.original new file mode 100644 index 0000000000000000000000000000000000000000..6b1d4faa1cc299135d841aa3652eda8ab22be527 GIT binary patch literal 42560 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 p*PAzu;kL08h>miRV5}u|0zBA#)iV(` subtaskIds = new ArrayList<>(); - private LocalDateTime endTime; - - public Epic(String name, Status status, String description, LocalDateTime startTime, long duration) { - super(name, status, description, startTime, duration); - } - - public Epic(int id, String name, String description, Status status, LocalDateTime startTime, long duration) { - super(name, status, description, startTime, duration); - setId(id); - } - - public void addSubtaskId(int id) { - subtaskIds.add(id); - } - - public void cleanSubtask() { - subtaskIds.clear(); - } - - public void removeSubtask(int id) { - subtaskIds.remove(id); - } - - public List getSubtaskIds() { - return subtaskIds; - } - - public TaskType getType() { - return TaskType.EPIC; - } - - public void setEndTime(LocalDateTime endTime) { - this.endTime = endTime; - } - - @Override - public String toString() { - return "Epic{" + - "subtaskIds=" + subtaskIds + - ", endTime=" + endTime + - '}'; - } -} diff --git a/src/task/manager/schedule/model/Subtask.java b/src/task/manager/schedule/model/Subtask.java deleted file mode 100644 index 0ff7bf7..0000000 --- a/src/task/manager/schedule/model/Subtask.java +++ /dev/null @@ -1,41 +0,0 @@ -package task.manager.schedule.model; - -import java.time.LocalDateTime; - -public class Subtask extends Task { - - private Integer epicId; - - public Subtask(String name, Status status, String description, LocalDateTime startTime, long duration, int epicId) { - - super(name, status, description, startTime, duration); - setEpicId(epicId); - } - - public Subtask(int id, String name, String description, Status status, LocalDateTime startTime, long duration, Integer epicId) { - super(name, status, description, startTime, duration); - setId(id); - setEpicId(epicId); - } - - @Override - public Integer getEpicId() { - return epicId; - } - - @Override - public TaskType getType() { - return TaskType.SUBTASK; - } - - public void setEpicId(Integer epicId) { - this.epicId = epicId; - } - - @Override - public String toString() { - return "Subtask{" + - "epicId=" + epicId + - '}'; - } -} diff --git a/src/task/manager/schedule/model/Task.java b/src/task/manager/schedule/model/Task.java deleted file mode 100644 index bcc4e8c..0000000 --- a/src/task/manager/schedule/model/Task.java +++ /dev/null @@ -1,118 +0,0 @@ -package task.manager.schedule.model; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.Objects; - -public class Task { - private int id; - private String name; - private String description; - private Status status; - - private LocalDateTime startTime; // LocalDateTime - private Duration duration; // минуты или Duration - - public Task(String name, Status status, String description, LocalDateTime startTime, long duration) { - this.name = name; - this.status = status; - this.description = description; - this.startTime = LocalDateTime.from(startTime); - this.duration = Duration.ofMinutes(duration); - } - - public Task(int id, String name, String description, Status status, LocalDateTime startTime, long duration) { - setId(id); - this.name = name; - this.status = status; - this.description = description; - this.startTime = startTime; - this.duration = Duration.ofMinutes(duration); - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public String getName() { - return name; - } - - public Integer getEpicId() { - return null; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description; - } - - public TaskType getType() { - return TaskType.TASK; - } - - public void setDescription(String description) { - this.description = description; - } - - public Status getStatus() { - return status; - } - - public void setStatus(Status status) { - this.status = status; - } - - public LocalDateTime getStartTime() { - return startTime; - } - - public void setStartTime(LocalDateTime startTime) { - this.startTime = startTime; - } - - public long getDuration() { - return duration.toMinutesPart(); - } - - public void setDuration(long duration) { - this.duration = Duration.ofMinutes(duration); - } - - public LocalDateTime getEndTime() { - return startTime.plus(duration); - } - - - @Override - public String toString() { - return "Task{" + - "id=" + id + - ", name='" + name + '\'' + - ", description='" + description + '\'' + - ", status=" + status + - ", startTime=" + startTime + - ", endTime=" + getEndTime() + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Task task = (Task) o; - return id == task.id && Objects.equals(name, task.name) && Objects.equals(description, task.description) && status == task.status && Objects.equals(startTime, task.startTime) && Objects.equals(duration, task.duration); - } - - @Override - public int hashCode() { - return Objects.hash(id, name, description, status, startTime, duration); - } -} diff --git a/src/task/manager/schedule/service/FileBackedTaskManager.java b/src/task/manager/schedule/service/FileBackedTaskManager.java index 861022e..eb0a0ad 100644 --- a/src/task/manager/schedule/service/FileBackedTaskManager.java +++ b/src/task/manager/schedule/service/FileBackedTaskManager.java @@ -11,7 +11,6 @@ import task.manager.schedule.exception.ManagerSaveException; import ru.yandex.javacource.golotin.schedule.model.*; import task.manager.schedule.service.inMemory.InMemoryTaskManager; -import task.manager.schedule.model.*; public class FileBackedTaskManager extends InMemoryTaskManager { From 3caa454e64550733a5f0b70773e2d83ffe770731 Mon Sep 17 00:00:00 2001 From: YG Date: Sat, 26 Apr 2025 20:56:57 +0300 Subject: [PATCH 2/9] feat: add all update entity --- .../manager/controller/EpicController.java | 23 +++-- .../manager/controller/SubtaskController.java | 23 +++-- .../manager/controller/TaskController.java | 23 +++-- .../dto/epic/EpicRequestUpdatedDto.java | 18 ++++ .../dto/subtask/SubtaskRequestUpdatedDto.java | 17 ++++ .../dto/task/TaskRequestUpdatedDto.java | 18 ++++ .../task/manager/mapper/EpicMapper.java | 12 ++- .../task/manager/mapper/SubtaskMapper.java | 3 + .../task/manager/mapper/TaskMapper.java | 8 +- .../manager/repository/EpicRepository.java | 4 +- .../manager/repository/SubtaskRepository.java | 4 +- .../task/manager/service/EpicService.java | 5 ++ .../task/manager/service/SubtaskService.java | 6 +- .../task/manager/service/TaskService.java | 5 ++ .../manager/service/impl/EpicServiceImpl.java | 32 ++++++- .../service/impl/SubtaskServiceImpl.java | 32 ++++++- .../manager/service/impl/TaskServiceImpl.java | 33 ++++++- .../task/manager/mapper/EpicMapperImpl.java | 84 +++++++++++++++++- .../manager/mapper/SubtaskMapperImpl.java | 2 +- .../task/manager/mapper/TaskMapperImpl.java | 43 ++++++++- .../compile/default-compile/createdFiles.lst | 3 + .../compile/default-compile/inputFiles.lst | 3 + .../target/service-1.0-SNAPSHOT.jar.original | Bin 42560 -> 50419 bytes 23 files changed, 363 insertions(+), 38 deletions(-) create mode 100644 service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java create mode 100644 service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java create mode 100644 service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java diff --git a/service/src/main/java/service/task/manager/controller/EpicController.java b/service/src/main/java/service/task/manager/controller/EpicController.java index f100995..35dc8ce 100644 --- a/service/src/main/java/service/task/manager/controller/EpicController.java +++ b/service/src/main/java/service/task/manager/controller/EpicController.java @@ -1,9 +1,13 @@ package service.task.manager.controller; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; import service.task.manager.service.EpicService; @@ -12,21 +16,23 @@ @Slf4j @CrossOrigin @RestController +@RequiredArgsConstructor @RequestMapping("/epic") public class EpicController { private final EpicService service; - public EpicController(EpicService service) { - this.service = service; - } - @PostMapping - public void create(@RequestBody EpicRequestCreatedDto dto) { + public void create(@RequestBody @Valid EpicRequestCreatedDto dto) { service.create(dto); } + @PutMapping + public EpicResponseDto update(@RequestBody @Valid EpicRequestUpdatedDto dto) { + return service.update(dto); + } + @GetMapping("/{id}") - public EpicResponseDto findById(@PathVariable Long id) { + public EpicResponseDto findById(@PathVariable @Positive @NotNull Long id) { return service.findById(id); } @@ -34,4 +40,9 @@ public EpicResponseDto findById(@PathVariable Long id) { public List findAll() { return service.findAll(); } + + @DeleteMapping("/{id}") + public void delete(@PathVariable @Positive @NotNull Long id) { + service.delete(id); + } } diff --git a/service/src/main/java/service/task/manager/controller/SubtaskController.java b/service/src/main/java/service/task/manager/controller/SubtaskController.java index 222ef79..94e1e3e 100644 --- a/service/src/main/java/service/task/manager/controller/SubtaskController.java +++ b/service/src/main/java/service/task/manager/controller/SubtaskController.java @@ -1,9 +1,13 @@ package service.task.manager.controller; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; import service.task.manager.service.SubtaskService; @@ -12,21 +16,23 @@ @Slf4j @CrossOrigin @RestController +@RequiredArgsConstructor @RequestMapping("/subtask") public class SubtaskController { private final SubtaskService service; - public SubtaskController(SubtaskService service) { - this.service = service; - } - @PostMapping - public void create(@RequestBody SubtaskRequestCreatedDto dto) { + public void create(@RequestBody @Valid SubtaskRequestCreatedDto dto) { service.create(dto); } + @PutMapping + public void update(@RequestBody @Valid SubtaskRequestUpdatedDto dto) { + service.update(dto); + } + @GetMapping("/{id}") - public SubtaskResponseDto findById(@PathVariable Long id) { + public SubtaskResponseDto findById(@PathVariable @Positive @NotNull Long id) { return service.findById(id); } @@ -34,4 +40,9 @@ public SubtaskResponseDto findById(@PathVariable Long id) { public List findAll() { return service.findAll(); } + + @DeleteMapping("/{id}") + public void delete(@PathVariable @Positive @NotNull Long id) { + service.delete(id); + } } diff --git a/service/src/main/java/service/task/manager/controller/TaskController.java b/service/src/main/java/service/task/manager/controller/TaskController.java index 9b65249..b378861 100644 --- a/service/src/main/java/service/task/manager/controller/TaskController.java +++ b/service/src/main/java/service/task/manager/controller/TaskController.java @@ -1,9 +1,13 @@ package service.task.manager.controller; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; import service.task.manager.service.TaskService; @@ -12,21 +16,23 @@ @Slf4j @CrossOrigin @RestController +@RequiredArgsConstructor @RequestMapping("/task") public class TaskController { private final TaskService service; - public TaskController(TaskService service) { - this.service = service; - } - @PostMapping - public void create(@RequestBody TaskRequestCreatedDto dto){ + public void create(@RequestBody @Valid TaskRequestCreatedDto dto) { service.create(dto); } + @PutMapping + public void update(@RequestBody @Valid TaskRequestUpdatedDto dto) { + service.update(dto); + } + @GetMapping("/{id}") - public TaskResponseDto get(@PathVariable Long id){ + public TaskResponseDto get(@PathVariable @Positive @NotNull Long id) { return service.findById(id); } @@ -34,4 +40,9 @@ public TaskResponseDto get(@PathVariable Long id){ public List getAll(){ return service.findAll(); } + + @DeleteMapping("/{id}") + public void delete(@PathVariable @Positive @NotNull Long id) { + service.delete(id); + } } diff --git a/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java b/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java new file mode 100644 index 0000000..4053264 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java @@ -0,0 +1,18 @@ +package service.task.manager.dto.epic; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import service.task.manager.model.Epic; +import service.task.manager.model.enums.Status; + +import java.time.Duration; + +/** + * DTO for {@link Epic} + */ +public record EpicRequestUpdatedDto(@NotNull(message = "null id") @Positive(message = "not positive id") Long id, + @NotBlank(message = "blank name") String name, + @NotBlank(message = "blank description") String description, + @NotNull(message = "null status") Status status, @NotNull Duration duration) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java new file mode 100644 index 0000000..a6caa7a --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java @@ -0,0 +1,17 @@ +package service.task.manager.dto.subtask; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import service.task.manager.model.enums.Status; + +import java.time.Duration; + +/** + * DTO for {@link service.task.manager.model.Subtask} + */ +public record SubtaskRequestUpdatedDto(@NotNull(message = "null id") @Positive(message = "not positive id") Long id, + @NotBlank(message = "blank name") String name, + @NotBlank(message = "blank description") String description, + @NotNull(message = "null status") Status status, @NotNull Duration duration) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java b/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java new file mode 100644 index 0000000..1b9d7f1 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java @@ -0,0 +1,18 @@ +package service.task.manager.dto.task; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import service.task.manager.model.enums.Status; + +import java.time.Duration; + +/** + * DTO for {@link service.task.manager.model.Task} + */ +public record TaskRequestUpdatedDto(@NotNull(message = "null id") @Positive(message = "not positive id") Long id, + @NotBlank(message = "blank name") String name, + @NotBlank(message = "blank description") String description, + @NotNull(message = "null status") Status status, + @NotNull(message = "null duration") Duration duration) { +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/mapper/EpicMapper.java b/service/src/main/java/service/task/manager/mapper/EpicMapper.java index a683c61..46c6457 100644 --- a/service/src/main/java/service/task/manager/mapper/EpicMapper.java +++ b/service/src/main/java/service/task/manager/mapper/EpicMapper.java @@ -1,12 +1,12 @@ package service.task.manager.mapper; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.ReportingPolicy; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.epic.EpicRequestCreatedDto; import service.task.manager.dto.epic.EpicResponseDto; import service.task.manager.model.Epic; +import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.model.Subtask; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) @@ -20,4 +20,12 @@ public interface EpicMapper { // Маппинг для подзадач (Subtask -> SubtaskDto) EpicResponseDto.SubtaskDto toSubtaskDto(Subtask subtask); + + Subtask toEntity(SubtaskRequestUpdatedDto subtaskRequestUpdatedDto); + + SubtaskRequestUpdatedDto toSubtaskRequestUpdatedDto(Subtask subtask); + + Epic toEntity(EpicRequestUpdatedDto epicRequestUpdatedDto); + + EpicRequestUpdatedDto toEpicDto(Epic epic); } diff --git a/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java b/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java index 1db910a..6ae312a 100644 --- a/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java +++ b/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java @@ -2,6 +2,7 @@ import org.mapstruct.*; import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; import service.task.manager.model.Epic; import service.task.manager.model.Subtask; @@ -14,4 +15,6 @@ public interface SubtaskMapper { // Маппинг из сущности Subtask в DTO ответа SubtaskResponseDto toResponseDto(Subtask subtask); + + Subtask toEntity(SubtaskRequestUpdatedDto subtaskRequestUpdatedDto); } diff --git a/service/src/main/java/service/task/manager/mapper/TaskMapper.java b/service/src/main/java/service/task/manager/mapper/TaskMapper.java index 5a69a8a..06db9e0 100644 --- a/service/src/main/java/service/task/manager/mapper/TaskMapper.java +++ b/service/src/main/java/service/task/manager/mapper/TaskMapper.java @@ -1,9 +1,7 @@ package service.task.manager.mapper; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingConstants; -import org.mapstruct.ReportingPolicy; +import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskRequestCreatedDto; import service.task.manager.dto.task.TaskResponseDto; import service.task.manager.model.Task; @@ -16,4 +14,8 @@ public interface TaskMapper { // Маппинг из сущности Task в DTO ответа TaskResponseDto toResponseDto(Task task); + + Task toEntity(TaskRequestUpdatedDto taskRequestUpdatedDto); + + TaskRequestUpdatedDto toTaskRequestUpdatedDto(Task task); } diff --git a/service/src/main/java/service/task/manager/repository/EpicRepository.java b/service/src/main/java/service/task/manager/repository/EpicRepository.java index 833effa..85a5c79 100644 --- a/service/src/main/java/service/task/manager/repository/EpicRepository.java +++ b/service/src/main/java/service/task/manager/repository/EpicRepository.java @@ -1,9 +1,9 @@ package service.task.manager.repository; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import service.task.manager.model.Epic; @Repository -public interface EpicRepository extends CrudRepository { +public interface EpicRepository extends JpaRepository { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/repository/SubtaskRepository.java b/service/src/main/java/service/task/manager/repository/SubtaskRepository.java index f74fbed..28b3dca 100644 --- a/service/src/main/java/service/task/manager/repository/SubtaskRepository.java +++ b/service/src/main/java/service/task/manager/repository/SubtaskRepository.java @@ -1,9 +1,9 @@ package service.task.manager.repository; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import service.task.manager.model.Subtask; @Repository -public interface SubtaskRepository extends CrudRepository { +public interface SubtaskRepository extends JpaRepository { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/service/EpicService.java b/service/src/main/java/service/task/manager/service/EpicService.java index 2814022..67894f2 100644 --- a/service/src/main/java/service/task/manager/service/EpicService.java +++ b/service/src/main/java/service/task/manager/service/EpicService.java @@ -1,6 +1,7 @@ package service.task.manager.service; import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; import java.util.List; @@ -8,7 +9,11 @@ public interface EpicService { void create(EpicRequestCreatedDto dto); + EpicResponseDto update(EpicRequestUpdatedDto dto); + EpicResponseDto findById(Long id); List findAll(); + + void delete(Long id); } diff --git a/service/src/main/java/service/task/manager/service/SubtaskService.java b/service/src/main/java/service/task/manager/service/SubtaskService.java index 401e0cb..7ebf70b 100644 --- a/service/src/main/java/service/task/manager/service/SubtaskService.java +++ b/service/src/main/java/service/task/manager/service/SubtaskService.java @@ -1,15 +1,19 @@ package service.task.manager.service; import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; -import service.task.manager.model.Subtask; import java.util.List; public interface SubtaskService { void create(SubtaskRequestCreatedDto dto); + SubtaskResponseDto update(SubtaskRequestUpdatedDto dto); + SubtaskResponseDto findById(Long id); List findAll(); + + void delete(Long id); } diff --git a/service/src/main/java/service/task/manager/service/TaskService.java b/service/src/main/java/service/task/manager/service/TaskService.java index d442afe..68d1a29 100644 --- a/service/src/main/java/service/task/manager/service/TaskService.java +++ b/service/src/main/java/service/task/manager/service/TaskService.java @@ -1,6 +1,7 @@ package service.task.manager.service; import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; import java.util.List; @@ -8,7 +9,11 @@ public interface TaskService { void create(TaskRequestCreatedDto dto); + TaskResponseDto update(TaskRequestUpdatedDto dto); + TaskResponseDto findById(Long id); List findAll(); + + void delete(Long id); } diff --git a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java index 6b55f90..22824f2 100644 --- a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java @@ -4,8 +4,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.error.NotFoundException; import service.task.manager.mapper.EpicMapper; +import service.task.manager.model.Epic; import service.task.manager.repository.EpicRepository; import service.task.manager.service.EpicService; @@ -24,7 +27,18 @@ public class EpicServiceImpl implements EpicService { */ @Override public void create(EpicRequestCreatedDto dto) { + repository.save(mapper.toEntity(dto)); + } + /** + * @param dto + * @return + */ + @Override + public EpicResponseDto update(EpicRequestUpdatedDto dto) { + Epic epic = mapper.toEntity(dto); + epic = repository.save(epic); + return mapper.toResponseDto(epic); } /** @@ -33,7 +47,11 @@ public void create(EpicRequestCreatedDto dto) { */ @Override public EpicResponseDto findById(Long id) { - return null; + return repository.findById(id). + stream() + .map(mapper::toResponseDto) + .findFirst() + .orElseThrow(() -> new NotFoundException("Epic not found")); } /** @@ -41,6 +59,16 @@ public EpicResponseDto findById(Long id) { */ @Override public List findAll() { - return List.of(); + return repository.findAll() + .stream().map(mapper::toResponseDto) + .toList(); + } + + /** + * @param id + */ + @Override + public void delete(Long id) { + repository.deleteById(id); } } diff --git a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java index dbc5d11..3cfce16 100644 --- a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java @@ -4,8 +4,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.error.NotFoundException; import service.task.manager.mapper.SubtaskMapper; +import service.task.manager.model.Epic; +import service.task.manager.model.Subtask; import service.task.manager.repository.SubtaskRepository; import service.task.manager.service.SubtaskService; @@ -25,7 +29,18 @@ public class SubtaskServiceImpl implements SubtaskService { */ @Override public void create(SubtaskRequestCreatedDto dto) { + repository.save(mapper.toEntity(dto)); + } + /** + * @param dto + * @return + */ + @Override + public SubtaskResponseDto update(SubtaskRequestUpdatedDto dto) { + Subtask subtask = mapper.toEntity(dto); + subtask = repository.save(subtask); + return mapper.toResponseDto(subtask); } /** @@ -34,7 +49,10 @@ public void create(SubtaskRequestCreatedDto dto) { */ @Override public SubtaskResponseDto findById(Long id) { - return null; + return repository.findById(id).stream() + .map(mapper::toResponseDto) + .findFirst() + .orElseThrow(() -> new NotFoundException("Subtask not found")); } /** @@ -42,6 +60,16 @@ public SubtaskResponseDto findById(Long id) { */ @Override public List findAll() { - return List.of(); + return repository.findAll().stream() + .map(mapper::toResponseDto) + .toList(); + } + + /** + * @param id + */ + @Override + public void delete(Long id) { + repository.deleteById(id); } } diff --git a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java index 2d52f57..5bc72bb 100644 --- a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java @@ -4,8 +4,11 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; +import service.task.manager.error.NotFoundException; import service.task.manager.mapper.TaskMapper; +import service.task.manager.model.Task; import service.task.manager.repository.TaskRepository; import service.task.manager.service.TaskService; @@ -23,7 +26,18 @@ public class TaskServiceImpl implements TaskService { */ @Override public void create(TaskRequestCreatedDto dto) { - //repository.save(mapper.toEntity(dto)); + repository.save(mapper.toEntity(dto)); + } + + /** + * @param dto + * @return + */ + @Override + public TaskResponseDto update(TaskRequestUpdatedDto dto) { + Task task = mapper.toEntity(dto); + task = repository.save(task); + return mapper.toResponseDto(task); } /** @@ -32,7 +46,10 @@ public void create(TaskRequestCreatedDto dto) { */ @Override public TaskResponseDto findById(Long id) { - return null; + return repository.findById(id).stream() + .map(mapper::toResponseDto) + .findFirst() + .orElseThrow(() -> new NotFoundException("Task not found")); } /** @@ -40,6 +57,16 @@ public TaskResponseDto findById(Long id) { */ @Override public List findAll() { - return List.of(); + return repository.findAll().stream() + .map(mapper::toResponseDto) + .toList(); + } + + /** + * @param id + */ + @Override + public void delete(Long id) { + repository.deleteById(id); } } diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java index 34204e8..f7159d6 100644 --- a/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java +++ b/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java @@ -7,7 +7,9 @@ import javax.annotation.processing.Generated; import org.springframework.stereotype.Component; import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.model.Epic; import service.task.manager.model.Subtask; import service.task.manager.model.enums.Status; @@ -15,7 +17,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-04-26T20:14:42+0300", + date = "2025-04-26T20:47:41+0300", comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" ) @Component @@ -89,6 +91,86 @@ public EpicResponseDto.SubtaskDto toSubtaskDto(Subtask subtask) { return subtaskDto; } + @Override + public Subtask toEntity(SubtaskRequestUpdatedDto subtaskRequestUpdatedDto) { + if ( subtaskRequestUpdatedDto == null ) { + return null; + } + + Subtask subtask = new Subtask(); + + subtask.setId( subtaskRequestUpdatedDto.id() ); + subtask.setName( subtaskRequestUpdatedDto.name() ); + subtask.setDescription( subtaskRequestUpdatedDto.description() ); + subtask.setStatus( subtaskRequestUpdatedDto.status() ); + subtask.setDuration( subtaskRequestUpdatedDto.duration() ); + + return subtask; + } + + @Override + public SubtaskRequestUpdatedDto toSubtaskRequestUpdatedDto(Subtask subtask) { + if ( subtask == null ) { + return null; + } + + Long id = null; + String name = null; + String description = null; + Status status = null; + Duration duration = null; + + id = subtask.getId(); + name = subtask.getName(); + description = subtask.getDescription(); + status = subtask.getStatus(); + duration = subtask.getDuration(); + + SubtaskRequestUpdatedDto subtaskRequestUpdatedDto = new SubtaskRequestUpdatedDto( id, name, description, status, duration ); + + return subtaskRequestUpdatedDto; + } + + @Override + public Epic toEntity(EpicRequestUpdatedDto epicRequestUpdatedDto) { + if ( epicRequestUpdatedDto == null ) { + return null; + } + + Epic epic = new Epic(); + + epic.setId( epicRequestUpdatedDto.id() ); + epic.setName( epicRequestUpdatedDto.name() ); + epic.setDescription( epicRequestUpdatedDto.description() ); + epic.setStatus( epicRequestUpdatedDto.status() ); + epic.setDuration( epicRequestUpdatedDto.duration() ); + + return epic; + } + + @Override + public EpicRequestUpdatedDto toEpicDto(Epic epic) { + if ( epic == null ) { + return null; + } + + Long id = null; + String name = null; + String description = null; + Status status = null; + Duration duration = null; + + id = epic.getId(); + name = epic.getName(); + description = epic.getDescription(); + status = epic.getStatus(); + duration = epic.getDuration(); + + EpicRequestUpdatedDto epicRequestUpdatedDto = new EpicRequestUpdatedDto( id, name, description, status, duration ); + + return epicRequestUpdatedDto; + } + protected List subtaskListToSubtaskDtoList(List list) { if ( list == null ) { return null; diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java index cf59bb0..90476cd 100644 --- a/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java +++ b/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java @@ -12,7 +12,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-04-26T20:14:42+0300", + date = "2025-04-26T20:47:41+0300", comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" ) @Component diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java index 3653f65..27104e6 100644 --- a/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java +++ b/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java @@ -5,6 +5,7 @@ import javax.annotation.processing.Generated; import org.springframework.stereotype.Component; import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; import service.task.manager.model.Task; import service.task.manager.model.enums.Status; @@ -12,7 +13,7 @@ @Generated( value = "org.mapstruct.ap.MappingProcessor", - date = "2025-04-26T20:14:42+0300", + date = "2025-04-26T20:47:41+0300", comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" ) @Component @@ -62,4 +63,44 @@ public TaskResponseDto toResponseDto(Task task) { return taskResponseDto; } + + @Override + public Task toEntity(TaskRequestUpdatedDto taskRequestUpdatedDto) { + if ( taskRequestUpdatedDto == null ) { + return null; + } + + Task task = new Task(); + + task.setId( taskRequestUpdatedDto.id() ); + task.setName( taskRequestUpdatedDto.name() ); + task.setDescription( taskRequestUpdatedDto.description() ); + task.setStatus( taskRequestUpdatedDto.status() ); + task.setDuration( taskRequestUpdatedDto.duration() ); + + return task; + } + + @Override + public TaskRequestUpdatedDto toTaskRequestUpdatedDto(Task task) { + if ( task == null ) { + return null; + } + + Long id = null; + String name = null; + String description = null; + Status status = null; + Duration duration = null; + + id = task.getId(); + name = task.getName(); + description = task.getDescription(); + status = task.getStatus(); + duration = task.getDuration(); + + TaskRequestUpdatedDto taskRequestUpdatedDto = new TaskRequestUpdatedDto( id, name, description, status, duration ); + + return taskRequestUpdatedDto; + } } diff --git a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 167b84a..aad5c2d 100644 --- a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -17,10 +17,13 @@ service/task/manager/service/impl/SubtaskServiceImpl.class service/task/manager/mapper/SubtaskMapper.class service/task/manager/repository/EpicRepository.class service/task/manager/repository/TaskRepository.class +service/task/manager/dto/epic/EpicRequestUpdatedDto.class +service/task/manager/dto/task/TaskRequestUpdatedDto.class service/task/manager/error/TaskConstraintViolationException.class service/task/manager/dto/epic/EpicRequestCreatedDto.class service/task/manager/dto/task/TaskResponseDto.class service/task/manager/service/SubtaskService.class +service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.class service/task/manager/controller/TaskController.class service/task/manager/service/impl/EpicServiceImpl.class service/task/manager/controller/EpicController.class diff --git a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index dd2d2ab..c3dc87f 100644 --- a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -16,13 +16,16 @@ /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/enums/Status.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Epic.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/SubtaskRepository.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/controller/TaskController.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Task.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Subtask.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/EpicRepository.java +/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/TaskService.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java /Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java diff --git a/service/target/service-1.0-SNAPSHOT.jar.original b/service/target/service-1.0-SNAPSHOT.jar.original index 6b1d4faa1cc299135d841aa3652eda8ab22be527..b06323c6c7a07eadfe9785e71798c69518e826f0 100644 GIT binary patch delta 19911 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 F-aoJ11n~d> delta 12394 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 a={#IULTp}Ph(d`%CF4UuMMgbb;Qs*W8g#G# From ff50a40c3c7780e64aecb7d639b52de69da8af4a Mon Sep 17 00:00:00 2001 From: YG Date: Sat, 26 Apr 2025 21:01:16 +0300 Subject: [PATCH 3/9] update gitignore --- .gitignore | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index d3bedd1..5d6e500 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,35 @@ -# Compiled class file -*.class +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ -# Log file -*.log -*.DS_Store - -# BlueJ files -*.ctxt +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache -# Mobile Tools for Java (J2ME) -.mtj.tmp/ +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +*.DS_Store -# Package Files # -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/image/** +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* +### VS Code ### +.vscode/ From 7a7576e89ec299bb491a665e8dc7981d6204f402 Mon Sep 17 00:00:00 2001 From: YG Date: Sat, 26 Apr 2025 21:06:33 +0300 Subject: [PATCH 4/9] fix: remove target --- .gitignore | 1 + service/target/classes/application.yml | 13 -- service/target/classes/schema.sql | 45 ----- .../task/manager/mapper/EpicMapperImpl.java | 186 ------------------ .../manager/mapper/SubtaskMapperImpl.java | 65 ------ .../task/manager/mapper/TaskMapperImpl.java | 106 ---------- service/target/maven-archiver/pom.properties | 3 - .../compile/default-compile/createdFiles.lst | 41 ---- .../compile/default-compile/inputFiles.lst | 34 ---- .../default-testCompile/createdFiles.lst | 0 .../default-testCompile/inputFiles.lst | 0 .../target/service-1.0-SNAPSHOT.jar.original | Bin 50419 -> 0 bytes 12 files changed, 1 insertion(+), 493 deletions(-) delete mode 100644 service/target/classes/application.yml delete mode 100644 service/target/classes/schema.sql delete mode 100644 service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java delete mode 100644 service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java delete mode 100644 service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java delete mode 100644 service/target/maven-archiver/pom.properties delete mode 100644 service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst delete mode 100644 service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst delete mode 100644 service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst delete mode 100644 service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst delete mode 100644 service/target/service-1.0-SNAPSHOT.jar.original diff --git a/.gitignore b/.gitignore index 5d6e500..e4a6f6e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ target/ *.ipr *.DS_Store + ### NetBeans ### /nbproject/private/ /nbbuild/ diff --git a/service/target/classes/application.yml b/service/target/classes/application.yml deleted file mode 100644 index 04967c9..0000000 --- a/service/target/classes/application.yml +++ /dev/null @@ -1,13 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:testdb - username: sa - password: - driver-class-name: org.h2.Driver - sql: - init: - mode: always - schema-locations: classpath:schema.sql - jpa: - properties: - hibernate.dialect: org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/service/target/classes/schema.sql b/service/target/classes/schema.sql deleted file mode 100644 index 4135a0a..0000000 --- a/service/target/classes/schema.sql +++ /dev/null @@ -1,45 +0,0 @@ --- Таблица для Epic -CREATE TABLE epic -( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT, - status VARCHAR(50), - start_time TIMESTAMP, - duration BIGINT, -- Хранится в секундах - end_time TIMESTAMP, - type VARCHAR(50) NOT NULL -); - --- Таблица для Subtask -CREATE TABLE subtask -( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - epic_id BIGINT, - name VARCHAR(255) NOT NULL, - description TEXT, - status VARCHAR(50), - start_time TIMESTAMP, - duration BIGINT, -- Хранится в секундах - end_time TIMESTAMP, - type VARCHAR(50) NOT NULL, - FOREIGN KEY (epic_id) REFERENCES epic (id) ON DELETE CASCADE -); - --- Таблица для Task -CREATE TABLE task -( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT, - status VARCHAR(50), - start_time TIMESTAMP, - duration BIGINT, -- Хранится в секундах - end_time TIMESTAMP, - type VARCHAR(50) NOT NULL -); - --- Опционально: Добавление индексов для оптимизации -CREATE INDEX idx_epic_status ON epic (status); -CREATE INDEX idx_subtask_epic_id ON subtask (epic_id); -CREATE INDEX idx_task_status ON task (status); \ No newline at end of file diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java deleted file mode 100644 index f7159d6..0000000 --- a/service/target/generated-sources/annotations/service/task/manager/mapper/EpicMapperImpl.java +++ /dev/null @@ -1,186 +0,0 @@ -package service.task.manager.mapper; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.processing.Generated; -import org.springframework.stereotype.Component; -import service.task.manager.dto.epic.EpicRequestCreatedDto; -import service.task.manager.dto.epic.EpicRequestUpdatedDto; -import service.task.manager.dto.epic.EpicResponseDto; -import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; -import service.task.manager.model.Epic; -import service.task.manager.model.Subtask; -import service.task.manager.model.enums.Status; -import service.task.manager.model.enums.TaskType; - -@Generated( - value = "org.mapstruct.ap.MappingProcessor", - date = "2025-04-26T20:47:41+0300", - comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" -) -@Component -public class EpicMapperImpl implements EpicMapper { - - @Override - public Epic toEntity(EpicRequestCreatedDto epicRequestCreatedDto) { - if ( epicRequestCreatedDto == null ) { - return null; - } - - Epic epic = new Epic(); - - epic.setName( epicRequestCreatedDto.name() ); - epic.setDescription( epicRequestCreatedDto.description() ); - epic.setStartTime( epicRequestCreatedDto.startTime() ); - epic.setDuration( epicRequestCreatedDto.duration() ); - - return epic; - } - - @Override - public EpicResponseDto toResponseDto(Epic epic) { - if ( epic == null ) { - return null; - } - - Long id = null; - List subtasks = null; - String name = null; - String description = null; - Status status = null; - LocalDateTime startTime = null; - Duration duration = null; - LocalDateTime endTime = null; - TaskType type = null; - - id = epic.getId(); - subtasks = subtaskListToSubtaskDtoList( epic.getSubtasks() ); - name = epic.getName(); - description = epic.getDescription(); - status = epic.getStatus(); - startTime = epic.getStartTime(); - duration = epic.getDuration(); - endTime = epic.getEndTime(); - type = epic.getType(); - - EpicResponseDto epicResponseDto = new EpicResponseDto( id, subtasks, name, description, status, startTime, duration, endTime, type ); - - return epicResponseDto; - } - - @Override - public EpicResponseDto.SubtaskDto toSubtaskDto(Subtask subtask) { - if ( subtask == null ) { - return null; - } - - Long id = null; - String name = null; - String description = null; - Status status = null; - - id = subtask.getId(); - name = subtask.getName(); - description = subtask.getDescription(); - status = subtask.getStatus(); - - EpicResponseDto.SubtaskDto subtaskDto = new EpicResponseDto.SubtaskDto( id, name, description, status ); - - return subtaskDto; - } - - @Override - public Subtask toEntity(SubtaskRequestUpdatedDto subtaskRequestUpdatedDto) { - if ( subtaskRequestUpdatedDto == null ) { - return null; - } - - Subtask subtask = new Subtask(); - - subtask.setId( subtaskRequestUpdatedDto.id() ); - subtask.setName( subtaskRequestUpdatedDto.name() ); - subtask.setDescription( subtaskRequestUpdatedDto.description() ); - subtask.setStatus( subtaskRequestUpdatedDto.status() ); - subtask.setDuration( subtaskRequestUpdatedDto.duration() ); - - return subtask; - } - - @Override - public SubtaskRequestUpdatedDto toSubtaskRequestUpdatedDto(Subtask subtask) { - if ( subtask == null ) { - return null; - } - - Long id = null; - String name = null; - String description = null; - Status status = null; - Duration duration = null; - - id = subtask.getId(); - name = subtask.getName(); - description = subtask.getDescription(); - status = subtask.getStatus(); - duration = subtask.getDuration(); - - SubtaskRequestUpdatedDto subtaskRequestUpdatedDto = new SubtaskRequestUpdatedDto( id, name, description, status, duration ); - - return subtaskRequestUpdatedDto; - } - - @Override - public Epic toEntity(EpicRequestUpdatedDto epicRequestUpdatedDto) { - if ( epicRequestUpdatedDto == null ) { - return null; - } - - Epic epic = new Epic(); - - epic.setId( epicRequestUpdatedDto.id() ); - epic.setName( epicRequestUpdatedDto.name() ); - epic.setDescription( epicRequestUpdatedDto.description() ); - epic.setStatus( epicRequestUpdatedDto.status() ); - epic.setDuration( epicRequestUpdatedDto.duration() ); - - return epic; - } - - @Override - public EpicRequestUpdatedDto toEpicDto(Epic epic) { - if ( epic == null ) { - return null; - } - - Long id = null; - String name = null; - String description = null; - Status status = null; - Duration duration = null; - - id = epic.getId(); - name = epic.getName(); - description = epic.getDescription(); - status = epic.getStatus(); - duration = epic.getDuration(); - - EpicRequestUpdatedDto epicRequestUpdatedDto = new EpicRequestUpdatedDto( id, name, description, status, duration ); - - return epicRequestUpdatedDto; - } - - protected List subtaskListToSubtaskDtoList(List list) { - if ( list == null ) { - return null; - } - - List list1 = new ArrayList( list.size() ); - for ( Subtask subtask : list ) { - list1.add( toSubtaskDto( subtask ) ); - } - - return list1; - } -} diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java deleted file mode 100644 index 90476cd..0000000 --- a/service/target/generated-sources/annotations/service/task/manager/mapper/SubtaskMapperImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -package service.task.manager.mapper; - -import java.time.Duration; -import java.time.LocalDateTime; -import javax.annotation.processing.Generated; -import org.springframework.stereotype.Component; -import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; -import service.task.manager.dto.subtask.SubtaskResponseDto; -import service.task.manager.model.Subtask; -import service.task.manager.model.enums.Status; -import service.task.manager.model.enums.TaskType; - -@Generated( - value = "org.mapstruct.ap.MappingProcessor", - date = "2025-04-26T20:47:41+0300", - comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" -) -@Component -public class SubtaskMapperImpl implements SubtaskMapper { - - @Override - public Subtask toEntity(SubtaskRequestCreatedDto subtaskRequestCreatedDto) { - if ( subtaskRequestCreatedDto == null ) { - return null; - } - - Subtask subtask = new Subtask(); - - subtask.setName( subtaskRequestCreatedDto.name() ); - subtask.setDescription( subtaskRequestCreatedDto.description() ); - subtask.setStartTime( subtaskRequestCreatedDto.startTime() ); - subtask.setDuration( subtaskRequestCreatedDto.duration() ); - - return subtask; - } - - @Override - public SubtaskResponseDto toResponseDto(Subtask subtask) { - if ( subtask == null ) { - return null; - } - - Long id = null; - String name = null; - String description = null; - Status status = null; - LocalDateTime startTime = null; - LocalDateTime endTime = null; - Duration duration = null; - TaskType type = null; - - id = subtask.getId(); - name = subtask.getName(); - description = subtask.getDescription(); - status = subtask.getStatus(); - startTime = subtask.getStartTime(); - endTime = subtask.getEndTime(); - duration = subtask.getDuration(); - type = subtask.getType(); - - SubtaskResponseDto subtaskResponseDto = new SubtaskResponseDto( id, name, description, status, startTime, endTime, duration, type ); - - return subtaskResponseDto; - } -} diff --git a/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java b/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java deleted file mode 100644 index 27104e6..0000000 --- a/service/target/generated-sources/annotations/service/task/manager/mapper/TaskMapperImpl.java +++ /dev/null @@ -1,106 +0,0 @@ -package service.task.manager.mapper; - -import java.time.Duration; -import java.time.LocalDateTime; -import javax.annotation.processing.Generated; -import org.springframework.stereotype.Component; -import service.task.manager.dto.task.TaskRequestCreatedDto; -import service.task.manager.dto.task.TaskRequestUpdatedDto; -import service.task.manager.dto.task.TaskResponseDto; -import service.task.manager.model.Task; -import service.task.manager.model.enums.Status; -import service.task.manager.model.enums.TaskType; - -@Generated( - value = "org.mapstruct.ap.MappingProcessor", - date = "2025-04-26T20:47:41+0300", - comments = "version: 1.6.0, compiler: javac, environment: Java 21.0.2 (Eclipse Adoptium)" -) -@Component -public class TaskMapperImpl implements TaskMapper { - - @Override - public Task toEntity(TaskRequestCreatedDto taskRequestCreatedDto) { - if ( taskRequestCreatedDto == null ) { - return null; - } - - Task task = new Task(); - - task.setName( taskRequestCreatedDto.name() ); - task.setDescription( taskRequestCreatedDto.description() ); - task.setStartTime( taskRequestCreatedDto.startTime() ); - task.setDuration( taskRequestCreatedDto.duration() ); - - return task; - } - - @Override - public TaskResponseDto toResponseDto(Task task) { - if ( task == null ) { - return null; - } - - Long id = null; - String name = null; - String description = null; - Status status = null; - LocalDateTime startTime = null; - LocalDateTime endTime = null; - Duration duration = null; - TaskType type = null; - - id = task.getId(); - name = task.getName(); - description = task.getDescription(); - status = task.getStatus(); - startTime = task.getStartTime(); - endTime = task.getEndTime(); - duration = task.getDuration(); - type = task.getType(); - - TaskResponseDto taskResponseDto = new TaskResponseDto( id, name, description, status, startTime, endTime, duration, type ); - - return taskResponseDto; - } - - @Override - public Task toEntity(TaskRequestUpdatedDto taskRequestUpdatedDto) { - if ( taskRequestUpdatedDto == null ) { - return null; - } - - Task task = new Task(); - - task.setId( taskRequestUpdatedDto.id() ); - task.setName( taskRequestUpdatedDto.name() ); - task.setDescription( taskRequestUpdatedDto.description() ); - task.setStatus( taskRequestUpdatedDto.status() ); - task.setDuration( taskRequestUpdatedDto.duration() ); - - return task; - } - - @Override - public TaskRequestUpdatedDto toTaskRequestUpdatedDto(Task task) { - if ( task == null ) { - return null; - } - - Long id = null; - String name = null; - String description = null; - Status status = null; - Duration duration = null; - - id = task.getId(); - name = task.getName(); - description = task.getDescription(); - status = task.getStatus(); - duration = task.getDuration(); - - TaskRequestUpdatedDto taskRequestUpdatedDto = new TaskRequestUpdatedDto( id, name, description, status, duration ); - - return taskRequestUpdatedDto; - } -} diff --git a/service/target/maven-archiver/pom.properties b/service/target/maven-archiver/pom.properties deleted file mode 100644 index bacc968..0000000 --- a/service/target/maven-archiver/pom.properties +++ /dev/null @@ -1,3 +0,0 @@ -artifactId=service -groupId=service.task.manager -version=1.0-SNAPSHOT diff --git a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst deleted file mode 100644 index aad5c2d..0000000 --- a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ /dev/null @@ -1,41 +0,0 @@ -service/task/manager/dto/task/TaskRequestCreatedDto.class -service/task/manager/model/Subtask.class -service/task/manager/service/EpicService.class -service/task/manager/model/enums/Status.class -service/task/manager/dto/epic/EpicResponseDto$SubtaskDto.class -service/task/manager/dto/subtask/SubtaskRequestCreatedDto.class -service/task/manager/dto/epic/EpicRequestCreatedDto$EpicRequestCreatedDtoBuilder.class -service/task/manager/error/ErrorHandler.class -service/task/manager/dto/subtask/SubtaskResponseDto.class -service/task/manager/mapper/EpicMapper.class -service/task/manager/error/NotFoundException.class -service/task/manager/repository/SubtaskRepository.class -service/task/manager/dto/epic/EpicResponseDto.class -service/task/manager/model/enums/TaskType.class -service/task/manager/error/ErrorHandler$ErrorResponse.class -service/task/manager/service/impl/SubtaskServiceImpl.class -service/task/manager/mapper/SubtaskMapper.class -service/task/manager/repository/EpicRepository.class -service/task/manager/repository/TaskRepository.class -service/task/manager/dto/epic/EpicRequestUpdatedDto.class -service/task/manager/dto/task/TaskRequestUpdatedDto.class -service/task/manager/error/TaskConstraintViolationException.class -service/task/manager/dto/epic/EpicRequestCreatedDto.class -service/task/manager/dto/task/TaskResponseDto.class -service/task/manager/service/SubtaskService.class -service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.class -service/task/manager/controller/TaskController.class -service/task/manager/service/impl/EpicServiceImpl.class -service/task/manager/controller/EpicController.class -service/task/manager/model/Epic.class -service/task/manager/TaskManagerApplication.class -service/task/manager/mapper/EpicMapperImpl.class -service/task/manager/controller/SubtaskController.class -service/task/manager/mapper/TaskMapperImpl.class -service/task/manager/dto/subtask/SubtaskRequestCreatedDto$SubtaskRequestCreatedDtoBuilder.class -service/task/manager/model/Task.class -service/task/manager/service/impl/TaskServiceImpl.class -service/task/manager/mapper/TaskMapper.class -service/task/manager/service/TaskService.class -service/task/manager/mapper/SubtaskMapperImpl.class -service/task/manager/error/InvalidTaskDataException.class diff --git a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst deleted file mode 100644 index c3dc87f..0000000 --- a/service/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ /dev/null @@ -1,34 +0,0 @@ -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/SubtaskService.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/controller/EpicController.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/mapper/EpicMapper.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/InvalidTaskDataException.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/controller/SubtaskController.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/TaskRepository.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/mapper/TaskMapper.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/TaskManagerApplication.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/ErrorHandler.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/enums/Status.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Epic.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/SubtaskRepository.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/controller/TaskController.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Task.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/Subtask.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/repository/EpicRepository.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/TaskService.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/service/EpicService.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/error/NotFoundException.java -/Users/admin/Documents/java-task-manager/service/src/main/java/service/task/manager/model/enums/TaskType.java diff --git a/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst deleted file mode 100644 index e69de29..0000000 diff --git a/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/service/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst deleted file mode 100644 index e69de29..0000000 diff --git a/service/target/service-1.0-SNAPSHOT.jar.original b/service/target/service-1.0-SNAPSHOT.jar.original deleted file mode 100644 index b06323c6c7a07eadfe9785e71798c69518e826f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50419 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 QzaZ2gHWU;>!SQeZ15rXyng9R* From 2d89addc5d7fed799cf24c8dd9a247f715f61b53 Mon Sep 17 00:00:00 2001 From: YG Date: Sun, 27 Apr 2025 23:17:09 +0300 Subject: [PATCH 5/9] feat: added logic layer service, documentation swagger and javadoc, log --- .../f04243a9-2346-4975-b5d1-b9ee4bd49928.xml | 2 +- service/pom.xml | 26 ++++- .../manager/controller/EpicController.java | 60 ++++++++-- .../manager/controller/SubtaskController.java | 61 ++++++++-- .../manager/controller/TaskController.java | 60 ++++++++-- .../dto/epic/EpicRequestCreatedDto.java | 29 +++-- .../dto/epic/EpicRequestUpdatedDto.java | 31 ++++- .../manager/dto/epic/EpicResponseDto.java | 51 +++++++- .../dto/subtask/SubtaskRequestCreatedDto.java | 31 +++-- .../dto/subtask/SubtaskRequestUpdatedDto.java | 30 ++++- .../dto/subtask/SubtaskResponseDto.java | 34 +++++- .../dto/task/TaskRequestCreatedDto.java | 25 +++- .../dto/task/TaskRequestUpdatedDto.java | 31 ++++- .../manager/dto/task/TaskResponseDto.java | 31 ++++- .../task/manager/error/ConflictException.java | 7 ++ .../task/manager/error/ErrorHandler.java | 6 + .../task/manager/mapper/EpicMapper.java | 15 ++- .../task/manager/mapper/SubtaskMapper.java | 19 ++- .../task/manager/mapper/TaskMapper.java | 13 ++- .../manager/repository/EpicRepository.java | 1 + .../manager/repository/SubtaskRepository.java | 1 + .../manager/repository/TaskRepository.java | 1 + .../manager/service/impl/EpicServiceImpl.java | 105 ++++++++++++++--- .../service/impl/SubtaskServiceImpl.java | 110 +++++++++++++++--- .../manager/service/impl/TaskServiceImpl.java | 87 +++++++++++--- service/src/main/resources/application.yml | 15 ++- 26 files changed, 741 insertions(+), 141 deletions(-) create mode 100644 service/src/main/java/service/task/manager/error/ConflictException.java diff --git a/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml index 3ab49a9..7a05fb5 100644 --- a/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml +++ b/.idea/dataSources/f04243a9-2346-4975-b5d1-b9ee4bd49928.xml @@ -169,7 +169,7 @@ 1 - 2025-04-26.07:46:18 + 2025-04-27.14:25:53 1 diff --git a/service/pom.xml b/service/pom.xml index b88716d..36cc7cf 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.4 + 3.2.5 service.task.manager @@ -23,11 +23,6 @@ 1.18.34 - - io.swagger.core.v3 - swagger-annotations - 2.1.10 - org.springframework.boot spring-boot-starter-web @@ -101,6 +96,25 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.0 + + + generate-javadoc + generate-resources + + javadoc + + + + + 21 + none + ${project.basedir}/docs/javadoc + + diff --git a/service/src/main/java/service/task/manager/controller/EpicController.java b/service/src/main/java/service/task/manager/controller/EpicController.java index 35dc8ce..a7b497c 100644 --- a/service/src/main/java/service/task/manager/controller/EpicController.java +++ b/service/src/main/java/service/task/manager/controller/EpicController.java @@ -1,10 +1,17 @@ package service.task.manager.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import service.task.manager.dto.epic.EpicRequestCreatedDto; import service.task.manager.dto.epic.EpicRequestUpdatedDto; @@ -18,31 +25,66 @@ @RestController @RequiredArgsConstructor @RequestMapping("/epic") +@Tag(name = "Epic API", description = "API for managing epics") public class EpicController { private final EpicService service; @PostMapping - public void create(@RequestBody @Valid EpicRequestCreatedDto dto) { + @Operation(summary = "Create a new epic", description = "Creates a new epic with the provided data.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Epic created successfully"), + @ApiResponse(responseCode = "409", description = "Epic with the same name already exists") + }) + public ResponseEntity create(@RequestBody @Valid EpicRequestCreatedDto dto) { + log.info("Creating epic with name: {}", dto.name()); service.create(dto); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping - public EpicResponseDto update(@RequestBody @Valid EpicRequestUpdatedDto dto) { - return service.update(dto); + @Operation(summary = "Update an existing epic", description = "Updates an epic with the provided data.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Epic updated successfully"), + @ApiResponse(responseCode = "404", description = "Epic not found") + }) + public ResponseEntity update(@RequestBody @Valid EpicRequestUpdatedDto dto) { + log.info("Updating epic with ID: {}", dto.id()); + EpicResponseDto updatedEpic = service.update(dto); + return ResponseEntity.ok(updatedEpic); } @GetMapping("/{id}") - public EpicResponseDto findById(@PathVariable @Positive @NotNull Long id) { - return service.findById(id); + @Operation(summary = "Get an epic by ID", description = "Retrieves an epic by its ID, including its subtasks.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Epic retrieved successfully"), + @ApiResponse(responseCode = "404", description = "Epic not found") + }) + public ResponseEntity findById( + @Parameter(description = "ID of the epic to retrieve") @PathVariable @Positive @NotNull Long id) { + log.info("Fetching epic with ID: {}", id); + EpicResponseDto epic = service.findById(id); + return ResponseEntity.ok(epic); } @GetMapping - public List findAll() { - return service.findAll(); + @Operation(summary = "Get all epics", description = "Retrieves a list of all epics.") + @ApiResponse(responseCode = "200", description = "List of epics retrieved successfully") + public ResponseEntity> findAll() { + log.info("Fetching all epics"); + List epics = service.findAll(); + return ResponseEntity.ok(epics); } @DeleteMapping("/{id}") - public void delete(@PathVariable @Positive @NotNull Long id) { + @Operation(summary = "Delete an epic by ID", description = "Deletes an epic by its ID.") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Epic deleted successfully"), + @ApiResponse(responseCode = "404", description = "Epic not found") + }) + public ResponseEntity delete( + @Parameter(description = "ID of the epic to delete") @PathVariable @Positive @NotNull Long id) { + log.info("Deleting epic with ID: {}", id); service.delete(id); + return ResponseEntity.noContent().build(); } -} +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/SubtaskController.java b/service/src/main/java/service/task/manager/controller/SubtaskController.java index 94e1e3e..5b0e25a 100644 --- a/service/src/main/java/service/task/manager/controller/SubtaskController.java +++ b/service/src/main/java/service/task/manager/controller/SubtaskController.java @@ -1,10 +1,17 @@ package service.task.manager.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; @@ -18,31 +25,67 @@ @RestController @RequiredArgsConstructor @RequestMapping("/subtask") +@Tag(name = "Subtask API", description = "API for managing subtasks") public class SubtaskController { private final SubtaskService service; @PostMapping - public void create(@RequestBody @Valid SubtaskRequestCreatedDto dto) { + @Operation(summary = "Create a new subtask", description = "Creates a new subtask with the provided data, associated with an epic.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Subtask created successfully"), + @ApiResponse(responseCode = "404", description = "Associated epic not found"), + @ApiResponse(responseCode = "409", description = "Subtask with the same name already exists") + }) + public ResponseEntity create(@RequestBody @Valid SubtaskRequestCreatedDto dto) { + log.info("Creating subtask with name: {}", dto.name()); service.create(dto); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping - public void update(@RequestBody @Valid SubtaskRequestUpdatedDto dto) { - service.update(dto); + @Operation(summary = "Update an existing subtask", description = "Updates an existing subtask with the provided data.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Subtask updated successfully"), + @ApiResponse(responseCode = "404", description = "Subtask not found") + }) + public ResponseEntity update(@RequestBody @Valid SubtaskRequestUpdatedDto dto) { + log.info("Updating subtask with ID: {}", dto.id()); + SubtaskResponseDto updatedSubtask = service.update(dto); + return ResponseEntity.ok(updatedSubtask); } @GetMapping("/{id}") - public SubtaskResponseDto findById(@PathVariable @Positive @NotNull Long id) { - return service.findById(id); + @Operation(summary = "Get a subtask by ID", description = "Retrieves a subtask by its ID.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Subtask retrieved successfully"), + @ApiResponse(responseCode = "404", description = "Subtask not found") + }) + public ResponseEntity findById( + @Parameter(description = "ID of the subtask to retrieve") @PathVariable @Positive @NotNull Long id) { + log.info("Fetching subtask with ID: {}", id); + SubtaskResponseDto subtask = service.findById(id); + return ResponseEntity.ok(subtask); } @GetMapping - public List findAll() { - return service.findAll(); + @Operation(summary = "Get all subtasks", description = "Retrieves a list of all subtasks.") + @ApiResponse(responseCode = "200", description = "List of subtasks retrieved successfully") + public ResponseEntity> findAll() { + log.info("Fetching all subtasks"); + List subtasks = service.findAll(); + return ResponseEntity.ok(subtasks); } @DeleteMapping("/{id}") - public void delete(@PathVariable @Positive @NotNull Long id) { + @Operation(summary = "Delete a subtask by ID", description = "Deletes a subtask by its ID.") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Subtask deleted successfully"), + @ApiResponse(responseCode = "404", description = "Subtask not found") + }) + public ResponseEntity delete( + @Parameter(description = "ID of the subtask to delete") @PathVariable @Positive @NotNull Long id) { + log.info("Deleting subtask with ID: {}", id); service.delete(id); + return ResponseEntity.noContent().build(); } -} +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/TaskController.java b/service/src/main/java/service/task/manager/controller/TaskController.java index b378861..06f7be7 100644 --- a/service/src/main/java/service/task/manager/controller/TaskController.java +++ b/service/src/main/java/service/task/manager/controller/TaskController.java @@ -1,10 +1,17 @@ package service.task.manager.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import service.task.manager.dto.task.TaskRequestCreatedDto; import service.task.manager.dto.task.TaskRequestUpdatedDto; @@ -18,31 +25,66 @@ @RestController @RequiredArgsConstructor @RequestMapping("/task") +@Tag(name = "Task API", description = "API for managing tasks") public class TaskController { private final TaskService service; @PostMapping - public void create(@RequestBody @Valid TaskRequestCreatedDto dto) { + @Operation(summary = "Create a new task", description = "Creates a new task with the provided data.") + @ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "Task created successfully"), + @ApiResponse(responseCode = "409", description = "Task with the same name already exists") + }) + public ResponseEntity create(@RequestBody @Valid TaskRequestCreatedDto dto) { + log.info("Creating task with name: {}", dto.name()); service.create(dto); + return ResponseEntity.status(HttpStatus.CREATED).build(); } @PutMapping - public void update(@RequestBody @Valid TaskRequestUpdatedDto dto) { - service.update(dto); + @Operation(summary = "Update an existing task", description = "Updates an existing task with the provided data.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Task updated successfully"), + @ApiResponse(responseCode = "404", description = "Task not found") + }) + public ResponseEntity update(@RequestBody @Valid TaskRequestUpdatedDto dto) { + log.info("Updating task with ID: {}", dto.id()); + TaskResponseDto updatedTask = service.update(dto); + return ResponseEntity.ok(updatedTask); } @GetMapping("/{id}") - public TaskResponseDto get(@PathVariable @Positive @NotNull Long id) { - return service.findById(id); + @Operation(summary = "Get a task by ID", description = "Retrieves a task by its ID.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Task retrieved successfully"), + @ApiResponse(responseCode = "404", description = "Task not found") + }) + public ResponseEntity get( + @Parameter(description = "ID of the task to retrieve") @PathVariable @Positive @NotNull Long id) { + log.info("Fetching task with ID: {}", id); + TaskResponseDto task = service.findById(id); + return ResponseEntity.ok(task); } @GetMapping - public List getAll(){ - return service.findAll(); + @Operation(summary = "Get all tasks", description = "Retrieves a list of all tasks.") + @ApiResponse(responseCode = "200", description = "List of tasks retrieved successfully") + public ResponseEntity> getAll() { + log.info("Fetching all tasks"); + List tasks = service.findAll(); + return ResponseEntity.ok(tasks); } @DeleteMapping("/{id}") - public void delete(@PathVariable @Positive @NotNull Long id) { + @Operation(summary = "Delete a task by ID", description = "Deletes a task by its ID.") + @ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "Task deleted successfully"), + @ApiResponse(responseCode = "404", description = "Task not found") + }) + public ResponseEntity delete( + @Parameter(description = "ID of the task to delete") @PathVariable @Positive @NotNull Long id) { + log.info("Deleting task with ID: {}", id); service.delete(id); + return ResponseEntity.noContent().build(); } -} +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java b/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java index 095df43..da54e47 100644 --- a/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java +++ b/service/src/main/java/service/task/manager/dto/epic/EpicRequestCreatedDto.java @@ -1,5 +1,6 @@ package service.task.manager.dto.epic; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Builder; @@ -8,15 +9,25 @@ import java.time.LocalDateTime; /** - * DTO for {@link service.task.manager.model.Epic} + * DTO for creating a new epic. */ +@Schema(description = "DTO for creating a new epic") @Builder -public record EpicRequestCreatedDto(@NotBlank - String name, - @NotBlank(message = "blank description") - String description, - @NotNull(message = "null start time") - LocalDateTime startTime, - @NotNull(message = "null duration") - Duration duration) { +public record EpicRequestCreatedDto( + @Schema(description = "Name of the epic", example = "Project Planning", required = true) + @NotBlank(message = "blank name") + String name, + + @Schema(description = "Description of the epic", example = "Planning phase of the project", required = true) + @NotBlank(message = "blank description") + String description, + + @Schema(description = "Start time of the epic", example = "2025-04-27T10:00:00", required = true) + @NotNull(message = "null start time") + LocalDateTime startTime, + + @Schema(description = "Duration of the epic", example = "PT24H", required = true) + @NotNull(message = "null duration") + Duration duration +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java b/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java index 4053264..0fc68f4 100644 --- a/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java +++ b/service/src/main/java/service/task/manager/dto/epic/EpicRequestUpdatedDto.java @@ -1,18 +1,37 @@ package service.task.manager.dto.epic; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import service.task.manager.model.Epic; import service.task.manager.model.enums.Status; import java.time.Duration; /** - * DTO for {@link Epic} + * DTO for updating an existing epic. */ -public record EpicRequestUpdatedDto(@NotNull(message = "null id") @Positive(message = "not positive id") Long id, - @NotBlank(message = "blank name") String name, - @NotBlank(message = "blank description") String description, - @NotNull(message = "null status") Status status, @NotNull Duration duration) { +@Schema(description = "DTO for updating an existing epic") +public record EpicRequestUpdatedDto( + @Schema(description = "ID of the epic to update", example = "1", required = true) + @NotNull(message = "null id") + @Positive(message = "not positive id") + Long id, + + @Schema(description = "Updated name of the epic", example = "Updated Project Planning", required = true) + @NotBlank(message = "blank name") + String name, + + @Schema(description = "Updated description of the epic", example = "Updated planning phase", required = true) + @NotBlank(message = "blank description") + String description, + + @Schema(description = "Updated status of the epic", example = "IN_PROGRESS", required = true) + @NotNull(message = "null status") + Status status, + + @Schema(description = "Updated duration of the epic", example = "PT48H", required = true) + @NotNull(message = "null duration") + Duration duration +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java b/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java index 9f9d053..8938b17 100644 --- a/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java +++ b/service/src/main/java/service/task/manager/dto/epic/EpicResponseDto.java @@ -1,5 +1,6 @@ package service.task.manager.dto.epic; +import io.swagger.v3.oas.annotations.media.Schema; import service.task.manager.model.enums.Status; import service.task.manager.model.enums.TaskType; @@ -8,13 +9,53 @@ import java.util.List; /** - * DTO for {@link service.task.manager.model.Epic} + * DTO for retrieving an epic with its details and subtasks. */ -public record EpicResponseDto(Long id, List subtasks, String name, String description, Status status, - LocalDateTime startTime, Duration duration, LocalDateTime endTime, TaskType type) { +@Schema(description = "DTO for retrieving an epic with its details and subtasks") +public record EpicResponseDto( + @Schema(description = "ID of the epic", example = "1") + Long id, + + @Schema(description = "List of subtasks associated with the epic") + List subtasks, + + @Schema(description = "Name of the epic", example = "Project Planning") + String name, + + @Schema(description = "Description of the epic", example = "Planning phase of the project") + String description, + + @Schema(description = "Status of the epic", example = "NEW") + Status status, + + @Schema(description = "Start time of the epic", example = "2025-04-27T10:00:00") + LocalDateTime startTime, + + @Schema(description = "Duration of the epic", example = "PT24H") + Duration duration, + + @Schema(description = "End time of the epic", example = "2025-04-28T10:00:00") + LocalDateTime endTime, + + @Schema(description = "Type of the task (always EPIC)", example = "EPIC") + TaskType type +) { /** - * DTO for {@link service.task.manager.model.Subtask} + * DTO for retrieving a subtask within an epic. */ - public record SubtaskDto(Long id, String name, String description, Status status) { + @Schema(description = "DTO for retrieving a subtask within an epic") + public record SubtaskDto( + @Schema(description = "ID of the subtask", example = "1") + Long id, + + @Schema(description = "Name of the subtask", example = "Task 1") + String name, + + @Schema(description = "Description of the subtask", example = "First task in the epic") + String description, + + @Schema(description = "Status of the subtask", example = "NEW") + Status status + ) { } } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java index 810cad5..f4bda8e 100644 --- a/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java +++ b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestCreatedDto.java @@ -1,20 +1,37 @@ package service.task.manager.dto.subtask; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Builder; -import service.task.manager.model.Subtask; import java.time.Duration; import java.time.LocalDateTime; /** - * DTO for {@link Subtask} + * DTO for creating a new subtask. */ +@Schema(description = "DTO for creating a new subtask") @Builder -public record SubtaskRequestCreatedDto(Long epicId, - @NotBlank(message = "blank name") String name, - @NotBlank(message = "blank description") String description, - @NotNull(message = "null start time") LocalDateTime startTime, - @NotNull Duration duration) { +public record SubtaskRequestCreatedDto( + @Schema(description = "ID of the epic to which the subtask belongs", example = "1", required = true) + @NotNull(message = "null epic ID") + Long epicId, + + @Schema(description = "Name of the subtask", example = "Task 1", required = true) + @NotBlank(message = "blank name") + String name, + + @Schema(description = "Description of the subtask", example = "First task in the epic", required = true) + @NotBlank(message = "blank description") + String description, + + @Schema(description = "Start time of the subtask", example = "2025-04-27T10:00:00", required = true) + @NotNull(message = "null start time") + LocalDateTime startTime, + + @Schema(description = "Duration of the subtask", example = "PT24H", required = true) + @NotNull(message = "null duration") + Duration duration +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java index a6caa7a..f258da5 100644 --- a/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java +++ b/service/src/main/java/service/task/manager/dto/subtask/SubtaskRequestUpdatedDto.java @@ -1,5 +1,6 @@ package service.task.manager.dto.subtask; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -8,10 +9,29 @@ import java.time.Duration; /** - * DTO for {@link service.task.manager.model.Subtask} + * DTO for updating an existing subtask. */ -public record SubtaskRequestUpdatedDto(@NotNull(message = "null id") @Positive(message = "not positive id") Long id, - @NotBlank(message = "blank name") String name, - @NotBlank(message = "blank description") String description, - @NotNull(message = "null status") Status status, @NotNull Duration duration) { +@Schema(description = "DTO for updating an existing subtask") +public record SubtaskRequestUpdatedDto( + @Schema(description = "ID of the subtask to update", example = "1", required = true) + @NotNull(message = "null id") + @Positive(message = "not positive id") + Long id, + + @Schema(description = "Updated name of the subtask", example = "Updated Task 1", required = true) + @NotBlank(message = "blank name") + String name, + + @Schema(description = "Updated description of the subtask", example = "Updated first task", required = true) + @NotBlank(message = "blank description") + String description, + + @Schema(description = "Updated status of the subtask", example = "IN_PROGRESS", required = true) + @NotNull(message = "null status") + Status status, + + @Schema(description = "Updated duration of the subtask", example = "PT48H", required = true) + @NotNull(message = "null duration") + Duration duration +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java b/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java index eeaae9a..dbcf06c 100644 --- a/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java +++ b/service/src/main/java/service/task/manager/dto/subtask/SubtaskResponseDto.java @@ -1,5 +1,6 @@ package service.task.manager.dto.subtask; +import io.swagger.v3.oas.annotations.media.Schema; import service.task.manager.model.enums.Status; import service.task.manager.model.enums.TaskType; @@ -7,8 +8,35 @@ import java.time.LocalDateTime; /** - * DTO for {@link service.task.manager.model.Subtask} + * DTO for retrieving a subtask with its details. */ -public record SubtaskResponseDto(Long id, String name, String description, Status status, LocalDateTime startTime, - LocalDateTime endTime, Duration duration, TaskType type) { +@Schema(description = "DTO for retrieving a subtask with its details") +public record SubtaskResponseDto( + @Schema(description = "ID of the subtask", example = "1") + Long id, + + @Schema(description = "ID of the epic to which the subtask belongs", example = "1") + Long epicId, + + @Schema(description = "Name of the subtask", example = "Task 1") + String name, + + @Schema(description = "Description of the subtask", example = "First task in the epic") + String description, + + @Schema(description = "Status of the subtask", example = "NEW") + Status status, + + @Schema(description = "Start time of the subtask", example = "2025-04-27T10:00:00") + LocalDateTime startTime, + + @Schema(description = "End time of the subtask", example = "2025-04-28T10:00:00") + LocalDateTime endTime, + + @Schema(description = "Duration of the subtask", example = "PT24H") + Duration duration, + + @Schema(description = "Type of the task (always SUBTASK)", example = "SUBTASK") + TaskType type +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java b/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java index a315852..325b668 100644 --- a/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java +++ b/service/src/main/java/service/task/manager/dto/task/TaskRequestCreatedDto.java @@ -1,5 +1,6 @@ package service.task.manager.dto.task; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -7,10 +8,24 @@ import java.time.LocalDateTime; /** - * DTO for {@link service.task.manager.model.Task} + * DTO for creating a new task. */ -public record TaskRequestCreatedDto(@NotBlank(message = "blank name") String name, - @NotBlank(message = "blank description") String description, - @NotNull(message = "null start time ") LocalDateTime startTime, - @NotNull Duration duration) { +@Schema(description = "DTO for creating a new task") +public record TaskRequestCreatedDto( + @Schema(description = "Name of the task", example = "Standalone Task", required = true) + @NotBlank(message = "blank name") + String name, + + @Schema(description = "Description of the task", example = "A standalone task", required = true) + @NotBlank(message = "blank description") + String description, + + @Schema(description = "Start time of the task", example = "2025-04-27T10:00:00", required = true) + @NotNull(message = "null start time") + LocalDateTime startTime, + + @Schema(description = "Duration of the task", example = "PT24H", required = true) + @NotNull(message = "null duration") + Duration duration +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java b/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java index 1b9d7f1..8677443 100644 --- a/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java +++ b/service/src/main/java/service/task/manager/dto/task/TaskRequestUpdatedDto.java @@ -1,5 +1,6 @@ package service.task.manager.dto.task; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -8,11 +9,29 @@ import java.time.Duration; /** - * DTO for {@link service.task.manager.model.Task} + * DTO for updating an existing task. */ -public record TaskRequestUpdatedDto(@NotNull(message = "null id") @Positive(message = "not positive id") Long id, - @NotBlank(message = "blank name") String name, - @NotBlank(message = "blank description") String description, - @NotNull(message = "null status") Status status, - @NotNull(message = "null duration") Duration duration) { +@Schema(description = "DTO for updating an existing task") +public record TaskRequestUpdatedDto( + @Schema(description = "ID of the task to update", example = "1", required = true) + @NotNull(message = "null id") + @Positive(message = "not positive id") + Long id, + + @Schema(description = "Updated name of the task", example = "Updated Task", required = true) + @NotBlank(message = "blank name") + String name, + + @Schema(description = "Updated description of the task", example = "Updated standalone task", required = true) + @NotBlank(message = "blank description") + String description, + + @Schema(description = "Updated status of the task", example = "IN_PROGRESS", required = true) + @NotNull(message = "null status") + Status status, + + @Schema(description = "Updated duration of the task", example = "PT48H", required = true) + @NotNull(message = "null duration") + Duration duration +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java b/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java index 467e687..0e60736 100644 --- a/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java +++ b/service/src/main/java/service/task/manager/dto/task/TaskResponseDto.java @@ -1,5 +1,6 @@ package service.task.manager.dto.task; +import io.swagger.v3.oas.annotations.media.Schema; import service.task.manager.model.enums.Status; import service.task.manager.model.enums.TaskType; @@ -7,8 +8,32 @@ import java.time.LocalDateTime; /** - * DTO for {@link service.task.manager.model.Task} + * DTO for retrieving a task with its details. */ -public record TaskResponseDto(Long id, String name, String description, Status status, LocalDateTime startTime, - LocalDateTime endTime, Duration duration, TaskType type) { +@Schema(description = "DTO for retrieving a task with its details") +public record TaskResponseDto( + @Schema(description = "ID of the task", example = "1") + Long id, + + @Schema(description = "Name of the task", example = "Standalone Task") + String name, + + @Schema(description = "Description of the task", example = "A standalone task") + String description, + + @Schema(description = "Status of the task", example = "NEW") + Status status, + + @Schema(description = "Start time of the task", example = "2025-04-27T10:00:00") + LocalDateTime startTime, + + @Schema(description = "End time of the task", example = "2025-04-28T10:00:00") + LocalDateTime endTime, + + @Schema(description = "Duration of the task", example = "PT24H") + Duration duration, + + @Schema(description = "Type of the task (always TASK)", example = "TASK") + TaskType type +) { } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/error/ConflictException.java b/service/src/main/java/service/task/manager/error/ConflictException.java new file mode 100644 index 0000000..cd69165 --- /dev/null +++ b/service/src/main/java/service/task/manager/error/ConflictException.java @@ -0,0 +1,7 @@ +package service.task.manager.error; + +public class ConflictException extends RuntimeException { + public ConflictException(String message) { + super(message); + } +} diff --git a/service/src/main/java/service/task/manager/error/ErrorHandler.java b/service/src/main/java/service/task/manager/error/ErrorHandler.java index 1466790..b0d1b02 100644 --- a/service/src/main/java/service/task/manager/error/ErrorHandler.java +++ b/service/src/main/java/service/task/manager/error/ErrorHandler.java @@ -26,6 +26,12 @@ public ErrorResponse handleTaskConstraintViolationException(TaskConstraintViolat return new ErrorResponse(ex.getMessage()); } + @ExceptionHandler(ConflictException.class) + @ResponseStatus(HttpStatus.CONFLICT) + public ErrorResponse handleConflictException(ConflictException ex) { + return new ErrorResponse(ex.getMessage()); + } + record ErrorResponse(String message) { } diff --git a/service/src/main/java/service/task/manager/mapper/EpicMapper.java b/service/src/main/java/service/task/manager/mapper/EpicMapper.java index 46c6457..742ace8 100644 --- a/service/src/main/java/service/task/manager/mapper/EpicMapper.java +++ b/service/src/main/java/service/task/manager/mapper/EpicMapper.java @@ -1,12 +1,14 @@ package service.task.manager.mapper; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; +import org.mapstruct.MappingTarget; import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.model.Epic; -import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.model.Subtask; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) @@ -27,5 +29,14 @@ public interface EpicMapper { Epic toEntity(EpicRequestUpdatedDto epicRequestUpdatedDto); + Epic toEntity(EpicResponseDto dto); + EpicRequestUpdatedDto toEpicDto(Epic epic); + + @Mapping(target = "id", ignore = true) // Не обновляем ID + @Mapping(target = "startTime", ignore = true) // Оставляем startTime из базы + @Mapping(target = "subtasks", ignore = true) + @Mapping(target = "endTime", ignore = true) + // endTime рассчитывается в @PreUpdate + void updateTaskFromDto(EpicRequestUpdatedDto dto, @MappingTarget Epic epic); } diff --git a/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java b/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java index 6ae312a..062a2fe 100644 --- a/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java +++ b/service/src/main/java/service/task/manager/mapper/SubtaskMapper.java @@ -1,20 +1,31 @@ package service.task.manager.mapper; -import org.mapstruct.*; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.MappingTarget; import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; -import service.task.manager.model.Epic; import service.task.manager.model.Subtask; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public interface SubtaskMapper { // Маппинг из DTO в сущность Subtask - Subtask toEntity(SubtaskRequestCreatedDto subtaskRequestCreatedDto); + Subtask toEntity(SubtaskRequestCreatedDto dto); // Маппинг из сущности Subtask в DTO ответа SubtaskResponseDto toResponseDto(Subtask subtask); - Subtask toEntity(SubtaskRequestUpdatedDto subtaskRequestUpdatedDto); + Subtask toEntity(SubtaskRequestUpdatedDto dto); + + Subtask toEntity(SubtaskResponseDto dto); + + @Mapping(target = "id", ignore = true) // Не обновляем ID + @Mapping(target = "epic", ignore = true) + @Mapping(target = "startTime", ignore = true) // Оставляем startTime из базы + @Mapping(target = "endTime", ignore = true) + // endTime рассчитывается в @PreUpdate + void updateSubtaskFromDto(SubtaskRequestUpdatedDto dto, @MappingTarget Subtask subtask); } diff --git a/service/src/main/java/service/task/manager/mapper/TaskMapper.java b/service/src/main/java/service/task/manager/mapper/TaskMapper.java index 06db9e0..c6062b4 100644 --- a/service/src/main/java/service/task/manager/mapper/TaskMapper.java +++ b/service/src/main/java/service/task/manager/mapper/TaskMapper.java @@ -1,8 +1,10 @@ package service.task.manager.mapper; import org.mapstruct.Mapper; -import service.task.manager.dto.task.TaskRequestUpdatedDto; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; import service.task.manager.model.Task; @@ -17,5 +19,14 @@ public interface TaskMapper { Task toEntity(TaskRequestUpdatedDto taskRequestUpdatedDto); + Task toEntity(TaskResponseDto dto); + TaskRequestUpdatedDto toTaskRequestUpdatedDto(Task task); + + // Метод для обновления существующей сущности + @Mapping(target = "id", ignore = true) // Не обновляем ID + @Mapping(target = "startTime", ignore = true) // Оставляем startTime из базы + @Mapping(target = "endTime", ignore = true) + // endTime рассчитывается в @PreUpdate + void updateTaskFromDto(TaskRequestUpdatedDto dto, @MappingTarget Task task); } diff --git a/service/src/main/java/service/task/manager/repository/EpicRepository.java b/service/src/main/java/service/task/manager/repository/EpicRepository.java index 85a5c79..21f0e52 100644 --- a/service/src/main/java/service/task/manager/repository/EpicRepository.java +++ b/service/src/main/java/service/task/manager/repository/EpicRepository.java @@ -6,4 +6,5 @@ @Repository public interface EpicRepository extends JpaRepository { + boolean existsByName(String name); } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/repository/SubtaskRepository.java b/service/src/main/java/service/task/manager/repository/SubtaskRepository.java index 28b3dca..c59c6fb 100644 --- a/service/src/main/java/service/task/manager/repository/SubtaskRepository.java +++ b/service/src/main/java/service/task/manager/repository/SubtaskRepository.java @@ -6,4 +6,5 @@ @Repository public interface SubtaskRepository extends JpaRepository { + boolean existsByName(String name); } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/repository/TaskRepository.java b/service/src/main/java/service/task/manager/repository/TaskRepository.java index 13cfa45..5b63d35 100644 --- a/service/src/main/java/service/task/manager/repository/TaskRepository.java +++ b/service/src/main/java/service/task/manager/repository/TaskRepository.java @@ -6,4 +6,5 @@ @Repository public interface TaskRepository extends JpaRepository { + boolean existsByName(String name); } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java index 22824f2..865bdf2 100644 --- a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java @@ -3,17 +3,24 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import service.task.manager.dto.epic.EpicRequestCreatedDto; import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.error.ConflictException; import service.task.manager.error.NotFoundException; import service.task.manager.mapper.EpicMapper; import service.task.manager.model.Epic; +import service.task.manager.model.enums.Status; import service.task.manager.repository.EpicRepository; import service.task.manager.service.EpicService; import java.util.List; +/** + * Service implementation for managing epics. + * Provides methods to create, update, retrieve, and delete epics. + */ @Slf4j @Service @RequiredArgsConstructor @@ -21,54 +28,116 @@ public class EpicServiceImpl implements EpicService { private final EpicRepository repository; private final EpicMapper mapper; - /** - * @param dto + * Creates a new epic based on the provided DTO. + *

+ * This method checks if an epic with the same name already exists in the database. + * If it does, a {@link ConflictException} is thrown. Otherwise, the epic is saved + * with a status of {@link Status#NEW} and the end time calculated based on its start time + * and duration. + *

+ * @param dto The DTO containing epic creation data (must include name, start time, and duration). + * @throws ConflictException If an epic with the same name already exists. */ + @Transactional @Override public void create(EpicRequestCreatedDto dto) { - repository.save(mapper.toEntity(dto)); + log.info("Attempting to create epic with name: {}", dto.name()); + if (repository.existsByName(dto.name())) { + log.warn("Epic creation failed: Epic with name {} already exists", dto.name()); + throw new ConflictException("Epic with name " + dto.name() + " already exists"); + } + + Epic epic = addEndTimeEpicAndStatus(mapper.toEntity(dto)); + repository.save(epic); + log.info("Epic created successfully with name: {}", dto.name()); } /** - * @param dto - * @return + * Updates an existing epic with the provided data. + *

+ * This method retrieves the epic by its ID, updates its fields using the provided DTO, + * and saves the changes to the database. + *

+ * @param dto The DTO containing updated epic data (must include epic ID). + * @return The updated epic as a DTO. + * @throws NotFoundException If the epic with the specified ID does not exist. */ + @Transactional @Override public EpicResponseDto update(EpicRequestUpdatedDto dto) { - Epic epic = mapper.toEntity(dto); - epic = repository.save(epic); - return mapper.toResponseDto(epic); + log.info("Attempting to update epic with ID: {}", dto.id()); + Epic existingEpic = mapper.toEntity(findById(dto.id())); + mapper.updateTaskFromDto(dto, existingEpic); + Epic updatedEpic = repository.save(existingEpic); + log.info("Epic with ID {} updated successfully", updatedEpic.getId()); + return mapper.toResponseDto(updatedEpic); } /** - * @param id - * @return + * Retrieves an epic by its ID. + * @param id The ID of the epic to retrieve. + * @return The epic as a DTO. + * @throws NotFoundException If the epic with the specified ID does not exist. */ + @Transactional(readOnly = true) @Override public EpicResponseDto findById(Long id) { - return repository.findById(id). - stream() + log.info("Fetching epic with ID: {}", id); + EpicResponseDto epic = repository.findById(id) + .stream() .map(mapper::toResponseDto) .findFirst() - .orElseThrow(() -> new NotFoundException("Epic not found")); + .orElseThrow(() -> { + log.warn("Epic with ID {} not found", id); + return new NotFoundException("Epic with ID " + id + " not found"); + }); + log.info("Epic with ID {} retrieved successfully", id); + return epic; } /** - * @return + * Retrieves all epics. + * @return A list of all epics as DTOs. */ + @Transactional(readOnly = true) @Override public List findAll() { - return repository.findAll() - .stream().map(mapper::toResponseDto) + log.info("Fetching all epics"); + List epics = repository.findAll() + .stream() + .map(mapper::toResponseDto) .toList(); + log.info("Retrieved {} epics", epics.size()); + return epics; } /** - * @param id + * Deletes an epic by its ID. + *

+ * This method deletes the epic directly from the database. If the epic does not exist, + * no exception is thrown as per the current implementation. + *

+ * @param id The ID of the epic to delete. */ + @Transactional @Override public void delete(Long id) { + log.info("Attempting to delete epic with ID: {}", id); repository.deleteById(id); + log.info("Epic with ID {} deleted successfully", id); + } + + /** + * Sets the status to NEW and calculates the end time for the given epic. + * @param epic The epic to modify. + * @return The modified epic with updated status and end time. + */ + private Epic addEndTimeEpicAndStatus(Epic epic) { + log.debug("Setting status and calculating end time for epic"); + epic.calculateEndTime(); + epic.setStatus(Status.NEW); + log.debug("Epic status set to NEW and end time calculated"); + return epic; } -} +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java index 3cfce16..7ed406a 100644 --- a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java @@ -3,73 +3,149 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import service.task.manager.dto.epic.EpicResponseDto; import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.error.ConflictException; import service.task.manager.error.NotFoundException; +import service.task.manager.mapper.EpicMapper; import service.task.manager.mapper.SubtaskMapper; import service.task.manager.model.Epic; import service.task.manager.model.Subtask; +import service.task.manager.model.enums.Status; import service.task.manager.repository.SubtaskRepository; +import service.task.manager.service.EpicService; import service.task.manager.service.SubtaskService; import java.util.List; +/** + * Service implementation for managing subtasks. + * Provides methods to create, update, retrieve, and delete subtasks associated with epics. + */ @Slf4j @Service @RequiredArgsConstructor public class SubtaskServiceImpl implements SubtaskService { private final SubtaskRepository repository; + private final EpicService epicService; private final SubtaskMapper mapper; - - + private final EpicMapper epicMapper; /** - * @param dto + * Creates a new subtask based on the provided DTO. + *

+ * This method verifies the existence of the associated epic and checks for duplicate subtask names. + * If the epic does not exist, a {@link NotFoundException} will be thrown by the EpicService. + * If a subtask with the same name already exists, a {@link ConflictException} is thrown. + *

+ * @param dto The DTO containing subtask creation data (must include name and epic ID). + * @throws ConflictException If a subtask with the same name already exists. + * @throws NotFoundException If the associated epic does not exist. */ + @Transactional @Override public void create(SubtaskRequestCreatedDto dto) { - repository.save(mapper.toEntity(dto)); + log.info("Attempting to create subtask with name: {}", dto.name()); + + EpicResponseDto epicDto = epicService.findById(dto.epicId()); + Epic epic = epicMapper.toEntity(epicDto); + + if (repository.existsByName(dto.name())) { + log.warn("Subtask creation failed: Subtask with name {} already exists", dto.name()); + throw new ConflictException("Subtask with name " + dto.name() + " already exists"); + } + + Subtask subtask = addEndTimeSubtaskAndStatus(mapper.toEntity(dto)); + subtask.setEpic(epic); + repository.save(subtask); + log.info("Subtask created successfully with name: {}", dto.name()); } /** - * @param dto - * @return + * Updates an existing subtask with the provided data. + *

+ * This method retrieves the subtask by its ID, updates its fields using the provided DTO, + * and saves the changes to the database. + *

+ * @param dto The DTO containing updated subtask data (must include subtask ID). + * @return The updated subtask as a DTO. + * @throws NotFoundException If the subtask with the specified ID does not exist. */ + @Transactional @Override public SubtaskResponseDto update(SubtaskRequestUpdatedDto dto) { - Subtask subtask = mapper.toEntity(dto); - subtask = repository.save(subtask); - return mapper.toResponseDto(subtask); + log.info("Attempting to update subtask with ID: {}", dto.id()); + + Subtask existingSubtask = mapper.toEntity(findById(dto.id())); + mapper.updateSubtaskFromDto(dto, existingSubtask); + Subtask updatedSubtask = repository.save(existingSubtask); + log.info("Subtask with ID {} updated successfully", updatedSubtask.getId()); + return mapper.toResponseDto(updatedSubtask); } /** - * @param id - * @return + * Retrieves a subtask by its ID. + * @param id The ID of the subtask to retrieve. + * @return The subtask as a DTO. + * @throws NotFoundException If the subtask with the specified ID does not exist. */ + @Transactional(readOnly = true) @Override public SubtaskResponseDto findById(Long id) { - return repository.findById(id).stream() + log.info("Fetching subtask with ID: {}", id); + SubtaskResponseDto subtask = repository.findById(id).stream() .map(mapper::toResponseDto) .findFirst() - .orElseThrow(() -> new NotFoundException("Subtask not found")); + .orElseThrow(() -> { + log.warn("Subtask with ID {} not found", id); + return new NotFoundException("Subtask with ID " + id + " not found"); + }); + log.info("Subtask with ID {} retrieved successfully", id); + return subtask; } /** - * @return + * Retrieves all subtasks. + * @return A list of all subtasks as DTOs. */ + @Transactional(readOnly = true) @Override public List findAll() { - return repository.findAll().stream() + log.info("Fetching all subtasks"); + List subtasks = repository.findAll().stream() .map(mapper::toResponseDto) .toList(); + log.info("Retrieved {} subtasks", subtasks.size()); + return subtasks; } /** - * @param id + * Deletes a subtask by its ID. + * @param id The ID of the subtask to delete. + * @throws NotFoundException If the subtask with the specified ID does not exist. */ + @Transactional @Override public void delete(Long id) { + log.info("Attempting to delete subtask with ID: {}", id); + findById(id); // Проверяет существование repository.deleteById(id); + log.info("Subtask with ID {} deleted successfully", id); + } + + /** + * Sets the status to NEW and calculates the end time for the given subtask. + * @param subtask The subtask to modify. + * @return The modified subtask with updated status and end time. + */ + private Subtask addEndTimeSubtaskAndStatus(Subtask subtask) { + log.debug("Setting status and calculating end time for subtask"); + subtask.calculateEndTime(); + subtask.setStatus(Status.NEW); + log.debug("Subtask status set to NEW and end time calculated"); + return subtask; } -} +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java index 5bc72bb..1bb73e7 100644 --- a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java @@ -3,17 +3,24 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import service.task.manager.dto.task.TaskRequestCreatedDto; import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; +import service.task.manager.error.ConflictException; import service.task.manager.error.NotFoundException; import service.task.manager.mapper.TaskMapper; import service.task.manager.model.Task; +import service.task.manager.model.enums.Status; import service.task.manager.repository.TaskRepository; import service.task.manager.service.TaskService; import java.util.List; +/** + * Service implementation for managing tasks. + * Provides methods to create, update, retrieve, and delete tasks. + */ @Slf4j @Service @RequiredArgsConstructor @@ -22,51 +29,101 @@ public class TaskServiceImpl implements TaskService { private final TaskMapper mapper; /** - * @param dto + * Creates a new task based on the provided DTO. + * @param dto The DTO containing task creation data. + * @throws ConflictException If a task with the same name already exists. */ + @Transactional @Override public void create(TaskRequestCreatedDto dto) { - repository.save(mapper.toEntity(dto)); + log.info("Attempting to create task with name: {}", dto.name()); + if (repository.existsByName(dto.name())) { + log.warn("Task creation failed: Task with name {} already exists", dto.name()); + throw new ConflictException("Task with name " + dto.name() + " already exists"); + } + + Task task = addEndTimeTaskAndStatus(mapper.toEntity(dto)); + repository.save(task); + log.info("Task created successfully with name: {}", dto.name()); } /** - * @param dto - * @return + * Updates an existing task with the provided data. + * @param dto The DTO containing updated task data. + * @return The updated task as a DTO. + * @throws NotFoundException If the task with the specified ID does not exist. */ + @Transactional @Override public TaskResponseDto update(TaskRequestUpdatedDto dto) { - Task task = mapper.toEntity(dto); - task = repository.save(task); - return mapper.toResponseDto(task); + log.info("Attempting to update task with ID: {}", dto.id()); + Task existingTask = mapper.toEntity(findById(dto.id())); + mapper.updateTaskFromDto(dto, existingTask); + Task updatedTask = repository.save(existingTask); + log.info("Task with ID {} updated successfully", updatedTask.getId()); + return mapper.toResponseDto(updatedTask); } /** - * @param id - * @return + * Retrieves a task by its ID. + * @param id The ID of the task to retrieve. + * @return The task as a DTO. + * @throws NotFoundException If the task with the specified ID does not exist. */ + @Transactional(readOnly = true) @Override public TaskResponseDto findById(Long id) { - return repository.findById(id).stream() + log.info("Fetching task with ID: {}", id); + TaskResponseDto task = repository.findById(id).stream() .map(mapper::toResponseDto) .findFirst() - .orElseThrow(() -> new NotFoundException("Task not found")); + .orElseThrow(() -> { + log.warn("Task with ID {} not found", id); + return new NotFoundException("Task with ID " + id + " not found"); + }); + log.info("Task with ID {} retrieved successfully", id); + return task; } /** - * @return + * Retrieves all tasks. + * @return A list of all tasks as DTOs. */ + @Transactional(readOnly = true) @Override public List findAll() { - return repository.findAll().stream() + log.info("Fetching all tasks"); + List tasks = repository.findAll().stream() .map(mapper::toResponseDto) .toList(); + log.info("Retrieved {} tasks", tasks.size()); + return tasks; } /** - * @param id + * Deletes a task by its ID. + * @param id The ID of the task to delete. + * @throws NotFoundException If the task with the specified ID does not exist. */ + @Transactional @Override public void delete(Long id) { + log.info("Attempting to delete task with ID: {}", id); + findById(id); // Проверяет существование repository.deleteById(id); + log.info("Task with ID {} deleted successfully", id); + } + + /** + * Sets the status to NEW and calculates the end time for the given task. + * @param task The task to modify. + * @return The modified task with updated status and end time. + */ + private Task addEndTimeTaskAndStatus(Task task) { + log.debug("Setting status and calculating end time for task"); + task.calculateEndTime(); + task.setStatus(Status.NEW); + log.debug("Task status set to NEW and end time calculated"); + return task; } -} +} \ No newline at end of file diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml index 04967c9..3c4728b 100644 --- a/service/src/main/resources/application.yml +++ b/service/src/main/resources/application.yml @@ -10,4 +10,17 @@ spring: schema-locations: classpath:schema.sql jpa: properties: - hibernate.dialect: org.hibernate.dialect.H2Dialect \ No newline at end of file + hibernate.dialect: org.hibernate.dialect.H2Dialect + +# Путь к Swagger UI +springdoc: + swagger-ui: + path: /swagger-ui.html + tags-sorter: alpha + operations-sorter: alpha + info: + title: Task Manager API + version: 1.0.0 + api-docs: + enabled: true + path: /v3/api-docs \ No newline at end of file From 737e3c0692a54aa457b03a28be397585d9baf871 Mon Sep 17 00:00:00 2001 From: YG Date: Tue, 29 Apr 2025 19:07:15 +0300 Subject: [PATCH 6/9] feat: added test controller and exception validation --- .idea/misc.xml | 9 + amplicode.xml | 12 + .../manager/controller/EpicController.java | 6 +- .../manager/controller/SubtaskController.java | 6 +- .../manager/controller/TaskController.java | 6 +- .../task/manager/error/ErrorHandler.java | 54 +++- .../controller/EpicControllerTest.java | 253 +++++++++++++++++ .../controller/SubtaskControllerTest.java | 256 ++++++++++++++++++ .../controller/TaskControllerTest.java | 223 +++++++++++++++ 9 files changed, 817 insertions(+), 8 deletions(-) create mode 100644 amplicode.xml create mode 100644 service/src/test/java/service/task/manager/controller/EpicControllerTest.java create mode 100644 service/src/test/java/service/task/manager/controller/SubtaskControllerTest.java create mode 100644 service/src/test/java/service/task/manager/controller/TaskControllerTest.java diff --git a/.idea/misc.xml b/.idea/misc.xml index 1ceb38f..864598a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,14 @@ + + +
+ + org.springframework.boot + spring-boot-starter-data-redis + + + org.mockito + mockito-core + 5.12.0 + test + + + org.mockito + mockito-junit-jupiter + 5.12.0 + test + diff --git a/service/src/main/java/service/task/manager/config/RedisConfig.java b/service/src/main/java/service/task/manager/config/RedisConfig.java new file mode 100644 index 0000000..74d82ec --- /dev/null +++ b/service/src/main/java/service/task/manager/config/RedisConfig.java @@ -0,0 +1,20 @@ +package service.task.manager.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import service.task.manager.model.HistoryEntry; + +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(HistoryEntry.class)); + return template; + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/HistoryController.java b/service/src/main/java/service/task/manager/controller/HistoryController.java new file mode 100644 index 0000000..d877200 --- /dev/null +++ b/service/src/main/java/service/task/manager/controller/HistoryController.java @@ -0,0 +1,60 @@ +package service.task.manager.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import service.task.manager.model.HistoryEntry; +import service.task.manager.service.HistoryService; + +import java.util.List; + +/** + * Controller for managing and retrieving the history of task accesses. + */ +@Slf4j +@CrossOrigin +@RestController +@RequiredArgsConstructor +@RequestMapping("/history") +@Tag(name = "History API", description = "API for retrieving the history of task accesses") +public class HistoryController { + + private final HistoryService service; + + /** + * Retrieves the history of task accesses. + * The history contains the last 10 records of calls to the findBy(long id) method for tasks, epics, and subtasks. + * + * @return a list of history entries + */ + @GetMapping + @Operation(summary = "Get task access history", description = "Retrieves the history of the last 10 task accesses (Task, Epic, Subtask) made via the findBy(long id) method.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "History retrieved successfully", + content = @Content(mediaType = "application/json", + array = @ArraySchema(schema = @Schema(implementation = HistoryEntry.class)))), + @ApiResponse(responseCode = "500", description = "Internal server error, possibly due to Redis connectivity issues", + content = @Content) + }) + public List getHistory() { + try { + log.info("Received request to retrieve task access history"); + List history = service.getHistory(); + log.debug("Successfully retrieved history with {} entries", history.size()); + return history; + } catch (Exception e) { + log.error("Failed to retrieve task access history: {}", e.getMessage(), e); + throw new RuntimeException("Failed to retrieve history", e); + } + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/model/HistoryEntry.java b/service/src/main/java/service/task/manager/model/HistoryEntry.java new file mode 100644 index 0000000..c0f6bf4 --- /dev/null +++ b/service/src/main/java/service/task/manager/model/HistoryEntry.java @@ -0,0 +1,14 @@ +package service.task.manager.model; + +import lombok.*; +import service.task.manager.model.enums.TaskType; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@ToString +public class HistoryEntry { + private TaskType type; + private Long id; +} diff --git a/service/src/main/java/service/task/manager/service/HistoryService.java b/service/src/main/java/service/task/manager/service/HistoryService.java new file mode 100644 index 0000000..beadd90 --- /dev/null +++ b/service/src/main/java/service/task/manager/service/HistoryService.java @@ -0,0 +1,13 @@ +package service.task.manager.service; + +import service.task.manager.model.HistoryEntry; +import service.task.manager.model.enums.TaskType; + +import java.util.List; + +public interface HistoryService { + + void addToHistory(TaskType type, Long id); + + List getHistory(); +} diff --git a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java index 865bdf2..9ffd425 100644 --- a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java @@ -14,6 +14,7 @@ import service.task.manager.model.enums.Status; import service.task.manager.repository.EpicRepository; import service.task.manager.service.EpicService; +import service.task.manager.service.HistoryService; import java.util.List; @@ -27,6 +28,7 @@ public class EpicServiceImpl implements EpicService { private final EpicRepository repository; private final EpicMapper mapper; + private final HistoryService history; /** * Creates a new epic based on the provided DTO. @@ -92,6 +94,7 @@ public EpicResponseDto findById(Long id) { log.warn("Epic with ID {} not found", id); return new NotFoundException("Epic with ID " + id + " not found"); }); + history.addToHistory(epic.type(), epic.id()); log.info("Epic with ID {} retrieved successfully", id); return epic; } diff --git a/service/src/main/java/service/task/manager/service/impl/HistoryServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/HistoryServiceImpl.java new file mode 100644 index 0000000..d020ac6 --- /dev/null +++ b/service/src/main/java/service/task/manager/service/impl/HistoryServiceImpl.java @@ -0,0 +1,83 @@ +package service.task.manager.service.impl; + +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import service.task.manager.model.HistoryEntry; +import service.task.manager.model.enums.TaskType; +import service.task.manager.service.HistoryService; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of the service for managing the history of accesses to tasks (Task, Epic, Subtask) using Redis. + * The history stores the last 10 records of calls to the findBy(long id) method for tasks, epics, and subtasks. + * If the number of records exceeds the limit, the oldest record is removed. + */ +@Slf4j +@Service +public class HistoryServiceImpl implements HistoryService { + + private static final String HISTORY_KEY = "history"; + private static final int HISTORY_SIZE = 10; + + private final RedisTemplate redisTemplate; + private final ListOperations listOps; + + @Autowired + public HistoryServiceImpl(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + this.listOps = redisTemplate.opsForList(); + } + + /** + * Adds a record to the history of findBy(long id) method calls. + * If the history size exceeds the limit (10 records), the oldest record is removed. + * + * @param type the type of the task (TASK, EPIC, SUBTASK) + * @param id the identifier of the task + */ + @Override + public void addToHistory(TaskType type, Long id) { + try { + HistoryEntry entry = new HistoryEntry(type, id); + log.info("Adding entry to history: type={}, id={}", type, id); + listOps.rightPush(HISTORY_KEY, entry); + Long size = listOps.size(HISTORY_KEY); + if (size != null && size > HISTORY_SIZE) { + log.debug("History size exceeded limit ({}), removing oldest entry", HISTORY_SIZE); + listOps.leftPop(HISTORY_KEY); + } + } catch (Exception e) { + log.error("Failed to add entry to history: type={}, id={}, error={}", type, id, e.getMessage(), e); + } + } + + /** + * Retrieves the list of entries from the history of method calls. + * If the history is empty or an error occurs while retrieving data, an empty list is returned. + * + * @return the list of history entries + */ + @Override + public List getHistory() { + try { + log.info("Retrieving call history"); + List history = listOps.range(HISTORY_KEY, 0, -1); + if (history == null) { + log.warn("History is empty or failed to retrieve data from Redis"); + return new ArrayList<>(); + } + log.debug("Successfully retrieved {} entries from history", history.size()); + return history; + } catch (Exception e) { + log.error("Failed to retrieve history: {}", e.getMessage(), e); + return new ArrayList<>(); + } + } +} \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java index 7ed406a..0a2d47e 100644 --- a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java @@ -17,6 +17,7 @@ import service.task.manager.model.enums.Status; import service.task.manager.repository.SubtaskRepository; import service.task.manager.service.EpicService; +import service.task.manager.service.HistoryService; import service.task.manager.service.SubtaskService; import java.util.List; @@ -33,6 +34,7 @@ public class SubtaskServiceImpl implements SubtaskService { private final EpicService epicService; private final SubtaskMapper mapper; private final EpicMapper epicMapper; + private final HistoryService history; /** * Creates a new subtask based on the provided DTO. @@ -103,6 +105,7 @@ public SubtaskResponseDto findById(Long id) { log.warn("Subtask with ID {} not found", id); return new NotFoundException("Subtask with ID " + id + " not found"); }); + history.addToHistory(subtask.type(),subtask.id()); log.info("Subtask with ID {} retrieved successfully", id); return subtask; } diff --git a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java index 1bb73e7..1ba09da 100644 --- a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java @@ -13,6 +13,7 @@ import service.task.manager.model.Task; import service.task.manager.model.enums.Status; import service.task.manager.repository.TaskRepository; +import service.task.manager.service.HistoryService; import service.task.manager.service.TaskService; import java.util.List; @@ -27,6 +28,7 @@ public class TaskServiceImpl implements TaskService { private final TaskRepository repository; private final TaskMapper mapper; + private final HistoryService history; /** * Creates a new task based on the provided DTO. @@ -81,6 +83,7 @@ public TaskResponseDto findById(Long id) { log.warn("Task with ID {} not found", id); return new NotFoundException("Task with ID " + id + " not found"); }); + history.addToHistory(task.type(),task.id()); log.info("Task with ID {} retrieved successfully", id); return task; } diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml index 3c4728b..8530f08 100644 --- a/service/src/main/resources/application.yml +++ b/service/src/main/resources/application.yml @@ -11,6 +11,10 @@ spring: jpa: properties: hibernate.dialect: org.hibernate.dialect.H2Dialect + data: + redis: + host: localhost + port: 6379 # Путь к Swagger UI springdoc: diff --git a/service/src/test/java/service/task/manager/service/HistoryServiceImplTest.java b/service/src/test/java/service/task/manager/service/HistoryServiceImplTest.java new file mode 100644 index 0000000..ad3471b --- /dev/null +++ b/service/src/test/java/service/task/manager/service/HistoryServiceImplTest.java @@ -0,0 +1,91 @@ +package service.task.manager.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.redis.core.ListOperations; +import org.springframework.data.redis.core.RedisTemplate; +import service.task.manager.model.HistoryEntry; +import service.task.manager.model.enums.TaskType; +import service.task.manager.service.impl.HistoryServiceImpl; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class HistoryServiceImplTest { + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private ListOperations listOps; + + @InjectMocks + private HistoryServiceImpl historyService; + + @BeforeEach + void setUp() { + // Ensure redisTemplate.opsForList() returns the mocked listOps + when(redisTemplate.opsForList()).thenReturn(listOps); + } + + @Test + void addToHistory_WithinLimit_AddsEntry() { + // Mock the size of the list and the range result + when(listOps.size("history")).thenReturn(1L); + when(listOps.range("history", 0, -1)).thenReturn(List.of(new HistoryEntry(TaskType.EPIC, 1L))); + + // Call the method under test + historyService.addToHistory(TaskType.EPIC, 1L); + List history = historyService.getHistory(); + + // Verify the result + assertEquals(1, history.size()); + assertEquals(TaskType.EPIC, history.get(0).getType()); + assertEquals(1L, history.get(0).getId()); + + // Verify interactions with listOps + verify(listOps, times(1)).rightPush("history", new HistoryEntry(TaskType.EPIC, 1L)); + } + + @Test + void addToHistory_ExceedsLimit_RemovesOldest() { + // Create a list to simulate the history entries + List historyEntries = new ArrayList<>(); + + // Simulate adding 11 entries + for (long i = 1; i <= 11; i++) { + // Mock the size of the list to increase with each addition + when(listOps.size("history")).thenReturn(i); + historyService.addToHistory(TaskType.TASK, i); + historyEntries.add(new HistoryEntry(TaskType.TASK, i)); + + // If size exceeds MAX_HISTORY_SIZE (10), simulate leftPop + if (i > 10) { + historyEntries.remove(0); // Remove the oldest entry + } + } + + // Mock the final state of the history after trimming + when(listOps.range("history", 0, -1)).thenReturn(historyEntries); + + // Call getHistory to retrieve the result + List history = historyService.getHistory(); + + // Verify the result + assertEquals(10, history.size()); + assertEquals(2L, history.get(0).getId()); // First entry (id=1) should be removed + assertEquals(11L, history.get(9).getId()); + + // Verify interactions with listOps + verify(listOps, times(11)).rightPush(eq("history"), any(HistoryEntry.class)); + verify(listOps, times(1)).leftPop("history"); // Should trim once after exceeding limit + } +} \ No newline at end of file From 70532bb61cc0dd894d6c54cad65174583bf88bf8 Mon Sep 17 00:00:00 2001 From: YG Date: Mon, 5 May 2025 20:38:08 +0300 Subject: [PATCH 8/9] feat: add test services, methods prioritized, readme, docker --- service/Dockerfile | 9 +- .../manager/controller/EpicController.java | 17 + .../manager/controller/HistoryController.java | 2 +- .../service/task/manager/controller/README.md | 105 ++++++ .../manager/controller/SubtaskController.java | 17 + .../manager/controller/TaskController.java | 17 + .../java/service/task/manager/dto/README.md | 153 +++++++++ .../ConflictException.java | 2 +- .../{error => exception}/ErrorHandler.java | 2 +- .../InvalidTaskDataException.java | 2 +- .../NotFoundException.java | 2 +- .../service/task/manager/exception/README.md | 90 +++++ .../TaskConstraintViolationException.java | 2 +- .../task/manager/service/EpicService.java | 2 + .../task/manager/service/SubtaskService.java | 2 + .../task/manager/service/TaskService.java | 2 + .../manager/service/impl/EpicServiceImpl.java | 42 ++- .../service/impl/HistoryServiceImpl.java | 4 +- .../service/impl/SubtaskServiceImpl.java | 42 ++- .../manager/service/impl/TaskServiceImpl.java | 42 ++- service/src/main/resources/application.yml | 2 +- .../controller/EpicControllerTest.java | 4 +- .../controller/SubtaskControllerTest.java | 6 +- .../controller/TaskControllerTest.java | 4 +- .../manager/service/EpicServiceImplTest.java | 235 +++++++++++++ .../service/HistoryServiceImplTest.java | 153 ++++++--- .../service/SubtaskServiceImplTest.java | 311 ++++++++++++++++++ .../manager/service/TaskServiceImplTest.java | 265 +++++++++++++++ src/task/manager/schedule/Main.java | 6 - .../exception/ManagerSaveException.java | 18 - .../schedule/exception/NotFoundException.java | 18 - .../manager/schedule/server/Endpoint.java | 19 -- .../schedule/server/EndpointHandler.java | 39 --- .../manager/schedule/server/HttpHandler.java | 203 ------------ .../schedule/server/HttpTaskServer.java | 33 -- .../manager/schedule/server/TaskHandler.java | 31 -- .../service/FileBackedTaskManager.java | 180 ---------- .../schedule/service/HistoryManager.java | 15 - .../manager/schedule/service/Manager.java | 15 - .../manager/schedule/service/TaskManager.java | 49 --- .../inMemory/InMemoryHistoryManager.java | 92 ------ .../service/inMemory/InMemoryTaskManager.java | 295 ----------------- .../manager/schedule/module/EpicTest.java | 19 -- .../service/FileBackedTaskManagerTest.java | 49 --- .../service/InMemoryTaskManagerTest.java | 110 ------- 45 files changed, 1458 insertions(+), 1269 deletions(-) create mode 100644 service/src/main/java/service/task/manager/controller/README.md create mode 100644 service/src/main/java/service/task/manager/dto/README.md rename service/src/main/java/service/task/manager/{error => exception}/ConflictException.java (77%) rename service/src/main/java/service/task/manager/{error => exception}/ErrorHandler.java (98%) rename service/src/main/java/service/task/manager/{error => exception}/InvalidTaskDataException.java (79%) rename service/src/main/java/service/task/manager/{error => exception}/NotFoundException.java (77%) create mode 100644 service/src/main/java/service/task/manager/exception/README.md rename service/src/main/java/service/task/manager/{error => exception}/TaskConstraintViolationException.java (80%) create mode 100644 service/src/test/java/service/task/manager/service/EpicServiceImplTest.java create mode 100644 service/src/test/java/service/task/manager/service/SubtaskServiceImplTest.java create mode 100644 service/src/test/java/service/task/manager/service/TaskServiceImplTest.java delete mode 100644 src/task/manager/schedule/Main.java delete mode 100644 src/task/manager/schedule/exception/ManagerSaveException.java delete mode 100644 src/task/manager/schedule/exception/NotFoundException.java delete mode 100644 src/task/manager/schedule/server/Endpoint.java delete mode 100644 src/task/manager/schedule/server/EndpointHandler.java delete mode 100644 src/task/manager/schedule/server/HttpHandler.java delete mode 100644 src/task/manager/schedule/server/HttpTaskServer.java delete mode 100644 src/task/manager/schedule/server/TaskHandler.java delete mode 100644 src/task/manager/schedule/service/FileBackedTaskManager.java delete mode 100644 src/task/manager/schedule/service/HistoryManager.java delete mode 100644 src/task/manager/schedule/service/Manager.java delete mode 100644 src/task/manager/schedule/service/TaskManager.java delete mode 100644 src/task/manager/schedule/service/inMemory/InMemoryHistoryManager.java delete mode 100644 src/task/manager/schedule/service/inMemory/InMemoryTaskManager.java delete mode 100644 test/task/manager/schedule/module/EpicTest.java delete mode 100644 test/task/manager/schedule/service/FileBackedTaskManagerTest.java delete mode 100644 test/task/manager/schedule/service/InMemoryTaskManagerTest.java diff --git a/service/Dockerfile b/service/Dockerfile index 9fd6682..e2225cf 100644 --- a/service/Dockerfile +++ b/service/Dockerfile @@ -1,4 +1,5 @@ -FROM ubuntu:latest -LABEL authors="admin" - -ENTRYPOINT ["top", "-b"] \ No newline at end of file +FROM eclipse-temurin:21-jre-jammy +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} service.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /service.jar"] \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/EpicController.java b/service/src/main/java/service/task/manager/controller/EpicController.java index 59aac2b..aa386bd 100644 --- a/service/src/main/java/service/task/manager/controller/EpicController.java +++ b/service/src/main/java/service/task/manager/controller/EpicController.java @@ -89,4 +89,21 @@ public ResponseEntity delete( service.delete(id); return ResponseEntity.noContent().build(); } + + @GetMapping("/prioritized") + @Operation( + summary = "Retrieve prioritized epics", + description = "Returns a list of all epics sorted by priority: IN_PROGRESS first, then NEW, and finally DONE. " + + "Within each status, epics are sorted by end time (earliest first)." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "Successfully retrieved the list of prioritized epics"), + @ApiResponse(responseCode = "500", + description = "Internal server exception") + }) + public ResponseEntity> prioritized() { + log.info("Fetching prioritized epics"); + return ResponseEntity.ok(service.prioritized()); + } } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/HistoryController.java b/service/src/main/java/service/task/manager/controller/HistoryController.java index d877200..4b39be1 100644 --- a/service/src/main/java/service/task/manager/controller/HistoryController.java +++ b/service/src/main/java/service/task/manager/controller/HistoryController.java @@ -43,7 +43,7 @@ public class HistoryController { @ApiResponse(responseCode = "200", description = "History retrieved successfully", content = @Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = HistoryEntry.class)))), - @ApiResponse(responseCode = "500", description = "Internal server error, possibly due to Redis connectivity issues", + @ApiResponse(responseCode = "500", description = "Internal server exception, possibly due to Redis connectivity issues", content = @Content) }) public List getHistory() { diff --git a/service/src/main/java/service/task/manager/controller/README.md b/service/src/main/java/service/task/manager/controller/README.md new file mode 100644 index 0000000..9fe2ab7 --- /dev/null +++ b/service/src/main/java/service/task/manager/controller/README.md @@ -0,0 +1,105 @@ +# README.md для папки `controller` + +## Описание + +Папка `controller` содержит REST-контроллеры приложения `service.task.manager`, разработанного на Spring Boot. Контроллеры обрабатывают HTTP-запросы для управления задачами, эпиками, подзадачами и историей доступа. Все эндпоинты задокументированы с использованием аннотаций OpenAPI (Swagger), что упрощает интеграцию и тестирование API. Логирование реализовано через SLF4J, а валидация данных — через Jakarta Validation. + +## Структура папки + +Папка включает следующие контроллеры: + +### 1. `EpicController.java` +**Описание**: Управляет эпиками — крупными задачами, содержащими подзадачи. +**Эндпоинты**: +- `POST /epic` — Создание нового эпика. +- `PUT /epic` — Обновление существующего эпика. +- `GET /epic/{id}` — Получение эпика по ID (включая подзадачи). +- `GET /epic` — Получение списка всех эпиков. +- `DELETE /epic/{id}` — Удаление эпика по ID. +- `GET /epic/prioritized` — Получение эпиков, отсортированных по приоритету (`IN_PROGRESS`, `NEW`, `DONE`) и времени завершения. + +### 2. `HistoryController.java` +**Описание**: Предоставляет доступ к истории вызовов метода `findById` для задач, эпиков и подзадач. +**Эндпоинты**: +- `GET /history` — Получение последних 10 записей истории доступа. + +### 3. `SubtaskController.java` +**Описание**: Управляет подзадачами, связанными с эпиками. +**Эндпоинты**: +- `POST /subtask` — Создание новой подзадачи. +- `PUT /subtask` — Обновление существующей подзадачи. +- `GET /subtask/{id}` — Получение подзадачи по ID. +- `GET /subtask` — Получение списка всех подзадач. +- `DELETE /subtask/{id}` — Удаление подзадачи по ID. +- `GET /subtask/prioritized` — Получение подзадач, отсортированных по приоритету (`IN_PROGRESS`, `NEW`, `DONE`) и времени завершения. + +### 4. `TaskController.java` +**Описание**: Управляет задачами — основными единицами работы в системе. +**Эндпоинты**: +- `POST /task` — Создание новой задачи. +- `PUT /task` — Обновление существующей задачи. +- `GET /task/{id}` — Получение задачи по ID. +- `GET /task` — Получение списка всех задач. +- `DELETE /task/{id}` — Удаление задачи по ID. +- `GET /task/prioritized` — Получение задач, отсортированных по приоритету (`IN_PROGRESS`, `NEW`, `DONE`) и времени завершения. + +## Основные особенности + +- **Документация API**: Эндпоинты аннотированы с помощью `@Operation`, `@ApiResponses`, `@Tag` для генерации спецификации OpenAPI. Документация доступна через Swagger UI по пути `/swagger-ui.html`. +- **Логирование**: Используется SLF4J (`@Slf4j`) для записи операций (создание, обновление, удаление, получение) в логи. +- **Валидация данных**: Входящие запросы проверяются с помощью аннотаций `@Valid`, `@NotNull`, `@Positive`. +- **CORS**: Поддержка кросс-доменных запросов через `@CrossOrigin`. +- **Коды HTTP-ответов**: + - `201 Created` — Успешное создание ресурса. + - `200 OK` — Успешное получение или обновление ресурса. + - `204 No Content` — Успешное удаление ресурса. + - `404 Not Found` — Ресурс не найден. + - `409 Conflict` — Конфликт (например, дублирование имени). + - `500 Internal Server Error` — Внутренняя ошибка (например, проблемы с Redis в `HistoryController`). +- **Сортировка по приоритету**: Эндпоинты `/prioritized` возвращают списки, отсортированные по статусу (`IN_PROGRESS` → `NEW` → `DONE`) и времени завершения (от раннего к позднему). + +## Зависимости + +Контроллеры используют сервисы, инжектируемые через конструктор (`@RequiredArgsConstructor`): +- `EpicService` — для работы с эпиками. +- `HistoryService` — для работы с историей доступа (зависит от Redis). +- `SubtaskService` — для работы с подзадачами. +- `TaskService` — для работы с задачами. + +## Документация API + +API документируется с использованием Springdoc OpenAPI. Доступ к документации: +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +Конфигурация Springdoc: +- Название API: **Task Manager API** +- Версия: **1.0.0** +- Сортировка: Теги и операции сортируются по алфавиту (`tags-sorter: alpha`, `operations-sorter: alpha`). + +## Пример использования + +Контроллеры предназначены для взаимодействия с клиентскими приложениями через REST API. Для тестирования используйте Swagger UI или инструменты вроде `curl`. + +**Пример создания эпика**: +```bash +curl -X POST http://localhost:8080/epic \ +-H "Content-Type: application/json" \ +-d '{"name": "Новый эпик", "description": "Описание эпика"}' +``` + +**Пример получения истории**: +```bash +curl -X GET http://localhost:8080/history +``` + +## Примечания + +- **Redis**: Для работы `HistoryController` требуется подключение к Redis, так как история хранится в кэше. +- **DTO**: Контроллеры используют объекты передачи данных (DTO) для строгой типизации и валидации. +- **RESTful-дизайн**: Эндпоинты следуют принципам REST, обеспечивая удобное взаимодействие. +- **Swagger UI**: Документация API доступна по пути `/swagger-ui.html` после запуска приложения. + +## Дополнительно + +Для подробной информации о структуре DTO или сервисах см. папки `dto` и `service`. По вопросам настройки или расширения API обращайтесь к документации проекта или разработчикам. \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/SubtaskController.java b/service/src/main/java/service/task/manager/controller/SubtaskController.java index 933a4d3..f5e9b56 100644 --- a/service/src/main/java/service/task/manager/controller/SubtaskController.java +++ b/service/src/main/java/service/task/manager/controller/SubtaskController.java @@ -90,4 +90,21 @@ public ResponseEntity delete( service.delete(id); return ResponseEntity.noContent().build(); } + + @GetMapping("/prioritized") + @Operation( + summary = "Retrieve prioritized subtasks", + description = "Returns a list of all subtasks sorted by priority: IN_PROGRESS first, then NEW, and finally DONE. " + + "Within each status, subtasks are sorted by end time (earliest first)." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "Successfully retrieved the list of prioritized subtasks"), + @ApiResponse(responseCode = "500", + description = "Internal server exception") + }) + public ResponseEntity> prioritized() { + log.info("Fetching prioritized subtasks"); + return ResponseEntity.ok(service.prioritized()); + } } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/controller/TaskController.java b/service/src/main/java/service/task/manager/controller/TaskController.java index 8ec5844..c37f739 100644 --- a/service/src/main/java/service/task/manager/controller/TaskController.java +++ b/service/src/main/java/service/task/manager/controller/TaskController.java @@ -89,4 +89,21 @@ public ResponseEntity delete( service.delete(id); return ResponseEntity.noContent().build(); } + + @GetMapping("/prioritized") + @Operation( + summary = "Retrieve prioritized tasks", + description = "Returns a list of all tasks sorted by priority: IN_PROGRESS first, then NEW, and finally DONE. " + + "Within each status, tasks are sorted by end time (earliest first)." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", + description = "Successfully retrieved the list of prioritized tasks"), + @ApiResponse(responseCode = "500", + description = "Internal server exception") + }) + public ResponseEntity> prioritized() { + log.info("Fetching prioritized subtasks"); + return ResponseEntity.ok(service.prioritized()); + } } \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/dto/README.md b/service/src/main/java/service/task/manager/dto/README.md new file mode 100644 index 0000000..391d701 --- /dev/null +++ b/service/src/main/java/service/task/manager/dto/README.md @@ -0,0 +1,153 @@ +# README.md для папки `dto` + +## Описание + +Папка `dto` содержит классы объектов передачи данных (Data Transfer Objects, DTO) для приложения `service.task.manager`. DTO используются для обмена данными между клиентскими запросами и серверной логикой, обеспечивая строгую типизацию, валидацию и документирование. Все DTO аннотированы с использованием OpenAPI (Swagger) для автоматической генерации документации API. Папка разделена на три подпапки: `task`, `epic` и `subtask`, каждая из которых отвечает за соответствующие сущности. + +## Структура папки + +### 1. Папка `epic` +Содержит DTO для работы с эпиками — крупными задачами, которые могут включать подзадачи. + +- **`EpicRequestCreatedDto.java`** + **Описание**: DTO для создания нового эпика. + **Поля**: + - `name` (`String`, обязательное, не пустое) — Название эпика. + - `description` (`String`, обязательное, не пустое) — Описание эпика. + - `startTime` (`LocalDateTime`, обязательное) — Время начала эпика. + - `duration` (`Duration`, обязательное) — Длительность эпика. + **Особенности**: Использует аннотацию `@Builder` для удобного создания объектов. + +- **`EpicRequestUpdatedDto.java`** + **Описание**: DTO для обновления существующего эпика. + **Поля**: + - `id` (`Long`, обязательное, положительное) — Идентификатор эпика. + - `name` (`String`, обязательное, не пустое) — Обновленное название. + - `description` (`String`, обязательное, не пустое) — Обновленное описание. + - `status` (`Status`, обязательное) — Обновленный статус (`NEW`, `IN_PROGRESS`, `DONE`). + - `duration` (`Duration`, обязательное) — Обновленная длительность. + +- **`EpicResponseDto.java`** + **Описание**: DTO для возврата данных об эпике, включая связанные подзадачи. + **Поля**: + - `id` (`Long`) — Идентификатор эпика. + - `subtasks` (`List`) — Список подзадач. + - `name` (`String`) — Название эпика. + - `description` (`String`) — Описание эпика. + - `status` (`Status`) — Статус эпика. + - `startTime` (`LocalDateTime`) — Время начала. + - `duration` (`Duration`) — Длительность. + - `endTime` (`LocalDateTime`) — Время окончания. + - `type` (`TaskType`) — Тип задачи (всегда `EPIC`). + **Внутренний рекорд**: + - `SubtaskDto` — Вложенный DTO для представления подзадач внутри эпика с полями `id`, `name`, `description`, `status`. + +### 2. Папка `subtask` +Содержит DTO для работы с подзадачами, которые связаны с эпиками. + +- **`SubtaskRequestCreatedDto.java`** + **Описание**: DTO для создания новой подзадачи. + **Поля**: + - `epicId` (`Long`, обязательное) — Идентификатор эпика, к которому привязана подзадача. + - `name` (`String`, обязательное, не пустое) — Название подзадачи. + - `description` (`String`, обязательное, не пустое) — Описание подзадачи. + - `startTime` (`LocalDateTime`, обязательное) — Время начала. + - `duration` (`Duration`, обязательное) — Длительность. + **Особенности**: Использует аннотацию `@Builder`. + +- **`SubtaskRequestUpdatedDto.java`** + **Описание**: DTO для обновления существующей подзадачи. + **Поля**: + - `id` (`Long`, обязательное, положительное) — Идентификатор подзадачи. + - `name` (`String`, обязательное, не пустое) — Обновленное название. + - `description` (`String`, обязательное, не пустое) — Обновленное описание. + - `status` (`Status`, обязательное) — Обновленный статус. + - `duration` (`Duration`, обязательное) — Обновленная длительность. + +- **`SubtaskResponseDto.java`** + **Описание**: DTO для возврата данных о подзадаче. + **Поля**: + - `id` (`Long`) — Идентификатор подзадачи. + - `epicId` (`Long`) — Идентификатор связанного эпика. + - `name` (`String`) — Название подзадачи. + - `description` (`String`) — Описание подзадачи. + - `status` (`Status`) — Статус подзадачи. + - `startTime` (`LocalDateTime`) — Время начала. + - `endTime` (`LocalDateTime`) — Время окончания. + - `duration` (`Duration`) — Длительность. + - `type` (`TaskType`) — Тип задачи (всегда `SUBTASK`). + +### 3. Папка `task` +Содержит DTO для работы с задачами — независимыми единицами работы. + +- **`TaskRequestCreatedDto.java`** + **Описание**: DTO для создания новой задачи. + **Поля**: + - `name` (`String`, обязательное, не пустое) — Название задачи. + - `description` (`String`, обязательное, не пустое) — Описание задачи. + - `startTime` (`LocalDateTime`, обязательное) — Время начала. + - `duration` (`Duration`, обязательное) — Длительность. + +- **`TaskRequestUpdatedDto.java`** + **Описание**: DTO для обновления существующей задачи. + **Поля**: + - `id` (`Long`, обязательное, положительное) — Идентификатор задачи. + - `name` (`String`, обязательное, не пустое) — Обновленное название. + - `description` (`String`, обязательное, не пустое) — Обновленное описание. + - `status` (`Status`, обязательное) — Обновленный статус. + - `duration` (`Duration`, обязательное) — Обновленная длительность. + +- **`TaskResponseDto.java`** + **Описание**: DTO для возврата данных о задаче. + **Поля**: + - `id` (`Long`) — Идентификатор задачи. + - `name` (`String`) — Название задачи. + - `description` (`String`) — Описание задачи. + - `status` (`Status`) — Статус задачи. + - `startTime` (`LocalDateTime`) — Время начала. + - `endTime` (`LocalDateTime`) — Время окончания. + - `duration` (`Duration`) — Длительность. + - `type` (`TaskType`) — Тип задачи (всегда `TASK`). + +## Основные особенности + +- **Документация**: Все DTO аннотированы с помощью `@Schema` для интеграции с Swagger. Поля содержат примеры и описания. +- **Валидация**: Используются аннотации Jakarta Validation (`@NotBlank`, `@NotNull`, `@Positive`) для проверки входных данных. +- **Типизация**: DTO реализованы как `record` для неизменяемости и компактности кода. +- **Перечисления**: + - `Status` (`NEW`, `IN_PROGRESS`, `DONE`) — для указания статуса задач, эпиков и подзадач. + - `TaskType` (`TASK`, `EPIC`, `SUBTASK`) — для идентификации типа сущности. +- **Временные данные**: Поля `startTime`, `endTime` и `duration` используют `LocalDateTime` и `Duration` для работы с временными интервалами. + +## Использование + +DTO используются в контроллерах (`controller`) для обработки запросов и ответов. Они обеспечивают: +- **Создание**: `RequestCreatedDto` для передачи данных при создании сущностей. +- **Обновление**: `RequestUpdatedDto` для изменения существующих сущностей. +- **Получение**: `ResponseDto` для возврата данных клиенту. + +**Пример JSON для создания эпика**: +```json +{ + "name": "Проектный план", + "description": "Планирование проекта", + "startTime": "2025-04-27T10:00:00", + "duration": "PT24H" +} +``` + +## Документация API + +DTO документируются через Springdoc OpenAPI. Доступ к документации: +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +## Примечания + +- DTO разделены по сущностям (`task`, `epic`, `subtask`) для лучшей организации кода. +- Поля в `ResponseDto` включают дополнительные данные (например, `endTime`, `type`), которые вычисляются на сервере. +- Для корректной работы требуется наличие перечислений `Status` и `TaskType` в пакете `service.task.manager.model.enums`. + +## Дополнительно + +Для информации о контроллерах или сервисах см. папки `controller` и `service`. Если возникают вопросы по настройке или расширению DTO, обратитесь к документации проекта или разработчикам. \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/error/ConflictException.java b/service/src/main/java/service/task/manager/exception/ConflictException.java similarity index 77% rename from service/src/main/java/service/task/manager/error/ConflictException.java rename to service/src/main/java/service/task/manager/exception/ConflictException.java index cd69165..5588ba5 100644 --- a/service/src/main/java/service/task/manager/error/ConflictException.java +++ b/service/src/main/java/service/task/manager/exception/ConflictException.java @@ -1,4 +1,4 @@ -package service.task.manager.error; +package service.task.manager.exception; public class ConflictException extends RuntimeException { public ConflictException(String message) { diff --git a/service/src/main/java/service/task/manager/error/ErrorHandler.java b/service/src/main/java/service/task/manager/exception/ErrorHandler.java similarity index 98% rename from service/src/main/java/service/task/manager/error/ErrorHandler.java rename to service/src/main/java/service/task/manager/exception/ErrorHandler.java index 7bb0c18..e19e2e6 100644 --- a/service/src/main/java/service/task/manager/error/ErrorHandler.java +++ b/service/src/main/java/service/task/manager/exception/ErrorHandler.java @@ -1,4 +1,4 @@ -package service.task.manager.error; +package service.task.manager.exception; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; diff --git a/service/src/main/java/service/task/manager/error/InvalidTaskDataException.java b/service/src/main/java/service/task/manager/exception/InvalidTaskDataException.java similarity index 79% rename from service/src/main/java/service/task/manager/error/InvalidTaskDataException.java rename to service/src/main/java/service/task/manager/exception/InvalidTaskDataException.java index a74186e..8ef0b8c 100644 --- a/service/src/main/java/service/task/manager/error/InvalidTaskDataException.java +++ b/service/src/main/java/service/task/manager/exception/InvalidTaskDataException.java @@ -1,4 +1,4 @@ -package service.task.manager.error; +package service.task.manager.exception; public class InvalidTaskDataException extends RuntimeException { public InvalidTaskDataException(String message) { diff --git a/service/src/main/java/service/task/manager/error/NotFoundException.java b/service/src/main/java/service/task/manager/exception/NotFoundException.java similarity index 77% rename from service/src/main/java/service/task/manager/error/NotFoundException.java rename to service/src/main/java/service/task/manager/exception/NotFoundException.java index f4f56c6..6610a7b 100644 --- a/service/src/main/java/service/task/manager/error/NotFoundException.java +++ b/service/src/main/java/service/task/manager/exception/NotFoundException.java @@ -1,4 +1,4 @@ -package service.task.manager.error; +package service.task.manager.exception; public class NotFoundException extends RuntimeException { public NotFoundException(String message) { diff --git a/service/src/main/java/service/task/manager/exception/README.md b/service/src/main/java/service/task/manager/exception/README.md new file mode 100644 index 0000000..d05e645 --- /dev/null +++ b/service/src/main/java/service/task/manager/exception/README.md @@ -0,0 +1,90 @@ +# README.md для папки `exception` + +## Описание + +Папка `exception` содержит классы исключений и обработчик ошибок для приложения `service.task.manager`. Эти компоненты обеспечивают централизованную обработку ошибок, возникающих при выполнении запросов к REST API, и возвращают клиенту понятные сообщения об ошибках с соответствующими HTTP-статусами. Исключения используются для обработки специфичных ошибок, таких как отсутствие ресурса, конфликты или некорректные данные, а обработчик (`ErrorHandler`) преобразует их в стандартизированные ответы. + +## Структура папки + +### 1. Исключения + +- **`ConflictException.java`** + **Описание**: Исключение, выбрасываемое при конфликте данных, например, при попытке создать ресурс с уже существующим именем. + **Использование**: Возвращает HTTP-статус `409 Conflict`. + **Пример**: Попытка создать эпик с названием, которое уже используется. + +- **`InvalidTaskDataException.java`** + **Описание**: Исключение для случаев, когда данные задачи, эпика или подзадачи некорректны (например, логические ошибки в данных). + **Использование**: Возвращает HTTP-статус `400 Bad Request`. + **Пример**: Указана некорректная длительность задачи. + +- **`NotFoundException.java`** + **Описание**: Исключение, выбрасываемое, когда запрашиваемый ресурс (задача, эпик, подзадача) не найден. + **Использование**: Возвращает HTTP-статус `404 Not Found`. + **Пример**: Запрос эпика по несуществующему ID. + +- **`TaskConstraintViolationException.java`** + **Описание**: Исключение для случаев, когда нарушены ограничения целостности данных задачи (например, бизнес-правила). + **Использование**: Возвращает HTTP-статус `400 Bad Request`. + **Пример**: Попытка создать подзадачу без указания связанного эпика. + +### 2. Обработчик ошибок + +- **`ErrorHandler.java`** + **Описание**: Централизованный обработчик исключений, реализованный с использованием `@RestControllerAdvice`. Преобразует исключения в HTTP-ответы с соответствующими статусами и сообщениями об ошибках. + **Обработанные исключения**: + - `NotFoundException` → `404 Not Found`, возвращает `ErrorResponse` с сообщением. + - `InvalidTaskDataException` → `400 Bad Request`, возвращает `ErrorResponse` с сообщением. + - `TaskConstraintViolationException` → `400 Bad Request`, возвращает `ErrorResponse` с сообщением. + - `ConflictException` → `409 Conflict`, возвращает `ErrorResponse` с сообщением. + - `MethodArgumentNotValidException` → `400 Bad Request`, возвращает `Map` с ошибками валидации полей DTO. + - `ConstraintViolationException` → `400 Bad Request`, возвращает `Map` с ошибками валидации (например, для `@PathVariable`). + - `HandlerMethodValidationException` → `400 Bad Request`, возвращает `Map` с ошибками валидации параметров метода. + **Внутренний рекорд**: + - `ErrorResponse` — Простая структура для возврата сообщения об ошибке в формате JSON: `{ "message": "Ошибка" }`. + +## Основные особенности + +- **Централизованная обработка**: `ErrorHandler` обрабатывает все исключения в одном месте, обеспечивая единообразие ответов. +- **Документация ошибок**: HTTP-статусы и сообщения об ошибках соответствуют стандартам REST API и документируются в Swagger (см. `/swagger-ui.html`). +- **Валидация**: + - Ошибки валидации DTO (`MethodArgumentNotValidException`) возвращают словарь с названиями полей и сообщениями об ошибках. + - Ошибки валидации параметров (`ConstraintViolationException`, `HandlerMethodValidationException`) также возвращают словарь с указанием проблемных параметров. +- **Гибкость**: Исключения покрывают различные сценарии (конфликты, отсутствие данных, некорректные запросы), что упрощает расширение системы. + +## Использование + +Исключения выбрасываются в сервисах или контроллерах, когда возникают ошибки. `ErrorHandler` автоматически перехватывает их и формирует ответ для клиента. + +**Пример ответа для `NotFoundException`**: +```json +{ + "message": "Эпик с ID 1 не найден" +} +``` + +**Пример ответа для `MethodArgumentNotValidException`**: +```json +{ + "name": "blank name", + "description": "blank description" +} +``` + +## Документация API + +Ошибки документируются в Swagger UI, доступном по пути: +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +Каждый эндпоинт в контроллерах (`controller`) включает описание возможных ошибок и соответствующих HTTP-статусов (например, `404`, `409`). + +## Примечания + +- **Расширяемость**: Новые исключения можно добавить, создав класс, унаследованный от `RuntimeException`, и добавив его обработку в `ErrorHandler`. +- **Логирование**: Рекомендуется добавить логирование ошибок в `ErrorHandler` для отладки (например, с использованием SLF4J). +- **Консистентность**: Все ответы об ошибках возвращаются в формате JSON, что упрощает их обработку на клиентской стороне. + +## Дополнительно + +Для информации о контроллерах, DTO или сервисах см. соответствующие папки (`controller`, `dto`, `service`). По вопросам настройки или расширения системы обработки ошибок обратитесь к документации проекта или разработчикам. \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java b/service/src/main/java/service/task/manager/exception/TaskConstraintViolationException.java similarity index 80% rename from service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java rename to service/src/main/java/service/task/manager/exception/TaskConstraintViolationException.java index 3a5b163..7589e37 100644 --- a/service/src/main/java/service/task/manager/error/TaskConstraintViolationException.java +++ b/service/src/main/java/service/task/manager/exception/TaskConstraintViolationException.java @@ -1,4 +1,4 @@ -package service.task.manager.error; +package service.task.manager.exception; public class TaskConstraintViolationException extends RuntimeException { public TaskConstraintViolationException(String message) { diff --git a/service/src/main/java/service/task/manager/service/EpicService.java b/service/src/main/java/service/task/manager/service/EpicService.java index 67894f2..d96fcd5 100644 --- a/service/src/main/java/service/task/manager/service/EpicService.java +++ b/service/src/main/java/service/task/manager/service/EpicService.java @@ -16,4 +16,6 @@ public interface EpicService { List findAll(); void delete(Long id); + + List prioritized(); } diff --git a/service/src/main/java/service/task/manager/service/SubtaskService.java b/service/src/main/java/service/task/manager/service/SubtaskService.java index 7ebf70b..d1d542f 100644 --- a/service/src/main/java/service/task/manager/service/SubtaskService.java +++ b/service/src/main/java/service/task/manager/service/SubtaskService.java @@ -16,4 +16,6 @@ public interface SubtaskService { List findAll(); void delete(Long id); + + List prioritized(); } diff --git a/service/src/main/java/service/task/manager/service/TaskService.java b/service/src/main/java/service/task/manager/service/TaskService.java index 68d1a29..198594e 100644 --- a/service/src/main/java/service/task/manager/service/TaskService.java +++ b/service/src/main/java/service/task/manager/service/TaskService.java @@ -16,4 +16,6 @@ public interface TaskService { List findAll(); void delete(Long id); + + List prioritized(); } diff --git a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java index 9ffd425..9968109 100644 --- a/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/EpicServiceImpl.java @@ -7,8 +7,8 @@ import service.task.manager.dto.epic.EpicRequestCreatedDto; import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; -import service.task.manager.error.ConflictException; -import service.task.manager.error.NotFoundException; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.NotFoundException; import service.task.manager.mapper.EpicMapper; import service.task.manager.model.Epic; import service.task.manager.model.enums.Status; @@ -16,6 +16,7 @@ import service.task.manager.service.EpicService; import service.task.manager.service.HistoryService; +import java.util.Comparator; import java.util.List; /** @@ -131,6 +132,43 @@ public void delete(Long id) { log.info("Epic with ID {} deleted successfully", id); } + /** + * Retrieves all epics sorted by priority and end time. + *

+ * Epics are sorted first by status in the order: IN_PROGRESS, NEW, DONE. + * Within each status group, epics are sorted by end time (earliest first). + *

+ * + * @return A list of all epics as DTOs, sorted by priority and end time. + */ + @Override + public List prioritized() { + log.info("Fetching all epics sorted by priority and end time"); + List epics = repository.findAll() + .stream() + .map(mapper::toResponseDto) + .sorted(Comparator + .comparing(this::getStatusPriority) + .thenComparing(EpicResponseDto::endTime)) + .toList(); + log.info("Retrieved {} prioritized epics", epics.size()); + return epics; + } + + /** + * Helper method to assign priority based on status. + * + * @param dto The epic DTO. + * @return The priority value (lower means higher priority). + */ + private int getStatusPriority(EpicResponseDto dto) { + return switch (dto.status()) { + case IN_PROGRESS -> 1; + case NEW -> 2; + case DONE -> 3; + }; + } + /** * Sets the status to NEW and calculates the end time for the given epic. * @param epic The epic to modify. diff --git a/service/src/main/java/service/task/manager/service/impl/HistoryServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/HistoryServiceImpl.java index d020ac6..22146dc 100644 --- a/service/src/main/java/service/task/manager/service/impl/HistoryServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/HistoryServiceImpl.java @@ -54,13 +54,13 @@ public void addToHistory(TaskType type, Long id) { listOps.leftPop(HISTORY_KEY); } } catch (Exception e) { - log.error("Failed to add entry to history: type={}, id={}, error={}", type, id, e.getMessage(), e); + log.error("Failed to add entry to history: type={}, id={}, exception={}", type, id, e.getMessage(), e); } } /** * Retrieves the list of entries from the history of method calls. - * If the history is empty or an error occurs while retrieving data, an empty list is returned. + * If the history is empty or an exception occurs while retrieving data, an empty list is returned. * * @return the list of history entries */ diff --git a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java index 0a2d47e..ad4df3c 100644 --- a/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/SubtaskServiceImpl.java @@ -8,8 +8,8 @@ import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; -import service.task.manager.error.ConflictException; -import service.task.manager.error.NotFoundException; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.NotFoundException; import service.task.manager.mapper.EpicMapper; import service.task.manager.mapper.SubtaskMapper; import service.task.manager.model.Epic; @@ -20,6 +20,7 @@ import service.task.manager.service.HistoryService; import service.task.manager.service.SubtaskService; +import java.util.Comparator; import java.util.List; /** @@ -139,6 +140,43 @@ public void delete(Long id) { log.info("Subtask with ID {} deleted successfully", id); } + /** + * Retrieves all subtasks sorted by priority and end time. + *

+ * Epics are sorted first by status in the order: IN_PROGRESS, NEW, DONE. + * Within each status group, subtasks are sorted by end time (earliest first). + *

+ * + * @return A list of all subtasks as DTOs, sorted by priority and end time. + */ + @Override + public List prioritized() { + log.info("Fetching all subtasks sorted by priority and end time"); + List subtask = repository.findAll() + .stream() + .map(mapper::toResponseDto) + .sorted(Comparator + .comparing(this::getStatusPriority) + .thenComparing(SubtaskResponseDto::endTime)) + .toList(); + log.info("Retrieved {} prioritized subtasks", subtask.size()); + return subtask; + } + + /** + * Helper method to assign priority based on status. + * + * @param dto The subtask DTO. + * @return The priority value (lower means higher priority). + */ + private int getStatusPriority(SubtaskResponseDto dto) { + return switch (dto.status()) { + case IN_PROGRESS -> 1; + case NEW -> 2; + case DONE -> 3; + }; + } + /** * Sets the status to NEW and calculates the end time for the given subtask. * @param subtask The subtask to modify. diff --git a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java index 1ba09da..3556d49 100644 --- a/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java +++ b/service/src/main/java/service/task/manager/service/impl/TaskServiceImpl.java @@ -7,8 +7,8 @@ import service.task.manager.dto.task.TaskRequestCreatedDto; import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; -import service.task.manager.error.ConflictException; -import service.task.manager.error.NotFoundException; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.NotFoundException; import service.task.manager.mapper.TaskMapper; import service.task.manager.model.Task; import service.task.manager.model.enums.Status; @@ -16,6 +16,7 @@ import service.task.manager.service.HistoryService; import service.task.manager.service.TaskService; +import java.util.Comparator; import java.util.List; /** @@ -117,6 +118,43 @@ public void delete(Long id) { log.info("Task with ID {} deleted successfully", id); } + /** + * Retrieves all tasks sorted by priority and end time. + *

+ * Epics are sorted first by status in the order: IN_PROGRESS, NEW, DONE. + * Within each status group, tasks are sorted by end time (earliest first). + *

+ * + * @return A list of all tasks as DTOs, sorted by priority and end time. + */ + @Override + public List prioritized() { + log.info("Fetching all subtasks sorted by priority and end time"); + List tasks = repository.findAll() + .stream() + .map(mapper::toResponseDto) + .sorted(Comparator + .comparing(this::getStatusPriority) + .thenComparing(TaskResponseDto::endTime)) + .toList(); + log.info("Retrieved {} prioritized subtasks", tasks.size()); + return tasks; + } + + /** + * Helper method to assign priority based on status. + * + * @param dto The task DTO. + * @return The priority value (lower means higher priority). + */ + private int getStatusPriority(TaskResponseDto dto) { + return switch (dto.status()) { + case IN_PROGRESS -> 1; + case NEW -> 2; + case DONE -> 3; + }; + } + /** * Sets the status to NEW and calculates the end time for the given task. * @param task The task to modify. diff --git a/service/src/main/resources/application.yml b/service/src/main/resources/application.yml index 8530f08..cd1718f 100644 --- a/service/src/main/resources/application.yml +++ b/service/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: datasource: - url: jdbc:h2:mem:testdb + url: jdbc:h2:mem:task-manager username: sa password: driver-class-name: org.h2.Driver diff --git a/service/src/test/java/service/task/manager/controller/EpicControllerTest.java b/service/src/test/java/service/task/manager/controller/EpicControllerTest.java index 582d02c..4fde163 100644 --- a/service/src/test/java/service/task/manager/controller/EpicControllerTest.java +++ b/service/src/test/java/service/task/manager/controller/EpicControllerTest.java @@ -11,8 +11,8 @@ import service.task.manager.dto.epic.EpicRequestCreatedDto; import service.task.manager.dto.epic.EpicRequestUpdatedDto; import service.task.manager.dto.epic.EpicResponseDto; -import service.task.manager.error.ConflictException; -import service.task.manager.error.NotFoundException; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.NotFoundException; import service.task.manager.model.enums.Status; import service.task.manager.model.enums.TaskType; import service.task.manager.service.EpicService; diff --git a/service/src/test/java/service/task/manager/controller/SubtaskControllerTest.java b/service/src/test/java/service/task/manager/controller/SubtaskControllerTest.java index 3f6a0a3..40d03a4 100644 --- a/service/src/test/java/service/task/manager/controller/SubtaskControllerTest.java +++ b/service/src/test/java/service/task/manager/controller/SubtaskControllerTest.java @@ -10,9 +10,9 @@ import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; import service.task.manager.dto.subtask.SubtaskResponseDto; -import service.task.manager.error.ConflictException; -import service.task.manager.error.ErrorHandler; -import service.task.manager.error.NotFoundException; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.ErrorHandler; +import service.task.manager.exception.NotFoundException; import service.task.manager.model.enums.Status; import service.task.manager.model.enums.TaskType; import service.task.manager.service.SubtaskService; diff --git a/service/src/test/java/service/task/manager/controller/TaskControllerTest.java b/service/src/test/java/service/task/manager/controller/TaskControllerTest.java index efefd7f..bfff349 100644 --- a/service/src/test/java/service/task/manager/controller/TaskControllerTest.java +++ b/service/src/test/java/service/task/manager/controller/TaskControllerTest.java @@ -10,8 +10,8 @@ import service.task.manager.dto.task.TaskRequestCreatedDto; import service.task.manager.dto.task.TaskRequestUpdatedDto; import service.task.manager.dto.task.TaskResponseDto; -import service.task.manager.error.ErrorHandler; -import service.task.manager.error.NotFoundException; +import service.task.manager.exception.ErrorHandler; +import service.task.manager.exception.NotFoundException; import service.task.manager.model.enums.Status; import service.task.manager.model.enums.TaskType; import service.task.manager.service.TaskService; diff --git a/service/src/test/java/service/task/manager/service/EpicServiceImplTest.java b/service/src/test/java/service/task/manager/service/EpicServiceImplTest.java new file mode 100644 index 0000000..4be2639 --- /dev/null +++ b/service/src/test/java/service/task/manager/service/EpicServiceImplTest.java @@ -0,0 +1,235 @@ +package service.task.manager.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import service.task.manager.dto.epic.EpicRequestCreatedDto; +import service.task.manager.dto.epic.EpicRequestUpdatedDto; +import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.NotFoundException; +import service.task.manager.mapper.EpicMapper; +import service.task.manager.model.Epic; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; +import service.task.manager.repository.EpicRepository; +import service.task.manager.service.impl.EpicServiceImpl; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class EpicServiceImplTest { + + @Mock + private EpicRepository repository; + + @Mock + private EpicMapper mapper; + + @Mock + private HistoryService history; + + @InjectMocks + private EpicServiceImpl service; + + // Sample data for tests + private final LocalDateTime now = LocalDateTime.now(); + private final Duration duration = Duration.ofHours(24); + private final LocalDateTime endTime = now.plus(duration); + + // --- Tests for create method --- + + @Test + void create_ShouldCreateEpic_WhenNameIsUnique() { + // Arrange + EpicRequestCreatedDto dto = new EpicRequestCreatedDto("New Epic", "Test epic created", now, duration); + Epic epic = new Epic(); + epic.setName(dto.name()); + epic.setDescription(dto.description()); + epic.setStartTime(dto.startTime()); + epic.setDuration(dto.duration()); + epic.setStatus(Status.NEW); + epic.setSubtasks(new ArrayList<>()); + + when(repository.existsByName(dto.name())).thenReturn(false); + when(mapper.toEntity(dto)).thenReturn(epic); + when(repository.save(any(Epic.class))).thenReturn(epic); + + // Act + try { + service.create(dto); + } catch (Exception e) { + System.out.println("Exception in create: " + e.getMessage()); + e.printStackTrace(); + throw e; + } + + // Assert + verify(repository, times(1)).existsByName(dto.name()); + verify(mapper, times(1)).toEntity(dto); + verify(repository, times(1)).save(epic); + assertEquals(Status.NEW, epic.getStatus()); + assertEquals(endTime, epic.getEndTime()); + assertEquals(TaskType.EPIC, epic.getType()); + } + + @Test + void create_ShouldThrowConflictException_WhenNameExists() { + // Arrange + EpicRequestCreatedDto dto = new EpicRequestCreatedDto("Existing Epic", "Description", now, duration); + when(repository.existsByName(dto.name())).thenReturn(true); + + // Act & Assert + assertThrows(ConflictException.class, () -> service.create(dto)); + verify(repository, times(1)).existsByName(dto.name()); + verify(mapper, never()).toEntity((EpicRequestCreatedDto) any()); + verify(repository, never()).save(any()); + } + + // --- Tests for update method --- + + @Test + void update_ShouldUpdateEpic_WhenEpicExists() { + // Arrange + Long epicId = 1L; + EpicRequestUpdatedDto dto = new EpicRequestUpdatedDto(epicId, "Updated Epic", "Updated Description", Status.IN_PROGRESS, duration); + Epic existingEpic = new Epic(); + existingEpic.setId(epicId); + existingEpic.setName("Old Epic"); + existingEpic.setDescription("Old Description"); + existingEpic.setStartTime(now.minusDays(1)); + existingEpic.setDuration(Duration.ofHours(12)); + existingEpic.setStatus(Status.NEW); + existingEpic.setSubtasks(new ArrayList<>()); + + EpicResponseDto responseDto = new EpicResponseDto(epicId, new ArrayList<>(), "Updated Epic", "Updated Description", Status.IN_PROGRESS, now.minusDays(1), duration, now.minusDays(1).plus(duration), TaskType.EPIC); + + when(repository.findById(epicId)).thenReturn(Optional.of(existingEpic)); + when(mapper.toResponseDto(existingEpic)).thenReturn(responseDto); + when(mapper.toEntity(responseDto)).thenReturn(existingEpic); + when(repository.save(existingEpic)).thenReturn(existingEpic); + + // Act + EpicResponseDto result = service.update(dto); + + // Assert + verify(repository, times(1)).findById(epicId); + verify(mapper, times(1)).updateTaskFromDto(dto, existingEpic); + verify(repository, times(1)).save(existingEpic); + assertEquals("Updated Epic", result.name()); + assertEquals(now.minusDays(1).plus(duration), result.endTime()); + assertEquals(TaskType.EPIC, result.type()); + } + + @Test + void update_ShouldThrowNotFoundException_WhenEpicDoesNotExist() { + // Arrange + Long epicId = 1L; + EpicRequestUpdatedDto dto = new EpicRequestUpdatedDto(epicId, "Updated Epic", "Updated Description", Status.IN_PROGRESS, duration); + when(repository.findById(epicId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> service.update(dto)); + verify(repository, times(1)).findById(epicId); + verify(mapper, never()).updateTaskFromDto(any(), any()); + verify(repository, never()).save(any()); + } + + // --- Tests for findById method --- + + @Test + void findById_ShouldReturnEpic_WhenEpicExists() { + // Arrange + Long epicId = 1L; + Epic epic = new Epic(); + epic.setId(epicId); + epic.setName("Epic"); + epic.setDescription("Description"); + epic.setStartTime(now); + epic.setDuration(duration); + epic.setEndTime(endTime); + epic.setStatus(Status.NEW); + epic.setSubtasks(new ArrayList<>()); + + EpicResponseDto dto = new EpicResponseDto(epicId, new ArrayList<>(), "Epic", "Description", Status.NEW, now, duration, endTime, TaskType.EPIC); + when(repository.findById(epicId)).thenReturn(Optional.of(epic)); + when(mapper.toResponseDto(epic)).thenReturn(dto); + + // Act + EpicResponseDto result = service.findById(epicId); + + // Assert + verify(repository, times(1)).findById(epicId); + verify(mapper, times(1)).toResponseDto(epic); + verify(history, times(1)).addToHistory(TaskType.EPIC, epicId); + assertEquals(dto, result); + assertEquals(endTime, result.endTime()); + assertEquals(TaskType.EPIC, result.type()); + } + + @Test + void findById_ShouldThrowNotFoundException_WhenEpicDoesNotExist() { + // Arrange + Long epicId = 1L; + when(repository.findById(epicId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> service.findById(epicId)); + verify(repository, times(1)).findById(epicId); + verify(mapper, never()).toResponseDto(any()); + verify(history, never()).addToHistory(any(), any()); + } + + // --- Tests for findAll method --- + + @Test + void findAll_ShouldReturnAllEpics() { + // Arrange + Epic epic1 = new Epic(); + epic1.setId(1L); + epic1.setName("Epic1"); + epic1.setDescription("Desc1"); + epic1.setStartTime(now); + epic1.setDuration(duration); + epic1.setEndTime(endTime); + epic1.setStatus(Status.NEW); + epic1.setSubtasks(new ArrayList<>()); + + Epic epic2 = new Epic(); + epic2.setId(2L); + epic2.setName("Epic2"); + epic2.setDescription("Desc2"); + epic2.setStartTime(now); + epic2.setDuration(duration); + epic2.setEndTime(endTime); + epic2.setStatus(Status.NEW); + epic2.setSubtasks(new ArrayList<>()); + + List epics = List.of(epic1, epic2); + EpicResponseDto dto1 = new EpicResponseDto(1L, new ArrayList<>(), "Epic1", "Desc1", Status.NEW, now, duration, endTime, TaskType.EPIC); + EpicResponseDto dto2 = new EpicResponseDto(2L, new ArrayList<>(), "Epic2", "Desc2", Status.NEW, now, duration, endTime, TaskType.EPIC); + when(repository.findAll()).thenReturn(epics); + when(mapper.toResponseDto(epic1)).thenReturn(dto1); + when(mapper.toResponseDto(epic2)).thenReturn(dto2); + + // Act + List result = service.findAll(); + + // Assert + verify(repository, times(1)).findAll(); + verify(mapper, times(2)).toResponseDto(any(Epic.class)); + assertEquals(2, result.size()); + assertTrue(result.contains(dto1)); + assertTrue(result.contains(dto2)); + } +} \ No newline at end of file diff --git a/service/src/test/java/service/task/manager/service/HistoryServiceImplTest.java b/service/src/test/java/service/task/manager/service/HistoryServiceImplTest.java index ad3471b..5c80b96 100644 --- a/service/src/test/java/service/task/manager/service/HistoryServiceImplTest.java +++ b/service/src/test/java/service/task/manager/service/HistoryServiceImplTest.java @@ -12,15 +12,20 @@ import service.task.manager.model.enums.TaskType; import service.task.manager.service.impl.HistoryServiceImpl; -import java.util.ArrayList; +import java.lang.reflect.Field; import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class HistoryServiceImplTest { + private static final String HISTORY_KEY = "history"; + private static final int HISTORY_SIZE = 10; + @Mock private RedisTemplate redisTemplate; @@ -31,61 +36,105 @@ class HistoryServiceImplTest { private HistoryServiceImpl historyService; @BeforeEach - void setUp() { - // Ensure redisTemplate.opsForList() returns the mocked listOps - when(redisTemplate.opsForList()).thenReturn(listOps); + void setUp() throws NoSuchFieldException, IllegalAccessException { + // Use reflection to inject listOps into historyService + Field listOpsField = HistoryServiceImpl.class.getDeclaredField("listOps"); + listOpsField.setAccessible(true); + listOpsField.set(historyService, listOps); } @Test - void addToHistory_WithinLimit_AddsEntry() { - // Mock the size of the list and the range result - when(listOps.size("history")).thenReturn(1L); - when(listOps.range("history", 0, -1)).thenReturn(List.of(new HistoryEntry(TaskType.EPIC, 1L))); - - // Call the method under test - historyService.addToHistory(TaskType.EPIC, 1L); - List history = historyService.getHistory(); - - // Verify the result - assertEquals(1, history.size()); - assertEquals(TaskType.EPIC, history.get(0).getType()); - assertEquals(1L, history.get(0).getId()); - - // Verify interactions with listOps - verify(listOps, times(1)).rightPush("history", new HistoryEntry(TaskType.EPIC, 1L)); + void addToHistory_ShouldAddEntrySuccessfully() { + // Arrange + TaskType taskType = TaskType.TASK; + Long id = 1L; + when(listOps.rightPush(eq(HISTORY_KEY), any(HistoryEntry.class))).thenReturn(1L); + when(listOps.size(HISTORY_KEY)).thenReturn(1L); + + // Act + historyService.addToHistory(taskType, id); + + // Assert + verify(listOps, times(1)).rightPush(eq(HISTORY_KEY), any(HistoryEntry.class)); + verify(listOps, never()).leftPop(HISTORY_KEY); } @Test - void addToHistory_ExceedsLimit_RemovesOldest() { - // Create a list to simulate the history entries - List historyEntries = new ArrayList<>(); - - // Simulate adding 11 entries - for (long i = 1; i <= 11; i++) { - // Mock the size of the list to increase with each addition - when(listOps.size("history")).thenReturn(i); - historyService.addToHistory(TaskType.TASK, i); - historyEntries.add(new HistoryEntry(TaskType.TASK, i)); - - // If size exceeds MAX_HISTORY_SIZE (10), simulate leftPop - if (i > 10) { - historyEntries.remove(0); // Remove the oldest entry - } - } - - // Mock the final state of the history after trimming - when(listOps.range("history", 0, -1)).thenReturn(historyEntries); - - // Call getHistory to retrieve the result - List history = historyService.getHistory(); - - // Verify the result - assertEquals(10, history.size()); - assertEquals(2L, history.get(0).getId()); // First entry (id=1) should be removed - assertEquals(11L, history.get(9).getId()); - - // Verify interactions with listOps - verify(listOps, times(11)).rightPush(eq("history"), any(HistoryEntry.class)); - verify(listOps, times(1)).leftPop("history"); // Should trim once after exceeding limit + void addToHistory_ShouldRemoveOldestEntryWhenSizeExceedsLimit() { + // Arrange + TaskType taskType = TaskType.EPIC; + Long id = 2L; + when(listOps.rightPush(eq(HISTORY_KEY), any(HistoryEntry.class))).thenReturn(11L); + when(listOps.size(HISTORY_KEY)).thenReturn(11L); + when(listOps.leftPop(HISTORY_KEY)).thenReturn(new HistoryEntry(TaskType.TASK, 1L)); + + // Act + historyService.addToHistory(taskType, id); + + // Assert + verify(listOps, times(1)).rightPush(eq(HISTORY_KEY), any(HistoryEntry.class)); + verify(listOps, times(1)).leftPop(HISTORY_KEY); + } + + @Test + void addToHistory_ShouldHandleExceptionGracefully() { + // Arrange + TaskType taskType = TaskType.SUBTASK; + Long id = 3L; + when(listOps.rightPush(eq(HISTORY_KEY), any(HistoryEntry.class))) + .thenThrow(new RuntimeException("Redis exception")); + + // Act + historyService.addToHistory(taskType, id); + + // Assert + verify(listOps, times(1)).rightPush(eq(HISTORY_KEY), any(HistoryEntry.class)); + verify(listOps, never()).size(HISTORY_KEY); + verify(listOps, never()).leftPop(HISTORY_KEY); + } + + @Test + void getHistory_ShouldReturnHistoryEntries() { + // Arrange + List expectedHistory = List.of( + new HistoryEntry(TaskType.TASK, 1L), + new HistoryEntry(TaskType.EPIC, 2L) + ); + when(listOps.range(HISTORY_KEY, 0, -1)).thenReturn(expectedHistory); + + // Act + List actualHistory = historyService.getHistory(); + + // Assert + assertEquals(expectedHistory, actualHistory); + verify(listOps, times(1)).range(HISTORY_KEY, 0, -1); + } + + @Test + void getHistory_ShouldReturnEmptyListWhenHistoryIsNull() { + // Arrange + when(listOps.range(HISTORY_KEY, 0, -1)).thenReturn(null); + + // Act + List actualHistory = historyService.getHistory(); + + // Assert + assertNotNull(actualHistory); + assertTrue(actualHistory.isEmpty()); + verify(listOps, times(1)).range(HISTORY_KEY, 0, -1); + } + + @Test + void getHistory_ShouldReturnEmptyListOnException() { + // Arrange + when(listOps.range(HISTORY_KEY, 0, -1)).thenThrow(new RuntimeException("Redis exception")); + + // Act + List actualHistory = historyService.getHistory(); + + // Assert + assertNotNull(actualHistory); + assertTrue(actualHistory.isEmpty()); + verify(listOps, times(1)).range(HISTORY_KEY, 0, -1); } } \ No newline at end of file diff --git a/service/src/test/java/service/task/manager/service/SubtaskServiceImplTest.java b/service/src/test/java/service/task/manager/service/SubtaskServiceImplTest.java new file mode 100644 index 0000000..2e05b80 --- /dev/null +++ b/service/src/test/java/service/task/manager/service/SubtaskServiceImplTest.java @@ -0,0 +1,311 @@ +package service.task.manager.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import service.task.manager.dto.epic.EpicResponseDto; +import service.task.manager.dto.subtask.SubtaskRequestCreatedDto; +import service.task.manager.dto.subtask.SubtaskRequestUpdatedDto; +import service.task.manager.dto.subtask.SubtaskResponseDto; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.NotFoundException; +import service.task.manager.mapper.EpicMapper; +import service.task.manager.mapper.SubtaskMapper; +import service.task.manager.model.Epic; +import service.task.manager.model.Subtask; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; +import service.task.manager.repository.SubtaskRepository; +import service.task.manager.service.impl.SubtaskServiceImpl; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Unit tests for SubtaskServiceImpl. + */ +@ExtendWith(MockitoExtension.class) +class SubtaskServiceImplTest { + + @Mock + private SubtaskRepository repository; + + @Mock + private EpicService epicService; + + @Mock + private SubtaskMapper mapper; + + @Mock + private EpicMapper epicMapper; + + @Mock + private HistoryService history; + + @InjectMocks + private SubtaskServiceImpl service; + + // Sample data for tests + private final LocalDateTime now = LocalDateTime.now(); + private final Duration duration = Duration.ofHours(24); + private final LocalDateTime endTime = now.plus(duration); + private final Long epicId = 1L; + private final Long subtaskId = 1L; + + // --- Tests for create method --- + + @Test + void create_ShouldCreateSubtask_WhenNameIsUniqueAndEpicExists() { + // Arrange + SubtaskRequestCreatedDto dto = new SubtaskRequestCreatedDto(epicId, "New Subtask", "Description", now, duration); + EpicResponseDto epicDto = new EpicResponseDto(epicId, new ArrayList<>(), "Epic", "Epic Description", Status.NEW, now, duration, endTime, TaskType.EPIC); + Epic epic = new Epic(); + epic.setId(epicId); + epic.setName("Epic"); + epic.setDescription("Epic Description"); + epic.setSubtasks(new ArrayList<>()); + + Subtask subtask = new Subtask(); + subtask.setName(dto.name()); + subtask.setDescription(dto.description()); + subtask.setStartTime(dto.startTime()); + subtask.setDuration(dto.duration()); + subtask.setEpic(epic); + + when(epicService.findById(epicId)).thenReturn(epicDto); + when(epicMapper.toEntity(epicDto)).thenReturn(epic); + when(repository.existsByName(dto.name())).thenReturn(false); + when(mapper.toEntity(dto)).thenReturn(subtask); + when(repository.save(any(Subtask.class))).thenReturn(subtask); + + // Act + service.create(dto); + + // Assert + verify(epicService, times(1)).findById(epicId); + verify(epicMapper, times(1)).toEntity(epicDto); + verify(repository, times(1)).existsByName(dto.name()); + verify(mapper, times(1)).toEntity(dto); + verify(repository, times(1)).save(subtask); + assertEquals(Status.NEW, subtask.getStatus()); + assertEquals(endTime, subtask.getEndTime()); + assertEquals(TaskType.SUBTASK, subtask.getType()); + assertEquals(epic, subtask.getEpic()); + } + + @Test + void create_ShouldThrowConflictException_WhenNameExists() { + // Arrange + SubtaskRequestCreatedDto dto = new SubtaskRequestCreatedDto(epicId, "Existing Subtask", "Description", now, duration); + EpicResponseDto epicDto = new EpicResponseDto(epicId, new ArrayList<>(), "Epic", "Epic Description", Status.NEW, now, duration, endTime, TaskType.EPIC); + + when(epicService.findById(epicId)).thenReturn(epicDto); + when(repository.existsByName(dto.name())).thenReturn(true); + + // Act & Assert + assertThrows(ConflictException.class, () -> service.create(dto)); + verify(epicService, times(1)).findById(epicId); + verify(repository, times(1)).existsByName(dto.name()); + verify(mapper, never()).toEntity((SubtaskRequestCreatedDto) any()); + verify(repository, never()).save(any()); + } + + @Test + void create_ShouldThrowNotFoundException_WhenEpicDoesNotExist() { + // Arrange + SubtaskRequestCreatedDto dto = new SubtaskRequestCreatedDto(epicId, "New Subtask", "Description", now, duration); + when(epicService.findById(epicId)).thenThrow(new NotFoundException("Epic with ID " + epicId + " not found")); + + // Act & Assert + assertThrows(NotFoundException.class, () -> service.create(dto)); + verify(epicService, times(1)).findById(epicId); + verify(repository, never()).existsByName(any()); + verify(mapper, never()).toEntity((SubtaskRequestCreatedDto) any()); + verify(repository, never()).save(any()); + } + + // --- Tests for update method --- + + @Test + void update_ShouldUpdateSubtask_WhenSubtaskExists() { + // Arrange + SubtaskRequestUpdatedDto dto = new SubtaskRequestUpdatedDto(subtaskId, "Updated Subtask", "Updated Description", Status.IN_PROGRESS, duration); + Subtask existingSubtask = new Subtask(); + existingSubtask.setId(subtaskId); + existingSubtask.setName("Old Subtask"); + existingSubtask.setDescription("Old Description"); + existingSubtask.setStartTime(now.minusDays(1)); + existingSubtask.setDuration(Duration.ofHours(12)); + existingSubtask.setStatus(Status.NEW); + + SubtaskResponseDto responseDto = new SubtaskResponseDto(subtaskId, epicId, "Updated Subtask", "Updated Description", Status.IN_PROGRESS, now.minusDays(1), now.minusDays(1).plus(duration), duration, TaskType.SUBTASK); + + when(repository.findById(subtaskId)).thenReturn(Optional.of(existingSubtask)); + when(mapper.toResponseDto(existingSubtask)).thenReturn(responseDto); + when(mapper.toEntity(responseDto)).thenReturn(existingSubtask); + when(repository.save(existingSubtask)).thenReturn(existingSubtask); + + // Act + SubtaskResponseDto result = service.update(dto); + + // Assert + verify(repository, times(1)).findById(subtaskId); + verify(mapper, times(1)).updateSubtaskFromDto(dto, existingSubtask); + verify(repository, times(1)).save(existingSubtask); + assertEquals("Updated Subtask", result.name()); + assertEquals(now.minusDays(1).plus(duration), result.endTime()); + assertEquals(TaskType.SUBTASK, result.type()); + } + + @Test + void update_ShouldThrowNotFoundException_WhenSubtaskDoesNotExist() { + // Arrange + SubtaskRequestUpdatedDto dto = new SubtaskRequestUpdatedDto(subtaskId, "Updated Subtask", "Updated Description", Status.IN_PROGRESS, duration); + when(repository.findById(subtaskId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> service.update(dto)); + verify(repository, times(1)).findById(subtaskId); + verify(mapper, never()).updateSubtaskFromDto(any(), any()); + verify(repository, never()).save(any()); + } + + // --- Tests for findById method --- + + @Test + void findById_ShouldReturnSubtask_WhenSubtaskExists() { + // Arrange + Subtask subtask = new Subtask(); + subtask.setId(subtaskId); + subtask.setName("Subtask"); + subtask.setDescription("Description"); + subtask.setStartTime(now); + subtask.setDuration(duration); + subtask.setEndTime(endTime); + subtask.setStatus(Status.NEW); + Epic epic = new Epic(); + epic.setId(epicId); + subtask.setEpic(epic); + + SubtaskResponseDto dto = new SubtaskResponseDto(subtaskId, epicId, "Subtask", "Description", Status.NEW, now, endTime, duration, TaskType.SUBTASK); + when(repository.findById(subtaskId)).thenReturn(Optional.of(subtask)); + when(mapper.toResponseDto(subtask)).thenReturn(dto); + + // Act + SubtaskResponseDto result = service.findById(subtaskId); + + // Assert + verify(repository, times(1)).findById(subtaskId); + verify(mapper, times(1)).toResponseDto(subtask); + verify(history, times(1)).addToHistory(TaskType.SUBTASK, subtaskId); + assertEquals(dto, result); + assertEquals(endTime, result.endTime()); + assertEquals(TaskType.SUBTASK, result.type()); + } + + @Test + void findById_ShouldThrowNotFoundException_WhenSubtaskDoesNotExist() { + // Arrange + when(repository.findById(subtaskId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> service.findById(subtaskId)); + verify(repository, times(1)).findById(subtaskId); + verify(mapper, never()).toResponseDto(any()); + verify(history, never()).addToHistory(any(), any()); + } + + // --- Tests for findAll method --- + + @Test + void findAll_ShouldReturnAllSubtasks() { + // Arrange + Subtask subtask1 = new Subtask(); + subtask1.setId(1L); + subtask1.setName("Subtask1"); + subtask1.setDescription("Desc1"); + subtask1.setStartTime(now); + subtask1.setDuration(duration); + subtask1.setEndTime(endTime); + subtask1.setStatus(Status.NEW); + Epic epic1 = new Epic(); + epic1.setId(epicId); + subtask1.setEpic(epic1); + + Subtask subtask2 = new Subtask(); + subtask2.setId(2L); + subtask2.setName("Subtask2"); + subtask2.setDescription("Desc2"); + subtask2.setStartTime(now); + subtask2.setDuration(duration); + subtask2.setEndTime(endTime); + subtask2.setStatus(Status.NEW); + subtask2.setEpic(epic1); + + List subtasks = List.of(subtask1, subtask2); + SubtaskResponseDto dto1 = new SubtaskResponseDto(1L, epicId, "Subtask1", "Desc1", Status.NEW, now, endTime, duration, TaskType.SUBTASK); + SubtaskResponseDto dto2 = new SubtaskResponseDto(2L, epicId, "Subtask2", "Desc2", Status.NEW, now, endTime, duration, TaskType.SUBTASK); + when(repository.findAll()).thenReturn(subtasks); + when(mapper.toResponseDto(subtask1)).thenReturn(dto1); + when(mapper.toResponseDto(subtask2)).thenReturn(dto2); + + // Act + List result = service.findAll(); + + // Assert + verify(repository, times(1)).findAll(); + verify(mapper, times(2)).toResponseDto(any(Subtask.class)); + assertEquals(2, result.size()); + assertTrue(result.contains(dto1)); + assertTrue(result.contains(dto2)); + } + + // --- Tests for delete method --- + + @Test + void delete_ShouldDeleteSubtask_WhenSubtaskExists() { + // Arrange + Subtask subtask = new Subtask(); + subtask.setId(subtaskId); + subtask.setName("Subtask"); + subtask.setDescription("Description"); + subtask.setStartTime(now); + subtask.setDuration(duration); + subtask.setEndTime(endTime); + subtask.setStatus(Status.NEW); + Epic epic = new Epic(); + epic.setId(epicId); + subtask.setEpic(epic); + + SubtaskResponseDto dto = new SubtaskResponseDto(subtaskId, epicId, "Subtask", "Description", Status.NEW, now, endTime, duration, TaskType.SUBTASK); + when(repository.findById(subtaskId)).thenReturn(Optional.of(subtask)); + when(mapper.toResponseDto(subtask)).thenReturn(dto); + + // Act + service.delete(subtaskId); + + // Assert + verify(repository, times(1)).findById(subtaskId); + verify(repository, times(1)).deleteById(subtaskId); + } + + @Test + void delete_ShouldThrowNotFoundException_WhenSubtaskDoesNotExist() { + // Arrange + when(repository.findById(subtaskId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> service.delete(subtaskId)); + verify(repository, times(1)).findById(subtaskId); + verify(repository, never()).deleteById(any()); + } +} diff --git a/service/src/test/java/service/task/manager/service/TaskServiceImplTest.java b/service/src/test/java/service/task/manager/service/TaskServiceImplTest.java new file mode 100644 index 0000000..ec998e1 --- /dev/null +++ b/service/src/test/java/service/task/manager/service/TaskServiceImplTest.java @@ -0,0 +1,265 @@ +package service.task.manager.service; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import service.task.manager.dto.task.TaskRequestCreatedDto; +import service.task.manager.dto.task.TaskRequestUpdatedDto; +import service.task.manager.dto.task.TaskResponseDto; +import service.task.manager.exception.ConflictException; +import service.task.manager.exception.NotFoundException; +import service.task.manager.mapper.TaskMapper; +import service.task.manager.model.Task; +import service.task.manager.model.enums.Status; +import service.task.manager.model.enums.TaskType; +import service.task.manager.repository.TaskRepository; +import service.task.manager.service.impl.TaskServiceImpl; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * Unit tests for TaskServiceImpl, aligned with updated DTOs and Task model. + */ +@ExtendWith(MockitoExtension.class) +class TaskServiceImplTest { + + @Mock + private TaskRepository repository; + + @Mock + private TaskMapper mapper; + + @Mock + private HistoryService history; + + @InjectMocks + private TaskServiceImpl taskService; + + // Sample data for tests + private final LocalDateTime now = LocalDateTime.now(); + private final Duration duration = Duration.ofHours(24); + private final LocalDateTime endTime = now.plus(duration); + + // --- Tests for create method --- + + @Test + void create_ShouldCreateTask_WhenNameIsUnique() { + // Arrange + TaskRequestCreatedDto dto = new TaskRequestCreatedDto("New Task", "Description", now, duration); + Task task = new Task(); + task.setName(dto.name()); + task.setDescription(dto.description()); + task.setStartTime(dto.startTime()); + task.setDuration(dto.duration()); + + when(repository.existsByName(dto.name())).thenReturn(false); + when(mapper.toEntity(dto)).thenReturn(task); + when(repository.save(any(Task.class))).thenReturn(task); + + // Act + taskService.create(dto); + + // Assert + verify(repository, times(1)).existsByName(dto.name()); + verify(mapper, times(1)).toEntity(dto); + verify(repository, times(1)).save(task); + assertEquals(Status.NEW, task.getStatus()); // Status set by addEndTimeTaskAndStatus + assertEquals(endTime, task.getEndTime()); // End time calculated + assertEquals(TaskType.TASK, task.getType()); // Default type + } + + @Test + void create_ShouldThrowConflictException_WhenNameExists() { + // Arrange + TaskRequestCreatedDto dto = new TaskRequestCreatedDto("Existing Task", "Description", now, duration); + when(repository.existsByName(dto.name())).thenReturn(true); + + // Act & Assert + assertThrows(ConflictException.class, () -> taskService.create(dto)); + verify(repository, times(1)).existsByName(dto.name()); + verify(mapper, never()).toEntity((TaskRequestCreatedDto) any()); + verify(repository, never()).save(any()); + } + + // --- Tests for update method --- + + @Test + void update_ShouldUpdateTask_WhenTaskExists() { + // Arrange + Long taskId = 1L; + TaskRequestUpdatedDto dto = new TaskRequestUpdatedDto(taskId, "Updated Task", "Updated Description", Status.IN_PROGRESS, duration); + Task existingTask = new Task(); + existingTask.setId(taskId); + existingTask.setName("Old Task"); + existingTask.setDescription("Old Description"); + existingTask.setStartTime(now.minusDays(1)); + existingTask.setDuration(Duration.ofHours(12)); + existingTask.setStatus(Status.NEW); + + TaskResponseDto responseDto = new TaskResponseDto(taskId, "Updated Task", "Updated Description", Status.IN_PROGRESS, now.minusDays(1), now.minusDays(1).plus(duration), duration, TaskType.TASK); + + when(repository.findById(taskId)).thenReturn(Optional.of(existingTask)); + when(mapper.toResponseDto(existingTask)).thenReturn(responseDto); + when(mapper.toEntity(responseDto)).thenReturn(existingTask); + when(repository.save(existingTask)).thenReturn(existingTask); + + // Act + TaskResponseDto result = taskService.update(dto); + + // Assert + verify(repository, times(1)).findById(taskId); // Called by findById internally + verify(mapper, times(1)).updateTaskFromDto(dto, existingTask); + verify(repository, times(1)).save(existingTask); + assertEquals("Updated Task", result.name()); + assertEquals(now.minusDays(1).plus(duration), result.endTime()); // End time recalculated + assertEquals(TaskType.TASK, result.type()); + } + + @Test + void update_ShouldThrowNotFoundException_WhenTaskDoesNotExist() { + // Arrange + Long taskId = 1L; + TaskRequestUpdatedDto dto = new TaskRequestUpdatedDto(taskId, "Updated Task", "Updated Description", Status.IN_PROGRESS, duration); + when(repository.findById(taskId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> taskService.update(dto)); + verify(repository, times(1)).findById(taskId); + verify(mapper, never()).updateTaskFromDto(any(), any()); + verify(repository, never()).save(any()); + } + + // --- Tests for findById method --- + + @Test + void findById_ShouldReturnTask_WhenTaskExists() { + // Arrange + Long taskId = 1L; + Task task = new Task(); + task.setId(taskId); + task.setName("Task"); + task.setDescription("Description"); + task.setStartTime(now); + task.setDuration(duration); + task.setEndTime(endTime); + task.setStatus(Status.NEW); + + TaskResponseDto dto = new TaskResponseDto(taskId, "Task", "Description", Status.NEW, now, endTime, duration, TaskType.TASK); + when(repository.findById(taskId)).thenReturn(Optional.of(task)); + when(mapper.toResponseDto(task)).thenReturn(dto); + + // Act + TaskResponseDto result = taskService.findById(taskId); + + // Assert + verify(repository, times(1)).findById(taskId); + verify(mapper, times(1)).toResponseDto(task); + verify(history, times(1)).addToHistory(TaskType.TASK, taskId); + assertEquals(dto, result); + assertEquals(endTime, result.endTime()); + assertEquals(TaskType.TASK, result.type()); + } + + @Test + void findById_ShouldThrowNotFoundException_WhenTaskDoesNotExist() { + // Arrange + Long taskId = 1L; + when(repository.findById(taskId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> taskService.findById(taskId)); + verify(repository, times(1)).findById(taskId); + verify(mapper, never()).toResponseDto(any()); + verify(history, never()).addToHistory(any(), any()); + } + + // --- Tests for findAll method --- + + @Test + void findAll_ShouldReturnAllTasks() { + // Arrange + Task task1 = new Task(); + task1.setId(1L); + task1.setName("Task1"); + task1.setDescription("Desc1"); + task1.setStartTime(now); + task1.setDuration(duration); + task1.setEndTime(endTime); + task1.setStatus(Status.NEW); + + Task task2 = new Task(); + task2.setId(2L); + task2.setName("Task2"); + task2.setDescription("Desc2"); + task2.setStartTime(now); + task2.setDuration(duration); + task2.setEndTime(endTime); + task2.setStatus(Status.NEW); + + List tasks = List.of(task1, task2); + TaskResponseDto dto1 = new TaskResponseDto(1L, "Task1", "Desc1", Status.NEW, now, endTime, duration, TaskType.TASK); + TaskResponseDto dto2 = new TaskResponseDto(2L, "Task2", "Desc2", Status.NEW, now, endTime, duration, TaskType.TASK); + when(repository.findAll()).thenReturn(tasks); + when(mapper.toResponseDto(task1)).thenReturn(dto1); + when(mapper.toResponseDto(task2)).thenReturn(dto2); + + // Act + List result = taskService.findAll(); + + // Assert + verify(repository, times(1)).findAll(); + verify(mapper, times(2)).toResponseDto(any(Task.class)); + assertEquals(2, result.size()); + assertTrue(result.contains(dto1)); + assertTrue(result.contains(dto2)); + } + + // --- Tests for delete method --- + + @Test + void delete_ShouldDeleteTask_WhenTaskExists() { + // Arrange + Long taskId = 1L; + Task task = new Task(); + task.setId(taskId); + task.setName("Task"); + task.setDescription("Description"); + task.setStartTime(now); + task.setDuration(duration); + task.setEndTime(endTime); + task.setStatus(Status.NEW); + + TaskResponseDto dto = new TaskResponseDto(taskId, "Task", "Description", + Status.NEW, now, endTime, duration, TaskType.TASK); + when(repository.findById(taskId)).thenReturn(Optional.of(task)); + when(mapper.toResponseDto(task)).thenReturn(dto); + + // Act + taskService.delete(taskId); + + // Assert + verify(repository, times(1)).findById(taskId); // Called by findById internally + verify(repository, times(1)).deleteById(taskId); + } + + @Test + void delete_ShouldThrowNotFoundException_WhenTaskDoesNotExist() { + // Arrange + Long taskId = 1L; + when(repository.findById(taskId)).thenReturn(Optional.empty()); + + // Act & Assert + assertThrows(NotFoundException.class, () -> taskService.delete(taskId)); + verify(repository, times(1)).findById(taskId); + verify(repository, never()).deleteById(any()); + } +} \ No newline at end of file diff --git a/src/task/manager/schedule/Main.java b/src/task/manager/schedule/Main.java deleted file mode 100644 index 68e87c4..0000000 --- a/src/task/manager/schedule/Main.java +++ /dev/null @@ -1,6 +0,0 @@ -package task.manager.schedule; - -public class Main { - public static void main(String[] args) { - } -} \ No newline at end of file diff --git a/src/task/manager/schedule/exception/ManagerSaveException.java b/src/task/manager/schedule/exception/ManagerSaveException.java deleted file mode 100644 index 338f552..0000000 --- a/src/task/manager/schedule/exception/ManagerSaveException.java +++ /dev/null @@ -1,18 +0,0 @@ -package task.manager.schedule.exception; - -public class ManagerSaveException extends RuntimeException { - public ManagerSaveException() { - } - - public ManagerSaveException(String message) { - super(message); - } - - public ManagerSaveException(String message, Throwable cause) { - super(message, cause); - } - - public ManagerSaveException(Throwable cause) { - super(cause); - } -} diff --git a/src/task/manager/schedule/exception/NotFoundException.java b/src/task/manager/schedule/exception/NotFoundException.java deleted file mode 100644 index ff8d235..0000000 --- a/src/task/manager/schedule/exception/NotFoundException.java +++ /dev/null @@ -1,18 +0,0 @@ -package task.manager.schedule.exception; - -public class NotFoundException extends RuntimeException { - public NotFoundException() { - } - - public NotFoundException(String message) { - super(message); - } - - public NotFoundException(String message, Throwable cause) { - super(message, cause); - } - - public NotFoundException(Throwable cause) { - super(cause); - } -} diff --git a/src/task/manager/schedule/server/Endpoint.java b/src/task/manager/schedule/server/Endpoint.java deleted file mode 100644 index dfce98e..0000000 --- a/src/task/manager/schedule/server/Endpoint.java +++ /dev/null @@ -1,19 +0,0 @@ -package task.manager.schedule.server; - -public enum Endpoint { - GET_TASKS, - GET_BY_ID, - GET_EPICS_ID_SUBTASKS, - GET_SUBTASKS, - GET_HISTORY, - GET_PRIORITIZED, - - - POST, - - - DELETE_BY_ID, - - - UNKNOWN -} diff --git a/src/task/manager/schedule/server/EndpointHandler.java b/src/task/manager/schedule/server/EndpointHandler.java deleted file mode 100644 index fc6ce12..0000000 --- a/src/task/manager/schedule/server/EndpointHandler.java +++ /dev/null @@ -1,39 +0,0 @@ -package task.manager.schedule.server; - -public class EndpointHandler { - public Endpoint getEndpoint(String requestPath, String requestMethod) { - if (requestPath.equals("/tasks") && requestMethod.equals("GET")) { - return Endpoint.GET_TASKS; - } else if (requestPath.equals("/tasks") && requestMethod.equals("POST")) { - return Endpoint.POST; - } else if (requestPath.equals("/tasks/" + requestPath.split("/")[2]) && requestMethod.equals("GET")) { - return Endpoint.GET_BY_ID; - } else if (requestPath.equals("/tasks/" + requestPath.split("/")[2]) && requestMethod.equals("DELETE")) { - return Endpoint.DELETE_BY_ID; - } else if (requestPath.equals("/epics") && requestMethod.equals("GET")) { - return Endpoint.GET_TASKS; - } else if (requestPath.equals("/epics") && requestMethod.equals("POST")) { - return Endpoint.POST; - } else if (requestPath.equals("epics/" + requestPath.split("/")[2]) && requestMethod.equals("GET")) { - return Endpoint.GET_BY_ID; - } else if (requestPath.equals("/epics/" + requestPath.split("/")[2]) && requestMethod.equals("DELETE")) { - return Endpoint.DELETE_BY_ID; - } else if (requestPath.equals("/epics/" + requestPath.split("/")[2] + "/subtasks") && requestMethod.equals("GET")) { - return Endpoint.GET_EPICS_ID_SUBTASKS; - } else if (requestPath.equals("/subtasks") && requestMethod.equals("GET")) { - return Endpoint.GET_SUBTASKS; - } else if (requestPath.equals("/subtasks") && requestMethod.equals("POST")) { - return Endpoint.POST; - } else if (requestPath.equals("/subtasks/" + requestPath.split("/")[2]) && requestMethod.equals("GET")) { - return Endpoint.GET_BY_ID; - } else if (requestPath.equals("subtasks/" + requestPath.split("/")[2]) && requestMethod.equals("DELETE")) { - return Endpoint.DELETE_BY_ID; - } else if (requestPath.endsWith("history") && requestMethod.equals("GET")) { - return Endpoint.GET_HISTORY; - } else if (requestPath.endsWith("prioritized") && requestMethod.equals("GET")) { - return Endpoint.GET_PRIORITIZED; - } else { - return Endpoint.UNKNOWN; - } - } -} diff --git a/src/task/manager/schedule/server/HttpHandler.java b/src/task/manager/schedule/server/HttpHandler.java deleted file mode 100644 index b46831d..0000000 --- a/src/task/manager/schedule/server/HttpHandler.java +++ /dev/null @@ -1,203 +0,0 @@ -package task.manager.schedule.server; - -import com.google.gson.*; -import com.sun.net.httpserver.HttpExchange; -import task.manager.schedule.exception.ManagerSaveException; -import task.manager.schedule.exception.NotFoundException; -import task.manager.schedule.model.Epic; -import task.manager.schedule.model.Subtask; -import task.manager.schedule.model.Task; -import task.manager.schedule.service.TaskManager; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -public class HttpHandler implements com.sun.net.httpserver.HttpHandler { - TaskManager manager; - TaskHandler taskHandler; - EndpointHandler endpointHandler; - public HttpHandler(TaskManager manager) { - this.manager = manager; - this.taskHandler = new TaskHandler(); - this.endpointHandler = new EndpointHandler(); - } - - @Override - public void handle(HttpExchange h) throws IOException { - GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.setPrettyPrinting(); - Gson gson = gsonBuilder.create(); - Endpoint endpoint = endpointHandler.getEndpoint(h.getRequestURI().getPath(), h.getRequestMethod()); - final String path = h.getRequestURI().getPath(); - switch (endpoint) { - case GET_TASKS: - if (path.equals("/tasks")) { - sendText(h, gson.toJson(manager.getTasks().toString())); - break; - }else if(path.equals("/epics")) { - sendText(h, gson.toJson(manager.getEpics().toString())); - break; - } - case GET_BY_ID: - try { - int id = Integer.parseInt(h.getRequestURI().getPath().split("/")[2]); - if(path.equals("/tasks/"+ id)){ - sendText(h, gson.toJson(manager.getTask(id).toString())); - break; - }else if(path.equals("/epics/"+ id)){ - sendText(h, gson.toJson(manager.getEpic(id).toString())); - break; - }else if(path.equals("/subtasks/"+ id)){ - sendText(h, gson.toJson(manager.getSubtask(id).toString())); - break; - } - } catch (NotFoundException e) { - sendNotFound(h, "Данной задачи не существует по id: " + h.getRequestURI().getPath().split("/")[2]); - break; - } - case GET_EPICS_ID_SUBTASKS: - try { - sendText(h, gson.toJson(manager.getEpicSubtasks(Integer.parseInt(h.getRequestURI().getPath().split("/")[2])).toString())); - break; - } catch (NotFoundException e) { - sendNotFound(h, "Данного эпика не существует по id: " + e.getMessage()); - break; - } - case GET_HISTORY: - sendText(h, gson.toJson(manager.getHistory().toString())); - break; - case GET_PRIORITIZED: - sendText(h, gson.toJson(manager.getPrioritizedTasks().toString())); - break; - case POST: - convertTask(h); - break; - case DELETE_BY_ID: - try { - int id = Integer.parseInt(h.getRequestURI().getPath().split("/")[2]); - if(path.equals("/tasks/"+id)) { - manager.deleteTask(id); - sendText(h,""); - break; - } else if(path.equals("/epics/"+id)) { - manager.deleteEpic(id); - sendText(h,""); - break; - }else if(path.equals("/subtasks/"+id)) { - manager.deleteSubtask(id); - sendText(h,""); - break; - } - } catch (NotFoundException e) { - sendNotFound(h, "Данной задачи не существует по id: " + e.getMessage()); - break; - } - default: - errorServer(h); - break; - } - } - - protected void sendText(HttpExchange h, String text) throws IOException { - try (h) { - byte[] resp = text.getBytes(StandardCharsets.UTF_8); - h.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); - h.sendResponseHeaders(200, resp.length); - h.getResponseBody().write(resp); - } - } - - protected void sendNotFound(HttpExchange h, String text) throws IOException { - try (h) { - byte[] resp = text.getBytes(StandardCharsets.UTF_8); - h.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); - h.sendResponseHeaders(404, resp.length); - h.getResponseBody().write(resp); - } - } - - protected void updateText(HttpExchange h) throws IOException { - try (h) { - h.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); - h.sendResponseHeaders(201, 0); - } - } - protected void errorServer(HttpExchange h) throws IOException { - try (h) { - h.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); - h.sendResponseHeaders(500, 0); - } - } - - protected void sendHasInteractions(HttpExchange h, String text) throws IOException { - try (h) { - byte[] resp = text.getBytes(StandardCharsets.UTF_8); - h.getResponseHeaders().add("Content-Type", "application/json;charset=utf-8"); - h.sendResponseHeaders(406, resp.length); - h.getResponseBody().write(resp); - } - } - - - private void convertTask(HttpExchange h) throws IOException { - InputStream inputStream = h.getRequestBody(); - final String path = h.getRequestURI().getPath(); - final String[] body = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).split(","); - Task task = taskHandler.taskFromString(body); - if (path.equals("/tasks")) { - if (body[0].split("=")[0].equals("id")) { - try { - manager.updateTask(task); - updateText(h); - } catch (ManagerSaveException e) { - sendHasInteractions(h, e.getMessage()); - } - } else { - try { - manager.createTask(task); - updateText(h); - } catch (ManagerSaveException e) { - sendHasInteractions(h, e.getMessage()); - } - } - } - if (path.equals("/epics")) { - if (body[0].split("=")[0].equals("id")) { - int taskId = Integer.parseInt(body[0].split("=")[1]); - try { - manager.updateEpic(new Epic(task.getId(), task.getName(), task.getDescription(), task.getStatus(), task.getStartTime(), task.getDuration())); - updateText(h); - } catch (ManagerSaveException e) { - sendHasInteractions(h, e.getMessage()); - } - } else { - try { - manager.createEpic(new Epic(task.getName(), task.getStatus(), task.getDescription(), task.getStartTime(), task.getDuration())); - updateText(h); - } catch (ManagerSaveException e) { - sendHasInteractions(h, e.getMessage()); - } - } - } - if (path.equals("/subtasks")) { - final int epicId = Integer.parseInt(body[6].split("=")[1]); - if (body[0].split("=")[0].equals("id")) { - int taskId = Integer.parseInt(body[0].split("=")[1]); - try { - manager.updateSubtask(new Subtask(task.getId(), task.getName(), task.getDescription(), task.getStatus(), task.getStartTime(), task.getDuration(), epicId)); - updateText(h); - } catch (ManagerSaveException e) { - sendHasInteractions(h, e.getMessage()); - } - } else { - try { - manager.createSubtask(new Subtask(task.getName(), task.getStatus(), task.getDescription(), task.getStartTime(), task.getDuration(), epicId)); - updateText(h); - } catch (ManagerSaveException e) { - sendHasInteractions(h, e.getMessage()); - } - } - } - } -} diff --git a/src/task/manager/schedule/server/HttpTaskServer.java b/src/task/manager/schedule/server/HttpTaskServer.java deleted file mode 100644 index 3dd0383..0000000 --- a/src/task/manager/schedule/server/HttpTaskServer.java +++ /dev/null @@ -1,33 +0,0 @@ -package task.manager.schedule.server; -import com.sun.net.httpserver.HttpServer; -import task.manager.schedule.model.Status; -import task.manager.schedule.model.Task; -import task.manager.schedule.service.Manager; -import task.manager.schedule.service.TaskManager; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.time.LocalDateTime; - -public class HttpTaskServer { - private static final int PORT = 8080; - public static void main(String[] args) throws IOException { - TaskManager taskManager = Manager.getDefault(); - - taskManager.createTask(new Task("Дом", Status.NEW, "Убраться в кухни и ванной", LocalDateTime.now(),40)); - taskManager.createTask(new Task("Работа", Status.IN_PROGRESS, "Сделать куча рутины и пойти домой:)",LocalDateTime.now().plusDays(1),50)); - - - - HttpHandler httpHandler = new HttpHandler(taskManager); - - HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0); - server.createContext("/tasks",httpHandler); - server.createContext("/epics",httpHandler); - server.createContext("/subtasks", httpHandler); - server.createContext("/history", httpHandler); - server.createContext("/prioritized", httpHandler); - server.start(); - System.out.println("HTTP-сервер запущен на " + PORT + " порту!"); - } -} diff --git a/src/task/manager/schedule/server/TaskHandler.java b/src/task/manager/schedule/server/TaskHandler.java deleted file mode 100644 index afc2f6d..0000000 --- a/src/task/manager/schedule/server/TaskHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package task.manager.schedule.server; - -import task.manager.schedule.model.Status; -import task.manager.schedule.model.Task; - -import java.time.Duration; -import java.time.LocalDateTime; - -public class TaskHandler { - public Task taskFromString(String[] body){ - if (body[0].split("=")[0].equals("id")) { - int taskId = Integer.parseInt(body[0].split("=")[1]); - final String name = body[1].split("=")[1]; - final String description = body[2].split("=")[1]; - final Status status = Status.valueOf(body[3].split("=")[1]); - final LocalDateTime startTime = LocalDateTime.parse(body[4].split("=")[1]); - final LocalDateTime endTime = LocalDateTime.parse(body[5].split("=")[1].replaceAll("}\"", "").trim()); - final Duration duration = Duration.between(startTime, endTime); - return new Task(taskId, name, description, status, startTime, duration.toMinutesPart()); - } else { - final String name = body[0].split("=")[1]; - final String description = body[1].split("=")[1]; - final Status status = Status.valueOf(body[2].split("=")[1]); - final LocalDateTime startTime = LocalDateTime.parse(body[3].split("=")[1]); - final LocalDateTime endTime = LocalDateTime.parse(body[4].split("=")[1].replaceAll("}\"", "").trim()); - final Duration duration = Duration.between(startTime, endTime); - return new Task(name, status, description, startTime, duration.toMinutesPart()); - } - } - -} diff --git a/src/task/manager/schedule/service/FileBackedTaskManager.java b/src/task/manager/schedule/service/FileBackedTaskManager.java deleted file mode 100644 index eb0a0ad..0000000 --- a/src/task/manager/schedule/service/FileBackedTaskManager.java +++ /dev/null @@ -1,180 +0,0 @@ -package task.manager.schedule.service; - -import java.io.*; - -import java.nio.file.Files; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.Map; - -import task.manager.schedule.exception.ManagerSaveException; -import ru.yandex.javacource.golotin.schedule.model.*; -import task.manager.schedule.service.inMemory.InMemoryTaskManager; - -public class FileBackedTaskManager extends InMemoryTaskManager { - - private static final String HEADER = "id,type,name,status,description,epic"; - private final File file; - - public FileBackedTaskManager(File file) { - super(Manager.getDefaultHistory()); - this.file = file; - } - - public static FileBackedTaskManager loadFromFile(File file) { - final FileBackedTaskManager taskManager = new FileBackedTaskManager(file); - try { - final String csv = Files.readString(file.toPath()); - final String[] lines = csv.split(System.lineSeparator()); - int generatorId = 0; - for (int i = 1; i < lines.length; i++) { - String line = lines[i]; - if (line.isEmpty()) { - break; - } - final Task task = taskFromString(line); - final int id = task.getId(); - if (id > generatorId) { - generatorId = id; - } - if (task.getType() == TaskType.TASK) { - taskManager.createTask(task); - } else if (task.getType() == TaskType.SUBTASK) { - taskManager.createSubtask(new Subtask(task.getId(), task.getName(), task.getDescription(), - task.getStatus(), task.getStartTime(), task.getDuration(), task.getEpicId())); - } else if (task.getType() == TaskType.EPIC) { - taskManager.createEpic(new Epic(task.getId(), task.getName(), task.getDescription(), - task.getStatus(), task.getStartTime(), task.getDuration())); - for (Subtask subtask : taskManager.subtasks.values()) {// Поиск подзадач эпика - if (subtask.getEpicId() == task.getId()) { - Epic epic = taskManager.epics.get(task.getId()); - epic.addSubtaskId(subtask.getId()); - } - } - } - } - for (Map.Entry e : taskManager.subtasks.entrySet()) { - final Subtask subtask = e.getValue(); - final Epic epic = taskManager.epics.get(subtask.getEpicId()); - epic.addSubtaskId(subtask.getId()); - } - taskManager.counterId = generatorId; - } catch (IOException e) { - throw new ManagerSaveException("Невозможно прочитать файл: " + file.getName(), e); - } - return taskManager; - } - - @Override - public Task createTask(Task task) { - Task newTask = super.createTask(task); - saveToFile(); - return newTask; - } - - @Override - public Epic createEpic(Epic epic) { - Epic newEpic = super.createEpic(epic); - saveToFile(); - return newEpic; - } - - @Override - public Subtask createSubtask(Subtask subtask) { - Subtask newSubtask = super.createSubtask(subtask); - saveToFile(); - return newSubtask; - } - - @Override - public void updateTask(Task task) { - super.updateTask(task); - saveToFile(); - } - - @Override - public void updateEpic(Epic epic) { - super.updateEpic(epic); - saveToFile(); - } - - @Override - public void updateSubtask(Subtask subTask) { - super.updateSubtask(subTask); - saveToFile(); - } - - @Override - public void deleteTask(int id) { - super.deleteTask(id); - saveToFile(); - } - - @Override - public void deleteEpic(int id) { - super.deleteEpic(id); - saveToFile(); - } - - @Override - public void deleteSubtask(int id) { - super.deleteSubtask(id); - saveToFile(); - } - - public static String toString(Task task) { - return task.getId() + "," + task.getType() + "," + task.getName() + "," + task.getStatus() + "," + - task.getDescription() + "," + (task.getType().equals(TaskType.SUBTASK) ? task.getEpicId() : ""+task.getStartTime()+","+task.getEndTime()); - } - - - public static Task taskFromString(String value) { - final String[] values = value.split(","); - final int id = Integer.parseInt(values[0]); - final TaskType type = TaskType.valueOf(values[1]); - final String name = values[2]; - final Status status = Status.valueOf(values[3]); - final String description = values[4]; - final LocalDateTime startTime = LocalDateTime.parse(values[5]); - final Duration duration = Duration.between(LocalDateTime.parse(values[5]), LocalDateTime.parse(values[6])); - if (type == TaskType.TASK) { - return new Task(id, name, description, status, startTime, duration.toMinutesPart()); - } - if (type == TaskType.SUBTASK) { - final int epicId = Integer.parseInt(values[7]); - return new Subtask(id, name, description, status, startTime, duration.toMinutesPart(), epicId); - } - - return new Epic(id, name, description, status, startTime, duration.toMinutesPart()); - } - - protected void saveToFile() { - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { - writer.write(HEADER); - writer.newLine(); - - for (Map.Entry entry : tasks.entrySet()) { - final Task task = entry.getValue(); - writer.write(toString(task)); - writer.newLine(); - } - - for (Map.Entry entry : subtasks.entrySet()) { - final Task task = entry.getValue(); - writer.write(toString(task)); - writer.newLine(); - } - - for (Map.Entry entry : epics.entrySet()) { - final Task task = entry.getValue(); - writer.write(toString(task)); - writer.newLine(); - } - - writer.newLine(); - } catch (IOException e) { - throw new ManagerSaveException("Ошибка сохранения файла: " + file.getName(), e); - } - } -} diff --git a/src/task/manager/schedule/service/HistoryManager.java b/src/task/manager/schedule/service/HistoryManager.java deleted file mode 100644 index 9b81442..0000000 --- a/src/task/manager/schedule/service/HistoryManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package task.manager.schedule.service; - -import java.util.List; - -import task.manager.schedule.model.Task; - -public interface HistoryManager { - void add(Task task); - - List getAll(); - - void remove(int id); - - -} \ No newline at end of file diff --git a/src/task/manager/schedule/service/Manager.java b/src/task/manager/schedule/service/Manager.java deleted file mode 100644 index a14dbc8..0000000 --- a/src/task/manager/schedule/service/Manager.java +++ /dev/null @@ -1,15 +0,0 @@ -package task.manager.schedule.service; - -import task.manager.schedule.service.inMemory.InMemoryHistoryManager; - -import java.io.File; - -public class Manager { - public static TaskManager getDefault() { - return new FileBackedTaskManager(new File("resources/task.csv")); - } - - public static InMemoryHistoryManager getDefaultHistory() { - return new InMemoryHistoryManager(); - } -} diff --git a/src/task/manager/schedule/service/TaskManager.java b/src/task/manager/schedule/service/TaskManager.java deleted file mode 100644 index 2b6fc36..0000000 --- a/src/task/manager/schedule/service/TaskManager.java +++ /dev/null @@ -1,49 +0,0 @@ -package task.manager.schedule.service; - -import task.manager.schedule.model.Epic; -import task.manager.schedule.model.Subtask; -import task.manager.schedule.model.Task; - -import java.util.List; - -public interface TaskManager { - Task createTask(Task task); - - Task createEpic(Epic epic); - - Subtask createSubtask(Subtask subtask); - - void updateTask(Task task); - - void updateEpic(Epic epic); - - void updateSubtask(Subtask subtask); - - void cleanTasks(); - - void cleanSubtasks(); - - void cleanEpics(); - - void deleteTask(int id); - - void deleteSubtask(int id); - - void deleteEpic(int id); - - List getTasks(); - - List getEpics(); - - List getEpicSubtasks(int epicId); - - Task getTask(int id); - - Epic getEpic(int id); - - Subtask getSubtask(int id); - - List getHistory(); - - List getPrioritizedTasks(); -} diff --git a/src/task/manager/schedule/service/inMemory/InMemoryHistoryManager.java b/src/task/manager/schedule/service/inMemory/InMemoryHistoryManager.java deleted file mode 100644 index 6db73c0..0000000 --- a/src/task/manager/schedule/service/inMemory/InMemoryHistoryManager.java +++ /dev/null @@ -1,92 +0,0 @@ -package task.manager.schedule.service.inMemory; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import task.manager.schedule.model.Task; -import task.manager.schedule.service.HistoryManager; - -public class InMemoryHistoryManager implements HistoryManager { - - private static class Node { - Task item; - Node next; - Node prev; - - Node(Node prev, Task element, Node next) {// присвоение prev, element, next - this.item = element; - this.next = next; - this.prev = prev; - } - } - - private static final Map history = new HashMap<>(); - private Node first; - private Node last; - - @Override - public void add(Task task) {// добавление Task - - if (task == null) { - return; - }else { - remove(task.getId()); - linkLast(task); - history.put(task.getId(), last); - - } - } - - @Override - public List getAll() {// вывод списка истории - List list = new ArrayList<>(); - Node current = first; - while (current != null) { - - current = current.next; - } - return list; - } - - - @Override - public void remove(int id) {// удаление по id - final Node node = history.remove(id); - if (node == null) { - return; - } - removeNode(node); - } - - private void removeNode(Node node) { - - if (node.prev != null) { - node.prev.next = node.next; - if (node.next == null) { - last = node.prev; - } else { - node.next.prev = node.prev; - } - } else { - first = node.next; - if (first == null) { - last = null; - } else { - first.prev = null; - } - } - } - - private void linkLast(Task task) {// двигаем историю - final Node lastLink = last; - final Node newNode = new Node(lastLink, task, null); - last = newNode; - if (lastLink == null) { - first = newNode; - } else { - lastLink.next = newNode; - } - } -} \ No newline at end of file diff --git a/src/task/manager/schedule/service/inMemory/InMemoryTaskManager.java b/src/task/manager/schedule/service/inMemory/InMemoryTaskManager.java deleted file mode 100644 index 4b6b71b..0000000 --- a/src/task/manager/schedule/service/inMemory/InMemoryTaskManager.java +++ /dev/null @@ -1,295 +0,0 @@ -package task.manager.schedule.service.inMemory; - -import task.manager.schedule.exception.ManagerSaveException; -import task.manager.schedule.exception.NotFoundException; -import task.manager.schedule.model.Epic; -import task.manager.schedule.model.Status; -import task.manager.schedule.model.Subtask; -import task.manager.schedule.model.Task; -import task.manager.schedule.service.HistoryManager; -import task.manager.schedule.service.TaskManager; - -import java.time.LocalDateTime; -import java.util.*; -import java.util.stream.Collectors; - - -public class InMemoryTaskManager implements TaskManager { - protected int counterId = 0; - protected final Map tasks; - protected final Map epics; - protected final Map subtasks; - protected final HistoryManager historyManager; - protected final Set prioritizedTasks; - - public InMemoryTaskManager(HistoryManager historyManager) { - this.historyManager = historyManager; // 3 - this.tasks = new HashMap<>(); - this.epics = new HashMap<>(); - this.subtasks = new HashMap<>(); - this.prioritizedTasks = new TreeSet<>(Comparator.comparing(Task::getStartTime)); - } - - @Override - public Task createTask(Task task) {// создание Task - final int id = ++counterId; - task.setId(id); - addPriorityTask(task); - tasks.put(id, task); - return task; - } - - @Override - public Epic createEpic(Epic epic) {// создание Epic - final int id = ++counterId; - epic.setId(id); - addPriorityTask(epic); - epics.put(id, epic); - return epic; - } - - - @Override - public Subtask createSubtask(Subtask subtask) {// создание Subtask - final int epicId = subtask.getEpicId(); - Epic epic = epics.get(epicId); - if (epic == null) { - return null; - } - updateEpicDuration(epic); - final int id = ++counterId; - subtask.setId(id); - addPriorityTask(subtask); - subtasks.put(id, subtask); - epic.addSubtaskId(subtask.getId()); - updateEpicStatus(epicId); - return subtask; - } - - @Override - public void updateTask(Task task) {// обновление Task - final Task savedTask = tasks.get(task.getId()); - if (savedTask == null) { - return; - } - addPriorityTask(task); - tasks.put(task.getId(), task); - } - - @Override - public void updateEpic(Epic epic) {// обновление Epic - final Epic savedEpic = epics.get(epic.getId()); - if (savedEpic == null) { - return; - } - savedEpic.setName(epic.getName()); - savedEpic.setDescription(epic.getDescription()); - addPriorityTask(savedEpic); - epics.put(epic.getId(), epic); - } - - @Override - public void updateSubtask(Subtask subtask) {// обновление Subtask - final int epicId = subtask.getEpicId(); - final Subtask savedSubtask = subtasks.get(subtask.getId()); - if (savedSubtask == null) { - return; - } - final Epic epic = epics.get(epicId); - if (epic == null) { - return; - } - addPriorityTask(savedSubtask); - subtasks.put(subtask.getId(), subtask); - updateEpic(epicId);// обновление статуса у Epic - } - - @Override - public void cleanTasks() { - tasks.clear(); - }// очистка списка Tasks - - public void cleanSubtasks() {// очистка списка Subtasks - for (Epic epic : epics.values()) { - epic.cleanSubtask(); - updateEpicStatus(epic.getId()); - } - subtasks.clear(); - } - - @Override - public void cleanEpics() {// очистка списка Epics и Subtasks - epics.clear(); - subtasks.clear(); - - } - - @Override - public void deleteTask(int id) { - tasks.remove(id); - }// удаление по id Task - - @Override - public void deleteSubtask(int id) {// удаление по id Subtask - Subtask subtask = subtasks.remove(id); - if (subtask == null) { - return; - } - Epic epic = epics.get(subtask.getEpicId()); - epic.removeSubtask(id); - updateEpicStatus(epic.getId()); - } - - @Override - public void deleteEpic(int id) {// удаление по id Epic - Epic epic = epics.remove(id); - if (epic == null) { - return; - } - for (Integer subtaskId : epic.getSubtaskIds()) { - subtasks.remove(subtaskId); - } - } - - @Override - public List getTasks() { - return new ArrayList<>(tasks.values()); - }// получаем список Tasks - - @Override - public List getEpics() { - return new ArrayList<>(epics.values()); - }// получаем список Epics - - @Override - public List getEpicSubtasks(int epicId) {// получаем список Epic с Subtasks - final Epic epic = epics.get(epicId); - if (epic == null) { - throw new NotFoundException("Задача с ид=" + epicId); - } - updateEpic(epicId); - return epic.getSubtaskIds().stream().map(subtasks::get).collect(Collectors.toList()); - } - - @Override - public Task getTask(int id) {// получаем Task по id - final Task task = tasks.get(id); - if (task == null) { - throw new NotFoundException("Задача с ид=" + id); - } - historyManager.add(task); - return task; - - } - - @Override - public Epic getEpic(int id) {// получаем Epic по id - final Epic epic = epics.get(id); - if (epic == null) { - throw new NotFoundException("Эпик с ид=" + id); - } - historyManager.add(epic); - return epic; - } - - @Override - public Subtask getSubtask(int id) {// получаем Subtask по id - final Subtask subtask = subtasks.get(id); - if (subtask == null) { - throw new NotFoundException("Подзадача с ид=" + id); - } - historyManager.add(subtask); - return subtask; - } - - @Override - public List getHistory() {// получаем список истории - return historyManager.getAll(); - } - - private void updateEpicStatus(int epicId) {// обновление статуса Epic - Epic epic = epics.get(epicId); - List subtasks = epic.getSubtaskIds().stream() - .filter(this.subtasks::containsKey) - .map(this.subtasks::get) - .toList(); - for (Subtask statusSubtask : subtasks) { - short subtaskNew = 0; - short subtaskDone = 0; - if (statusSubtask.getStatus() == Status.IN_PROGRESS) { - epic.setStatus(Status.IN_PROGRESS); - break; - } else if (statusSubtask.getStatus() == Status.NEW) { - subtaskNew++; - } else if (statusSubtask.getStatus() == Status.DONE) { - subtaskDone++; - } - if (subtaskDone == subtasks.size()) { - epic.setStatus(Status.DONE); - break; - } - if (subtaskNew == subtasks.size()) { - epic.setStatus(Status.NEW); - } else { - epic.setStatus(Status.IN_PROGRESS); - } - break; - } - } - - private void updateEpicDuration(Epic epic) { - List subs = epic.getSubtaskIds(); - if (subs.isEmpty()) { - epic.setDuration(0L); - return; - } - LocalDateTime start = LocalDateTime.MAX; - LocalDateTime end = LocalDateTime.MIN; - long duration = 0L; - for (int id : subs) { - final Subtask subtask = subtasks.get(id); - final LocalDateTime startTime = subtask.getStartTime(); - final LocalDateTime endTime = subtask.getEndTime(); - if (startTime.isBefore(start)) { - start = startTime; - } - if (endTime.isAfter(end)) { - end = endTime; - } - duration += subtask.getDuration(); - } - epic.setDuration(duration); - epic.setStartTime(start); - epic.setEndTime(end); - } - - protected void updateEpic(int epicId) { - Epic epic = epics.get(epicId); - updateEpicStatus(epicId); - updateEpicDuration(epic); - } - - private void addPriorityTask(Task task) { - final LocalDateTime startTime = task.getStartTime(); - final LocalDateTime endTime = task.getEndTime(); - for (Task t : prioritizedTasks) { - final LocalDateTime existStart = t.getStartTime(); - final LocalDateTime existEnd = t.getEndTime(); - if (!endTime.isAfter(existStart)) { - continue; - } - if (!existEnd.isAfter(startTime)) { - continue; - } - - throw new ManagerSaveException("Задача пересекаются с id=" + t.getId() + " c " + existStart + " по " + existEnd); - } - - prioritizedTasks.add(task); - } - - @Override - public List getPrioritizedTasks() { - return new ArrayList<>(prioritizedTasks); - } -} diff --git a/test/task/manager/schedule/module/EpicTest.java b/test/task/manager/schedule/module/EpicTest.java deleted file mode 100644 index 4a159da..0000000 --- a/test/task/manager/schedule/module/EpicTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.yandex.javacource.golotin.schedule.test.model; - -import org.junit.jupiter.api.Test; -import ru.yandex.javacource.golotin.schedule.model.Epic; -import ru.yandex.javacource.golotin.schedule.model.Status; - -import static org.junit.jupiter.api.Assertions.*; - -class EpicTest { - - @Test - void shouldEqualsWithCopy() { - Epic epic = new Epic("name", Status.NEW, "desc"); - Epic epicExpected = new Epic("name1", Status.NEW, "desc"); - assertEquals(epicExpected, epic); - - } - -} \ No newline at end of file diff --git a/test/task/manager/schedule/service/FileBackedTaskManagerTest.java b/test/task/manager/schedule/service/FileBackedTaskManagerTest.java deleted file mode 100644 index b691320..0000000 --- a/test/task/manager/schedule/service/FileBackedTaskManagerTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package ru.yandex.javacource.golotin.schedule.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import ru.yandex.javacource.golotin.schedule.model.Status; -import ru.yandex.javacource.golotin.schedule.model.Task; - -import java.io.File; -import java.time.LocalDateTime; - -import static org.junit.jupiter.api.Assertions.*; - -class FileBackedTaskManagerTest { - TaskManager taskManager; - @BeforeEach - void beforeEach() { - taskManager = Manager.getDefault(); - - } - - @Test - void createTask() { - LocalDateTime localDateTime = LocalDateTime.now().plusDays(1); - Task task = new Task("test", Status.NEW, "testing", localDateTime,30); - Task task2 = new Task("test2", Status.NEW, "testing2",LocalDateTime.now(),45); - taskManager.createTask(task); - taskManager.createTask(task2); - assertEquals(taskManager.getTasks(),FileBackedTaskManager.loadFromFile(new File("resources/task.csv")).getTasks()); - } - - @Test - void updateTask() { - Task task = new Task("test", Status.NEW, "testing", LocalDateTime.now(),30); - taskManager.createTask(task); - Task task2 = new Task(0,"test2","testing2", Status.NEW, LocalDateTime.now(),45); - taskManager.updateTask(task2); - assertEquals(task, FileBackedTaskManager.loadFromFile(new File("resources/task.csv")).getTask(1)); - } - - @Test - void deleteTask() { - Task task = new Task("test", Status.NEW, "testing", LocalDateTime.now(),30); - taskManager.createTask(task); - TaskManager taskManager1 = taskManager; - taskManager.deleteTask(task.getId()); - assertNotEquals(taskManager, taskManager1); - } - -} \ No newline at end of file diff --git a/test/task/manager/schedule/service/InMemoryTaskManagerTest.java b/test/task/manager/schedule/service/InMemoryTaskManagerTest.java deleted file mode 100644 index 245f262..0000000 --- a/test/task/manager/schedule/service/InMemoryTaskManagerTest.java +++ /dev/null @@ -1,110 +0,0 @@ -package ru.yandex.javacource.golotin.schedule.test.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import ru.yandex.javacource.golotin.schedule.exception.ManagerSaveException; -import ru.yandex.javacource.golotin.schedule.model.Status; -import ru.yandex.javacource.golotin.schedule.model.Task; -import ru.yandex.javacource.golotin.schedule.service.InMemoryTaskManager; -import ru.yandex.javacource.golotin.schedule.service.Manager; -import ru.yandex.javacource.golotin.schedule.service.TaskManager; - - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -class InMemoryTaskManagerTest { - TaskManager taskManager; - - @BeforeEach - void beforeEach() { - taskManager = Manager.getDefault(); - } - - - @Test - void shouldCreateTask() { - LocalDateTime localDateTime = LocalDateTime.now().plusDays(1); - Task task = new Task("test", Status.NEW, "testing", localDateTime,30); - Task task2 = new Task("test2", Status.NEW, "testing2",LocalDateTime.now(),45); - Task task3 = task; - assertEquals(task, task2); - assertSame(task, task3); - - Task result = taskManager.createTask(task); - - assertNotNull(result); - Task clone = taskManager.getTask(result.getId()); - assertEquals(clone.getId(), result.getId()); - assertEquals(clone.getName(), result.getName()); - assertTrue(taskManager.getTasks().contains(task)); - - } - - @Test - void shouldUpdateTask() { - LocalDateTime localDateTime = LocalDateTime.now().plusDays(1); - Task task = new Task("test", Status.NEW, "testing", localDateTime,30); - taskManager.createTask(task); - Task task2 = new Task(0,"test2", "testing2", Status.NEW, LocalDateTime.now(),45); - taskManager.updateTask(task2); - assertEquals(task, taskManager.getTask(1)); - } - - @Test - void shouldDeleteTask() { - LocalDateTime localDateTime = LocalDateTime.now().plusDays(1); - Task task = new Task("test", Status.NEW, "testing", localDateTime,30); - Task task2 = new Task("test2", Status.NEW, "testing2", LocalDateTime.now(),45); - taskManager.createTask(task); - taskManager.createTask(task2); - taskManager.deleteTask(1); - Exception exception = assertThrows(ManagerSaveException.class, ()->taskManager.getTask(1)); - String expectedMessage = "Задача с ид=" + 1; - String actualMessage = exception.getMessage(); - assertTrue(actualMessage.contains(expectedMessage)); - - } - - @Test - void shouldCleanTask() { - LocalDateTime localDateTime = LocalDateTime.now().plusDays(1); - Task task = new Task("test", Status.NEW, "testing", LocalDateTime.now(),30); - Task task2 = new Task("test2", Status.NEW, "testing2", localDateTime,45); - taskManager.createTask(task); - taskManager.createTask(task2); - taskManager.cleanTasks(); - assertEquals(taskManager.getTasks(), taskManager.getEpics()); - } - - @Test - void shouldGetTasks() { - LocalDateTime localDateTime = LocalDateTime.now().plusDays(1); - Task task = new Task("test", Status.NEW, "testing", localDateTime,30); - Task task2 = new Task("test2", Status.NEW, "testing2", LocalDateTime.now(),45); - taskManager.createTask(task); - taskManager.createTask(task2); - - List tasks = new ArrayList<>(); - tasks.add(task); - tasks.add(task2); - assertEquals(tasks, taskManager.getTasks()); - } - - @Test - void shouldaddPriorityTask(){ - LocalDateTime localDateTime = LocalDateTime.now(); - Task task = new Task("test", Status.NEW, "testing", localDateTime,30); - Task task2 = new Task("test2", Status.NEW, "testing2", localDateTime,45); - taskManager.createTask(task); - Exception exception = assertThrows(ManagerSaveException.class, () -> taskManager.createTask(task2)); - - String expectedMessage = "Задача пересекаются с id=" + task.getId() + " c " + task.getStartTime() + " по " + task.getEndTime(); - String actualMessage = exception.getMessage(); - assertTrue(actualMessage.contains(expectedMessage)); - - } -} \ No newline at end of file From bdece452a534bfeb090a42a565549fb8fdea6513 Mon Sep 17 00:00:00 2001 From: YG Date: Mon, 5 May 2025 21:46:49 +0300 Subject: [PATCH 9/9] feat:added readme,docker-compose --- README.md | 203 ++++++++++++++---- docker-compose.yaml | 39 ++++ .../service/task/manager/mapper/README.md | 96 +++++++++ .../java/service/task/manager/model/README.md | 136 ++++++++++++ .../service/task/manager/repository/README.md | 86 ++++++++ .../service/task/manager/service/README.md | 147 +++++++++++++ 6 files changed, 660 insertions(+), 47 deletions(-) create mode 100644 docker-compose.yaml create mode 100644 service/src/main/java/service/task/manager/mapper/README.md create mode 100644 service/src/main/java/service/task/manager/model/README.md create mode 100644 service/src/main/java/service/task/manager/repository/README.md create mode 100644 service/src/main/java/service/task/manager/service/README.md diff --git a/README.md b/README.md index ffe84a5..70811df 100644 --- a/README.md +++ b/README.md @@ -2,97 +2,206 @@ ![Task-manager Banner](image_task-manager.jpeg) -![Java](https://img.shields.io/badge/Java-21-orange) +![Java](https://img.shields.io/badge/Java-21-orange) +![Spring](https://img.shields.io/badge/Spring-3.3-green) +![Redis](https://img.shields.io/badge/Redis-7.2-red) +![H2](https://img.shields.io/badge/H2-2.2-blue) +![Docker](https://img.shields.io/badge/Docker-27.0-blue) +![Docker Compose](https://img.shields.io/badge/Docker_Compose-3.8-blue) ![JUnit](https://img.shields.io/badge/JUnit-5-orange) +![Mockito](https://img.shields.io/badge/Mockito-5.12-yellow) --- ## 📝 Описание проекта -**java-task-manager** — это консольное приложение для управления задачами, созданное мной с нуля для упрощения планирования и отслеживания рабочего процесса. Это учебный проект, который помог мне отточить навыки работы с **Java Core** и разобраться в принципах управления задачами в памяти приложения. Это не просто программа — это инструмент, который помогает организовать хаос и привести дела в порядок. +**java-task-manager** — это современное веб-приложение для управления задачами, разработанное с использованием **Spring Boot**. Проект позволяет эффективно организовывать задачи, эпики и подзадачи, отслеживать их статус и историю просмотров. Это учебный проект, созданный для отработки навыков работы с **Spring Framework**, **JPA**, **Redis**, и другими технологиями, а также для реализации полноценного REST API с документацией через Swagger. **Почему я создал этот проект?** -- Чтобы научиться управлять задачами без внешних баз данных. -- Отработать работу с приоритетами и временными метками. -- Реализовать функционал истории просмотров задач. +- Чтобы изучить разработку RESTful-приложений с использованием Spring Boot. +- Освоить интеграцию с Redis для хранения истории просмотров. +- Практиковать тестирование с JUnit и Mockito. +- Реализовать контейнеризацию с помощью Docker и Docker Compose. **Какую проблему решает?** -Если вы хотите навести порядок в своих задачах или попрактиковаться в управлении временем, java-task-manager станет вашим помощником. Всё управление происходит в одном месте — просто и удобно. +Проект помогает структурировать рабочие процессы, предоставляя удобный REST API для управления задачами, эпиками и подзадачами. Он подходит как для личного использования, так и для изучения современных подходов к разработке серверных приложений. **Технологии:** -- **Java 21** — ядро проекта. -- **HttpServer** и **HttpExchange** — для локального сервера. -- **JUnit 5** — для тестирования кода. +- **Java 21** — Язык программирования. +- **Spring Boot 3.3** — Основа приложения, включая Spring Web, Spring Data JPA. +- **Redis 7.2** — Для хранения истории просмотров. +- **H2 Database 2.2** — Встроенная база данных для хранения задач, эпиков и подзадач. +- **Docker** и **Docker Compose** — Для контейнеризации приложения и зависимостей. +- **JUnit 5** и **Mockito** — Для модульного и интеграционного тестирования. +- **MapStruct** — Для маппинга между DTO и сущностями. +- **Swagger (Springdoc)** — Для документации API. +- **Lombok** — Для упрощения кода. --- ## 🚀 Возможности -Вот что умеет **java-task-manager**: -- **Управление задачами:** - - Создание, обновление и удаление задач (всё хранится в памяти). -- **Время и приоритеты:** Привязка задач ко времени и установка уровней приоритета. +**java-task-manager** предоставляет следующие функции: +- **Управление задачами, эпиками и подзадачами:** + - Создание, обновление, получение и удаление через REST API. + - Поддержка временных меток (`startTime`, `endTime`, `duration`) и статусов (`NEW`, `IN_PROGRESS`, `DONE`). - **Типы задач:** - - **Эпики** — крупные задачи. - - **Задачи** — основные единицы работы. - - **Подзадачи** — детализация эпиков. -- **История просмотров:** Отслеживание задач, которые вы просматривали. -- **Локальный сервер:** Работает на порту 8080. -- **Эндпоинты API:** - - `/tasks` — список всех задач. - - `/epics` — работа с эпиками. - - `/subtasks` — управление подзадачами. - - `/history` — история просмотров. - - `/prioritized` — задачи по приоритету. + - **Эпики** — Крупные задачи, содержащие подзадачи. + - **Задачи** — Независимые единицы работы. + - **Подзадачи** — Детализация эпиков, связанные с ними. +- **Приоритизация:** Получение списков задач, эпиков и подзадач, отсортированных по статусу (`IN_PROGRESS` → `NEW` → `DONE`) и времени завершения. +- **История просмотров:** Отслеживание последних 10 просмотров задач, эпиков и подзадач (хранится в Redis). +- **REST API:** + - `/task` — Управление задачами. + - `/epic` — Управление эпиками. + - `/subtask` — Управление подзадачами. + - `/history` — Получение истории просмотров. + - `/prioritized` — Получение приоритетных списков. +- **Документация API:** Доступна через Swagger UI (`/swagger-ui.html`). +- **Тестирование:** Написаны тесты для контроллеров и сервисов с использованием JUnit и Mockito. +- **Контейнеризация:** Возможность запуска приложения и Redis в Docker через Docker Compose. --- ## 🛠️ Как запустить проект ### Требования -- **Java 21** или выше. -- Любая операционная система: Windows, macOS, Linux. - -### Инструкция -1. **Склонируйте репозиторий:** +- **Java 21** или выше (для локального запуска). +- **Docker** и **Docker Compose** (для запуска в контейнерах). +- **Maven** (для сборки проекта). +- Операционная система: Windows, macOS, Linux. + +### Инструкция для локального запуска +1. **Склонируйте репозиторий:** + ```bash + git clone https://github.com/1EVILGUN1/java-task-manager.git + ``` +2. **Перейдите в папку проекта:** + ```bash + cd java-task-manager + ``` +3. **Убедитесь, что Redis запущен локально или в Docker:** + ```bash + docker run -d --name redis -p 6379:6379 redis:7.2 + ``` +4. **Соберите проект с помощью Maven:** + ```bash + mvn clean install + ``` +5. **Запустите приложение:** + ```bash + mvn spring-boot:run + ``` +6. **Проверьте работу:** + - Откройте браузер и перейдите на `http://localhost:8080/swagger-ui.html` для доступа к документации API. + - Используйте эндпоинты, например, `http://localhost:8080/task` для работы с задачами. + +### Инструкция для запуска с Docker Compose +1. **Склонируйте репозиторий (если ещё не сделано):** + ```bash git clone https://github.com/1EVILGUN1/java-task-manager.git -2. **Перейдите в папку проекта:** + ``` +2. **Перейдите в папку проекта:** + ```bash cd java-task-manager -3. **Запустите приложение:** - - В среде разработки (IntelliJ IDEA, Eclipse, VSCode): найдите файл `Main.java` и нажмите "Run". - - Или через терминал: - javac Main.java - java Main -4. **Проверьте работу:** - Откройте браузер и перейдите на `http://localhost:8080`. + ``` +3. **Запустите приложение и Redis с помощью Docker Compose:** + ```bash + docker-compose up -d --build + ``` +4. **Проверьте работу:** + - Перейдите на `http://localhost:8080/swagger-ui.html` для проверки API. + - Для остановки: + ```bash + docker-compose down + ``` + +--- + +## 🧪 Тестирование -Если всё сделано верно, сервер запустится, и вы сможете взаимодействовать с задачами! +Проект включает тесты для: +- **Контроллеров**: Проверка эндпоинтов с использованием `@WebMvcTest` и Mockito для имитации сервисов. +- **Сервисов**: Проверка бизнес-логики с использованием `@SpringBootTest` и Mockito для имитации репозиториев. + +Запуск тестов: +```bash +mvn test +``` --- ## 📋 Планы на будущее -Я планирую развивать проект дальше: -- Добавить больше тестов с использованием **JUnit**. -- Улучшить производительность работы с задачами в памяти. -- Расширить API, например, добавить эндпоинт `/tasks/completed` для завершенных задач. +- Добавить эндпоинт `/tasks/completed` для получения завершенных задач. +- Реализовать аутентификацию и авторизацию через Spring Security. +- Добавить поддержку PostgreSQL как альтернативной базы данных. +- Расширить тестовое покрытие, включая интеграционные тесты для работы с Redis. +- Оптимизировать производительность сортировки в методе `/prioritized`. +- Добавить CI/CD через GitHub Actions для автоматической сборки и тестирования. --- ## 🌟 Почему стоит попробовать? -- **Простота:** Минимум зависимостей, всё в одном месте. -- **Практичность:** Удобное управление задачами и приоритетами. -- **Обучение:** Отличный пример для изучения Java Core. +- **Мощный стек технологий:** Spring Boot, Redis, H2, Docker — всё, что нужно для современного приложения. +- **Полноценный REST API:** Удобный интерфейс с документацией через Swagger. +- **Тестирование:** Надежный код с покрытием тестами для контроллеров и сервисов. +- **Контейнеризация:** Легкий запуск приложения и Redis с помощью Docker Compose. +- **Обучение:** Отличный пример для изучения Spring Framework и микросервисной архитектуры. --- ## 📬 Контакты -- **Email:** [yasha.golotin@mail.ru](mailto:yasha.golotin.mail.ru) +- **Email:** [yasha.golotin@mail.ru](mailto:yasha.golotin@mail.ru) --- ## 🤝 Как помочь проекту -Если хотите внести свой вклад, я буду рад! Форкните репозиторий, предложите свои идеи через pull request и присоединяйтесь к развитию java-task-manager. \ No newline at end of file +Хотите внести свой вклад? Форкните репозиторий, предложите улучшения через pull request или поделитесь идеями! Возможные направления: +- Расширение функционала API. +- Улучшение тестового покрытия. +- Оптимизация работы с Redis или H2. +- Улучшение `docker-compose.yml` или Dockerfile. + +Присоединяйтесь к развитию **java-task-manager**! + +--- + +## 📚 Структура проекта + +- **`src/main/java/service/task.manager`**: + - `controller` — REST-контроллеры для обработки HTTP-запросов. + - `dto` — Объекты передачи данных (DTO) для задач, эпиков и подзадач. + - `exception` — Пользовательские исключения и обработчик ошибок. + - `mapper` — MapStruct-мапперы для преобразования между DTO и сущностями. + - `model` — JPA-сущности (`Task`, `Epic`, `Subtask`) и перечисления. + - `repository` — Spring Data JPA репозитории для работы с базой данных. + - `service` — Интерфейсы и реализации бизнес-логики. +- **`src/test`** — Тесты для контроллеров и сервисов (JUnit, Mockito). +- **`Dockerfile`** — Для сборки образа приложения. +- **`docker-compose.yml`** — Для запуска приложения и Redis. + +--- + +## 🐳 Примечания по Docker + +- **Dockerfile**: Настроен для сборки Spring Boot приложения на базе `eclipse-temurin:21-jre-jammy`. +- **Docker Compose**: Автоматически запускает приложение и Redis, связывая их через сеть `task-manager-network`. +- **Конфигурация**: Настройки Redis и H2 задаются через переменные окружения в `docker-compose.yml` или `application.properties`. + +--- + +## 🔗 Полезные ссылки + +- [Swagger UI](http://localhost:8080/swagger-ui.html) — Документация API. +- [Spring Boot](https://spring.io/projects/spring-boot) — Документация фреймворка. +- [Redis](https://redis.io) — Документация по Redis. +- [Docker](https://docs.docker.com) — Руководство по Docker. +- [Docker Compose](https://docs.docker.com/compose/) — Документация по Docker Compose. + +--- + +**java-task-manager** — это не просто менеджер задач, это шаг к освоению современных технологий разработки! 🚀 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..7e1e37c --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,39 @@ +services: + task-manager: + build: + context: . + dockerfile: Dockerfile + image: java-task-manager:latest + container_name: task-manager + ports: + - "8080:8080" + environment: + - JAVA_OPTS=-Xms512m -Xmx512m + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PORT=6379 + - SPRING_DATASOURCE_URL=jdbc:h2:mem:taskmanager;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + - SPRING_DATASOURCE_DRIVER_CLASS_NAME=org.h2.Driver + - SPRING_DATASOURCE_USERNAME=sa + - SPRING_DATASOURCE_PASSWORD= + - SPRING_JPA_HIBERNATE_DDL_AUTO=update + depends_on: + - redis + networks: + - task-manager-network + + redis: + image: redis:7.2 + container_name: redis + ports: + - "6379:6379" + volumes: + - redis-data:/data + networks: + - task-manager-network + +networks: + task-manager-network: + driver: bridge + +volumes: + redis-data: \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/mapper/README.md b/service/src/main/java/service/task/manager/mapper/README.md new file mode 100644 index 0000000..408f977 --- /dev/null +++ b/service/src/main/java/service/task/manager/mapper/README.md @@ -0,0 +1,96 @@ +# README.md для папки `mapper` + +## Описание + +Папка `mapper` содержит интерфейсы мапперов, реализованные с использованием библиотеки MapStruct для приложения `service.task.manager`. Мапперы обеспечивают преобразование между объектами передачи данных (DTO) и сущностями базы данных (`Task`, `Epic`, `Subtask`), а также между различными типами DTO. Это позволяет отделить логику преобразования данных от сервисного слоя, упрощая код и повышая его читаемость. Все мапперы интегрированы со Spring и используют аннотации для точной настройки маппинга. + +## Структура папки + +Папка включает следующие интерфейсы мапперов: + +### 1. `EpicMapper.java` +**Описание**: Интерфейс для преобразования данных, связанных с эпиками (`Epic`) и их подзадачами (`Subtask`). +**Методы**: +- `toEntity(EpicRequestCreatedDto) → Epic` — Преобразует DTO для создания эпика в сущность. +- `toResponseDto(Epic) → EpicResponseDto` — Преобразует сущность эпика в DTO ответа (включает подзадачи). +- `toSubtaskDto(Subtask) → EpicResponseDto.SubtaskDto` — Преобразует подзадачу в DTO для включения в ответ эпика. +- `toEntity(SubtaskRequestUpdatedDto) → Subtask` — Преобразует DTO обновления подзадачи в сущность. +- `toSubtaskRequestUpdatedDto(Subtask) → SubtaskRequestUpdatedDto` — Преобразует подзадачу в DTO обновления. +- `toEntity(EpicRequestUpdatedDto) → Epic` — Преобразует DTO обновления эпика в сущность. +- `toEntity(EpicResponseDto) → Epic` — Преобразует DTO ответа в сущность. +- `toEpicDto(Epic) → EpicRequestUpdatedDto` — Преобразует сущность эпика в DTO обновления. +- `updateTaskFromDto(EpicRequestUpdatedDto, @MappingTarget Epic)` — Обновляет сущность эпика на основе DTO, игнорируя поля `id`, `startTime`, `subtasks`, `endTime` (последнее рассчитывается в `@PreUpdate`). + +**Особенности**: +- Игнорирует `endTime`, так как оно вычисляется автоматически. +- Поддерживает вложенный маппинг подзадач для `EpicResponseDto`. + +### 2. `SubtaskMapper.java` +**Описание**: Интерфейс для преобразования данных, связанных с подзадачами (`Subtask`). +**Методы**: +- `toEntity(SubtaskRequestCreatedDto) → Subtask` — Преобразует DTO для создания подзадачи в сущность. +- `toResponseDto(Subtask) → SubtaskResponseDto` — Преобразует сущность подзадачи в DTO ответа. +- `toEntity(SubtaskRequestUpdatedDto) → Subtask` — Преобразует DTO обновления подзадачи в сущность. +- `toEntity(SubtaskResponseDto) → Subtask` — Преобразует DTO ответа в сущность. +- `updateSubtaskFromDto(SubtaskRequestUpdatedDto, @MappingTarget Subtask)` — Обновляет сущность подзадачи на основе DTO, игнорируя поля `id`, `epic`, `startTime`, `endTime` (последнее рассчитывается в `@PreUpdate`). + +**Особенности**: +- Игнорирует поле `epic`, чтобы сохранить связь с эпикой из базы данных. +- Поддерживает обновление подзадачи без изменения неизменяемых полей. + +### 3. `TaskMapper.java` +**Описание**: Интерфейс для преобразования данных, связанных с задачами (`Task`). +**Методы**: +- `toEntity(TaskRequestCreatedDto) → Task` — Преобразует DTO для создания задачи в сущность. +- `toResponseDto(Task) → TaskResponseDto` — Преобразует сущность задачи в DTO ответа. +- `toEntity(TaskRequestUpdatedDto) → Task` — Преобразует DTO обновления задачи в сущность. +- `toEntity(TaskResponseDto) → Task` — Преобразует DTO ответа в сущность. +- `toTaskRequestUpdatedDto(Task) → TaskRequestUpdatedDto` — Преобразует сущность задачи в DTO обновления. +- `updateTaskFromDto(TaskRequestUpdatedDto, @MappingTarget Task)` — Обновляет сущность задачи на основе DTO, игнорируя поля `id`, `startTime`, `endTime` (последнее рассчитывается в `@PreUpdate`). + +**Особенности**: +- Игнорирует `startTime` и `endTime` при обновлении, чтобы сохранить значения из базы. + +## Основные особенности + +- **MapStruct**: Все мапперы используют библиотеку MapStruct, которая генерирует реализацию маппинга во время компиляции, обеспечивая высокую производительность и типобезопасность. +- **Интеграция со Spring**: Аннотация `@Mapper(componentModel = "spring")` позволяет инжектировать мапперы как Spring-бины. +- **Аннотации `@Mapping`**: + - Используются для точной настройки маппинга, например, для игнорирования полей (`id`, `startTime`, `endTime`, `subtasks`, `epic`). + - Обеспечивают корректное преобразование сложных структур, таких как список подзадач в `EpicResponseDto`. +- **Обновление сущностей**: Методы `update...FromDto` используют `@MappingTarget` для частичного обновления сущностей, сохраняя неизменяемые поля. +- **Документация**: Мапперы косвенно документируются через DTO, которые содержат аннотации Swagger (`@Schema`). + +## Использование + +Мапперы используются в сервисах (`service`) для преобразования данных между DTO (из контроллеров) и сущностями (для работы с базой данных). Они упрощают обработку запросов, минимизируя ручной код преобразования. + +**Пример использования в сервисе**: +```java +Epic epic = epicMapper.toEntity(epicRequestCreatedDto); +epicRepository.save(epic); +EpicResponseDto response = epicMapper.toResponseDto(epic); +``` + +## Зависимости + +- **MapStruct**: Библиотека для генерации мапперов. +- **DTO**: Пакет `service.task.manager.dto` (папки `task`, `epic`, `subtask`). +- **Модели**: Пакет `service.task.manager.model` (классы `Task`, `Epic`, `Subtask`). +- **Spring**: Для интеграции мапперов как бинов. + +## Документация API + +Мапперы не имеют прямой документации в Swagger, но их использование отражено в DTO, которые описаны в Swagger UI: +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +## Примечания + +- **Игнорируемые поля**: Поля, такие как `id`, `startTime`, `endTime`, игнорируются при обновлении, чтобы избежать нежелательных изменений. `endTime` рассчитывается автоматически в сущностях с помощью аннотации `@PreUpdate`. +- **Производительность**: MapStruct генерирует эффективный код, минимизируя накладные расходы на преобразование. +- **Расширяемость**: Для добавления новых маппингов достаточно расширить существующие интерфейсы или создать новые. + +## Дополнительно + +Для информации о DTO, контроллерах или сервисах см. соответствующие папки (`dto`, `controller`, `service`). По вопросам настройки или расширения мапперов обратитесь к документации проекта или разработчикам. \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/model/README.md b/service/src/main/java/service/task/manager/model/README.md new file mode 100644 index 0000000..10d0055 --- /dev/null +++ b/service/src/main/java/service/task/manager/model/README.md @@ -0,0 +1,136 @@ +# README.md для папки `model` + +## Описание + +Папка `model` содержит сущности (модели) и перечисления, используемые для представления данных в приложении `service.task.manager`. Модели описывают структуру данных для задач, эпиков, подзадач и записей истории, которые хранятся в базе данных. Папка также включает подпапку `enums`, содержащую перечисления для статусов и типов задач. Сущности аннотированы с использованием JPA (Jakarta Persistence API) для работы с реляционной базой данных через Hibernate. + +## Структура папки + +### 1. Модели + +- **`Epic.java`** + **Описание**: Сущность, представляющая эпик — крупную задачу, которая может содержать подзадачи. + **Поля**: + - `id` (`Long`, автоинкремент) — Уникальный идентификатор эпика. + - `subtasks` (`List`) — Список связанных подзадач (связь `@OneToMany`). + - `name` (`String`, обязательное) — Название эпика. + - `description` (`String`) — Описание эпика. + - `status` (`Status`) — Статус эпика (`NEW`, `IN_PROGRESS`, `DONE`). + - `startTime` (`LocalDateTime`) — Время начала эпика. + - `duration` (`Duration`) — Длительность эпика. + - `endTime` (`LocalDateTime`) — Время окончания, рассчитывается автоматически. + - `type` (`TaskType`, только для чтения) — Тип задачи (всегда `EPIC`). + **Особенности**: + - Аннотация `@PrePersist` и `@PreUpdate` для автоматического расчета `endTime` на основе `startTime` и `duration`. + - Связь с подзадачами настроена с каскадным удалением (`cascade = CascadeType.ALL`, `orphanRemoval = true`). + +- **`HistoryEntry.java`** + **Описание**: Класс для представления записи в истории доступа к задачам, эпикам или подзадачам. + **Поля**: + - `type` (`TaskType`) — Тип сущности (`TASK`, `SUBTASK`, `EPIC`). + - `id` (`Long`) — Идентификатор сущности. + **Особенности**: + - Не является JPA-сущностью, используется для передачи данных о вызове метода `findById` (хранится в Redis). + +- **`Subtask.java`** + **Описание**: Сущность, представляющая подзадачу, связанную с эпикой. + **Поля**: + - `id` (`Long`, автоинкремент) — Уникальный идентификатор подзадачи. + - `epic` (`Epic`) — Связанный эпик (связь `@ManyToOne`). + - `name` (`String`, обязательное) — Название подзадачи. + - `description` (`String`) — Описание подзадачи. + - `status` (`Status`) — Статус подзадачи. + - `startTime` (`LocalDateTime`) — Время начала. + - `endTime` (`LocalDateTime`) — Время окончания, рассчитывается автоматически. + - `duration` (`Duration`) — Длительность подзадачи. + - `type` (`TaskType`, только для чтения) — Тип задачи (всегда `SUBTASK`). + **Особенности**: + - Автоматический расчет `endTime` через `@PrePersist` и `@PreUpdate`. + - Связь с эпикой через внешний ключ (`epic_id`). + +- **`Task.java`** + **Описание**: Сущность, представляющая независимую задачу. + **Поля**: + - `id` (`Long`, автоинкремент) — Уникальный идентификатор задачи. + - `name` (`String`, обязательное) — Название задачи. + - `description` (`String`) — Описание задачи. + - `status` (`Status`) — Статус задачи. + - `startTime` (`LocalDateTime`) — Время начала. + - `endTime` (`LocalDateTime`) — Время окончания, рассчитывается автоматически. + - `duration` (`Duration`) — Длительность задачи. + - `type` (`TaskType`, только для чтения) — Тип задачи (всегда `TASK`). + **Особенности**: + - Автоматический расчет `endTime` через `@PrePersist` и `@PreUpdate`. + +### 2. Папка `enums` + +- **`Status.java`** + **Описание**: Перечисление, определяющее возможные статусы задач, эпиков и подзадач. + **Значения**: + - `NEW` — Новая задача/эпик/подзадача. + - `IN_PROGRESS` — В процессе выполнения. + - `DONE` — Завершена. + **Использование**: Хранится в базе данных как строка (`@Enumerated(EnumType.STRING)`). + +- **`TaskType.java`** + **Описание**: Перечисление, определяющее тип сущности. + **Значения**: + - `TASK` — Независимая задача. + - `SUBTASK` — Подзадача, связанная с эпикой. + - `EPIC` — Эпик, содержащий подзадачи. + **Использование**: Используется для идентификации типа сущности в базе данных и в истории доступа. + +## Основные особенности + +- **JPA-аннотации**: + - `@Entity` и `@Table` для маппинга сущностей на таблицы базы данных (`task`, `epic`, `subtask`). + - `@Id` и `@GeneratedValue` для автоинкрементных идентификаторов. + - `@OneToMany` и `@ManyToOne` для связей между эпиками и подзадачами. + - `@Enumerated(EnumType.STRING)` для хранения перечислений как строк. +- **Автоматический расчет `endTime`**: Все сущности (`Task`, `Epic`, `Subtask`) используют метод `calculateEndTime`, вызываемый перед сохранением или обновлением, для вычисления `endTime` на основе `startTime` и `duration`. +- **Lombok**: Аннотации `@Getter`, `@Setter`, `@ToString`, `@RequiredArgsConstructor` и др. используются для упрощения кода. +- **Равенство и хэш-код**: Реализованы методы `equals` и `hashCode`, основанные на поле `id`, для корректной работы с коллекциями и Hibernate. +- **Исключение циклических ссылок**: В `@ToString` для `Epic` исключено поле `subtasks`, чтобы избежать рекурсии при выводе. + +## Использование + +Модели используются в сервисах и репозиториях для работы с базой данных. Они представляют данные, хранимые в таблицах, и используются мапперами (`mapper`) для преобразования в DTO и обратно. + +**Пример использования в сервисе**: +```java +Epic epic = new Epic(); +epic.setName("Новый эпик"); +epic.setStartTime(LocalDateTime.now()); +epic.setDuration(Duration.ofHours(24)); +epicRepository.save(epic); // endTime рассчитается автоматически +``` + +**Пример записи в историю**: +```java +HistoryEntry entry = new HistoryEntry(TaskType.EPIC, epic.getId()); +historyService.save(entry); +``` + +## Зависимости + +- **Hibernate/JPA**: Для работы с базой данных. +- **Lombok**: Для генерации геттеров, сеттеров и других методов. +- **DTO**: Пакет `service.task.manager.dto` для преобразования моделей в DTO через мапперы. +- **MapStruct**: Пакет `service.task.manager.mapper` для маппинга. + +## Документация API + +Модели не документируются напрямую в Swagger, но их структура отражена в DTO, которые описаны в Swagger UI: +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +## Примечания + +- **Автоматический расчет `endTime`**: Поле `endTime` не должно устанавливаться вручную, так как оно вычисляется автоматически. +- **Связи**: Подзадачи автоматически удаляются при удалении эпика благодаря настройке `orphanRemoval = true`. +- **Перечисления**: `Status` и `TaskType` хранятся как строки в базе данных для удобства чтения и расширения. +- **История**: `HistoryEntry` используется только для кэширования в Redis, а не для хранения в реляционной базе данных. + +## Дополнительно + +Для информации о мапперах, DTO, контроллерах или сервисах см. соответствующие папки (`mapper`, `dto`, `controller`, `service`). По вопросам настройки или расширения моделей обратитесь к документации проекта или разработчикам. \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/repository/README.md b/service/src/main/java/service/task/manager/repository/README.md new file mode 100644 index 0000000..813d859 --- /dev/null +++ b/service/src/main/java/service/task/manager/repository/README.md @@ -0,0 +1,86 @@ +# README.md для папки `repository` + +## Описание + +Папка `repository` содержит интерфейсы репозиториев для работы с базой данных в приложении `service.task.manager`. Репозитории реализованы с использованием Spring Data JPA и предоставляют методы для выполнения CRUD-операций (создание, чтение, обновление, удаление) над сущностями `Task`, `Epic` и `Subtask`. Каждый репозиторий расширяет интерфейс `JpaRepository`, что обеспечивает стандартные методы для работы с данными, а также включает дополнительные методы для специфичных проверок. + +## Структура папки + +### 1. `EpicRepository.java` +**Описание**: Интерфейс репозитория для работы с сущностью `Epic` (эпики). +**Методы**: +- Наследуемые от `JpaRepository`: + - `save`, `findById`, `findAll`, `deleteById`, и др. — стандартные CRUD-операции. +- Специфичный метод: + - `boolean existsByName(String name)` — Проверяет, существует ли эпик с указанным именем. + **Использование**: Используется для создания, получения, обновления и удаления эпиков, а также для проверки уникальности имени эпика. + +### 2. `SubtaskRepository.java` +**Описание**: Интерфейс репозитория для работы с сущностью `Subtask` (подзадачи). +**Методы**: +- Наследуемые от `JpaRepository`: + - `save`, `findById`, `findAll`, `deleteById`, и др. — стандартные CRUD-операции. +- Специфичный метод: + - `boolean existsByName(String name)` — Проверяет, существует ли подзадача с указанным именем. + **Использование**: Используется для управления подзадачами, связанными с эпиками, и проверки уникальности имени подзадачи. + +### 3. `TaskRepository.java` +**Описание**: Интерфейс репозитория для работы с сущностью `Task` (задачи). +**Методы**: +- Наследуемые от `JpaRepository`: + - `save`, `findById`, `findAll`, `deleteById`, и др. — стандартные CRUD-операции. +- Специфичный метод: + - `boolean existsByName(String name)` — Проверяет, существует ли задача с указанным именем. + **Использование**: Используется для управления независимыми задачами и проверки уникальности имени задачи. + +## Основные особенности + +- **Spring Data JPA**: Репозитории используют Spring Data JPA для автоматической генерации запросов к базе данных на основе имен методов и аннотаций. +- **Аннотация `@Repository`**: Помечает интерфейсы как Spring-бины, интегрируемые с контекстом приложения. +- **Уникальность имен**: Метод `existsByName` в каждом репозитории позволяет проверять уникальность имени сущности, что предотвращает создание дубликатов (например, используется для генерации исключения `ConflictException`). +- **Типизация**: Репозитории строго типизированы для работы с соответствующими сущностями (`Epic`, `Subtask`, `Task`) и их идентификаторами (`Long`). +- **Каскадные операции**: Для `EpicRepository` операции над эпиками автоматически применяются к связанным подзадачам благодаря настройке `cascade = CascadeType.ALL` в модели `Epic`. + +## Использование + +Репозитории используются в сервисах (`service`) для взаимодействия с базой данных. Они предоставляют удобный интерфейс для выполнения операций без необходимости написания SQL-запросов. + +**Пример использования в сервисе**: +```java +@Service +@RequiredArgsConstructor +public class EpicService { + private final EpicRepository epicRepository; + + public void create(Epic epic) { + if (epicRepository.existsByName(epic.getName())) { + throw new ConflictException("Эпик с именем " + epic.getName() + " уже существует"); + } + epicRepository.save(epic); + } +} +``` + +## Зависимости + +- **Spring Data JPA**: Для реализации репозиториев и взаимодействия с базой данных. +- **Hibernate**: Как реализация JPA для маппинга сущностей на таблицы. +- **Модели**: Пакет `service.task.manager.model` (классы `Task`, `Epic`, `Subtask`). +- **Исключения**: Пакет `service.task.manager.exception` (например, `ConflictException` для обработки ошибок). + +## Документация API + +Репозитории не документируются напрямую в Swagger, но их функциональность отражена в эндпоинтах контроллеров, которые используют сервисы и, соответственно, репозитории. Документация доступна по: +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +## Примечания + +- **Автоматические методы**: `JpaRepository` предоставляет множество встроенных методов, таких как `findAll`, `save`, `delete`, что минимизирует необходимость написания пользовательских запросов. +- **Проверка уникальности**: Метод `existsByName` используется для предотвращения дублирования имен, что является частью бизнес-логики (например, валидация перед сохранением). +- **Производительность**: Spring Data JPA оптимизирует запросы, но для сложных операций рекомендуется использовать `@Query` или другие механизмы, если потребуется. +- **Расширяемость**: Для добавления новых методов достаточно расширить интерфейсы репозиториев, используя соглашения об именовании Spring Data или аннотацию `@Query`. + +## Дополнительно + +Для информации о моделях, сервисах, мапперах или контроллерах см. соответствующие папки (`model`, `service`, `mapper`, `controller`). По вопросам настройки или расширения репозиториев обратитесь к документации проекта или разработчикам. \ No newline at end of file diff --git a/service/src/main/java/service/task/manager/service/README.md b/service/src/main/java/service/task/manager/service/README.md new file mode 100644 index 0000000..bfcf83a --- /dev/null +++ b/service/src/main/java/service/task/manager/service/README.md @@ -0,0 +1,147 @@ +# README.md для папки `service` + +## Описание + +Папка `service` содержит интерфейсы сервисов и их реализации для приложения `service.task.manager`. Сервисы реализуют бизнес-логику приложения, включая создание, обновление, получение и удаление задач, эпиков, подзадач, а также управление историей доступа. Интерфейсы определяют контракт для каждого сервиса, а их реализации находятся в подпапке `impl`. Все сервисы используют Spring-компоненты, транзакции, мапперы, репозитории и обработку исключений для обеспечения надежной работы. + +## Структура папки + +### 1. Интерфейсы сервисов + +- **`EpicService.java`** + **Описание**: Интерфейс для управления эпиками. + **Методы**: + - `create(EpicRequestCreatedDto)` — Создание нового эпика. + - `update(EpicRequestUpdatedDto) → EpicResponseDto` — Обновление существующего эпика. + - `findById(Long) → EpicResponseDto` — Получение эпика по ID. + - `findAll() → List` — Получение всех эпиков. + - `delete(Long)` — Удаление эпика по ID. + - `prioritized() → List` — Получение эпиков, отсортированных по приоритету (`IN_PROGRESS`, `NEW`, `DONE`) и времени завершения. + +- **`HistoryService.java`** + **Описание**: Интерфейс для управления историей доступа к задачам, эпикам и подзадачам. + **Методы**: + - `addToHistory(TaskType, Long)` — Добавление записи в историю (тип сущности и ID). + - `getHistory() → List` — Получение последних 10 записей истории. + +- **`SubtaskService.java`** + **Описание**: Интерфейс для управления подзадачами. + **Методы**: + - `create(SubtaskRequestCreatedDto)` — Создание новой подзадачи. + - `update(SubtaskRequestUpdatedDto) → SubtaskResponseDto` — Обновление существующей подзадачи. + - `findById(Long) → SubtaskResponseDto` — Получение подзадачи по ID. + - `findAll() → List` — Получение всех подзадач. + - `delete(Long)` — Удаление подзадачи по ID. + - `prioritized() → List` — Получение подзадач, отсортированных по приоритету и времени завершения. + +- **`TaskService.java`** + **Описание**: Интерфейс для управления задачами. + **Методы**: + - `create(TaskRequestCreatedDto)` — Создание новой задачи. + - `update(TaskRequestUpdatedDto) → TaskResponseDto` — Обновление существующей задачи. + - `findById(Long) → TaskResponseDto` — Получение задачи по ID. + - `findAll() → List` — Получение всех задач. + - `delete(Long)` — Удаление задачи по ID. + - `prioritized() → List` — Получение задач, отсортированных по приоритету и времени завершения. + +### 2. Папка `impl` (реализации сервисов) + +- **`EpicServiceImpl.java`** + **Описание**: Реализация `EpicService` для управления эпиками. + **Особенности**: + - Проверяет уникальность имени эпика с помощью `EpicRepository.existsByName`. + - Устанавливает статус `NEW` для новых эпиков и вызывает расчет `endTime`. + - Использует `EpicMapper` для преобразования между DTO и сущностями. + - Добавляет запись в историю при вызове `findById` через `HistoryService`. + - Реализует сортировку в методе `prioritized` по статусу (`IN_PROGRESS` → `NEW` → `DONE`) и `endTime`. + - Управляет транзакциями с помощью `@Transactional` (чтение — `readOnly = true`). + +- **`HistoryServiceImpl.java`** + **Описание**: Реализация `HistoryService` для хранения и получения истории доступа в Redis. + **Особенности**: + - Хранит до 10 записей в Redis (ключ `history`), удаляя старую запись при превышении лимита. + - Использует `RedisTemplate` и `ListOperations` для работы с Redis. + - Логирует ошибки и возвращает пустой список в случае сбоя. + - Не использует транзакции, так как работает с кэшем, а не с базой данных. + +- **`SubtaskServiceImpl.java`** + **Описание**: Реализация `SubtaskService` для управления подзадачами. + **Особенности**: + - Проверяет существование эпика через `EpicService.findById` перед созданием подзадачи. + - Проверяет уникальность имени подзадачи с помощью `SubtaskRepository.existsByName`. + - Устанавливает статус `NEW` и вызывает расчет `endTime` для новых подзадач. + - Использует `SubtaskMapper` и `EpicMapper` для преобразования данных. + - Добавляет запись в историю при вызове `findById`. + - Реализует сортировку в методе `prioritized` аналогично эпикам. + - Использует `@Transactional` для управления транзакциями. + +- **`TaskServiceImpl.java`** + **Описание**: Реализация `TaskService` для управления задачами. + **Особенности**: + - Проверяет уникальность имени задачи с помощью `TaskRepository.existsByName`. + - Устанавливает статус `NEW` и вызывает расчет `endTime` для новых задач. + - Использует `TaskMapper` для преобразования между DTO и сущностями. + - Добавляет запись в историю при вызове `findById`. + - Реализует сортировку в методе `prioritized` аналогично эпикам и подзадачам. + - Использует `@Transactional` для управления транзакциями. + +## Основные особенности + +- **Интерфейсы и реализации**: Разделение на интерфейсы и их реализации (`impl`) упрощает тестирование и замену реализаций. +- **Транзакции**: Используется аннотация `@Transactional` для обеспечения целостности данных; операции чтения помечены как `readOnly = true` для оптимизации. +- **Логирование**: Все сервисы используют SLF4J (`@Slf4j`) для логирования операций и ошибок. +- **Обработка ошибок**: + - `NotFoundException` для отсутствующих ресурсов. + - `ConflictException` для дублирования имен. +- **Сортировка по приоритету**: Метод `prioritized` сортирует сущности по статусу (`IN_PROGRESS` → `NEW` → `DONE`) и времени завершения (`endTime`). +- **Redis для истории**: `HistoryServiceImpl` использует Redis для хранения истории доступа, обеспечивая быстрый доступ и ограничение размера (10 записей). +- **Маппинг**: Все сервисы используют мапперы (`EpicMapper`, `SubtaskMapper`, `TaskMapper`) для преобразования между DTO и сущностями. + +## Использование + +Сервисы используются в контроллерах (`controller`) для обработки запросов и реализации бизнес-логики. Они взаимодействуют с репозиториями для доступа к базе данных и мапперами для преобразования данных. + +**Пример использования в контроллере**: +```java +@PostMapping +public ResponseEntity create(@RequestBody @Valid EpicRequestCreatedDto dto) { + epicService.create(dto); + return ResponseEntity.status(HttpStatus.CREATED).build(); +} +``` + +**Пример получения истории**: +```java +@GetMapping +public List getHistory() { + return historyService.getHistory(); +} +``` + +## Зависимости + +- **Spring**: Для управления сервисами как бинами (`@Service`) и транзакциями (`@Transactional`). +- **Spring Data JPA**: Для взаимодействия с репозиториями (`EpicRepository`, `SubtaskRepository`, `TaskRepository`). +- **MapStruct**: Для маппинга между DTO и сущностями (`EpicMapper`, `SubtaskMapper`, `TaskMapper`). +- **Redis**: Для хранения истории в `HistoryServiceImpl` через `RedisTemplate`. +- **Lombok**: Для упрощения кода (`@RequiredArgsConstructor`, `@Slf4j`). +- **Исключения**: Пакет `service.task.manager.exception` для обработки ошибок (`NotFoundException`, `ConflictException`). +- **Модели**: Пакет `service.task.manager.model` для работы с сущностями (`Task`, `Epic`, `Subtask`, `HistoryEntry`). + +## Документация API + +Сервисы косвенно документируются через контроллеры, которые используют их методы. Полная документация доступна в Swagger UI: +- **Swagger UI**: `http://localhost:8080/swagger-ui.html` +- **OpenAPI JSON**: `http://localhost:8080/v3/api-docs` + +## Примечания + +- **Транзакционная целостность**: Все операции, изменяющие данные, выполняются в транзакциях для обеспечения согласованности. +- **Логирование**: Подробное логирование операций и ошибок помогает в отладке и мониторинге. +- **Ограничение истории**: `HistoryServiceImpl` хранит только последние 10 записей, что оптимизирует использование памяти в Redis. +- **Сортировка**: Логика сортировки в методе `prioritized` унифицирована для всех сущностей, но может быть расширена для дополнительных критериев. +- **Расширяемость**: Новые сервисы можно добавить, создав интерфейс и реализацию, следуя существующей структуре. + +## Дополнительно + +Для информации о моделях, репозиториях, мапперах или контроллерах см. соответствующие папки (`model`, `repository`, `mapper`, `controller`). По вопросам настройки или расширения сервисов обратитесь к документации проекта или разработчикам. \ No newline at end of file