From 9e5bac68378e4138da8073800771353e26683c3e Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 27 Nov 2025 17:10:09 +0000 Subject: [PATCH 01/67] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4a80115..19aec56 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/TvkQWWs6) # Features of modern Java # Цели и задачи л/р: From a378e0209e21f7c63cc9f19da3835772a811a0bc Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 14:43:23 +0300 Subject: [PATCH 02/67] initial --- build.gradle.kts | 6 ++++++ docker-compose.yml | 0 src/main/java/org/lab/Main.java | 9 ++++++--- .../use_cases/create/CreateEmployeeUseCase.java | 9 +++++++++ .../core/constants/employee/EmployeeType.java | 8 ++++++++ .../org/lab/domain/emploee/model/Employee.java | 17 +++++++++++++++++ .../org/lab/infra/db/client/DatabaseClient.java | 4 ++++ 7 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 docker-compose.yml create mode 100644 src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java create mode 100644 src/main/java/org/lab/core/constants/employee/EmployeeType.java create mode 100644 src/main/java/org/lab/domain/emploee/model/Employee.java create mode 100644 src/main/java/org/lab/infra/db/client/DatabaseClient.java diff --git a/build.gradle.kts b/build.gradle.kts index 79bf52a..d439324 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,12 @@ dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + implementation("io.javalin:javalin:6.1.3") + implementation("ch.qos.logback:logback-classic:1.4.11") + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + testCompileOnly("org.projectlombok:lombok:1.18.32") + testAnnotationProcessor("org.projectlombok:lombok:1.18.32") } tasks.test { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 22028ef..8ffb4e5 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -1,4 +1,7 @@ -void main() { - IO.println("Hello and welcome!"); -} +import io.javalin.Javalin; +void main() { + var app = Javalin.create(/*config*/) + .get("/", ctx -> ctx.result("Hello World")) + .start(7070); +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java new file mode 100644 index 0000000..56420f6 --- /dev/null +++ b/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java @@ -0,0 +1,9 @@ +package org.lab.application.employee.use_cases.create; + +import org.lab.domain.emploee.model.Employee; + +public class CreateEmployeeUseCase { + public Employee execute(Employee employee){ + return employee; + } +} diff --git a/src/main/java/org/lab/core/constants/employee/EmployeeType.java b/src/main/java/org/lab/core/constants/employee/EmployeeType.java new file mode 100644 index 0000000..24ca51c --- /dev/null +++ b/src/main/java/org/lab/core/constants/employee/EmployeeType.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.employee; + +public enum EmployeeType { + PROGRAMMER, + TESTER, + MANAGER, + TEAMLEAD +} diff --git a/src/main/java/org/lab/domain/emploee/model/Employee.java b/src/main/java/org/lab/domain/emploee/model/Employee.java new file mode 100644 index 0000000..2e7eef6 --- /dev/null +++ b/src/main/java/org/lab/domain/emploee/model/Employee.java @@ -0,0 +1,17 @@ +package org.lab.domain.emploee.model; + +import java.util.Date; + +import lombok.Data; + +import org.lab.core.constants.employee.EmployeeType; + +@Data +public class Employee { + private int id; + private String name; + private int age; + private EmployeeType type; + private int createdBy; + private Date createdDate; +} \ No newline at end of file diff --git a/src/main/java/org/lab/infra/db/client/DatabaseClient.java b/src/main/java/org/lab/infra/db/client/DatabaseClient.java new file mode 100644 index 0000000..c23ffc3 --- /dev/null +++ b/src/main/java/org/lab/infra/db/client/DatabaseClient.java @@ -0,0 +1,4 @@ +package org.lab.infra.db.client; + +public class DatabaseClient { +} From 3698964a84a02974d05f4579aaccddbda3d3c0cf Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 14:52:25 +0300 Subject: [PATCH 03/67] db client --- build.gradle.kts | 10 ++++++--- .../lab/infra/db/client/DatabaseClient.java | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d439324..feeff77 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,13 +10,17 @@ repositories { } dependencies { - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") implementation("io.javalin:javalin:6.1.3") implementation("ch.qos.logback:logback-classic:1.4.11") + implementation("org.postgresql:postgresql:42.7.8") + implementation("com.zaxxer:HikariCP:5.0.1") + compileOnly("org.projectlombok:lombok:1.18.32") annotationProcessor("org.projectlombok:lombok:1.18.32") + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") testCompileOnly("org.projectlombok:lombok:1.18.32") testAnnotationProcessor("org.projectlombok:lombok:1.18.32") } diff --git a/src/main/java/org/lab/infra/db/client/DatabaseClient.java b/src/main/java/org/lab/infra/db/client/DatabaseClient.java index c23ffc3..933946c 100644 --- a/src/main/java/org/lab/infra/db/client/DatabaseClient.java +++ b/src/main/java/org/lab/infra/db/client/DatabaseClient.java @@ -1,4 +1,26 @@ package org.lab.infra.db.client; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import java.sql.Connection; +import java.sql.SQLException; + public class DatabaseClient { + private static HikariDataSource dataSource; + + static { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:postgresql://postgres:5432/mydb"); + config.setUsername("postgres"); + config.setPassword("postgres"); + config.setMaximumPoolSize(10); // размер пула + dataSource = new HikariDataSource(config); + } + + public static Connection getConnection() + throws SQLException + { + return dataSource.getConnection(); + } } From c29eeb0571fbbbbf378d13c8c6ee86c927dc179a Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 14:56:58 +0300 Subject: [PATCH 04/67] =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=20?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B9=20=D1=81=D0=BE=D1=82=D1=80=D1=83=D0=B4=D0=BD=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../employee/EmployeeRepository.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java new file mode 100644 index 0000000..73116af --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -0,0 +1,28 @@ +package org.lab.infra.db.repository.employee; + +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.client.DatabaseClient; + +public class EmployeeRepository { + private DatabaseClient databaseClient; + + public EmployeeRepository() { + databaseClient = new DatabaseClient(); + } + + public Employee getById(int id) { + return new Employee(); + } + + public Employee create(String name) { + return new Employee(); + } + + public Object delete(int id) { + return null; + } + + public Employee update(Employee employee) { + return null; + } +} From ea8a9124000dd5303e53b8973e6ac103889987dc Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 15:02:53 +0300 Subject: [PATCH 05/67] dto file for user creation --- .../application/employee/dto/create/CreateEmployeeDTO.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java diff --git a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java new file mode 100644 index 0000000..fa618a4 --- /dev/null +++ b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java @@ -0,0 +1,4 @@ +package org.lab.application.employee.dto.create; + +public record CreateEmployeeDTO() { +} From 17ee7e1b81a2e1385dca2af477aa181b3e4fd839 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 15:03:08 +0300 Subject: [PATCH 06/67] mod --- .../employee/dto/create/CreateEmployeeDTO.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java index fa618a4..107961e 100644 --- a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java @@ -1,4 +1,15 @@ package org.lab.application.employee.dto.create; -public record CreateEmployeeDTO() { +import java.util.Date; + +import org.lab.core.constants.employee.EmployeeType; + +public record CreateEmployeeDTO( + int id, + String name, + int age, + EmployeeType type, + int createdBy, + Date createdDate +) { } From e17071fd55ff65a3cb39bc5dd1907bc62dde4238 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 15:12:18 +0300 Subject: [PATCH 07/67] mapper --- .../create/EmployeeCreateAdapter.java | 4 ++++ .../dto/create/CreateEmployeeDTO.java | 1 + .../lab/core/utils/mapper/ObjectMapper.java | 21 +++++++++++++++++++ .../lab/domain/interfaces/DomainObject.java | 4 ++++ .../domain/interfaces/PresentationObject.java | 4 ++++ 5 files changed, 34 insertions(+) create mode 100644 src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java create mode 100644 src/main/java/org/lab/core/utils/mapper/ObjectMapper.java create mode 100644 src/main/java/org/lab/domain/interfaces/DomainObject.java create mode 100644 src/main/java/org/lab/domain/interfaces/PresentationObject.java diff --git a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java new file mode 100644 index 0000000..37201e2 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.employee.create; + +public class EmployeeCreateAdapter { +} diff --git a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java index 107961e..1d618f6 100644 --- a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java @@ -3,6 +3,7 @@ import java.util.Date; import org.lab.core.constants.employee.EmployeeType; +import org.lab.application.s public record CreateEmployeeDTO( int id, diff --git a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java new file mode 100644 index 0000000..b67f8ec --- /dev/null +++ b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java @@ -0,0 +1,21 @@ +package org.lab.core.utils.mapper; + +import org.lab.domain.interfaces.DomainObject; +import org.lab.domain.interfaces.PresentationObject; + + +public class ObjectMapper { + public Object mapToDomain( + PresentationObject presentationObject, + DomainObject domainObject + ) { + return null; + } + + public Object mapToPresentation( + PresentationObject presentationObject, + DomainObject domainObject + ) { + return null; + } +} diff --git a/src/main/java/org/lab/domain/interfaces/DomainObject.java b/src/main/java/org/lab/domain/interfaces/DomainObject.java new file mode 100644 index 0000000..36fc69a --- /dev/null +++ b/src/main/java/org/lab/domain/interfaces/DomainObject.java @@ -0,0 +1,4 @@ +package org.lab.domain.interfaces; + +public interface DomainObject { +} diff --git a/src/main/java/org/lab/domain/interfaces/PresentationObject.java b/src/main/java/org/lab/domain/interfaces/PresentationObject.java new file mode 100644 index 0000000..269e57e --- /dev/null +++ b/src/main/java/org/lab/domain/interfaces/PresentationObject.java @@ -0,0 +1,4 @@ +package org.lab.domain.interfaces; + +public interface PresentationObject { +} From f9d9636682a13c472d439f28748149f7b16d2e73 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 15:13:14 +0300 Subject: [PATCH 08/67] impl to Employee domain model --- src/main/java/org/lab/domain/emploee/model/Employee.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lab/domain/emploee/model/Employee.java b/src/main/java/org/lab/domain/emploee/model/Employee.java index 2e7eef6..8f3e85d 100644 --- a/src/main/java/org/lab/domain/emploee/model/Employee.java +++ b/src/main/java/org/lab/domain/emploee/model/Employee.java @@ -5,9 +5,10 @@ import lombok.Data; import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.interfaces.DomainObject; @Data -public class Employee { +public class Employee implements DomainObject { private int id; private String name; private int age; From a229d63221ff919a907af44156996e9cf957a577 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 15:14:54 +0300 Subject: [PATCH 09/67] impl on dto --- .../application/employee/dto/create/CreateEmployeeDTO.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java index 1d618f6..38f8b8c 100644 --- a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java @@ -3,7 +3,7 @@ import java.util.Date; import org.lab.core.constants.employee.EmployeeType; -import org.lab.application.s +import org.lab.domain.interfaces.PresentationObject; public record CreateEmployeeDTO( int id, @@ -12,5 +12,5 @@ public record CreateEmployeeDTO( EmployeeType type, int createdBy, Date createdDate -) { +) implements PresentationObject { } From ffd062aa38d0fb1d8032ef6ef284f7607aa50f85 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 15:18:23 +0300 Subject: [PATCH 10/67] fixed dto --- .../employee/create/EmployeeCreateAdapter.java | 2 ++ .../employee/dto/create/CreateEmployeeDTO.java | 7 +------ .../employee/dto/get/GetEmployeeDTO.java | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/lab/application/employee/dto/get/GetEmployeeDTO.java diff --git a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java index 37201e2..49b82b4 100644 --- a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java @@ -1,4 +1,6 @@ package org.lab.api.adapters.employee.create; public class EmployeeCreateAdapter { + + public } diff --git a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java index 38f8b8c..337975a 100644 --- a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java @@ -1,16 +1,11 @@ package org.lab.application.employee.dto.create; -import java.util.Date; - import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.interfaces.PresentationObject; public record CreateEmployeeDTO( - int id, String name, int age, - EmployeeType type, - int createdBy, - Date createdDate + EmployeeType type ) implements PresentationObject { } diff --git a/src/main/java/org/lab/application/employee/dto/get/GetEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/get/GetEmployeeDTO.java new file mode 100644 index 0000000..7840ff5 --- /dev/null +++ b/src/main/java/org/lab/application/employee/dto/get/GetEmployeeDTO.java @@ -0,0 +1,16 @@ +package org.lab.application.employee.dto.get; + +import java.util.Date; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.interfaces.PresentationObject; + +public record GetEmployeeDTO( + int id, + String name, + int age, + EmployeeType type, + int createdBy, + Date createdDate +) implements PresentationObject { +} From 95337854d7751ba4d1629084d07a5c0f505456b8 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 15:29:09 +0300 Subject: [PATCH 11/67] adapter --- .../create/EmployeeCreateAdapter.java | 27 ++++++++++++++++++- .../lab/core/utils/mapper/ObjectMapper.java | 11 ++++---- .../exceptions/NotPermittedException.java | 7 +++++ 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/lab/domain/shared/exceptions/NotPermittedException.java diff --git a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java index 49b82b4..4140b23 100644 --- a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java @@ -1,6 +1,31 @@ package org.lab.api.adapters.employee.create; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.application.employee.dto.get.GetEmployeeDTO; +import org.lab.application.employee.dto.create.CreateEmployeeDTO; +import org.lab.domain.emploee.model.Employee; +import org.lab.application.employee.use_cases.create.CreateEmployeeUseCase; +import org.lab.domain.shared.exceptions.NotPermittedException; + public class EmployeeCreateAdapter { - public + private final ObjectMapper mapper = new ObjectMapper(); + private final CreateEmployeeUseCase useCase = new CreateEmployeeUseCase(); + + public GetEmployeeDTO getEmployee( + CreateEmployeeDTO createEmployeeDTO + ) { + try { + Employee employee = mapper.mapToDomain(createEmployeeDTO, Employee.class); + Employee createdEmployee = useCase.execute(employee); + GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( + createdEmployee, + GetEmployeeDTO.class + ); + return presentationEmployee; + } catch (NotPermittedException e) { + System.err.println(e.getMessage()); + return null; + } + } } diff --git a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java index b67f8ec..a427562 100644 --- a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java +++ b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java @@ -5,16 +5,17 @@ public class ObjectMapper { - public Object mapToDomain( + + public T mapToDomain( PresentationObject presentationObject, - DomainObject domainObject + Class domainClass ) { return null; } - public Object mapToPresentation( - PresentationObject presentationObject, - DomainObject domainObject + public T mapToPresentation( + DomainObject domainObject, + Class presentationObjectClass ) { return null; } diff --git a/src/main/java/org/lab/domain/shared/exceptions/NotPermittedException.java b/src/main/java/org/lab/domain/shared/exceptions/NotPermittedException.java new file mode 100644 index 0000000..745e299 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/NotPermittedException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class NotPermittedException extends RuntimeException { + public NotPermittedException(String message) { + super(message); + } +} From dd3bf5937d6d2a9b9e5cd6bef0d55d27ae118499 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 16:00:02 +0300 Subject: [PATCH 12/67] created use case for user creation, and validation --- .../create/EmployeeCreateAdapter.java | 11 ++++- .../dto/create/CreateEmployeeDTO.java | 3 +- .../employee/services/CreateValidator.java | 41 +++++++++++++++++++ .../create/CreateEmployeeUseCase.java | 15 ++++++- .../UserAlreadyExistsException.java | 7 ++++ .../lab/infra/db/client/DatabaseClient.java | 2 +- .../employee/EmployeeRepository.java | 2 +- 7 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/lab/application/employee/services/CreateValidator.java create mode 100644 src/main/java/org/lab/domain/shared/exceptions/UserAlreadyExistsException.java diff --git a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java index 4140b23..a2dfb82 100644 --- a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java @@ -6,6 +6,7 @@ import org.lab.domain.emploee.model.Employee; import org.lab.application.employee.use_cases.create.CreateEmployeeUseCase; import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.UserAlreadyExistsException; public class EmployeeCreateAdapter { @@ -17,15 +18,23 @@ public GetEmployeeDTO getEmployee( ) { try { Employee employee = mapper.mapToDomain(createEmployeeDTO, Employee.class); - Employee createdEmployee = useCase.execute(employee); + Employee createdEmployee = useCase.execute( + employee, + createEmployeeDTO.creatorId() + ); GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( createdEmployee, GetEmployeeDTO.class ); return presentationEmployee; + } catch (NotPermittedException e) { System.err.println(e.getMessage()); return null; + + } catch (UserAlreadyExistsException e) { + System.err.println("user already exists!!!"); + return null; } } } diff --git a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java index 337975a..3ffa8a7 100644 --- a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java @@ -6,6 +6,7 @@ public record CreateEmployeeDTO( String name, int age, - EmployeeType type + EmployeeType type, + int creatorId ) implements PresentationObject { } diff --git a/src/main/java/org/lab/application/employee/services/CreateValidator.java b/src/main/java/org/lab/application/employee/services/CreateValidator.java new file mode 100644 index 0000000..7590421 --- /dev/null +++ b/src/main/java/org/lab/application/employee/services/CreateValidator.java @@ -0,0 +1,41 @@ +package org.lab.application.employee.services; + +import java.util.concurrent.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.UserAlreadyExistsException; +import org.lab.core.constants.employee.EmployeeType; + +public class CreateValidator { + + private EmployeeRepository employeeRepository = new EmployeeRepository(); + + public void validate( + Employee employee, + int creatorId + ) + throws NotPermittedException, + UserAlreadyExistsException + { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ + scope.fork(() -> { + Employee creatorEmployee = employeeRepository.getById(creatorId); + if (creatorEmployee.getType() != EmployeeType.MANAGER) { + throw new NotPermittedException("Only manager can add employees"); + } + return creatorEmployee; + }); + scope.fork(() -> { + Employee employeeCreated = employeeRepository.getById(employee.getId()); + if (employeeCreated != null) { + throw new UserAlreadyExistsException("Employee already exists"); + } + }); + + scope.join(); + scope.throwIfFailed(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java index 56420f6..b705465 100644 --- a/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java @@ -1,9 +1,20 @@ package org.lab.application.employee.use_cases.create; import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.application.employee.services.CreateValidator; public class CreateEmployeeUseCase { - public Employee execute(Employee employee){ - return employee; + + private final EmployeeRepository employeeRepository = new EmployeeRepository(); + private final CreateValidator createValidator = new CreateValidator(); + + public Employee execute( + Employee employee, + int creatorId + ) { + createValidator.validate(employee, creatorId); + Employee createdEmployee = employeeRepository.create(employee); + return createdEmployee; } } diff --git a/src/main/java/org/lab/domain/shared/exceptions/UserAlreadyExistsException.java b/src/main/java/org/lab/domain/shared/exceptions/UserAlreadyExistsException.java new file mode 100644 index 0000000..1a03883 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/UserAlreadyExistsException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class UserAlreadyExistsException extends RuntimeException { + public UserAlreadyExistsException(String message) { + super(message); + } +} diff --git a/src/main/java/org/lab/infra/db/client/DatabaseClient.java b/src/main/java/org/lab/infra/db/client/DatabaseClient.java index 933946c..b5a620c 100644 --- a/src/main/java/org/lab/infra/db/client/DatabaseClient.java +++ b/src/main/java/org/lab/infra/db/client/DatabaseClient.java @@ -14,7 +14,7 @@ public class DatabaseClient { config.setJdbcUrl("jdbc:postgresql://postgres:5432/mydb"); config.setUsername("postgres"); config.setPassword("postgres"); - config.setMaximumPoolSize(10); // размер пула + config.setMaximumPoolSize(10); dataSource = new HikariDataSource(config); } diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 73116af..5223a3f 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -14,7 +14,7 @@ public Employee getById(int id) { return new Employee(); } - public Employee create(String name) { + public Employee create(Employee employee) { return new Employee(); } From 511b7144c059e9911b2cd06430941554515fe081 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 16:36:09 +0300 Subject: [PATCH 13/67] added exception handling --- .../create/EmployeeCreateAdapter.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java index a2dfb82..2a8c21f 100644 --- a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java @@ -1,5 +1,6 @@ package org.lab.api.adapters.employee.create; +import io.javalin.http.Context; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.application.employee.dto.get.GetEmployeeDTO; import org.lab.application.employee.dto.create.CreateEmployeeDTO; @@ -8,33 +9,36 @@ import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.domain.shared.exceptions.UserAlreadyExistsException; +import java.util.Map; + public class EmployeeCreateAdapter { private final ObjectMapper mapper = new ObjectMapper(); private final CreateEmployeeUseCase useCase = new CreateEmployeeUseCase(); - public GetEmployeeDTO getEmployee( - CreateEmployeeDTO createEmployeeDTO + public Context getEmployee( + Context ctx ) { try { - Employee employee = mapper.mapToDomain(createEmployeeDTO, Employee.class); + CreateEmployeeDTO dto = ctx.bodyAsClass(CreateEmployeeDTO.class); + Employee employee = mapper.mapToDomain(dto, Employee.class); Employee createdEmployee = useCase.execute( employee, - createEmployeeDTO.creatorId() + dto.creatorId() ); GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( createdEmployee, GetEmployeeDTO.class ); - return presentationEmployee; - - } catch (NotPermittedException e) { - System.err.println(e.getMessage()); - return null; + return ctx.status(201).json(presentationEmployee); } catch (UserAlreadyExistsException e) { - System.err.println("user already exists!!!"); - return null; + return ctx.status(409).json(Map.of("error", "User already exists")); + + } catch (NotPermittedException e) { + return ctx.status(403).json(Map.of("error", e.getMessage())); + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); } } } From c46d1403ceebf82a61fa260fd2891e44c26b752e Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 16:40:50 +0300 Subject: [PATCH 14/67] employee create route done --- src/main/java/org/lab/Main.java | 4 ++++ .../api/adapters/employee/create/EmployeeCreateAdapter.java | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 8ffb4e5..e64cec6 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -1,7 +1,11 @@ import io.javalin.Javalin; +import org.lab.api.adapters.employee.create.EmployeeCreateAdapter; void main() { var app = Javalin.create(/*config*/) .get("/", ctx -> ctx.result("Hello World")) .start(7070); + + EmployeeCreateAdapter adapter = new EmployeeCreateAdapter(); + app.post("/employee", adapter::createEmployee); } \ No newline at end of file diff --git a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java index 2a8c21f..9b9445e 100644 --- a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java @@ -16,7 +16,7 @@ public class EmployeeCreateAdapter { private final ObjectMapper mapper = new ObjectMapper(); private final CreateEmployeeUseCase useCase = new CreateEmployeeUseCase(); - public Context getEmployee( + public Context createEmployee( Context ctx ) { try { @@ -37,6 +37,7 @@ public Context getEmployee( } catch (NotPermittedException e) { return ctx.status(403).json(Map.of("error", e.getMessage())); + } catch (Exception e) { return ctx.status(500).json(Map.of("error", "Internal server error")); } From a3a53a95543e374e30b009d7f594d6b1040a31ec Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 17:09:21 +0300 Subject: [PATCH 15/67] dg to 21, fixed config --- build.gradle.kts | 34 ++++++++++++++++--- src/main/java/org/lab/Main.java | 27 +++++++++++---- .../employee/services/CreateValidator.java | 9 +++-- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index feeff77..5b36539 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,16 @@ plugins { id("java") + id("application") +} + +application { + mainClass.set("org.lab.Main") +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } } group = "org.lab" @@ -15,16 +26,29 @@ dependencies { implementation("org.postgresql:postgresql:42.7.8") implementation("com.zaxxer:HikariCP:5.0.1") - compileOnly("org.projectlombok:lombok:1.18.32") - annotationProcessor("org.projectlombok:lombok:1.18.32") + compileOnly("org.projectlombok:lombok:1.18.34") + annotationProcessor("org.projectlombok:lombok:1.18.34") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - testCompileOnly("org.projectlombok:lombok:1.18.32") - testAnnotationProcessor("org.projectlombok:lombok:1.18.32") + testCompileOnly("org.projectlombok:lombok:1.18.34") + testAnnotationProcessor("org.projectlombok:lombok:1.18.34") +} + +tasks.withType { + options.compilerArgs.add("--enable-preview") +} + +tasks.withType { + jvmArgs("--enable-preview") +} + +tasks.withType { + jvmArgs("--enable-preview") } tasks.test { useJUnitPlatform() -} \ No newline at end of file +} + diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index e64cec6..3af9522 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -1,11 +1,24 @@ +package org.lab; + import io.javalin.Javalin; import org.lab.api.adapters.employee.create.EmployeeCreateAdapter; -void main() { - var app = Javalin.create(/*config*/) - .get("/", ctx -> ctx.result("Hello World")) - .start(7070); +public class Main { + + public static void main(String[] args) { + Javalin app = Javalin.create(config -> { + }).start(7070); + + // Создаём адаптер + EmployeeCreateAdapter adapter = new EmployeeCreateAdapter(); + + // Регистрируем POST-эндпоинт + app.post("/employee", ctx -> { + // Передаём ctx в метод адаптера + adapter.createEmployee(ctx); + }); - EmployeeCreateAdapter adapter = new EmployeeCreateAdapter(); - app.post("/employee", adapter::createEmployee); -} \ No newline at end of file + // Пример GET для проверки + app.get("/", ctx -> ctx.result("Hello World")); + } +} diff --git a/src/main/java/org/lab/application/employee/services/CreateValidator.java b/src/main/java/org/lab/application/employee/services/CreateValidator.java index 7590421..f7e1028 100644 --- a/src/main/java/org/lab/application/employee/services/CreateValidator.java +++ b/src/main/java/org/lab/application/employee/services/CreateValidator.java @@ -19,23 +19,28 @@ public void validate( throws NotPermittedException, UserAlreadyExistsException { - try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ + try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ scope.fork(() -> { Employee creatorEmployee = employeeRepository.getById(creatorId); if (creatorEmployee.getType() != EmployeeType.MANAGER) { throw new NotPermittedException("Only manager can add employees"); } - return creatorEmployee; + return null; }); scope.fork(() -> { Employee employeeCreated = employeeRepository.getById(employee.getId()); if (employeeCreated != null) { throw new UserAlreadyExistsException("Employee already exists"); } + return null; }); scope.join(); scope.throwIfFailed(); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); } } } \ No newline at end of file From 4ffdd6bfedbaa0a5c89b92b3370b3fa21508a561 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 21:06:56 +0300 Subject: [PATCH 16/67] added docker --- .dockerignore | 5 +++++ Dockerfile | 17 +++++++++++++++++ build.gradle.kts | 3 +++ docker-compose.yml | 25 +++++++++++++++++++++++++ src/main/java/org/lab/Main.java | 14 ++------------ 5 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cb89368 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.gradle +build +out +*.iml +.idea diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ca56a5e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM eclipse-temurin:21-jdk AS build + +WORKDIR /app + +COPY . . + +RUN ./gradlew clean build -x test + +FROM eclipse-temurin:21-jre + +WORKDIR /app + +COPY --from=build /app/build/libs/*.jar app.jar + +EXPOSE 8080 + +CMD ["java", "-jar", "app.jar"] diff --git a/build.gradle.kts b/build.gradle.kts index 5b36539..1640870 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,6 +25,9 @@ dependencies { implementation("ch.qos.logback:logback-classic:1.4.11") implementation("org.postgresql:postgresql:42.7.8") implementation("com.zaxxer:HikariCP:5.0.1") + implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2") + implementation("com.fasterxml.jackson.core:jackson-core:2.17.2") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.17.2") compileOnly("org.projectlombok:lombok:1.18.34") annotationProcessor("org.projectlombok:lombok:1.18.34") diff --git a/docker-compose.yml b/docker-compose.yml index e69de29..c6d85c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +services: + postgres: + image: postgres:16 + container_name: app-postgres + restart: always + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mydb + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + + app: + build: . + container_name: java-app + depends_on: + - postgres + ports: + - "7070:7070" + restart: always + +volumes: + pgdata: \ No newline at end of file diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 3af9522..e1b3a30 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -6,19 +6,9 @@ public class Main { public static void main(String[] args) { - Javalin app = Javalin.create(config -> { - }).start(7070); - - // Создаём адаптер + Javalin app = Javalin.create(config -> {}).start(7070); EmployeeCreateAdapter adapter = new EmployeeCreateAdapter(); - - // Регистрируем POST-эндпоинт - app.post("/employee", ctx -> { - // Передаём ctx в метод адаптера - adapter.createEmployee(ctx); - }); - - // Пример GET для проверки app.get("/", ctx -> ctx.result("Hello World")); + app.post("/employee", adapter::createEmployee); } } From 011fcccf76cb793c05f64fa301e7a8736f26f533 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 21:21:53 +0300 Subject: [PATCH 17/67] updated gradle and dockerfile --- Dockerfile | 14 ++++++++++---- build.gradle.kts | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index ca56a5e..5eec42b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,16 +2,22 @@ FROM eclipse-temurin:21-jdk AS build WORKDIR /app -COPY . . +COPY build.gradle.kts settings.gradle.kts gradlew ./ +COPY gradle ./gradle -RUN ./gradlew clean build -x test +RUN chmod +x gradlew + +RUN ./gradlew build -x test --dry-run + +COPY src ./src +RUN ./gradlew clean fatJar -x test FROM eclipse-temurin:21-jre WORKDIR /app -COPY --from=build /app/build/libs/*.jar app.jar +COPY --from=build /app/build/libs/*-all.jar app.jar EXPOSE 8080 -CMD ["java", "-jar", "app.jar"] +CMD ["java", "--enable-preview", "-jar", "app.jar"] \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 1640870..662d6b8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,6 +39,20 @@ dependencies { testAnnotationProcessor("org.projectlombok:lombok:1.18.34") } +tasks.register("fatJar") { + archiveClassifier.set("all") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { + attributes["Main-Class"] = "org.lab.Main" + } + from(sourceSets.main.get().output) + + dependsOn(configurations.runtimeClasspath) + from({ + configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) } + }) +} + tasks.withType { options.compilerArgs.add("--enable-preview") } From a19dc229900710e34187c40ac7b0971d45258796 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 21:47:38 +0300 Subject: [PATCH 18/67] added object mapper implementation, added create method impl for employee repository --- .../lab/core/utils/mapper/ObjectMapper.java | 7 +++-- .../employee/EmployeeRepository.java | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java index a427562..52a7015 100644 --- a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java +++ b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java @@ -6,17 +6,20 @@ public class ObjectMapper { + private final com.fasterxml.jackson.databind.ObjectMapper mapper = + new com.fasterxml.jackson.databind.ObjectMapper(); + public T mapToDomain( PresentationObject presentationObject, Class domainClass ) { - return null; + return mapper.convertValue(presentationObject, domainClass); } public T mapToPresentation( DomainObject domainObject, Class presentationObjectClass ) { - return null; + return mapper.convertValue(domainObject, presentationObjectClass); } } diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 5223a3f..4b3088a 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -3,6 +3,8 @@ import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.client.DatabaseClient; +import java.sql.*; + public class EmployeeRepository { private DatabaseClient databaseClient; @@ -14,8 +16,29 @@ public Employee getById(int id) { return new Employee(); } - public Employee create(Employee employee) { - return new Employee(); + public Employee create(Employee employee) throws SQLException { + String sql = """ + INSERT INTO employees (name, age, type, createdBy, createdDate) + VALUES (?, ?, ?, ?, ?) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setString(1, employee.getName()); + stmt.setInt(2, employee.getAge()); + stmt.setString(3, employee.getType().name()); + stmt.setInt(4, employee.getCreatedBy()); + stmt.setTimestamp(5, new Timestamp(employee.getCreatedDate().getTime())); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + System.out.println("mapping should be here"); + } + } + } + return employee; } public Object delete(int id) { From b844331a51d68c91efd1a0cec77dc85369b0689b Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 21:55:03 +0300 Subject: [PATCH 19/67] delete method impl --- .../repository/employee/EmployeeRepository.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 4b3088a..614d726 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -16,9 +16,9 @@ public Employee getById(int id) { return new Employee(); } - public Employee create(Employee employee) throws SQLException { + public Employee create(Employee employee) { String sql = """ - INSERT INTO employees (name, age, type, createdBy, createdDate) + INSERT INTO employees (name, age, type, created_by, create_date) VALUES (?, ?, ?, ?, ?) RETURNING * """; @@ -37,11 +37,23 @@ INSERT INTO employees (name, age, type, createdBy, createdDate) System.out.println("mapping should be here"); } } + } catch (SQLException e) { + System.err.println(e.getMessage()); } return employee; } public Object delete(int id) { + String sql = "DELETE FROM employees WHERE id = ?"; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, id); + } catch (SQLException e) { + System.err.println(e.getMessage()); + } return null; } From 45e8d54f34c57bc19cffe09dc08793fbf71f433c Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 22:15:22 +0300 Subject: [PATCH 20/67] added employee deletion --- src/main/java/org/lab/Main.java | 9 ++++-- .../delete/EmployeeDeleteAdapter.java | 30 +++++++++++++++++++ .../dto/delete/DeleteEmployeeDTO.java | 9 ++++++ .../employee/services/CreateValidator.java | 9 ++---- .../services/EmployeePermissionValidator.java | 18 +++++++++++ .../delete/DeleteEmployeeUseCase.java | 18 +++++++++++ .../employee/EmployeeRepository.java | 4 ++- 7 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/lab/api/adapters/employee/delete/EmployeeDeleteAdapter.java create mode 100644 src/main/java/org/lab/application/employee/dto/delete/DeleteEmployeeDTO.java create mode 100644 src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java create mode 100644 src/main/java/org/lab/application/employee/use_cases/delete/DeleteEmployeeUseCase.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index e1b3a30..d132e90 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -2,13 +2,18 @@ import io.javalin.Javalin; import org.lab.api.adapters.employee.create.EmployeeCreateAdapter; +import org.lab.api.adapters.employee.delete.EmployeeDeleteAdapter; public class Main { public static void main(String[] args) { Javalin app = Javalin.create(config -> {}).start(7070); - EmployeeCreateAdapter adapter = new EmployeeCreateAdapter(); + EmployeeCreateAdapter createEmployeeAdapter = new EmployeeCreateAdapter(); + EmployeeDeleteAdapter deleteEmployeeAdapter = new EmployeeDeleteAdapter(); + app.get("/", ctx -> ctx.result("Hello World")); - app.post("/employee", adapter::createEmployee); + + app.post("/employee", createEmployeeAdapter::createEmployee); + app.delete("/employee", deleteEmployeeAdapter::deleteEmployee); } } diff --git a/src/main/java/org/lab/api/adapters/employee/delete/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/delete/EmployeeDeleteAdapter.java new file mode 100644 index 0000000..722d01d --- /dev/null +++ b/src/main/java/org/lab/api/adapters/employee/delete/EmployeeDeleteAdapter.java @@ -0,0 +1,30 @@ +package org.lab.api.adapters.employee.delete; + +import io.javalin.http.Context; +import org.lab.application.employee.dto.delete.DeleteEmployeeDTO; +import org.lab.domain.emploee.model.Employee; +import org.lab.application.employee.use_cases.delete.DeleteEmployeeUseCase; +import org.lab.domain.shared.exceptions.NotPermittedException; + +import java.util.Map; + +public class EmployeeDeleteAdapter { + + private final DeleteEmployeeUseCase useCase = new DeleteEmployeeUseCase(); + + public Context deleteEmployee( + Context ctx + ) { + try { + DeleteEmployeeDTO dto = ctx.bodyAsClass(DeleteEmployeeDTO.class); + useCase.execute(dto.deletedEmployeeId(), dto.employeeId()); + return ctx.status(201); + + } catch (NotPermittedException e) { + return ctx.status(403).json(Map.of("error", e.getMessage())); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/application/employee/dto/delete/DeleteEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/delete/DeleteEmployeeDTO.java new file mode 100644 index 0000000..64aad07 --- /dev/null +++ b/src/main/java/org/lab/application/employee/dto/delete/DeleteEmployeeDTO.java @@ -0,0 +1,9 @@ +package org.lab.application.employee.dto.delete; + +import org.lab.domain.interfaces.PresentationObject; + +public record DeleteEmployeeDTO( + int employeeId, + int deletedEmployeeId +) implements PresentationObject { +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/employee/services/CreateValidator.java b/src/main/java/org/lab/application/employee/services/CreateValidator.java index f7e1028..c149773 100644 --- a/src/main/java/org/lab/application/employee/services/CreateValidator.java +++ b/src/main/java/org/lab/application/employee/services/CreateValidator.java @@ -6,11 +6,11 @@ import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.domain.shared.exceptions.UserAlreadyExistsException; -import org.lab.core.constants.employee.EmployeeType; public class CreateValidator { - private EmployeeRepository employeeRepository = new EmployeeRepository(); + private final EmployeePermissionValidator validator = new EmployeePermissionValidator(); + private final EmployeeRepository employeeRepository = new EmployeeRepository(); public void validate( Employee employee, @@ -21,10 +21,7 @@ public void validate( { try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ scope.fork(() -> { - Employee creatorEmployee = employeeRepository.getById(creatorId); - if (creatorEmployee.getType() != EmployeeType.MANAGER) { - throw new NotPermittedException("Only manager can add employees"); - } + validator.validate(creatorId); return null; }); scope.fork(() -> { diff --git a/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java b/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java new file mode 100644 index 0000000..c5d5be4 --- /dev/null +++ b/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java @@ -0,0 +1,18 @@ +package org.lab.application.employee.services; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class EmployeePermissionValidator { + + private final EmployeeRepository employeeRepository = new EmployeeRepository(); + + public void validate(int employeeId) { + Employee creatorEmployee = employeeRepository.getById(employeeId); + if (creatorEmployee.getType() != EmployeeType.MANAGER) { + throw new NotPermittedException("Only manager can work with employees"); + } + } +} diff --git a/src/main/java/org/lab/application/employee/use_cases/delete/DeleteEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/delete/DeleteEmployeeUseCase.java new file mode 100644 index 0000000..a773200 --- /dev/null +++ b/src/main/java/org/lab/application/employee/use_cases/delete/DeleteEmployeeUseCase.java @@ -0,0 +1,18 @@ +package org.lab.application.employee.use_cases.delete; + +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.application.employee.services.EmployeePermissionValidator; + +public class DeleteEmployeeUseCase { + + private final EmployeeRepository employeeRepository = new EmployeeRepository(); + private final EmployeePermissionValidator validator = new EmployeePermissionValidator(); + + public void execute( + int deleteEmployeeId, + int employeeId + ) { + validator.validate(employeeId); + employeeRepository.delete(deleteEmployeeId); + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 614d726..604942c 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -31,7 +31,9 @@ INSERT INTO employees (name, age, type, created_by, create_date) stmt.setInt(2, employee.getAge()); stmt.setString(3, employee.getType().name()); stmt.setInt(4, employee.getCreatedBy()); - stmt.setTimestamp(5, new Timestamp(employee.getCreatedDate().getTime())); + stmt.setTimestamp(5, new Timestamp( + employee.getCreatedDate().getTime()) + ); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { System.out.println("mapping should be here"); From 7dc7cffac29f34d2195325187ab725a07ad5e82c Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Mon, 1 Dec 2025 22:37:55 +0300 Subject: [PATCH 21/67] get employee use_case --- src/main/java/org/lab/Main.java | 7 +++- .../{create => }/EmployeeCreateAdapter.java | 12 +++--- .../{delete => }/EmployeeDeleteAdapter.java | 7 ++-- .../adapters/employee/EmployeeGetAdapter.java | 40 +++++++++++++++++++ .../dto/{create => }/CreateEmployeeDTO.java | 2 +- .../dto/{delete => }/DeleteEmployeeDTO.java | 2 +- .../dto/{get => }/GetEmployeeDTO.java | 10 ++++- .../{create => }/CreateEmployeeUseCase.java | 2 +- .../{delete => }/DeleteEmployeeUseCase.java | 2 +- .../use_cases/GetEmployeeUseCase.java | 22 ++++++++++ 10 files changed, 88 insertions(+), 18 deletions(-) rename src/main/java/org/lab/api/adapters/employee/{create => }/EmployeeCreateAdapter.java (78%) rename src/main/java/org/lab/api/adapters/employee/{delete => }/EmployeeDeleteAdapter.java (76%) create mode 100644 src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java rename src/main/java/org/lab/application/employee/dto/{create => }/CreateEmployeeDTO.java (84%) rename src/main/java/org/lab/application/employee/dto/{delete => }/DeleteEmployeeDTO.java (78%) rename src/main/java/org/lab/application/employee/dto/{get => }/GetEmployeeDTO.java (61%) rename src/main/java/org/lab/application/employee/use_cases/{create => }/CreateEmployeeUseCase.java (92%) rename src/main/java/org/lab/application/employee/use_cases/{delete => }/DeleteEmployeeUseCase.java (90%) create mode 100644 src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index d132e90..bd4a2e3 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -1,8 +1,9 @@ package org.lab; import io.javalin.Javalin; -import org.lab.api.adapters.employee.create.EmployeeCreateAdapter; -import org.lab.api.adapters.employee.delete.EmployeeDeleteAdapter; +import org.lab.api.adapters.employee.EmployeeCreateAdapter; +import org.lab.api.adapters.employee.EmployeeDeleteAdapter; +import org.lab.api.adapters.employee.EmployeeGetAdapter; public class Main { @@ -10,10 +11,12 @@ public static void main(String[] args) { Javalin app = Javalin.create(config -> {}).start(7070); EmployeeCreateAdapter createEmployeeAdapter = new EmployeeCreateAdapter(); EmployeeDeleteAdapter deleteEmployeeAdapter = new EmployeeDeleteAdapter(); + EmployeeGetAdapter getEmployeeAdapter = new EmployeeGetAdapter(); app.get("/", ctx -> ctx.result("Hello World")); app.post("/employee", createEmployeeAdapter::createEmployee); app.delete("/employee", deleteEmployeeAdapter::deleteEmployee); + app.get("/employee/:id", getEmployeeAdapter::getEmployee); } } diff --git a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java similarity index 78% rename from src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java rename to src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java index 9b9445e..a0e627b 100644 --- a/src/main/java/org/lab/api/adapters/employee/create/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -1,11 +1,11 @@ -package org.lab.api.adapters.employee.create; +package org.lab.api.adapters.employee; import io.javalin.http.Context; import org.lab.core.utils.mapper.ObjectMapper; -import org.lab.application.employee.dto.get.GetEmployeeDTO; -import org.lab.application.employee.dto.create.CreateEmployeeDTO; +import org.lab.application.employee.dto.GetEmployeeDTOOut; +import org.lab.application.employee.dto.CreateEmployeeDTO; import org.lab.domain.emploee.model.Employee; -import org.lab.application.employee.use_cases.create.CreateEmployeeUseCase; +import org.lab.application.employee.use_cases.CreateEmployeeUseCase; import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.domain.shared.exceptions.UserAlreadyExistsException; @@ -26,9 +26,9 @@ public Context createEmployee( employee, dto.creatorId() ); - GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( + GetEmployeeDTOOut presentationEmployee = mapper.mapToPresentation( createdEmployee, - GetEmployeeDTO.class + GetEmployeeDTOOut.class ); return ctx.status(201).json(presentationEmployee); diff --git a/src/main/java/org/lab/api/adapters/employee/delete/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java similarity index 76% rename from src/main/java/org/lab/api/adapters/employee/delete/EmployeeDeleteAdapter.java rename to src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java index 722d01d..de8b596 100644 --- a/src/main/java/org/lab/api/adapters/employee/delete/EmployeeDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -1,9 +1,8 @@ -package org.lab.api.adapters.employee.delete; +package org.lab.api.adapters.employee; import io.javalin.http.Context; -import org.lab.application.employee.dto.delete.DeleteEmployeeDTO; -import org.lab.domain.emploee.model.Employee; -import org.lab.application.employee.use_cases.delete.DeleteEmployeeUseCase; +import org.lab.application.employee.dto.DeleteEmployeeDTO; +import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; import org.lab.domain.shared.exceptions.NotPermittedException; import java.util.Map; diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java new file mode 100644 index 0000000..d3378cb --- /dev/null +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -0,0 +1,40 @@ +package org.lab.api.adapters.employee; + +import io.javalin.http.Context; +import org.lab.application.employee.dto.GetEmployeeDTOIn; +import org.lab.application.employee.dto.GetEmployeeDTOOut; +import org.lab.application.employee.use_cases.GetEmployeeUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; + +import java.util.Map; + +public class EmployeeGetAdapter { + + private final GetEmployeeUseCase useCase = new GetEmployeeUseCase(); + private final ObjectMapper mapper = new ObjectMapper(); + + public Context getEmployee( + Context ctx + ) { + try { + GetEmployeeDTOIn dto = ctx.bodyAsClass(GetEmployeeDTOIn.class); + Employee receivedEmployee = useCase.execute( + dto.employeeId(), + dto.actorId() + ); + GetEmployeeDTOOut presentationEmployee = mapper.mapToPresentation( + receivedEmployee, + GetEmployeeDTOOut.class + ); + return ctx.status(201).json(presentationEmployee); + + } catch (NotPermittedException e) { + return ctx.status(403).json(Map.of("error", e.getMessage())); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java similarity index 84% rename from src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java rename to src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java index 3ffa8a7..1804c5b 100644 --- a/src/main/java/org/lab/application/employee/dto/create/CreateEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java @@ -1,4 +1,4 @@ -package org.lab.application.employee.dto.create; +package org.lab.application.employee.dto; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.interfaces.PresentationObject; diff --git a/src/main/java/org/lab/application/employee/dto/delete/DeleteEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/DeleteEmployeeDTO.java similarity index 78% rename from src/main/java/org/lab/application/employee/dto/delete/DeleteEmployeeDTO.java rename to src/main/java/org/lab/application/employee/dto/DeleteEmployeeDTO.java index 64aad07..d1b1e92 100644 --- a/src/main/java/org/lab/application/employee/dto/delete/DeleteEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/DeleteEmployeeDTO.java @@ -1,4 +1,4 @@ -package org.lab.application.employee.dto.delete; +package org.lab.application.employee.dto; import org.lab.domain.interfaces.PresentationObject; diff --git a/src/main/java/org/lab/application/employee/dto/get/GetEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java similarity index 61% rename from src/main/java/org/lab/application/employee/dto/get/GetEmployeeDTO.java rename to src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java index 7840ff5..9ebe04b 100644 --- a/src/main/java/org/lab/application/employee/dto/get/GetEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java @@ -1,11 +1,11 @@ -package org.lab.application.employee.dto.get; +package org.lab.application.employee.dto; import java.util.Date; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.interfaces.PresentationObject; -public record GetEmployeeDTO( +public record GetEmployeeDTOOut( int id, String name, int age, @@ -14,3 +14,9 @@ public record GetEmployeeDTO( Date createdDate ) implements PresentationObject { } + +public record GetEmployeeDTOIn( + int employeeId, + int actorId +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java similarity index 92% rename from src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java rename to src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java index b705465..89777c9 100644 --- a/src/main/java/org/lab/application/employee/use_cases/create/CreateEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java @@ -1,4 +1,4 @@ -package org.lab.application.employee.use_cases.create; +package org.lab.application.employee.use_cases; import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.repository.employee.EmployeeRepository; diff --git a/src/main/java/org/lab/application/employee/use_cases/delete/DeleteEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java similarity index 90% rename from src/main/java/org/lab/application/employee/use_cases/delete/DeleteEmployeeUseCase.java rename to src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java index a773200..c394a48 100644 --- a/src/main/java/org/lab/application/employee/use_cases/delete/DeleteEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java @@ -1,4 +1,4 @@ -package org.lab.application.employee.use_cases.delete; +package org.lab.application.employee.use_cases; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.application.employee.services.EmployeePermissionValidator; diff --git a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java new file mode 100644 index 0000000..dc70e3b --- /dev/null +++ b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java @@ -0,0 +1,22 @@ +package org.lab.application.employee.use_cases; + +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.application.employee.services.EmployeePermissionValidator; + +public class GetEmployeeUseCase { + private EmployeeRepository employeeRepository = + new EmployeeRepository(); + + private EmployeePermissionValidator employeePermissionValidator = + new EmployeePermissionValidator(); + + public Employee execute( + int employeeId, + int actorId + ) { + employeePermissionValidator.validate(actorId); + Employee employee = employeeRepository.getById(employeeId); + return employee; + } +} From 052cc2a5e7c0a2ec997cb37eb0bab7e4812351df Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Mon, 1 Dec 2025 22:39:33 +0300 Subject: [PATCH 22/67] fix dto in out --- .../lab/application/employee/dto/GetEmployeeDTOIn.java | 9 +++++++++ .../dto/{GetEmployeeDTO.java => GetEmployeeDTOOut.java} | 5 ----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java rename src/main/java/org/lab/application/employee/dto/{GetEmployeeDTO.java => GetEmployeeDTOOut.java} (76%) diff --git a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java new file mode 100644 index 0000000..3beca22 --- /dev/null +++ b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java @@ -0,0 +1,9 @@ +package org.lab.application.employee.dto; + +import org.lab.domain.interfaces.PresentationObject; + +public record GetEmployeeDTOIn( + int employeeId, + int actorId +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOOut.java similarity index 76% rename from src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java rename to src/main/java/org/lab/application/employee/dto/GetEmployeeDTOOut.java index 9ebe04b..6b0b4e4 100644 --- a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOOut.java @@ -15,8 +15,3 @@ public record GetEmployeeDTOOut( ) implements PresentationObject { } -public record GetEmployeeDTOIn( - int employeeId, - int actorId -) implements PresentationObject { -} From 8304257bcb1856a87bf8f248e3ad827296f45d77 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 2 Dec 2025 00:05:13 +0300 Subject: [PATCH 23/67] added getById implementation in the employee repository --- .dockerignore | 2 +- docker-compose.yml | 2 ++ src/main/java/org/lab/Main.java | 2 +- .../adapters/employee/EmployeeGetAdapter.java | 6 +++- .../services/EmployeePermissionValidator.java | 2 ++ .../lab/core/utils/mapper/ObjectMapper.java | 7 +++++ .../employee/EmployeeRepository.java | 31 +++++++++++++++++-- 7 files changed, 47 insertions(+), 5 deletions(-) diff --git a/.dockerignore b/.dockerignore index cb89368..cb8cea0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,4 @@ build out *.iml -.idea +.idea \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c6d85c9..6358243 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,8 @@ services: ports: - "7070:7070" restart: always + volumes: + - "./app:/var/www" volumes: pgdata: \ No newline at end of file diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index bd4a2e3..a08fc03 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -17,6 +17,6 @@ public static void main(String[] args) { app.post("/employee", createEmployeeAdapter::createEmployee); app.delete("/employee", deleteEmployeeAdapter::deleteEmployee); - app.get("/employee/:id", getEmployeeAdapter::getEmployee); + app.get("/employee", getEmployeeAdapter::getEmployee); } } diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java index d3378cb..4d059d1 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -19,7 +19,10 @@ public Context getEmployee( Context ctx ) { try { - GetEmployeeDTOIn dto = ctx.bodyAsClass(GetEmployeeDTOIn.class); + GetEmployeeDTOIn dto = new GetEmployeeDTOIn( + Integer.parseInt(ctx.queryParam("employeeId")), + Integer.parseInt(ctx.queryParam("actorId")) + ); Employee receivedEmployee = useCase.execute( dto.employeeId(), dto.actorId() @@ -34,6 +37,7 @@ public Context getEmployee( return ctx.status(403).json(Map.of("error", e.getMessage())); } catch (Exception e) { + System.err.println(e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java b/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java index c5d5be4..a4667fc 100644 --- a/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java +++ b/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java @@ -11,6 +11,8 @@ public class EmployeePermissionValidator { public void validate(int employeeId) { Employee creatorEmployee = employeeRepository.getById(employeeId); + System.out.println("hello"); + System.out.println(creatorEmployee); if (creatorEmployee.getType() != EmployeeType.MANAGER) { throw new NotPermittedException("Only manager can work with employees"); } diff --git a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java index 52a7015..1b89183 100644 --- a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java +++ b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java @@ -22,4 +22,11 @@ public T mapToPresentation( ) { return mapper.convertValue(domainObject, presentationObjectClass); } + + public T mapFromRaw( + Object rawObject, + Class domainClass + ) { + return mapper.convertValue(rawObject, domainClass); + } } diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 604942c..ffe6cdb 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -2,17 +2,45 @@ import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.client.DatabaseClient; +import org.lab.core.utils.mapper.ObjectMapper; import java.sql.*; +import java.util.HashMap; +import java.util.Map; public class EmployeeRepository { - private DatabaseClient databaseClient; + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; public EmployeeRepository() { databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); } public Employee getById(int id) { + String sql = "SELECT * FROM EMPLOYEES where id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, id); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + ResultSetMetaData metaData = rs.getMetaData(); + int columnCount = metaData.getColumnCount(); + Map raw = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnLabel(i); + Object value = rs.getObject(i); + raw.put(columnName, value); + } + System.out.println("value = " + raw); + return objectMapper.mapFromRaw(raw, Employee.class); + } + } + } catch (SQLException e) { + System.err.println(e.getMessage()); + } return new Employee(); } @@ -47,7 +75,6 @@ INSERT INTO employees (name, age, type, created_by, create_date) public Object delete(int id) { String sql = "DELETE FROM employees WHERE id = ?"; - try ( Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) From 6903f4b587277d570555024df2606b498d45f978 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 2 Dec 2025 20:38:24 +0300 Subject: [PATCH 24/67] sealed app objects --- src/main/java/org/lab/domain/interfaces/AppObject.java | 6 ++++++ src/main/java/org/lab/domain/interfaces/DomainObject.java | 3 ++- .../java/org/lab/domain/interfaces/PresentationObject.java | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/lab/domain/interfaces/AppObject.java diff --git a/src/main/java/org/lab/domain/interfaces/AppObject.java b/src/main/java/org/lab/domain/interfaces/AppObject.java new file mode 100644 index 0000000..18d2f83 --- /dev/null +++ b/src/main/java/org/lab/domain/interfaces/AppObject.java @@ -0,0 +1,6 @@ +package org.lab.domain.interfaces; + +sealed interface AppObject + permits DomainObject, + PresentationObject { +} diff --git a/src/main/java/org/lab/domain/interfaces/DomainObject.java b/src/main/java/org/lab/domain/interfaces/DomainObject.java index 36fc69a..ef1f806 100644 --- a/src/main/java/org/lab/domain/interfaces/DomainObject.java +++ b/src/main/java/org/lab/domain/interfaces/DomainObject.java @@ -1,4 +1,5 @@ package org.lab.domain.interfaces; -public interface DomainObject { +public non-sealed interface DomainObject + extends AppObject { } diff --git a/src/main/java/org/lab/domain/interfaces/PresentationObject.java b/src/main/java/org/lab/domain/interfaces/PresentationObject.java index 269e57e..f21ef1f 100644 --- a/src/main/java/org/lab/domain/interfaces/PresentationObject.java +++ b/src/main/java/org/lab/domain/interfaces/PresentationObject.java @@ -1,4 +1,5 @@ package org.lab.domain.interfaces; -public interface PresentationObject { +public non-sealed interface PresentationObject + extends AppObject { } From d002902d8be0f7c3da99b2a19c7631d8d4a89267 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 2 Dec 2025 20:50:59 +0300 Subject: [PATCH 25/67] direcotry for project domain --- .../project/ProjectCreateAdapter.java | 4 +++ .../project/ProjectDeleteAdapter.java | 4 +++ .../core/constants/project/ProjectStatus.java | 8 +++++ .../org/lab/domain/project/model/Project.java | 34 +++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java create mode 100644 src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java create mode 100644 src/main/java/org/lab/core/constants/project/ProjectStatus.java create mode 100644 src/main/java/org/lab/domain/project/model/Project.java diff --git a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java new file mode 100644 index 0000000..b797c07 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.project; + +public class ProjectCreateAdapter { +} diff --git a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java new file mode 100644 index 0000000..50b4728 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.project; + +public class ProjectDeleteAdapter { +} diff --git a/src/main/java/org/lab/core/constants/project/ProjectStatus.java b/src/main/java/org/lab/core/constants/project/ProjectStatus.java new file mode 100644 index 0000000..bb4a2df --- /dev/null +++ b/src/main/java/org/lab/core/constants/project/ProjectStatus.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.project; + +public enum ProjectStatus { + OPEN, + ACTIVE, + ON_HOLD, + DELTED +} diff --git a/src/main/java/org/lab/domain/project/model/Project.java b/src/main/java/org/lab/domain/project/model/Project.java new file mode 100644 index 0000000..abd1d47 --- /dev/null +++ b/src/main/java/org/lab/domain/project/model/Project.java @@ -0,0 +1,34 @@ +package org.lab.domain.project.model; + +import java.util.Date; +import java.util.List; + +import lombok.Data; + +import org.lab.domain.interfaces.DomainObject; +import org.lab.core.constants.project.ProjectStatus; + +@Data +public class Project implements DomainObject { + private int id; + private String name; + private String description; + + private int managerId; + private Integer teamLeadId; + + private List developerIds; + private List testerIds; + + private ProjectStatus status; + + private Integer currentMilestoneId; + private List milestoneIds; + + private List bugReportIds; + + private Date createdDate; + private int createdBy; + private Date updatedDate; + private Integer updatedBy; +} \ No newline at end of file From 5f94be614792b94df7af7da8d4c1e3c5c5a5335c Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 2 Dec 2025 21:02:27 +0300 Subject: [PATCH 26/67] directories in application layer --- .../application/project/use_cases/CreateProjectUseCase.java | 4 ++++ .../application/project/use_cases/DeleteProjectUseCase.java | 4 ++++ .../lab/application/project/use_cases/GetProjectUseCase.java | 4 ++++ .../lab/application/project/use_cases/ListProjectUseCase.java | 4 ++++ 4 files changed, 16 insertions(+) create mode 100644 src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java create mode 100644 src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java create mode 100644 src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java create mode 100644 src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java diff --git a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java new file mode 100644 index 0000000..6589b18 --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java @@ -0,0 +1,4 @@ +package org.lab.application.project.use_cases; + +public class CreateProjectUseCase { +} diff --git a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java new file mode 100644 index 0000000..40f4d1d --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java @@ -0,0 +1,4 @@ +package org.lab.application.project.use_cases; + +public class DeleteProjectUseCase { +} diff --git a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java new file mode 100644 index 0000000..d7e8b63 --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java @@ -0,0 +1,4 @@ +package org.lab.application.project.use_cases; + +public class GetProjectUseCase { +} diff --git a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java new file mode 100644 index 0000000..770d6b3 --- /dev/null +++ b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java @@ -0,0 +1,4 @@ +package org.lab.application.project.use_cases; + +public class ListProjectUseCase { +} From 3d827c8fbcd55ade593bb558ec9f88f8c046280e Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 2 Dec 2025 22:56:26 +0300 Subject: [PATCH 27/67] test + dep inj --- build.gradle.kts | 2 + src/main/java/org/lab/Main.java | 40 +++++- .../employee/EmployeeCreateAdapter.java | 12 +- .../employee/EmployeeDeleteAdapter.java | 8 +- .../adapters/employee/EmployeeGetAdapter.java | 12 +- .../employee/services/CreateValidator.java | 12 +- .../services/EmployeePermissionValidator.java | 10 +- .../use_cases/CreateEmployeeUseCase.java | 12 +- .../use_cases/DeleteEmployeeUseCase.java | 12 +- .../use_cases/GetEmployeeUseCase.java | 18 ++- .../exceptions/UserNotFoundException.java | 7 + .../repository/project/ProjectRepository.java | 15 ++ .../use_cases/TestCreateEmployeeUseCase.java | 135 ++++++++++++++++++ .../use_cases/TestDeleteEmployeeUseCase.java | 54 +++++++ .../use_cases/TestGetEmployeeUseCase.java | 71 +++++++++ 15 files changed, 399 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/lab/domain/shared/exceptions/UserNotFoundException.java create mode 100644 src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java create mode 100644 src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java create mode 100644 src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java create mode 100644 src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java diff --git a/build.gradle.kts b/build.gradle.kts index 662d6b8..bca8264 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,6 +32,8 @@ dependencies { compileOnly("org.projectlombok:lombok:1.18.34") annotationProcessor("org.projectlombok:lombok:1.18.34") + testImplementation("org.mockito:mockito-core:5.12.0") + testImplementation("org.mockito:mockito-inline:5.2.0") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index a08fc03..74f9be8 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -4,14 +4,48 @@ import org.lab.api.adapters.employee.EmployeeCreateAdapter; import org.lab.api.adapters.employee.EmployeeDeleteAdapter; import org.lab.api.adapters.employee.EmployeeGetAdapter; +import org.lab.application.employee.services.CreateValidator; +import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.application.employee.use_cases.CreateEmployeeUseCase; +import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; +import org.lab.application.employee.use_cases.GetEmployeeUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.infra.db.repository.employee.EmployeeRepository; public class Main { public static void main(String[] args) { Javalin app = Javalin.create(config -> {}).start(7070); - EmployeeCreateAdapter createEmployeeAdapter = new EmployeeCreateAdapter(); - EmployeeDeleteAdapter deleteEmployeeAdapter = new EmployeeDeleteAdapter(); - EmployeeGetAdapter getEmployeeAdapter = new EmployeeGetAdapter(); + + EmployeeCreateAdapter createEmployeeAdapter = new EmployeeCreateAdapter( + new ObjectMapper(), + new CreateEmployeeUseCase( + new EmployeeRepository(), + new CreateValidator( + new EmployeePermissionValidator( + new EmployeeRepository() + ), + new EmployeeRepository() + ) + ) + ); + EmployeeDeleteAdapter deleteEmployeeAdapter = new EmployeeDeleteAdapter( + new DeleteEmployeeUseCase( + new EmployeeRepository(), + new EmployeePermissionValidator( + new EmployeeRepository() + ) + ) + ); + EmployeeGetAdapter getEmployeeAdapter = new EmployeeGetAdapter( + new GetEmployeeUseCase( + new EmployeeRepository(), + new EmployeePermissionValidator( + new EmployeeRepository() + ) + ), + new ObjectMapper() + ); app.get("/", ctx -> ctx.result("Hello World")); diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java index a0e627b..dc40251 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -13,8 +13,16 @@ public class EmployeeCreateAdapter { - private final ObjectMapper mapper = new ObjectMapper(); - private final CreateEmployeeUseCase useCase = new CreateEmployeeUseCase(); + private final ObjectMapper mapper; + private final CreateEmployeeUseCase useCase; + + public EmployeeCreateAdapter( + ObjectMapper mapper, + CreateEmployeeUseCase useCase + ) { + this.mapper = mapper; + this.useCase = useCase; + } public Context createEmployee( Context ctx diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java index de8b596..026dba5 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -9,7 +9,13 @@ public class EmployeeDeleteAdapter { - private final DeleteEmployeeUseCase useCase = new DeleteEmployeeUseCase(); + private final DeleteEmployeeUseCase useCase; + + public EmployeeDeleteAdapter( + DeleteEmployeeUseCase useCase + ) { + this.useCase = useCase; + } public Context deleteEmployee( Context ctx diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java index 4d059d1..d17f36b 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -12,8 +12,16 @@ public class EmployeeGetAdapter { - private final GetEmployeeUseCase useCase = new GetEmployeeUseCase(); - private final ObjectMapper mapper = new ObjectMapper(); + private final GetEmployeeUseCase useCase; + private final ObjectMapper mapper; + + public EmployeeGetAdapter( + GetEmployeeUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } public Context getEmployee( Context ctx diff --git a/src/main/java/org/lab/application/employee/services/CreateValidator.java b/src/main/java/org/lab/application/employee/services/CreateValidator.java index c149773..b190b0a 100644 --- a/src/main/java/org/lab/application/employee/services/CreateValidator.java +++ b/src/main/java/org/lab/application/employee/services/CreateValidator.java @@ -9,8 +9,16 @@ public class CreateValidator { - private final EmployeePermissionValidator validator = new EmployeePermissionValidator(); - private final EmployeeRepository employeeRepository = new EmployeeRepository(); + private final EmployeePermissionValidator validator; + private final EmployeeRepository employeeRepository; + + public CreateValidator( + EmployeePermissionValidator validator, + EmployeeRepository employeeRepository + ) { + this.validator = validator; + this.employeeRepository = employeeRepository; + } public void validate( Employee employee, diff --git a/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java b/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java index a4667fc..490c819 100644 --- a/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java +++ b/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java @@ -7,12 +7,16 @@ public class EmployeePermissionValidator { - private final EmployeeRepository employeeRepository = new EmployeeRepository(); + private final EmployeeRepository employeeRepository; + + public EmployeePermissionValidator( + EmployeeRepository employeeRepository + ) { + this.employeeRepository = employeeRepository; + } public void validate(int employeeId) { Employee creatorEmployee = employeeRepository.getById(employeeId); - System.out.println("hello"); - System.out.println(creatorEmployee); if (creatorEmployee.getType() != EmployeeType.MANAGER) { throw new NotPermittedException("Only manager can work with employees"); } diff --git a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java index 89777c9..ce6d2c5 100644 --- a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java @@ -6,8 +6,16 @@ public class CreateEmployeeUseCase { - private final EmployeeRepository employeeRepository = new EmployeeRepository(); - private final CreateValidator createValidator = new CreateValidator(); + private final EmployeeRepository employeeRepository; + private final CreateValidator createValidator; + + public CreateEmployeeUseCase( + EmployeeRepository employeeRepository, + CreateValidator createValidator + ) { + this.employeeRepository = employeeRepository; + this.createValidator = createValidator; + } public Employee execute( Employee employee, diff --git a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java index c394a48..e60f98c 100644 --- a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java @@ -5,8 +5,16 @@ public class DeleteEmployeeUseCase { - private final EmployeeRepository employeeRepository = new EmployeeRepository(); - private final EmployeePermissionValidator validator = new EmployeePermissionValidator(); + private final EmployeeRepository employeeRepository; + private final EmployeePermissionValidator validator; + + public DeleteEmployeeUseCase( + EmployeeRepository employeeRepository, + EmployeePermissionValidator validator + ) { + this.employeeRepository = employeeRepository; + this.validator = validator; + } public void execute( int deleteEmployeeId, diff --git a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java index dc70e3b..b4541cc 100644 --- a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java @@ -1,15 +1,22 @@ package org.lab.application.employee.use_cases; import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.application.employee.services.EmployeePermissionValidator; public class GetEmployeeUseCase { - private EmployeeRepository employeeRepository = - new EmployeeRepository(); - private EmployeePermissionValidator employeePermissionValidator = - new EmployeePermissionValidator(); + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator employeePermissionValidator; + + public GetEmployeeUseCase( + EmployeeRepository employeeRepository, + EmployeePermissionValidator employeePermissionValidator + ) { + this.employeeRepository = employeeRepository; + this.employeePermissionValidator = employeePermissionValidator; + } public Employee execute( int employeeId, @@ -17,6 +24,9 @@ public Employee execute( ) { employeePermissionValidator.validate(actorId); Employee employee = employeeRepository.getById(employeeId); + if (employee == null) { + throw new UserNotFoundException(); + } return employee; } } diff --git a/src/main/java/org/lab/domain/shared/exceptions/UserNotFoundException.java b/src/main/java/org/lab/domain/shared/exceptions/UserNotFoundException.java new file mode 100644 index 0000000..ca1bec5 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/UserNotFoundException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException() { + super("Employee not found"); + } +} diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java new file mode 100644 index 0000000..cc263e6 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -0,0 +1,15 @@ +package org.lab.infra.db.repository.project; + +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.infra.db.client.DatabaseClient; + +public class ProjectRepository { + + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; + + public ProjectRepository() { + databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); + } +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java new file mode 100644 index 0000000..13fc781 --- /dev/null +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -0,0 +1,135 @@ +package org.lab.application.employee.use_cases; + +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.UserAlreadyExistsException; +import org.lab.application.employee.services.CreateValidator; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestCreateEmployeeUseCase { + + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator validator; + private CreateValidator createValidator; + private CreateEmployeeUseCase useCase; + + @BeforeEach + public void setUp() { + employeeRepository = Mockito.mock(EmployeeRepository.class); + validator = new EmployeePermissionValidator(employeeRepository); + createValidator = new CreateValidator(validator, employeeRepository); + useCase = new CreateEmployeeUseCase(employeeRepository, createValidator); + } + + @Test + public void testCreateEmployeeSuccess() { + Employee input = new Employee(); + input.setName("Tim Cock"); + input.setAge(12); + input.setType(EmployeeType.PROGRAMMER); + + Employee saved = new Employee(); + saved.setId(100); + saved.setName("Tim Cock"); + + Mockito.when(employeeRepository.create(input)).thenReturn(saved); + + Employee creator = new Employee(); + creator.setId(1); + creator.setType(EmployeeType.MANAGER); + Mockito.when(employeeRepository.getById(1)).thenReturn(creator); + + Employee result = useCase.execute(input, 1); + + Assertions.assertEquals(100, result.getId()); + + Mockito.verify(employeeRepository).create(input); + Mockito.verify(employeeRepository).getById(1); + } + + @Test + public void testCreateEmployeeRaisesUserAlreadyExistsException() { + Employee input = new Employee(); + input.setId(123); + input.setName("Tim Cock"); + + Employee creator = new Employee(); + creator.setId(1); + creator.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(creator); + Mockito.when(employeeRepository.getById(123)).thenReturn(input); + + RuntimeException thrown = Assertions.assertThrows( + RuntimeException.class, + () -> useCase.execute(input, 1) + ); + + Assertions.assertTrue( + thrown.getCause() instanceof ExecutionException && + ((ExecutionException) thrown.getCause()).getCause() instanceof UserAlreadyExistsException + ); + Mockito.verify(employeeRepository).getById(123); + } + + @Test + public void testCreateEmployeeRaisesNotPermittedException() { + Employee input = new Employee(); + input.setId(123); + input.setName("Tim Cock"); + + Employee creator = new Employee(); + creator.setId(1); + creator.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(creator); + Mockito.when(employeeRepository.getById(123)).thenReturn(null); + + RuntimeException thrown = Assertions.assertThrows( + RuntimeException.class, + () -> useCase.execute(input, 1) + ); + + Assertions.assertTrue( + thrown.getCause() instanceof ExecutionException && + ((ExecutionException) thrown.getCause()).getCause() instanceof NotPermittedException + ); + Mockito.verify(employeeRepository).getById(123); + Mockito.verify(employeeRepository).getById(1); + } + + @Test + public void testCreateEmployeeRaisesRuntimeException() { + Employee input = new Employee(); + input.setId(123); + input.setName("Tim Cock"); + + Employee creator = new Employee(); + creator.setId(1); + creator.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenThrow(new RuntimeException()); + Mockito.when(employeeRepository.getById(123)).thenThrow(new RuntimeException()); + + RuntimeException thrown = Assertions.assertThrows( + RuntimeException.class, + () -> useCase.execute(input, 1) + ); + + Assertions.assertTrue( + thrown.getCause() instanceof ExecutionException && + ((ExecutionException) thrown.getCause()).getCause() instanceof RuntimeException + ); + Mockito.verify(employeeRepository).getById(123); + Mockito.verify(employeeRepository).getById(1); + } +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java new file mode 100644 index 0000000..28b7008 --- /dev/null +++ b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java @@ -0,0 +1,54 @@ +package org.lab.application.employee.use_cases; + +import java.util.concurrent.ExecutionException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestDeleteEmployeeUseCase { + + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator validator; + private DeleteEmployeeUseCase useCase; + + @BeforeEach + public void setUp() { + employeeRepository = Mockito.mock(EmployeeRepository.class); + validator = new EmployeePermissionValidator(employeeRepository); + useCase = new DeleteEmployeeUseCase(employeeRepository, validator); + } + + @Test + public void testDeleteEmployeeSuccess() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + + useCase.execute(2, 1); + Mockito.verify(employeeRepository).getById(1); + } + + @Test + public void testDeleteEmployeeRaisesNotPermittedError() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + + RuntimeException thrown = Assertions.assertThrows( + NotPermittedException.class, + () -> useCase.execute(2, 1) + ); + } +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java new file mode 100644 index 0000000..417f31d --- /dev/null +++ b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java @@ -0,0 +1,71 @@ +package org.lab.application.employee.use_cases; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.mockito.Mockito; + +public class TestGetEmployeeUseCase { + + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator validator; + private GetEmployeeUseCase useCase; + + @BeforeEach + public void setUp() { + employeeRepository = Mockito.mock(EmployeeRepository.class); + validator = new EmployeePermissionValidator(employeeRepository); + useCase = new GetEmployeeUseCase(employeeRepository, validator); + } + + @Test + public void testGetEmployeeSuccess() { + Employee programmer = new Employee(); + programmer.setId(2); + programmer.setType(EmployeeType.PROGRAMMER); + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + Mockito.when(employeeRepository.getById(2)).thenReturn(programmer); + + useCase.execute(2, 1); + Mockito.verify(employeeRepository).getById(2); + } + + @Test + public void testGetEmployeeRaisesNotPermittedError() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.PROGRAMMER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + + RuntimeException thrown = Assertions.assertThrows( + NotPermittedException.class, + () -> useCase.execute(2, 1) + ); + } + + @Test + public void testGetEmployeeRaisesEmployeeNotFoundError() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + Mockito.when(employeeRepository.getById(1)).thenReturn(manager); + Mockito.when(employeeRepository.getById(2)).thenReturn(null); + + RuntimeException thrown = Assertions.assertThrows( + UserNotFoundException.class, + () -> useCase.execute(2, 1) + ); + } +} From 566fa39e7a33eeda5ac980f34cc35ac88ddd2188 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 2 Dec 2025 23:56:33 +0300 Subject: [PATCH 28/67] project use case creationg --- src/main/java/org/lab/Main.java | 17 +++++++++- .../project/ProjectCreateAdapter.java | 32 +++++++++++++++++++ .../employee/services/CreateValidator.java | 1 + .../use_cases/DeleteEmployeeUseCase.java | 2 +- .../use_cases/GetEmployeeUseCase.java | 2 +- .../project/dto/CreateProjectDTO.java | 17 ++++++++++ .../project/dto/GetProjectDTO.java | 32 +++++++++++++++++++ .../use_cases/CreateProjectUseCase.java | 23 +++++++++++++ .../services/EmployeePermissionValidator.java | 2 +- .../repository/project/ProjectRepository.java | 5 +++ .../use_cases/TestCreateEmployeeUseCase.java | 2 +- .../use_cases/TestDeleteEmployeeUseCase.java | 4 +-- .../use_cases/TestGetEmployeeUseCase.java | 2 +- 13 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/lab/application/project/dto/CreateProjectDTO.java create mode 100644 src/main/java/org/lab/application/project/dto/GetProjectDTO.java rename src/main/java/org/lab/application/{employee => shared}/services/EmployeePermissionValidator.java (94%) diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 74f9be8..88a420e 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -4,13 +4,16 @@ import org.lab.api.adapters.employee.EmployeeCreateAdapter; import org.lab.api.adapters.employee.EmployeeDeleteAdapter; import org.lab.api.adapters.employee.EmployeeGetAdapter; +import org.lab.api.adapters.project.ProjectCreateAdapter; import org.lab.application.employee.services.CreateValidator; -import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.application.project.use_cases.CreateProjectUseCase; +import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.application.employee.use_cases.CreateEmployeeUseCase; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; import org.lab.application.employee.use_cases.GetEmployeeUseCase; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.infra.db.repository.project.ProjectRepository; public class Main { @@ -47,10 +50,22 @@ public static void main(String[] args) { new ObjectMapper() ); + ProjectCreateAdapter createProjectAdapter = new ProjectCreateAdapter( + new ObjectMapper(), + new CreateProjectUseCase( + new ProjectRepository(), + new EmployeePermissionValidator( + new EmployeeRepository() + ) + ) + ); + app.get("/", ctx -> ctx.result("Hello World")); app.post("/employee", createEmployeeAdapter::createEmployee); app.delete("/employee", deleteEmployeeAdapter::deleteEmployee); app.get("/employee", getEmployeeAdapter::getEmployee); + + app.post("/project", createProjectAdapter::createProject); } } diff --git a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java index b797c07..9fb8db2 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java @@ -1,4 +1,36 @@ package org.lab.api.adapters.project; +import io.javalin.http.Context; + +import org.lab.application.project.dto.CreateProjectDTO; +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.CreateProjectUseCase; +import org.lab.domain.project.model.Project; +import org.lab.core.utils.mapper.ObjectMapper; + public class ProjectCreateAdapter { + + private final ObjectMapper objectMapper; + private final CreateProjectUseCase useCase; + + public ProjectCreateAdapter( + ObjectMapper objectMapper, + CreateProjectUseCase useCase + ) { + this.objectMapper = objectMapper; + this.useCase = useCase; + } + + public Context createProject( + Context ctx + ) { + CreateProjectDTO dto = ctx.bodyAsClass(CreateProjectDTO.class); + Project project = objectMapper.mapToDomain(dto, Project.class); + Project createdProject = useCase.execute(project); + GetProjectDTO presentationProject = objectMapper.mapToPresentation( + createdProject, + GetProjectDTO.class + ); + return ctx.status(201).json(presentationProject); + } } diff --git a/src/main/java/org/lab/application/employee/services/CreateValidator.java b/src/main/java/org/lab/application/employee/services/CreateValidator.java index b190b0a..a99eb46 100644 --- a/src/main/java/org/lab/application/employee/services/CreateValidator.java +++ b/src/main/java/org/lab/application/employee/services/CreateValidator.java @@ -2,6 +2,7 @@ import java.util.concurrent.*; +import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.domain.shared.exceptions.NotPermittedException; diff --git a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java index e60f98c..901925f 100644 --- a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java @@ -1,7 +1,7 @@ package org.lab.application.employee.use_cases; import org.lab.infra.db.repository.employee.EmployeeRepository; -import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeePermissionValidator; public class DeleteEmployeeUseCase { diff --git a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java index b4541cc..68759c9 100644 --- a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java @@ -3,7 +3,7 @@ import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.employee.EmployeeRepository; -import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeePermissionValidator; public class GetEmployeeUseCase { diff --git a/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java new file mode 100644 index 0000000..a485991 --- /dev/null +++ b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java @@ -0,0 +1,17 @@ +package org.lab.application.project.dto; + +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.interfaces.PresentationObject; + +import java.util.Date; +import java.util.List; + +public record CreateProjectDTO( + String name, + String description, + int managerId, + Integer teamLeadId, + ListdeveloperIds, + List testerIds +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/project/dto/GetProjectDTO.java b/src/main/java/org/lab/application/project/dto/GetProjectDTO.java new file mode 100644 index 0000000..0b96a2c --- /dev/null +++ b/src/main/java/org/lab/application/project/dto/GetProjectDTO.java @@ -0,0 +1,32 @@ +package org.lab.application.project.dto; + +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.interfaces.PresentationObject; + +import java.util.Date; +import java.util.List; + +public record GetProjectDTO( + int id, + String name, + String description, + + int managerId, + Integer teamLeadId, + + ListdeveloperIds, + List testerIds, + + ProjectStatus status, + + Integer currentMilestoneId, + List milestoneIds, + + List bugReportIds, + + Date createdDate, + int createdBy, + Date updatedDate, + Integer updatedBy +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java index 6589b18..79432ef 100644 --- a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java @@ -1,4 +1,27 @@ package org.lab.application.project.use_cases; +import org.lab.domain.project.model.Project; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.application.shared.services.EmployeePermissionValidator; + public class CreateProjectUseCase { + + private final ProjectRepository projectRepository; + private final EmployeePermissionValidator employeePermissionValidator; + + public CreateProjectUseCase( + ProjectRepository projectRepository, + EmployeePermissionValidator employeePermissionValidator + ) { + this.projectRepository = projectRepository; + this.employeePermissionValidator = employeePermissionValidator; + } + + public Project execute( + Project project + ) { + this.employeePermissionValidator.validate(project.getManagerId()); + Project domainProject = this.projectRepository.create(project); + return domainProject; + } } diff --git a/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java similarity index 94% rename from src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java rename to src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java index 490c819..df0428d 100644 --- a/src/main/java/org/lab/application/employee/services/EmployeePermissionValidator.java +++ b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java @@ -1,4 +1,4 @@ -package org.lab.application.employee.services; +package org.lab.application.shared.services; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index cc263e6..582d3e6 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -1,6 +1,7 @@ package org.lab.infra.db.repository.project; import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; import org.lab.infra.db.client.DatabaseClient; public class ProjectRepository { @@ -12,4 +13,8 @@ public ProjectRepository() { databaseClient = new DatabaseClient(); objectMapper = new ObjectMapper(); } + + public Project create(Project project) { + return new Project(); + } } diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index 13fc781..a4e2ca6 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.domain.shared.exceptions.UserAlreadyExistsException; import org.lab.application.employee.services.CreateValidator; diff --git a/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java index 28b7008..1235cde 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java @@ -1,13 +1,11 @@ package org.lab.application.employee.use_cases; -import java.util.concurrent.ExecutionException; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; diff --git a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java index 417f31d..e0f8530 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.lab.application.employee.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; From d8ca9f0ddaea6ca1c00c685b30d39fd7bf0cb216 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 00:01:18 +0300 Subject: [PATCH 29/67] get and list adapters --- .../java/org/lab/api/adapters/project/GetProjectAdapter.java | 4 ++++ .../java/org/lab/api/adapters/project/ListProjectAdapter.java | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java create mode 100644 src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java diff --git a/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java b/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java new file mode 100644 index 0000000..677f98c --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.project; + +public class GetProjectAdapter { +} diff --git a/src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java b/src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java new file mode 100644 index 0000000..703b582 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.project; + +public class ListProjectAdapter { +} From 1a3cf1a0ce628c556a171d522e40d62917c1ad84 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 00:55:32 +0300 Subject: [PATCH 30/67] use case for getting project info --- .../adapters/project/GetProjectAdapter.java | 1 + .../project/services/GetValidator.java | 64 +++++++++++++++++++ .../project/use_cases/GetProjectUseCase.java | 32 ++++++++++ .../services/ProjectMembershipValidator.java | 38 +++++++++++ .../exceptions/ProjectNotFoundException.java | 7 ++ .../repository/project/ProjectRepository.java | 4 ++ 6 files changed, 146 insertions(+) create mode 100644 src/main/java/org/lab/application/project/services/GetValidator.java create mode 100644 src/main/java/org/lab/domain/project/services/ProjectMembershipValidator.java create mode 100644 src/main/java/org/lab/domain/shared/exceptions/ProjectNotFoundException.java diff --git a/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java b/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java index 677f98c..c7387e1 100644 --- a/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java @@ -1,4 +1,5 @@ package org.lab.api.adapters.project; +import org.lab.application.project. public class GetProjectAdapter { } diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java new file mode 100644 index 0000000..43f89ef --- /dev/null +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -0,0 +1,64 @@ +package org.lab.application.project.services; + +import java.util.concurrent.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserAlreadyExistsException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public record Pair(Project project, Employee employee) {} + +public class GetValidator { + + private final ProjectRepository projectRepository; + private final EmployeeRepository employeeRepository; + + public GetValidator( + ProjectRepository projectRepository, + EmployeeRepository employeeRepository + ) { + this.projectRepository = projectRepository; + this.employeeRepository = employeeRepository; + } + + public Pair validate( + int projectId, + int employeeId + ) + throws NotPermittedException, + UserAlreadyExistsException + { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ + var projectFuture = scope.fork(() -> { + Project project = this.projectRepository.get(projectId); + if (project == null) { + throw new ProjectNotFoundException(); + } + return project; + }); + var employeeFuture = scope.fork(() -> { + Employee employee = this.employeeRepository.getById(employeeId); + if (employee == null) { + throw new UserNotFoundException(); + } + return employee; + }); + scope.join(); + scope.throwIfFailed(); + + Project project = projectFuture.get(); + Employee employee = employeeFuture.get(); + return new Pair(project, employee); + + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java index d7e8b63..baaefa8 100644 --- a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java @@ -1,4 +1,36 @@ package org.lab.application.project.use_cases; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.application.project.services.GetValidator; +import org.lab.application.project.services.Pair; +import org.lab.domain.project.services.ProjectMembershipValidator; + public class GetProjectUseCase { + + private final ProjectRepository projectRepository; + private final GetValidator getValidator; + private final ProjectMembershipValidator projectMembershipValidator; + + public GetProjectUseCase( + ProjectRepository projectRepository, + GetValidator getValidator, + ProjectMembershipValidator projectMembershipValidator + ) { + this.projectRepository = projectRepository; + this.getValidator = getValidator; + this.projectMembershipValidator = projectMembershipValidator; + } + + public Project execute( + int projectId, + int employeeId + ) { + Pair pair = this.getValidator.validate(projectId, employeeId); + Project project = pair.project(); + Employee employee = pair.employee(); + this.projectMembershipValidator.validate(employee, project); + return project; + } } diff --git a/src/main/java/org/lab/domain/project/services/ProjectMembershipValidator.java b/src/main/java/org/lab/domain/project/services/ProjectMembershipValidator.java new file mode 100644 index 0000000..6f5cea7 --- /dev/null +++ b/src/main/java/org/lab/domain/project/services/ProjectMembershipValidator.java @@ -0,0 +1,38 @@ +package org.lab.domain.project.services; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class ProjectMembershipValidator { + + public void validate( + Employee employee, + Project project + ) throws + NotPermittedException + { + switch (employee.getType()) { + case PROGRAMMER -> { + if (!project.getDeveloperIds().contains(employee.getId())) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + case TESTER -> { + if (!project.getTesterIds().contains(employee.getId())) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + case MANAGER -> { + if (project.getManagerId() != employee.getId()) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + case TEAMLEAD -> { + if (project.getTeamLeadId() != employee.getId()) { + throw new NotPermittedException("You are not permitted to perform this operation"); + } + } + } + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/ProjectNotFoundException.java b/src/main/java/org/lab/domain/shared/exceptions/ProjectNotFoundException.java new file mode 100644 index 0000000..e690d58 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/ProjectNotFoundException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class ProjectNotFoundException extends RuntimeException { + public ProjectNotFoundException() { + super("Project not found"); + } +} diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 582d3e6..a99700f 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -14,6 +14,10 @@ public ProjectRepository() { objectMapper = new ObjectMapper(); } + public Project get(int projectId) { + return new Project(); + } + public Project create(Project project) { return new Project(); } From ecc49d5e6a884529b70198779df667bc00c3e06d Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 01:08:14 +0300 Subject: [PATCH 31/67] adapter for get uc --- src/main/java/org/lab/Main.java | 15 +++++ .../adapters/project/GetProjectAdapter.java | 5 -- .../adapters/project/ProjectGetAdapter.java | 55 +++++++++++++++++++ .../project/services/GetValidator.java | 8 +-- .../application/project/services/Pair.java | 6 ++ .../project/use_cases/GetProjectUseCase.java | 3 - 6 files changed, 78 insertions(+), 14 deletions(-) delete mode 100644 src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java create mode 100644 src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java create mode 100644 src/main/java/org/lab/application/project/services/Pair.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 88a420e..6e9078e 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -5,13 +5,17 @@ import org.lab.api.adapters.employee.EmployeeDeleteAdapter; import org.lab.api.adapters.employee.EmployeeGetAdapter; import org.lab.api.adapters.project.ProjectCreateAdapter; +import org.lab.api.adapters.project.ProjectGetAdapter; import org.lab.application.employee.services.CreateValidator; +import org.lab.application.project.services.GetValidator; import org.lab.application.project.use_cases.CreateProjectUseCase; +import org.lab.application.project.use_cases.GetProjectUseCase; import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.application.employee.use_cases.CreateEmployeeUseCase; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; import org.lab.application.employee.use_cases.GetEmployeeUseCase; import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.services.ProjectMembershipValidator; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.infra.db.repository.project.ProjectRepository; @@ -59,6 +63,16 @@ public static void main(String[] args) { ) ) ); + ProjectGetAdapter projectGetAdapter = new ProjectGetAdapter( + new GetProjectUseCase( + new GetValidator( + new ProjectRepository(), + new EmployeeRepository() + ), + new ProjectMembershipValidator() + ), + new ObjectMapper() + ); app.get("/", ctx -> ctx.result("Hello World")); @@ -67,5 +81,6 @@ public static void main(String[] args) { app.get("/employee", getEmployeeAdapter::getEmployee); app.post("/project", createProjectAdapter::createProject); + app.get("/project", projectGetAdapter::getProject); } } diff --git a/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java b/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java deleted file mode 100644 index c7387e1..0000000 --- a/src/main/java/org/lab/api/adapters/project/GetProjectAdapter.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.lab.api.adapters.project; - -import org.lab.application.project. -public class GetProjectAdapter { -} diff --git a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java new file mode 100644 index 0000000..a10d79f --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java @@ -0,0 +1,55 @@ +package org.lab.api.adapters.project; + +import java.util.Map; + +import io.javalin.http.Context; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.GetProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class ProjectGetAdapter { + + private final GetProjectUseCase useCase; + private final ObjectMapper mapper; + + public ProjectGetAdapter( + GetProjectUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context getProject(Context ctx) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int projectId = Integer.parseInt(ctx.pathParam("projectId")); + Project project = useCase.execute( + employeeId, + projectId + ); + GetProjectDTO presentationProject = mapper.mapToPresentation( + project, + GetProjectDTO.class + ); + return ctx.status(201).json(presentationProject); + + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + + } catch (NotPermittedException e) { + return ctx.status(403).json(Map.of("error", "You do not have permission to perform this operation")); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesn't exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java index 43f89ef..b50ab18 100644 --- a/src/main/java/org/lab/application/project/services/GetValidator.java +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -4,15 +4,11 @@ import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; -import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.domain.shared.exceptions.ProjectNotFoundException; -import org.lab.domain.shared.exceptions.UserAlreadyExistsException; import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.project.ProjectRepository; import org.lab.infra.db.repository.employee.EmployeeRepository; -public record Pair(Project project, Employee employee) {} - public class GetValidator { private final ProjectRepository projectRepository; @@ -30,8 +26,8 @@ public Pair validate( int projectId, int employeeId ) - throws NotPermittedException, - UserAlreadyExistsException + throws ProjectNotFoundException, + UserNotFoundException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ var projectFuture = scope.fork(() -> { diff --git a/src/main/java/org/lab/application/project/services/Pair.java b/src/main/java/org/lab/application/project/services/Pair.java new file mode 100644 index 0000000..12787b7 --- /dev/null +++ b/src/main/java/org/lab/application/project/services/Pair.java @@ -0,0 +1,6 @@ +package org.lab.application.project.services; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; + +public record Pair(Project project, Employee employee) {} diff --git a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java index baaefa8..dcd7e96 100644 --- a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java @@ -9,16 +9,13 @@ public class GetProjectUseCase { - private final ProjectRepository projectRepository; private final GetValidator getValidator; private final ProjectMembershipValidator projectMembershipValidator; public GetProjectUseCase( - ProjectRepository projectRepository, GetValidator getValidator, ProjectMembershipValidator projectMembershipValidator ) { - this.projectRepository = projectRepository; this.getValidator = getValidator; this.projectMembershipValidator = projectMembershipValidator; } From e48094fdaadf7e3b6e2a868c433062eb2828aaba Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 22:15:13 +0300 Subject: [PATCH 32/67] added all usecases for project --- src/main/java/org/lab/Main.java | 5 ++- .../employee/EmployeeDeleteAdapter.java | 2 +- .../adapters/project/ListProjectAdapter.java | 4 -- .../project/ProjectDeleteAdapter.java | 45 +++++++++++++++++++ .../adapters/project/ProjectGetAdapter.java | 7 ++- .../adapters/project/ProjectListAdapter.java | 7 +++ .../project/services/GetValidator.java | 13 +++--- .../project/services/UserSpecFactory.java | 26 +++++++++++ .../use_cases/DeleteProjectUseCase.java | 32 +++++++++++++ .../project/use_cases/GetProjectUseCase.java | 1 - .../project/use_cases/ListProjectUseCase.java | 32 +++++++++++++ .../services/CurrentEmployeeProvider.java | 23 ++++++++++ .../employee/EmployeeRepository.java | 3 +- .../employee/spec/DeveloperSpec.java | 27 +++++++++++ .../repository/employee/spec/ManagerSpec.java | 27 +++++++++++ .../employee/spec/TeamLeaderSpec.java | 27 +++++++++++ .../repository/employee/spec/TesterSpec.java | 27 +++++++++++ .../repository/project/ProjectRepository.java | 12 +++++ .../org/lab/infra/db/spec/Specification.java | 4 ++ .../java/org/lab/infra/db/spec/SqlSpec.java | 9 ++++ 20 files changed, 316 insertions(+), 17 deletions(-) delete mode 100644 src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java create mode 100644 src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java create mode 100644 src/main/java/org/lab/application/project/services/UserSpecFactory.java create mode 100644 src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java create mode 100644 src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java create mode 100644 src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java create mode 100644 src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java create mode 100644 src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java create mode 100644 src/main/java/org/lab/infra/db/spec/Specification.java create mode 100644 src/main/java/org/lab/infra/db/spec/SqlSpec.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 6e9078e..c15b370 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -10,6 +10,7 @@ import org.lab.application.project.services.GetValidator; import org.lab.application.project.use_cases.CreateProjectUseCase; import org.lab.application.project.use_cases.GetProjectUseCase; +import org.lab.application.shared.services.CurrentEmployeeProvider; import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.application.employee.use_cases.CreateEmployeeUseCase; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; @@ -67,7 +68,9 @@ public static void main(String[] args) { new GetProjectUseCase( new GetValidator( new ProjectRepository(), - new EmployeeRepository() + new CurrentEmployeeProvider( + new EmployeeRepository() + ) ), new ProjectMembershipValidator() ), diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java index 026dba5..8c7f285 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -22,7 +22,7 @@ public Context deleteEmployee( ) { try { DeleteEmployeeDTO dto = ctx.bodyAsClass(DeleteEmployeeDTO.class); - useCase.execute(dto.deletedEmployeeId(), dto.employeeId()); + this.useCase.execute(dto.deletedEmployeeId(), dto.employeeId()); return ctx.status(201); } catch (NotPermittedException e) { diff --git a/src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java b/src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java deleted file mode 100644 index 703b582..0000000 --- a/src/main/java/org/lab/api/adapters/project/ListProjectAdapter.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.lab.api.adapters.project; - -public class ListProjectAdapter { -} diff --git a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java index 50b4728..7110b6c 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java @@ -1,4 +1,49 @@ package org.lab.api.adapters.project; +import java.util.Map; + +import io.javalin.http.Context; + +import org.lab.application.project.use_cases.DeleteProjectUseCase; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + public class ProjectDeleteAdapter { + + private final DeleteProjectUseCase useCase; + + public ProjectDeleteAdapter( + DeleteProjectUseCase useCase + ) { + this.useCase = useCase; + } + + public Context deleteProject( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int projectId = Integer.parseInt(ctx.pathParam("projectId")); + this.useCase.execute(employeeId, projectId); + return ctx.status(201); + + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesn't exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } } diff --git a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java index a10d79f..77ef71d 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java @@ -43,7 +43,12 @@ public Context getProject(Context ctx) { return ctx.status(409).json(Map.of("error", "User doesnt exist")); } catch (NotPermittedException e) { - return ctx.status(403).json(Map.of("error", "You do not have permission to perform this operation")); + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); } catch (ProjectNotFoundException e) { return ctx.status(404).json(Map.of("error", "Project doesn't exist")); diff --git a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java new file mode 100644 index 0000000..674ba93 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java @@ -0,0 +1,7 @@ +package org.lab.api.adapters.project; + +import org.lab.application.project.services.UserSpecFactory; +import org.lab.infra.db.repository.project.ProjectRepository; + +public class ProjectListAdapter { +} diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java index b50ab18..9177044 100644 --- a/src/main/java/org/lab/application/project/services/GetValidator.java +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -7,19 +7,19 @@ import org.lab.domain.shared.exceptions.ProjectNotFoundException; import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.project.ProjectRepository; -import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.application.shared.services.CurrentEmployeeProvider; public class GetValidator { private final ProjectRepository projectRepository; - private final EmployeeRepository employeeRepository; + private final CurrentEmployeeProvider currentEmployeeProvider; public GetValidator( ProjectRepository projectRepository, - EmployeeRepository employeeRepository + CurrentEmployeeProvider currentEmployeeProvider ) { this.projectRepository = projectRepository; - this.employeeRepository = employeeRepository; + this.currentEmployeeProvider = currentEmployeeProvider; } public Pair validate( @@ -38,10 +38,7 @@ public Pair validate( return project; }); var employeeFuture = scope.fork(() -> { - Employee employee = this.employeeRepository.getById(employeeId); - if (employee == null) { - throw new UserNotFoundException(); - } + Employee employee = this.currentEmployeeProvider.get(employeeId); return employee; }); scope.join(); diff --git a/src/main/java/org/lab/application/project/services/UserSpecFactory.java b/src/main/java/org/lab/application/project/services/UserSpecFactory.java new file mode 100644 index 0000000..d7af73e --- /dev/null +++ b/src/main/java/org/lab/application/project/services/UserSpecFactory.java @@ -0,0 +1,26 @@ +package org.lab.application.project.services; + +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.spec.TeamLeaderSpec; +import org.lab.infra.db.repository.employee.spec.TesterSpec; +import org.lab.infra.db.repository.employee.spec.DeveloperSpec; +import org.lab.infra.db.repository.employee.spec.ManagerSpec; +import org.lab.infra.db.spec.Specification; + +public class UserSpecFactory { + public Specification getForType( + Employee employee + ) { + switch (employee.getType()) { + case MANAGER: + return new ManagerSpec(employee.getId()); + case TEAMLEAD: + return new TeamLeaderSpec(employee.getId()); + case PROGRAMMER: + return new DeveloperSpec(employee.getId()); + case TESTER: + return new TesterSpec(employee.getId()); + } + return null; + } +} diff --git a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java index 40f4d1d..d03e61f 100644 --- a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java @@ -1,4 +1,36 @@ package org.lab.application.project.use_cases; +import org.lab.application.project.services.Pair; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.application.project.services.GetValidator; + public class DeleteProjectUseCase { + + private final ProjectRepository projectRepository; + private final GetValidator getValidator; + private ProjectMembershipValidator projectMembershipValidator; + + public DeleteProjectUseCase( + ProjectRepository projectRepository, + GetValidator getValidator, + ProjectMembershipValidator projectMembershipValidator + ) { + this.projectRepository = projectRepository; + this.getValidator = getValidator; + this.projectMembershipValidator = projectMembershipValidator; + } + + public void execute( + int employeeId, + int projectId + ) { + Pair pair = this.getValidator.validate(projectId, employeeId); + Employee employee = pair.employee(); + Project project = pair.project(); + this.projectMembershipValidator.validate(employee, project); + this.projectRepository.delete(projectId); + } } diff --git a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java index dcd7e96..057c938 100644 --- a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java @@ -2,7 +2,6 @@ import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; -import org.lab.infra.db.repository.project.ProjectRepository; import org.lab.application.project.services.GetValidator; import org.lab.application.project.services.Pair; import org.lab.domain.project.services.ProjectMembershipValidator; diff --git a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java index 770d6b3..9d42762 100644 --- a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java @@ -1,4 +1,36 @@ package org.lab.application.project.use_cases; +import java.util.List; + +import org.lab.application.project.services.UserSpecFactory; +import org.lab.application.shared.services.CurrentEmployeeProvider; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; + public class ListProjectUseCase { + + private final ProjectRepository projectRepository; + private final CurrentEmployeeProvider currentEmployeeProvider; + private final UserSpecFactory userSpecFactory; + + public ListProjectUseCase( + ProjectRepository projectRepository, + CurrentEmployeeProvider currentEmployeeProvider, + UserSpecFactory userSpecFactory + ) { + this.projectRepository = projectRepository; + this.currentEmployeeProvider = currentEmployeeProvider; + this.userSpecFactory = userSpecFactory; + } + + public List execute( + int employeeId + ) { + Employee employee = this.currentEmployeeProvider.get(employeeId); + Specification spec = this.userSpecFactory.getForType(employee); + List projects = this.projectRepository.list(spec); + return projects; + } } diff --git a/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java b/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java new file mode 100644 index 0000000..e29dd67 --- /dev/null +++ b/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java @@ -0,0 +1,23 @@ +package org.lab.application.shared.services; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class CurrentEmployeeProvider { + private final EmployeeRepository employeeRepository; + + public CurrentEmployeeProvider( + EmployeeRepository employeeRepository + ) { + this.employeeRepository = employeeRepository; + } + + public Employee get(int employeeId) { + Employee employee = employeeRepository.getById(employeeId); + if (employee == null) { + throw new UserNotFoundException(); + } + return employee; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index ffe6cdb..d2b2ee2 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -9,6 +9,7 @@ import java.util.Map; public class EmployeeRepository { + private final DatabaseClient databaseClient; private final ObjectMapper objectMapper; @@ -18,7 +19,7 @@ public EmployeeRepository() { } public Employee getById(int id) { - String sql = "SELECT * FROM EMPLOYEES where id = ?"; + String sql = "SELECT * FROM employees where id = ?"; try ( Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java new file mode 100644 index 0000000..0e52854 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.ArrayList; +import java.util.List; + +public class DeveloperSpec implements SqlSpec { + + private final int developerId; + + public DeveloperSpec(int developerId) { + this.developerId = developerId; + } + + @Override + public String toSql() { + return "? IN developerIds"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.developerId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java new file mode 100644 index 0000000..3ce3d4c --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.ArrayList; +import java.util.List; + +public class ManagerSpec implements SqlSpec { + + private final int managerId; + + public ManagerSpec(int managerId) { + this.managerId = managerId; + } + + @Override + public String toSql() { + return "managerId = ?"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.managerId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java new file mode 100644 index 0000000..a5ebc5b --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.ArrayList; +import java.util.List; + +public class TeamLeaderSpec implements SqlSpec { + + private final int teamLeaderId; + + public TeamLeaderSpec(int teamLeaderId) { + this.teamLeaderId = teamLeaderId; + } + + @Override + public String toSql() { + return "teamLeadId = ?"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.teamLeaderId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java new file mode 100644 index 0000000..e125c99 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java @@ -0,0 +1,27 @@ +package org.lab.infra.db.repository.employee.spec; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.ArrayList; +import java.util.List; + +public class TesterSpec implements SqlSpec { + + private final int testerId; + + public TesterSpec(int testerId) { + this.testerId = testerId; + } + + @Override + public String toSql() { + return "? IN testerIds"; + } + + @Override + public List getParams() { + List params = new ArrayList<>(); + params.add(this.testerId); + return params; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index a99700f..749b8fd 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -1,8 +1,12 @@ package org.lab.infra.db.repository.project; +import java.util.ArrayList; +import java.util.List; + import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.project.model.Project; import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.spec.Specification; public class ProjectRepository { @@ -21,4 +25,12 @@ public Project get(int projectId) { public Project create(Project project) { return new Project(); } + + public List list( + Specification specification + ) { + return new ArrayList<>(); + } + + public void delete(int projectId) {} } diff --git a/src/main/java/org/lab/infra/db/spec/Specification.java b/src/main/java/org/lab/infra/db/spec/Specification.java new file mode 100644 index 0000000..f42244a --- /dev/null +++ b/src/main/java/org/lab/infra/db/spec/Specification.java @@ -0,0 +1,4 @@ +package org.lab.infra.db.spec; + +public sealed interface Specification permits SqlSpec { +} diff --git a/src/main/java/org/lab/infra/db/spec/SqlSpec.java b/src/main/java/org/lab/infra/db/spec/SqlSpec.java new file mode 100644 index 0000000..fff7a55 --- /dev/null +++ b/src/main/java/org/lab/infra/db/spec/SqlSpec.java @@ -0,0 +1,9 @@ +package org.lab.infra.db.spec; + +import java.util.List; + +public non-sealed interface SqlSpec + extends Specification { + String toSql(); + List getParams(); +} From 1b01027d7b7019b54e6f789413acc9a5b67caf5f Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 22:29:40 +0300 Subject: [PATCH 33/67] register routes --- src/main/java/org/lab/Main.java | 31 +++++++++++++ .../adapters/project/ProjectListAdapter.java | 44 ++++++++++++++++++- .../services/CurrentEmployeeProvider.java | 6 ++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index c15b370..9c54ba1 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -5,11 +5,16 @@ import org.lab.api.adapters.employee.EmployeeDeleteAdapter; import org.lab.api.adapters.employee.EmployeeGetAdapter; import org.lab.api.adapters.project.ProjectCreateAdapter; +import org.lab.api.adapters.project.ProjectDeleteAdapter; import org.lab.api.adapters.project.ProjectGetAdapter; +import org.lab.api.adapters.project.ProjectListAdapter; import org.lab.application.employee.services.CreateValidator; import org.lab.application.project.services.GetValidator; +import org.lab.application.project.services.UserSpecFactory; import org.lab.application.project.use_cases.CreateProjectUseCase; +import org.lab.application.project.use_cases.DeleteProjectUseCase; import org.lab.application.project.use_cases.GetProjectUseCase; +import org.lab.application.project.use_cases.ListProjectUseCase; import org.lab.application.shared.services.CurrentEmployeeProvider; import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.application.employee.use_cases.CreateEmployeeUseCase; @@ -77,6 +82,30 @@ public static void main(String[] args) { new ObjectMapper() ); + ProjectDeleteAdapter projectDeleteAdapter = new ProjectDeleteAdapter( + new DeleteProjectUseCase( + new ProjectRepository(), + new GetValidator( + new ProjectRepository(), + new CurrentEmployeeProvider( + new EmployeeRepository() + ) + ), + new ProjectMembershipValidator() + ) + ); + + ProjectListAdapter projectListAdapter = new ProjectListAdapter( + new ListProjectUseCase( + new ProjectRepository(), + new CurrentEmployeeProvider( + new EmployeeRepository() + ), + new UserSpecFactory() + ), + new ObjectMapper() + ); + app.get("/", ctx -> ctx.result("Hello World")); app.post("/employee", createEmployeeAdapter::createEmployee); @@ -85,5 +114,7 @@ public static void main(String[] args) { app.post("/project", createProjectAdapter::createProject); app.get("/project", projectGetAdapter::getProject); + app.delete("/project", projectDeleteAdapter::deleteProject); + app.get("/project/{employeeId}", projectListAdapter::listProjects); } } diff --git a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java index 674ba93..8b04bb2 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java @@ -1,7 +1,47 @@ package org.lab.api.adapters.project; -import org.lab.application.project.services.UserSpecFactory; -import org.lab.infra.db.repository.project.ProjectRepository; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.javalin.http.Context; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.ListProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.UserNotFoundException; public class ProjectListAdapter { + + private final ListProjectUseCase useCase; + private final ObjectMapper mapper; + + public ProjectListAdapter( + ListProjectUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context listProjects( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + List projects = this.useCase.execute(employeeId); + List presentationProjects = new ArrayList<>(); + for (Project project : projects) { + GetProjectDTO presentationProject = this.mapper.mapToPresentation( + project, + GetProjectDTO.class + ); + presentationProjects.add(presentationProject); + } + return ctx.status(201).json(presentationProjects); + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + } + } } diff --git a/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java b/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java index e29dd67..55f8866 100644 --- a/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java +++ b/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java @@ -13,7 +13,11 @@ public CurrentEmployeeProvider( this.employeeRepository = employeeRepository; } - public Employee get(int employeeId) { + public Employee get( + int employeeId + ) throws + UserNotFoundException + { Employee employee = employeeRepository.getById(employeeId); if (employee == null) { throw new UserNotFoundException(); From 04e2b5028929569ceeeb739b0f301c354e52b046 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 22:39:35 +0300 Subject: [PATCH 34/67] simplified the logic of project get --- src/main/java/org/lab/Main.java | 20 ++++++++++--------- .../use_cases/GetEmployeeUseCase.java | 16 ++++++--------- .../project/services/GetValidator.java | 6 +++--- .../project/use_cases/ListProjectUseCase.java | 6 +++--- ...yeeProvider.java => EmployeeProvider.java} | 4 ++-- 5 files changed, 25 insertions(+), 27 deletions(-) rename src/main/java/org/lab/application/shared/services/{CurrentEmployeeProvider.java => EmployeeProvider.java} (90%) diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 9c54ba1..328a8a7 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -15,7 +15,7 @@ import org.lab.application.project.use_cases.DeleteProjectUseCase; import org.lab.application.project.use_cases.GetProjectUseCase; import org.lab.application.project.use_cases.ListProjectUseCase; -import org.lab.application.shared.services.CurrentEmployeeProvider; +import org.lab.application.shared.services.EmployeeProvider; import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.application.employee.use_cases.CreateEmployeeUseCase; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; @@ -52,9 +52,11 @@ public static void main(String[] args) { ); EmployeeGetAdapter getEmployeeAdapter = new EmployeeGetAdapter( new GetEmployeeUseCase( - new EmployeeRepository(), new EmployeePermissionValidator( new EmployeeRepository() + ), + new EmployeeProvider( + new EmployeeRepository() ) ), new ObjectMapper() @@ -73,7 +75,7 @@ public static void main(String[] args) { new GetProjectUseCase( new GetValidator( new ProjectRepository(), - new CurrentEmployeeProvider( + new EmployeeProvider( new EmployeeRepository() ) ), @@ -87,7 +89,7 @@ public static void main(String[] args) { new ProjectRepository(), new GetValidator( new ProjectRepository(), - new CurrentEmployeeProvider( + new EmployeeProvider( new EmployeeRepository() ) ), @@ -98,7 +100,7 @@ public static void main(String[] args) { ProjectListAdapter projectListAdapter = new ProjectListAdapter( new ListProjectUseCase( new ProjectRepository(), - new CurrentEmployeeProvider( + new EmployeeProvider( new EmployeeRepository() ), new UserSpecFactory() @@ -112,9 +114,9 @@ public static void main(String[] args) { app.delete("/employee", deleteEmployeeAdapter::deleteEmployee); app.get("/employee", getEmployeeAdapter::getEmployee); - app.post("/project", createProjectAdapter::createProject); - app.get("/project", projectGetAdapter::getProject); - app.delete("/project", projectDeleteAdapter::deleteProject); - app.get("/project/{employeeId}", projectListAdapter::listProjects); + app.post("/project/{employeeId}", createProjectAdapter::createProject); + app.get("/project/{projectId}/{employeeId}", projectGetAdapter::getProject); + app.delete("/project/{projectId}/{employeeId}", projectDeleteAdapter::deleteProject); + app.get("/project/list/{employeeId}", projectListAdapter::listProjects); } } diff --git a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java index 68759c9..a7e913c 100644 --- a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java @@ -1,21 +1,20 @@ package org.lab.application.employee.use_cases; import org.lab.domain.emploee.model.Employee; -import org.lab.domain.shared.exceptions.UserNotFoundException; -import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.application.shared.services.EmployeeProvider; import org.lab.application.shared.services.EmployeePermissionValidator; public class GetEmployeeUseCase { - private EmployeeRepository employeeRepository; private EmployeePermissionValidator employeePermissionValidator; + private EmployeeProvider employeeProvider; public GetEmployeeUseCase( - EmployeeRepository employeeRepository, - EmployeePermissionValidator employeePermissionValidator + EmployeePermissionValidator employeePermissionValidator, + EmployeeProvider employeeProvider ) { - this.employeeRepository = employeeRepository; this.employeePermissionValidator = employeePermissionValidator; + this.employeeProvider = employeeProvider; } public Employee execute( @@ -23,10 +22,7 @@ public Employee execute( int actorId ) { employeePermissionValidator.validate(actorId); - Employee employee = employeeRepository.getById(employeeId); - if (employee == null) { - throw new UserNotFoundException(); - } + Employee employee = this.employeeProvider.get(employeeId); return employee; } } diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java index 9177044..2890160 100644 --- a/src/main/java/org/lab/application/project/services/GetValidator.java +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -7,16 +7,16 @@ import org.lab.domain.shared.exceptions.ProjectNotFoundException; import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.project.ProjectRepository; -import org.lab.application.shared.services.CurrentEmployeeProvider; +import org.lab.application.shared.services.EmployeeProvider; public class GetValidator { private final ProjectRepository projectRepository; - private final CurrentEmployeeProvider currentEmployeeProvider; + private final EmployeeProvider currentEmployeeProvider; public GetValidator( ProjectRepository projectRepository, - CurrentEmployeeProvider currentEmployeeProvider + EmployeeProvider currentEmployeeProvider ) { this.projectRepository = projectRepository; this.currentEmployeeProvider = currentEmployeeProvider; diff --git a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java index 9d42762..ece7f2f 100644 --- a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java @@ -3,7 +3,7 @@ import java.util.List; import org.lab.application.project.services.UserSpecFactory; -import org.lab.application.shared.services.CurrentEmployeeProvider; +import org.lab.application.shared.services.EmployeeProvider; import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; import org.lab.infra.db.repository.project.ProjectRepository; @@ -12,12 +12,12 @@ public class ListProjectUseCase { private final ProjectRepository projectRepository; - private final CurrentEmployeeProvider currentEmployeeProvider; + private final EmployeeProvider currentEmployeeProvider; private final UserSpecFactory userSpecFactory; public ListProjectUseCase( ProjectRepository projectRepository, - CurrentEmployeeProvider currentEmployeeProvider, + EmployeeProvider currentEmployeeProvider, UserSpecFactory userSpecFactory ) { this.projectRepository = projectRepository; diff --git a/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java b/src/main/java/org/lab/application/shared/services/EmployeeProvider.java similarity index 90% rename from src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java rename to src/main/java/org/lab/application/shared/services/EmployeeProvider.java index 55f8866..ae0613e 100644 --- a/src/main/java/org/lab/application/shared/services/CurrentEmployeeProvider.java +++ b/src/main/java/org/lab/application/shared/services/EmployeeProvider.java @@ -4,10 +4,10 @@ import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.employee.EmployeeRepository; -public class CurrentEmployeeProvider { +public class EmployeeProvider { private final EmployeeRepository employeeRepository; - public CurrentEmployeeProvider( + public EmployeeProvider( EmployeeRepository employeeRepository ) { this.employeeRepository = employeeRepository; From f27270b6093ed99b403c3cf7685c26179d20fc21 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 22:57:39 +0300 Subject: [PATCH 35/67] repo methods --- .../employee/EmployeeRepository.java | 6 +- .../repository/project/ProjectRepository.java | 61 +++++++++++++++++-- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index d2b2ee2..238154e 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -42,7 +42,7 @@ public Employee getById(int id) { } catch (SQLException e) { System.err.println(e.getMessage()); } - return new Employee(); + return null; } public Employee create(Employee employee) { @@ -86,8 +86,4 @@ public Object delete(int id) { } return null; } - - public Employee update(Employee employee) { - return null; - } } diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 749b8fd..3e81256 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -1,12 +1,17 @@ package org.lab.infra.db.repository.project; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + import java.util.ArrayList; import java.util.List; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.project.model.Project; import org.lab.infra.db.client.DatabaseClient; -import org.lab.infra.db.spec.Specification; +import org.lab.infra.db.spec.SqlSpec; public class ProjectRepository { @@ -18,8 +23,24 @@ public ProjectRepository() { objectMapper = new ObjectMapper(); } - public Project get(int projectId) { - return new Project(); + public Project get( + int projectId + ) { + String sql = "SELECT * FROM projects WHERE id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, projectId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return objectMapper.mapFromRaw(rs, Project.class); + } + } + } catch (SQLException e) { + System.err.println(e.getMessage()); + } + return null; } public Project create(Project project) { @@ -27,10 +48,38 @@ public Project create(Project project) { } public List list( - Specification specification + SqlSpec specification ) { - return new ArrayList<>(); + String sql = "SELECT * FROM projects WHERE" + specification.toSql(); + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + List params = specification.getParams(); + for (int i = 0; i < params.size(); i++) { + stmt.setObject(i+1, params.get(i)); + } + try (ResultSet rs = stmt.executeQuery()) { + List projects = new ArrayList<>(); + while (rs.next()) { + projects.add(objectMapper.mapFromRaw(rs, Project.class)); + } + return projects; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } } - public void delete(int projectId) {} + public void delete(int projectId) { + String sql = "DELETE FROM projects WHERE id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, projectId); + } catch (SQLException e) { + System.err.println(e.getMessage()); + } + } } From 757e159b058428cef254bf343c179784e2284849 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Wed, 3 Dec 2025 23:17:10 +0300 Subject: [PATCH 36/67] added cast --- .../infra/db/repository/project/ProjectRepository.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 3e81256..3a537f5 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -11,6 +11,7 @@ import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.project.model.Project; import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.spec.Specification; import org.lab.infra.db.spec.SqlSpec; public class ProjectRepository { @@ -48,14 +49,15 @@ public Project create(Project project) { } public List list( - SqlSpec specification + Specification specification ) { - String sql = "SELECT * FROM projects WHERE" + specification.toSql(); + SqlSpec spec = (SqlSpec) specification; + String sql = "SELECT * FROM projects WHERE" + spec.toSql(); try ( Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) ) { - List params = specification.getParams(); + List params = spec.getParams(); for (int i = 0; i < params.size(); i++) { stmt.setObject(i+1, params.get(i)); } From 44c94677a3a905b65683c822136ba30f64683c3d Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Thu, 4 Dec 2025 00:36:39 +0300 Subject: [PATCH 37/67] tests for delete employee adapter --- src/main/java/org/lab/Main.java | 6 +- .../employee/EmployeeDeleteAdapter.java | 12 +- .../shared/exceptions/DatabaseException.java | 7 ++ .../employee/EmployeeRepository.java | 3 +- .../employee/TestEmployeeDeleteAdapter.java | 112 ++++++++++++++++++ .../use_cases/TestCreateEmployeeUseCase.java | 25 ---- .../use_cases/TestGetEmployeeUseCase.java | 5 +- 7 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/lab/domain/shared/exceptions/DatabaseException.java create mode 100644 src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 328a8a7..48b492d 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -111,10 +111,10 @@ public static void main(String[] args) { app.get("/", ctx -> ctx.result("Hello World")); app.post("/employee", createEmployeeAdapter::createEmployee); - app.delete("/employee", deleteEmployeeAdapter::deleteEmployee); - app.get("/employee", getEmployeeAdapter::getEmployee); + app.delete("/employee/{employeeId}/{actorId}", deleteEmployeeAdapter::deleteEmployee); + app.get("/employee/{employeeId}/{actorId}", getEmployeeAdapter::getEmployee); - app.post("/project/{employeeId}", createProjectAdapter::createProject); + app.post("/project", createProjectAdapter::createProject); app.get("/project/{projectId}/{employeeId}", projectGetAdapter::getProject); app.delete("/project/{projectId}/{employeeId}", projectDeleteAdapter::deleteProject); app.get("/project/list/{employeeId}", projectListAdapter::listProjects); diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java index 8c7f285..14a7e21 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -21,12 +21,18 @@ public Context deleteEmployee( Context ctx ) { try { - DeleteEmployeeDTO dto = ctx.bodyAsClass(DeleteEmployeeDTO.class); - this.useCase.execute(dto.deletedEmployeeId(), dto.employeeId()); + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int actorId = Integer.parseInt(ctx.pathParam("actorId")); + this.useCase.execute(employeeId, actorId); return ctx.status(201); } catch (NotPermittedException e) { - return ctx.status(403).json(Map.of("error", e.getMessage())); + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); } catch (Exception e) { return ctx.status(500).json(Map.of("error", "Internal server error")); diff --git a/src/main/java/org/lab/domain/shared/exceptions/DatabaseException.java b/src/main/java/org/lab/domain/shared/exceptions/DatabaseException.java new file mode 100644 index 0000000..dc5b230 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/DatabaseException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class DatabaseException extends RuntimeException { + public DatabaseException() { + super("Something went wrong with database operation"); + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 238154e..fe699aa 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -1,6 +1,7 @@ package org.lab.infra.db.repository.employee; import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.DatabaseException; import org.lab.infra.db.client.DatabaseClient; import org.lab.core.utils.mapper.ObjectMapper; @@ -40,7 +41,7 @@ public Employee getById(int id) { } } } catch (SQLException e) { - System.err.println(e.getMessage()); + throw new DatabaseException(); } return null; } diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java new file mode 100644 index 0000000..6618f97 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java @@ -0,0 +1,112 @@ +package org.lab.api.adapters.employee; + +import java.util.Map; + +import io.javalin.http.Context; +import org.mockito.Mockito; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.domain.shared.exceptions.DatabaseException; + +public class TestEmployeeDeleteAdapter { + private DeleteEmployeeUseCase deleteEmployeeUseCase; + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator employeePermissionValidator; + private EmployeeDeleteAdapter employeeDeleteAdapter; + private Context ctx; + + @BeforeEach + public void setUp() { + ctx = Mockito.mock(Context.class); + employeeRepository = Mockito.mock(EmployeeRepository.class); + employeePermissionValidator = new EmployeePermissionValidator( + employeeRepository + ); + deleteEmployeeUseCase = new DeleteEmployeeUseCase( + employeeRepository, + employeePermissionValidator + ); + employeeDeleteAdapter = new EmployeeDeleteAdapter(deleteEmployeeUseCase); + } + + @Test + public void testDeleteEmployeeFetchesNotPermittedException() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository.getById(notManagerEmployee.getId())).thenReturn(notManagerEmployee); + + Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json(Map.of("error", "You do not have permission to perform this operation")); + } + + @Test + public void testDeleteEmployeeFetchesUnexpectedExceptionWhenFetchingEmployee() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(notManagerEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } + + @Test + public void testDeleteEmployeeFetchesUnexpectedExceptionWhenDeletingEmployee() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.doThrow(DatabaseException.class).when(employeeRepository).delete(testEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index a4e2ca6..febd0d1 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -56,31 +56,6 @@ public void testCreateEmployeeSuccess() { Mockito.verify(employeeRepository).getById(1); } - @Test - public void testCreateEmployeeRaisesUserAlreadyExistsException() { - Employee input = new Employee(); - input.setId(123); - input.setName("Tim Cock"); - - Employee creator = new Employee(); - creator.setId(1); - creator.setType(EmployeeType.PROGRAMMER); - - Mockito.when(employeeRepository.getById(1)).thenReturn(creator); - Mockito.when(employeeRepository.getById(123)).thenReturn(input); - - RuntimeException thrown = Assertions.assertThrows( - RuntimeException.class, - () -> useCase.execute(input, 1) - ); - - Assertions.assertTrue( - thrown.getCause() instanceof ExecutionException && - ((ExecutionException) thrown.getCause()).getCause() instanceof UserAlreadyExistsException - ); - Mockito.verify(employeeRepository).getById(123); - } - @Test public void testCreateEmployeeRaisesNotPermittedException() { Employee input = new Employee(); diff --git a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java index e0f8530..10694cd 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeeProvider; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; @@ -14,14 +15,16 @@ public class TestGetEmployeeUseCase { private EmployeeRepository employeeRepository; + private EmployeeProvider employeeProvider; private EmployeePermissionValidator validator; private GetEmployeeUseCase useCase; @BeforeEach public void setUp() { employeeRepository = Mockito.mock(EmployeeRepository.class); + employeeProvider = new EmployeeProvider(employeeRepository); validator = new EmployeePermissionValidator(employeeRepository); - useCase = new GetEmployeeUseCase(employeeRepository, validator); + useCase = new GetEmployeeUseCase(validator, employeeProvider); } @Test From 005a0f7409b8d09da86994623bb558dc03547527 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Thu, 4 Dec 2025 00:43:13 +0300 Subject: [PATCH 38/67] added test file for get adapter, added success test for deeltion --- .../employee/EmployeeCreateAdapter.java | 12 +++++++--- .../employee/EmployeeDeleteAdapter.java | 1 - .../employee/TestEmployeeDeleteAdapter.java | 22 +++++++++++++++++++ .../employee/TestEmployeeGetAdapter.java | 4 ++++ 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java index dc40251..93a1ad9 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -1,11 +1,12 @@ package org.lab.api.adapters.employee; import io.javalin.http.Context; -import org.lab.core.utils.mapper.ObjectMapper; + import org.lab.application.employee.dto.GetEmployeeDTOOut; import org.lab.application.employee.dto.CreateEmployeeDTO; -import org.lab.domain.emploee.model.Employee; import org.lab.application.employee.use_cases.CreateEmployeeUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.domain.shared.exceptions.UserAlreadyExistsException; @@ -44,7 +45,12 @@ public Context createEmployee( return ctx.status(409).json(Map.of("error", "User already exists")); } catch (NotPermittedException e) { - return ctx.status(403).json(Map.of("error", e.getMessage())); + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); } catch (Exception e) { return ctx.status(500).json(Map.of("error", "Internal server error")); diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java index 14a7e21..cedf3ad 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -1,7 +1,6 @@ package org.lab.api.adapters.employee; import io.javalin.http.Context; -import org.lab.application.employee.dto.DeleteEmployeeDTO; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; import org.lab.domain.shared.exceptions.NotPermittedException; diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java index 6618f97..4eab84a 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeDeleteAdapter.java @@ -35,6 +35,28 @@ public void setUp() { employeeDeleteAdapter = new EmployeeDeleteAdapter(deleteEmployeeUseCase); } + @Test + public void testDeleteEmployeeSuccess() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("Manager"); + notManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository.getById(notManagerEmployee.getId())).thenReturn(notManagerEmployee); + + Mockito.when(ctx.status(201)).thenReturn(ctx); + + employeeDeleteAdapter.deleteEmployee(ctx); + Mockito.verify(ctx).status(201); + } + @Test public void testDeleteEmployeeFetchesNotPermittedException() { Employee notManagerEmployee = new Employee(); diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java new file mode 100644 index 0000000..87a7881 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.employee; + +public class TestEmployeeGetAdapter { +} From 55d1edb273960435aa9eecf97e9514da593693d4 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Thu, 4 Dec 2025 00:56:49 +0300 Subject: [PATCH 39/67] get success test --- .../adapters/employee/EmployeeGetAdapter.java | 19 +++--- .../employee/TestEmployeeGetAdapter.java | 63 +++++++++++++++++++ 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java index d17f36b..10ba92a 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -1,7 +1,6 @@ package org.lab.api.adapters.employee; import io.javalin.http.Context; -import org.lab.application.employee.dto.GetEmployeeDTOIn; import org.lab.application.employee.dto.GetEmployeeDTOOut; import org.lab.application.employee.use_cases.GetEmployeeUseCase; import org.lab.core.utils.mapper.ObjectMapper; @@ -27,13 +26,11 @@ public Context getEmployee( Context ctx ) { try { - GetEmployeeDTOIn dto = new GetEmployeeDTOIn( - Integer.parseInt(ctx.queryParam("employeeId")), - Integer.parseInt(ctx.queryParam("actorId")) - ); + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int actorId = Integer.parseInt(ctx.pathParam("actorId")); Employee receivedEmployee = useCase.execute( - dto.employeeId(), - dto.actorId() + employeeId, + actorId ); GetEmployeeDTOOut presentationEmployee = mapper.mapToPresentation( receivedEmployee, @@ -42,10 +39,14 @@ public Context getEmployee( return ctx.status(201).json(presentationEmployee); } catch (NotPermittedException e) { - return ctx.status(403).json(Map.of("error", e.getMessage())); + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); } catch (Exception e) { - System.err.println(e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java index 87a7881..3524d3f 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java @@ -1,4 +1,67 @@ package org.lab.api.adapters.employee; +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; +import org.lab.application.employee.use_cases.GetEmployeeUseCase; +import org.lab.application.shared.services.EmployeePermissionValidator; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.mockito.Mockito; + public class TestEmployeeGetAdapter { + private GetEmployeeUseCase getEmployeeUseCase; + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator employeePermissionValidator; + private EmployeeGetAdapter employeeGetAdapter; + private EmployeeProvider employeeProvider; + private Context ctx; + private ObjectMapper mapper; + + @BeforeEach + public void setUp() { + ctx = Mockito.mock(Context.class); + employeeRepository = Mockito.mock(EmployeeRepository.class); + employeePermissionValidator = new EmployeePermissionValidator( + employeeRepository + ); + employeeProvider = new EmployeeProvider(employeeRepository); + getEmployeeUseCase = new GetEmployeeUseCase( + employeePermissionValidator, + employeeProvider + ); + mapper = new ObjectMapper(); + employeeGetAdapter = new EmployeeGetAdapter( + getEmployeeUseCase, + mapper + ); + } + + @Test + public void testGetEmployeeSuccess() { + Employee ManagerEmployee = new Employee(); + ManagerEmployee.setId(1); + ManagerEmployee.setName("Manager"); + ManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository.getById(ManagerEmployee.getId())).thenReturn(ManagerEmployee); + Mockito.when(employeeRepository.getById(testEmployee.getId())).thenReturn(testEmployee); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + Mockito.verify(ctx).status(201); + } } From fbdd26041df2490510a8b4fa182068c4c058a906 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Thu, 4 Dec 2025 01:04:48 +0300 Subject: [PATCH 40/67] rm redundant dto --- .../lab/api/adapters/employee/EmployeeCreateAdapter.java | 6 +++--- .../lab/api/adapters/employee/EmployeeGetAdapter.java | 6 +++--- .../dto/{GetEmployeeDTOOut.java => GetEmployeeDTO.java} | 2 +- .../lab/application/employee/dto/GetEmployeeDTOIn.java | 9 --------- .../employee/use_cases/TestCreateEmployeeUseCase.java | 1 - 5 files changed, 7 insertions(+), 17 deletions(-) rename src/main/java/org/lab/application/employee/dto/{GetEmployeeDTOOut.java => GetEmployeeDTO.java} (91%) delete mode 100644 src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java index 93a1ad9..ac88e26 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -2,7 +2,7 @@ import io.javalin.http.Context; -import org.lab.application.employee.dto.GetEmployeeDTOOut; +import org.lab.application.employee.dto.GetEmployeeDTO; import org.lab.application.employee.dto.CreateEmployeeDTO; import org.lab.application.employee.use_cases.CreateEmployeeUseCase; import org.lab.core.utils.mapper.ObjectMapper; @@ -35,9 +35,9 @@ public Context createEmployee( employee, dto.creatorId() ); - GetEmployeeDTOOut presentationEmployee = mapper.mapToPresentation( + GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( createdEmployee, - GetEmployeeDTOOut.class + GetEmployeeDTO.class ); return ctx.status(201).json(presentationEmployee); diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java index 10ba92a..2535c4d 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -1,7 +1,7 @@ package org.lab.api.adapters.employee; import io.javalin.http.Context; -import org.lab.application.employee.dto.GetEmployeeDTOOut; +import org.lab.application.employee.dto.GetEmployeeDTO; import org.lab.application.employee.use_cases.GetEmployeeUseCase; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.emploee.model.Employee; @@ -32,9 +32,9 @@ public Context getEmployee( employeeId, actorId ); - GetEmployeeDTOOut presentationEmployee = mapper.mapToPresentation( + GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( receivedEmployee, - GetEmployeeDTOOut.class + GetEmployeeDTO.class ); return ctx.status(201).json(presentationEmployee); diff --git a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOOut.java b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java similarity index 91% rename from src/main/java/org/lab/application/employee/dto/GetEmployeeDTOOut.java rename to src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java index 6b0b4e4..9875ce6 100644 --- a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOOut.java +++ b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTO.java @@ -5,7 +5,7 @@ import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.interfaces.PresentationObject; -public record GetEmployeeDTOOut( +public record GetEmployeeDTO( int id, String name, int age, diff --git a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java b/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java deleted file mode 100644 index 3beca22..0000000 --- a/src/main/java/org/lab/application/employee/dto/GetEmployeeDTOIn.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.lab.application.employee.dto; - -import org.lab.domain.interfaces.PresentationObject; - -public record GetEmployeeDTOIn( - int employeeId, - int actorId -) implements PresentationObject { -} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index febd0d1..da7fa0a 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -9,7 +9,6 @@ import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.shared.exceptions.NotPermittedException; -import org.lab.domain.shared.exceptions.UserAlreadyExistsException; import org.lab.application.employee.services.CreateValidator; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; From 3c3fc1ccd3e3cb3bbc92edf9cd5fe40cb3960a50 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Thu, 4 Dec 2025 01:14:11 +0300 Subject: [PATCH 41/67] fail test for get --- .../employee/TestEmployeeGetAdapter.java | 86 ++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java index 3524d3f..b714da8 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java @@ -1,17 +1,20 @@ package org.lab.api.adapters.employee; +import java.util.Map; + import io.javalin.http.Context; +import org.mockito.Mockito; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; + import org.lab.application.employee.use_cases.GetEmployeeUseCase; import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.application.shared.services.EmployeeProvider; import org.lab.core.constants.employee.EmployeeType; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.DatabaseException; import org.lab.infra.db.repository.employee.EmployeeRepository; -import org.mockito.Mockito; public class TestEmployeeGetAdapter { private GetEmployeeUseCase getEmployeeUseCase; @@ -64,4 +67,83 @@ public void testGetEmployeeSuccess() { employeeGetAdapter.getEmployee(ctx); Mockito.verify(ctx).status(201); } + + @Test + public void testGetEmployeeHandlesNotPermittedException() { + Employee ManagerEmployee = new Employee(); + ManagerEmployee.setId(1); + ManagerEmployee.setName("Manager"); + ManagerEmployee.setType(EmployeeType.PROGRAMMER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository.getById(ManagerEmployee.getId())).thenReturn(ManagerEmployee); + Mockito.when(employeeRepository.getById(testEmployee.getId())).thenReturn(testEmployee); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json(Map.of("error", "You do not have permission to perform this operation")); + } + + @Test + public void testDeleteEmployeeFetchesUnexpectedExceptionWhenFetchingActor() { + Employee notManagerEmployee = new Employee(); + notManagerEmployee.setId(1); + notManagerEmployee.setName("NotManager"); + notManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + + Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(notManagerEmployee.getId()); + Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(notManagerEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } + + @Test + public void testDeleteEmployeeFetchesUnexpectedExceptionWhenFetchingEmployee() { + Employee ManagerEmployee = new Employee(); + ManagerEmployee.setId(1); + ManagerEmployee.setName("NotManager"); + ManagerEmployee.setType(EmployeeType.MANAGER); + + Employee testEmployee = new Employee(); + testEmployee.setId(2); + testEmployee.setName("test"); + testEmployee.setType(EmployeeType.TESTER); + + Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + + Mockito.when(employeeRepository.getById(ManagerEmployee.getId())).thenReturn(ManagerEmployee); + Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(testEmployee.getId()); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + employeeGetAdapter.getEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } } From d9c3d4ed3c1a1a2a97b1066b934302b96fbe36c0 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 5 Dec 2025 23:34:44 +0300 Subject: [PATCH 42/67] created method in repository --- .../ticket/dto/CreateTicketDTO.java | 4 ++ .../ticket/use_cases/CloseTicketUseCase.java | 4 ++ .../ticket/use_cases/CreateTicketUseCase.java | 4 ++ .../repository/project/ProjectRepository.java | 55 +++++++++++++++++-- .../employee/TestEmployeeGetAdapter.java | 4 +- 5 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java create mode 100644 src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java create mode 100644 src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java diff --git a/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java b/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java new file mode 100644 index 0000000..6b9174e --- /dev/null +++ b/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java @@ -0,0 +1,4 @@ +package org.lab.application.ticket.dto; + +public record CreateTicketDTO() { +} diff --git a/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java new file mode 100644 index 0000000..6a8cdaf --- /dev/null +++ b/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java @@ -0,0 +1,4 @@ +package org.lab.application.ticket.use_cases; + +public class CloseTicketUseCase { +} diff --git a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java new file mode 100644 index 0000000..f8edc43 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java @@ -0,0 +1,4 @@ +package org.lab.application.ticket.use_cases; + +public class CreateTicketUseCase { +} diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 3a537f5..4b4ee21 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -1,13 +1,11 @@ package org.lab.infra.db.repository.project; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.*; import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.core.JsonProcessingException; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.project.model.Project; import org.lab.infra.db.client.DatabaseClient; @@ -45,7 +43,47 @@ public Project get( } public Project create(Project project) { - return new Project(); + String sql = """ + INSERT INTO projects ( + name, + description, + managerId, + teamLeadId, + developerIds, + testerIds, + createdBy + ) + VALUES (?, ?, ?, ?, ?::jsonb, ?::jsonb, ?) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setString(1, project.getName()); + stmt.setString(2, project.getDescription()); + stmt.setInt(3, project.getManagerId()); + + if (project.getTeamLeadId() != null) + stmt.setInt(4, project.getTeamLeadId()); + else + stmt.setNull(4, Types.INTEGER); + + stmt.setString(5, toJson(project.getDeveloperIds())); + stmt.setString(6, toJson(project.getTesterIds())); + + stmt.setInt(7, project.getCreatedBy()); + + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + return objectMapper.mapFromRaw(rs, Project.class); + } + } + } catch (SQLException | JsonProcessingException e) { + System.err.println(e.getMessage()); + } + return null; } public List list( @@ -84,4 +122,11 @@ public void delete(int projectId) { System.err.println(e.getMessage()); } } + + private String toJson(List list) + throws + JsonProcessingException + { + return new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(list); + } } diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java index b714da8..6fa2e7a 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java @@ -94,7 +94,7 @@ public void testGetEmployeeHandlesNotPermittedException() { } @Test - public void testDeleteEmployeeFetchesUnexpectedExceptionWhenFetchingActor() { + public void testGetEmployeeFetchesUnexpectedExceptionWhenFetchingActor() { Employee notManagerEmployee = new Employee(); notManagerEmployee.setId(1); notManagerEmployee.setName("NotManager"); @@ -121,7 +121,7 @@ public void testDeleteEmployeeFetchesUnexpectedExceptionWhenFetchingActor() { } @Test - public void testDeleteEmployeeFetchesUnexpectedExceptionWhenFetchingEmployee() { + public void testGetEmployeeFetchesUnexpectedExceptionWhenFetchingEmployee() { Employee ManagerEmployee = new Employee(); ManagerEmployee.setId(1); ManagerEmployee.setName("NotManager"); From 4a920d7ee634adcb21fc072c27296e0f7db3b6fe Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 5 Dec 2025 23:46:51 +0300 Subject: [PATCH 43/67] added test for creating employee --- .../adapters/ticket/TicketCloseAdapter.java | 4 + .../adapters/ticket/TicketCreateAdapter.java | 4 + .../ticket/dto/CreateTicketDTO.java | 4 - .../employee/EmployeeRepository.java | 3 +- .../employee/TestEmployeeCreateAdapter.java | 135 ++++++++++++++++++ 5 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java create mode 100644 src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java delete mode 100644 src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java create mode 100644 src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java new file mode 100644 index 0000000..b4925db --- /dev/null +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.ticket; + +public class TicketCloseAdapter { +} diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java new file mode 100644 index 0000000..17ee441 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.ticket; + +public class TicketCreateAdapter { +} diff --git a/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java b/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java deleted file mode 100644 index 6b9174e..0000000 --- a/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.lab.application.ticket.dto; - -public record CreateTicketDTO() { -} diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index fe699aa..a0e7a7f 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -36,7 +36,6 @@ public Employee getById(int id) { Object value = rs.getObject(i); raw.put(columnName, value); } - System.out.println("value = " + raw); return objectMapper.mapFromRaw(raw, Employee.class); } } @@ -66,7 +65,7 @@ INSERT INTO employees (name, age, type, created_by, create_date) ); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - System.out.println("mapping should be here"); + return objectMapper.mapFromRaw(rs, Employee.class); } } } catch (SQLException e) { diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java new file mode 100644 index 0000000..3b231e6 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java @@ -0,0 +1,135 @@ +package org.lab.api.adapters.employee; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.employee.dto.CreateEmployeeDTO; +import org.lab.application.employee.dto.GetEmployeeDTO; +import org.lab.application.employee.use_cases.CreateEmployeeUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.Map; + +public class TestEmployeeCreateAdapter { + + private ObjectMapper mapper; + private CreateEmployeeUseCase useCase; + private EmployeeCreateAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + mapper = Mockito.mock(ObjectMapper.class); + useCase = Mockito.mock(CreateEmployeeUseCase.class); + ctx = Mockito.mock(Context.class); + adapter = new EmployeeCreateAdapter(mapper, useCase); + } + + @Test + public void testCreateEmployeeSuccess() { + CreateEmployeeDTO dto = new CreateEmployeeDTO( + "John", + 30, + EmployeeType.PROGRAMMER, + 99 + ); + + Employee mappedEmployee = new Employee(); + mappedEmployee.setName("John"); + mappedEmployee.setAge(30); + mappedEmployee.setType(EmployeeType.PROGRAMMER); + + Employee createdEmployee = new Employee(); + createdEmployee.setId(5); + createdEmployee.setName("John"); + createdEmployee.setAge(30); + createdEmployee.setType(EmployeeType.PROGRAMMER); + createdEmployee.setCreatedBy(99); + createdEmployee.setCreatedDate(new Date()); + + GetEmployeeDTO presentation = new GetEmployeeDTO( + createdEmployee.getId(), + createdEmployee.getName(), + createdEmployee.getAge(), + createdEmployee.getType(), + createdEmployee.getCreatedBy(), + createdEmployee.getCreatedDate() + ); + + Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)) + .thenReturn(dto); + + Mockito.when(mapper.mapToDomain(dto, Employee.class)) + .thenReturn(mappedEmployee); + + Mockito.when(useCase.execute(mappedEmployee, dto.creatorId())) + .thenReturn(createdEmployee); + + Mockito.when(mapper.mapToPresentation(createdEmployee, GetEmployeeDTO.class)) + .thenReturn(presentation); + + Mockito.when(ctx.status(201)).thenReturn(ctx); + Mockito.when(ctx.json(presentation)).thenReturn(ctx); + + adapter.createEmployee(ctx); + + Mockito.verify(ctx).status(201); + Mockito.verify(ctx).json(presentation); + } + + @Test + public void testCreateEmployeeNotPermitted() { + CreateEmployeeDTO dto = new CreateEmployeeDTO( + "John", + 30, + EmployeeType.PROGRAMMER, + 99 + ); + + Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)).thenReturn(dto); + Mockito.when(mapper.mapToDomain(dto, Employee.class)) + .thenReturn(new Employee()); + + Mockito.when(useCase.execute(Mockito.any(), Mockito.eq(dto.creatorId()))) + .thenThrow(new NotPermittedException("You do not have permission to perform this operation")); + + Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createEmployee(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of("error", "You do not have permission to perform this operation") + ); + } + + @Test + public void testCreateEmployeeUnexpectedError() { + CreateEmployeeDTO dto = new CreateEmployeeDTO( + "John", + 30, + EmployeeType.PROGRAMMER, + 99 + ); + + Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)).thenReturn(dto); + + Mockito.when(mapper.mapToDomain(dto, Employee.class)) + .thenThrow(new RuntimeException("boom")); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createEmployee(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} From 802d1c49d6b68c7923e52dba0b846d3e2876e44a Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 5 Dec 2025 23:50:45 +0300 Subject: [PATCH 44/67] added exception handling in projectcreateadapter --- .../project/ProjectCreateAdapter.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java index 9fb8db2..24f322f 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java @@ -7,6 +7,9 @@ import org.lab.application.project.use_cases.CreateProjectUseCase; import org.lab.domain.project.model.Project; import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; + +import java.util.Map; public class ProjectCreateAdapter { @@ -24,13 +27,25 @@ public ProjectCreateAdapter( public Context createProject( Context ctx ) { - CreateProjectDTO dto = ctx.bodyAsClass(CreateProjectDTO.class); - Project project = objectMapper.mapToDomain(dto, Project.class); - Project createdProject = useCase.execute(project); - GetProjectDTO presentationProject = objectMapper.mapToPresentation( - createdProject, - GetProjectDTO.class - ); - return ctx.status(201).json(presentationProject); + try { + CreateProjectDTO dto = ctx.bodyAsClass(CreateProjectDTO.class); + Project project = objectMapper.mapToDomain(dto, Project.class); + Project createdProject = useCase.execute(project); + GetProjectDTO presentationProject = objectMapper.mapToPresentation( + createdProject, + GetProjectDTO.class + ); + return ctx.status(201).json(presentationProject); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } } } From 5f8e286718e77143276861456ffd7e56749242a5 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 5 Dec 2025 23:54:59 +0300 Subject: [PATCH 45/67] added tests fro project create adapter --- .../project/dto/CreateProjectDTO.java | 2 - .../employee/TestEmployeeGetAdapter.java | 64 +++++-- .../project/TestProjectCreateAdapter.java | 168 ++++++++++++++++++ 3 files changed, 215 insertions(+), 19 deletions(-) create mode 100644 src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java diff --git a/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java index a485991..5cc1d19 100644 --- a/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java +++ b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java @@ -1,9 +1,7 @@ package org.lab.application.project.dto; -import org.lab.core.constants.project.ProjectStatus; import org.lab.domain.interfaces.PresentationObject; -import java.util.Date; import java.util.List; public record CreateProjectDTO( diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java index 6fa2e7a..ad3d07c 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeGetAdapter.java @@ -56,10 +56,16 @@ public void testGetEmployeeSuccess() { testEmployee.setName("test"); testEmployee.setType(EmployeeType.TESTER); - Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(ManagerEmployee.getId())); - Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); - Mockito.when(employeeRepository.getById(ManagerEmployee.getId())).thenReturn(ManagerEmployee); - Mockito.when(employeeRepository.getById(testEmployee.getId())).thenReturn(testEmployee); + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository + .getById(ManagerEmployee.getId())) + .thenReturn(ManagerEmployee); + Mockito.when(employeeRepository + .getById(testEmployee.getId())) + .thenReturn(testEmployee); Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); @@ -80,17 +86,28 @@ public void testGetEmployeeHandlesNotPermittedException() { testEmployee.setName("test"); testEmployee.setType(EmployeeType.TESTER); - Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(ManagerEmployee.getId())); - Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); - Mockito.when(employeeRepository.getById(ManagerEmployee.getId())).thenReturn(ManagerEmployee); - Mockito.when(employeeRepository.getById(testEmployee.getId())).thenReturn(testEmployee); + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(employeeRepository + .getById(ManagerEmployee.getId())) + .thenReturn(ManagerEmployee); + Mockito.when(employeeRepository + .getById(testEmployee.getId())) + .thenReturn(testEmployee); Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); employeeGetAdapter.getEmployee(ctx); Mockito.verify(ctx).status(403); - Mockito.verify(ctx).json(Map.of("error", "You do not have permission to perform this operation")); + Mockito.verify(ctx).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); } @Test @@ -105,11 +122,17 @@ public void testGetEmployeeFetchesUnexpectedExceptionWhenFetchingActor() { testEmployee.setName("test"); testEmployee.setType(EmployeeType.TESTER); - Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(notManagerEmployee.getId())); - Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(notManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); - Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(notManagerEmployee.getId()); - Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(notManagerEmployee.getId()); + Mockito.doThrow(DatabaseException.class) + .when(employeeRepository) + .getById(notManagerEmployee.getId()); + Mockito.doThrow(DatabaseException.class) + .when(employeeRepository) + .getById(notManagerEmployee.getId()); Mockito.when(ctx.status(500)).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); @@ -132,11 +155,18 @@ public void testGetEmployeeFetchesUnexpectedExceptionWhenFetchingEmployee() { testEmployee.setName("test"); testEmployee.setType(EmployeeType.TESTER); - Mockito.when(ctx.pathParam("actorId")).thenReturn(String.valueOf(ManagerEmployee.getId())); - Mockito.when(ctx.pathParam("employeeId")).thenReturn(String.valueOf(testEmployee.getId())); + Mockito.when(ctx.pathParam("actorId")) + .thenReturn(String.valueOf(ManagerEmployee.getId())); + Mockito.when(ctx.pathParam("employeeId")) + .thenReturn(String.valueOf(testEmployee.getId())); + + Mockito.when(employeeRepository + .getById(ManagerEmployee.getId())) + .thenReturn(ManagerEmployee); - Mockito.when(employeeRepository.getById(ManagerEmployee.getId())).thenReturn(ManagerEmployee); - Mockito.doThrow(DatabaseException.class).when(employeeRepository).getById(testEmployee.getId()); + Mockito.doThrow(DatabaseException.class) + .when(employeeRepository) + .getById(testEmployee.getId()); Mockito.when(ctx.status(500)).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java new file mode 100644 index 0000000..852a585 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java @@ -0,0 +1,168 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.dto.CreateProjectDTO; +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.CreateProjectUseCase; + +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class TestProjectCreateAdapter { + + private ObjectMapper mapper; + private CreateProjectUseCase useCase; + private ProjectCreateAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + mapper = Mockito.mock(ObjectMapper.class); + useCase = Mockito.mock(CreateProjectUseCase.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectCreateAdapter(mapper, useCase); + } + + @Test + public void testCreateProjectSuccess() { + CreateProjectDTO dto = new CreateProjectDTO( + "TestProject", + "Description", + 10, + 20, + List.of(1, 2), + List.of(3, 4) + ); + + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)) + .thenReturn(dto); + + Project mapped = new Project(); + Mockito.when(mapper.mapToDomain(dto, Project.class)) + .thenReturn(mapped); + + Project created = new Project(); + created.setId(111); + created.setName("TestProject"); + created.setDescription("Description"); + created.setManagerId(10); + created.setTeamLeadId(20); + created.setDeveloperIds(List.of(1, 2)); + created.setTesterIds(List.of(3, 4)); + created.setStatus(ProjectStatus.OPEN); + created.setMilestoneIds(List.of()); + created.setBugReportIds(List.of()); + created.setCreatedDate(new Date()); + created.setCreatedBy(99); + + Mockito.when(useCase.execute(mapped)) + .thenReturn(created); + + GetProjectDTO presentation = new GetProjectDTO( + created.getId(), + created.getName(), + created.getDescription(), + + created.getManagerId(), + created.getTeamLeadId(), + + created.getDeveloperIds(), + created.getTesterIds(), + + created.getStatus(), + + created.getCurrentMilestoneId(), + created.getMilestoneIds(), + + created.getBugReportIds(), + + created.getCreatedDate(), + created.getCreatedBy(), + created.getUpdatedDate(), + created.getUpdatedBy() + ); + + Mockito.when(mapper.mapToPresentation(created, GetProjectDTO.class)) + .thenReturn(presentation); + + Mockito.when(ctx.status(201)).thenReturn(ctx); + Mockito.when(ctx.json(presentation)).thenReturn(ctx); + + adapter.createProject(ctx); + + Mockito.verify(ctx).status(201); + Mockito.verify(ctx).json(presentation); + } + + @Test + public void testCreateProject_NotPermitted() { + CreateProjectDTO dto = new CreateProjectDTO( + "TestProject", + "Description", + 10, + 20, + List.of(), + List.of() + ); + + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)) + .thenReturn(dto); + + Mockito.when(mapper.mapToDomain(dto, Project.class)) + .thenReturn(new Project()); + + Mockito.when(useCase.execute(Mockito.any())) + .thenThrow( + new NotPermittedException( + "You do not have permission to perform this operation" + ) + ); + + Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createProject(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of("error", "You do not have permission to perform this operation") + ); + } + + @Test + public void testCreateProject_UnexpectedError() { + CreateProjectDTO dto = new CreateProjectDTO( + "TestProject", + "Description", + 10, + 20, + List.of(), + List.of() + ); + + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)) + .thenReturn(dto); + + Mockito.when(mapper.mapToDomain(dto, Project.class)) + .thenThrow(new RuntimeException("boom")); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.createProject(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} From 59b910d93ff3100ed7fadcf465d3d73686b580c1 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 00:05:39 +0300 Subject: [PATCH 46/67] tests for all project adapters --- .../project/ProjectDeleteAdapter.java | 2 +- .../adapters/project/ProjectGetAdapter.java | 4 +- .../adapters/project/ProjectListAdapter.java | 2 +- .../employee/TestEmployeeCreateAdapter.java | 4 +- .../project/TestProjectCreateAdapter.java | 5 +- .../project/TestProjectDeleteAdapter.java | 116 +++++++++++++ .../project/TestProjectGetAdapter.java | 155 ++++++++++++++++++ .../project/TestProjectListAdapter.java | 111 +++++++++++++ 8 files changed, 393 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/lab/api/adapters/project/TestProjectDeleteAdapter.java create mode 100644 src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java create mode 100644 src/test/java/org/lab/api/adapters/project/TestProjectListAdapter.java diff --git a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java index 7110b6c..7f19c3e 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java @@ -26,7 +26,7 @@ public Context deleteProject( int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); int projectId = Integer.parseInt(ctx.pathParam("projectId")); this.useCase.execute(employeeId, projectId); - return ctx.status(201); + return ctx.status(204); } catch (UserNotFoundException e) { return ctx.status(409).json(Map.of("error", "User doesnt exist")); diff --git a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java index 77ef71d..8b63666 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java @@ -37,7 +37,7 @@ public Context getProject(Context ctx) { project, GetProjectDTO.class ); - return ctx.status(201).json(presentationProject); + return ctx.status(200).json(presentationProject); } catch (UserNotFoundException e) { return ctx.status(409).json(Map.of("error", "User doesnt exist")); @@ -51,7 +51,7 @@ public Context getProject(Context ctx) { ); } catch (ProjectNotFoundException e) { - return ctx.status(404).json(Map.of("error", "Project doesn't exist")); + return ctx.status(404).json(Map.of("error", "Project doesnt exist")); } catch (Exception e) { return ctx.status(500).json(Map.of("error", "Internal server error")); diff --git a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java index 8b04bb2..93984b7 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java @@ -39,7 +39,7 @@ public Context listProjects( ); presentationProjects.add(presentationProject); } - return ctx.status(201).json(presentationProjects); + return ctx.status(200).json(presentationProjects); } catch (UserNotFoundException e) { return ctx.status(409).json(Map.of("error", "User doesnt exist")); } diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java index 3b231e6..71ab999 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java @@ -97,7 +97,9 @@ public void testCreateEmployeeNotPermitted() { .thenReturn(new Employee()); Mockito.when(useCase.execute(Mockito.any(), Mockito.eq(dto.creatorId()))) - .thenThrow(new NotPermittedException("You do not have permission to perform this operation")); + .thenThrow(new NotPermittedException( + "You do not have permission to perform this operation" + )); Mockito.when(ctx.status(403)).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java index 852a585..7111520 100644 --- a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java +++ b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java @@ -136,7 +136,10 @@ public void testCreateProject_NotPermitted() { Mockito.verify(ctx).status(403); Mockito.verify(ctx).json( - Map.of("error", "You do not have permission to perform this operation") + Map.of( + "error", + "You do not have permission to perform this operation" + ) ); } diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectDeleteAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectDeleteAdapter.java new file mode 100644 index 0000000..c2aa8d7 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectDeleteAdapter.java @@ -0,0 +1,116 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.use_cases.DeleteProjectUseCase; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +import org.mockito.Mockito; + +import java.util.Map; + +public class TestProjectDeleteAdapter { + + private DeleteProjectUseCase useCase; + private ProjectDeleteAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + useCase = Mockito.mock(DeleteProjectUseCase.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectDeleteAdapter(useCase); + } + + @Test + public void testDeleteProjectSuccess() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(ctx.status(204)).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(useCase).execute(10, 50); + Mockito.verify(ctx).status(204); + } + + @Test + public void testDeleteProject_UserNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new UserNotFoundException()) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(409)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(409); + Mockito.verify(ctx).json( + Map.of("error", "User doesnt exist") + ); + } + + @Test + public void testDeleteProject_NotPermitted() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new NotPermittedException("denied")) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + } + + @Test + public void testDeleteProject_ProjectNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new ProjectNotFoundException()) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(404)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(404); + Mockito.verify(ctx).json(Map.of("error", "Project doesn't exist")); + } + + @Test + public void testDeleteProject_UnexpectedError() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.doThrow(new RuntimeException("boom")) + .when(useCase).execute(10, 50); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.deleteProject(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java new file mode 100644 index 0000000..76e0fe2 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java @@ -0,0 +1,155 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.GetProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class TestProjectGetAdapter { + + private GetProjectUseCase useCase; + private ObjectMapper mapper; + private ProjectGetAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + useCase = Mockito.mock(GetProjectUseCase.class); + mapper = Mockito.mock(ObjectMapper.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectGetAdapter(useCase, mapper); + } + + @Test + public void testGetProjectSuccess() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Project project = new Project(); + project.setId(50); + project.setName("TestProject"); + project.setDescription("Description"); + project.setManagerId(10); + project.setTeamLeadId(20); + project.setDeveloperIds(List.of(1, 2)); + project.setTesterIds(List.of(3, 4)); + project.setStatus(ProjectStatus.OPEN); + project.setCreatedDate(new Date()); + project.setCreatedBy(99); + + Mockito.when(useCase.execute(10, 50)).thenReturn(project); + + GetProjectDTO dto = new GetProjectDTO( + project.getId(), + project.getName(), + project.getDescription(), + project.getManagerId(), + project.getTeamLeadId(), + project.getDeveloperIds(), + project.getTesterIds(), + project.getStatus(), + project.getCurrentMilestoneId(), + project.getMilestoneIds(), + project.getBugReportIds(), + project.getCreatedDate(), + project.getCreatedBy(), + project.getUpdatedDate(), + project.getUpdatedBy() + ); + + Mockito.when(mapper.mapToPresentation(project, GetProjectDTO.class)) + .thenReturn(dto); + + Mockito.when(ctx.status(200)).thenReturn(ctx); + Mockito.when(ctx.json(dto)).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(200); + Mockito.verify(ctx).json(dto); + } + + @Test + public void testGetProject_UserNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(10, 50)) + .thenThrow(new UserNotFoundException()); + + Mockito.when(ctx.status(409)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(409); + Mockito.verify(ctx).json(Map.of("error", "User doesnt exist")); + } + + @Test + public void testGetProject_NotPermitted() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(10, 50)) + .thenThrow(new NotPermittedException("denied")); + + Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(403); + Mockito.verify(ctx).json( + Map.of("error", "You do not have permission to perform this operation") + ); + } + + @Test + public void testGetProject_ProjectNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(10, 50)) + .thenThrow(new ProjectNotFoundException()); + + Mockito.when(ctx.status(404)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(404); + Mockito.verify(ctx).json(Map.of("error", "Project doesnt exist")); + } + + @Test + public void testGetProject_UnexpectedError() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); + + Mockito.when(useCase.execute(10, 50)) + .thenThrow(new RuntimeException("boom")); + + Mockito.when(ctx.status(500)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.getProject(ctx); + + Mockito.verify(ctx).status(500); + Mockito.verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectListAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectListAdapter.java new file mode 100644 index 0000000..6308dee --- /dev/null +++ b/src/test/java/org/lab/api/adapters/project/TestProjectListAdapter.java @@ -0,0 +1,111 @@ +package org.lab.api.adapters.project; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.project.dto.GetProjectDTO; +import org.lab.application.project.use_cases.ListProjectUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.model.Project; +import org.lab.core.constants.project.ProjectStatus; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +import org.mockito.Mockito; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class TestProjectListAdapter { + + private ListProjectUseCase useCase; + private ObjectMapper mapper; + private ProjectListAdapter adapter; + private Context ctx; + + @BeforeEach + public void setUp() { + useCase = Mockito.mock(ListProjectUseCase.class); + mapper = Mockito.mock(ObjectMapper.class); + ctx = Mockito.mock(Context.class); + adapter = new ProjectListAdapter(useCase, mapper); + } + + @Test + public void testListProjectsSuccess() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + + Project project1 = new Project(); + project1.setId(1); + project1.setName("Project1"); + project1.setDescription("Desc1"); + project1.setManagerId(10); + project1.setStatus(ProjectStatus.OPEN); + project1.setCreatedDate(new Date()); + project1.setCreatedBy(99); + + Project project2 = new Project(); + project2.setId(2); + project2.setName("Project2"); + project2.setDescription("Desc2"); + project2.setManagerId(10); + project2.setStatus(ProjectStatus.OPEN); + project2.setCreatedDate(new Date()); + project2.setCreatedBy(99); + + List projects = List.of(project1, project2); + + Mockito.when(useCase.execute(10)).thenReturn(projects); + + GetProjectDTO dto1 = new GetProjectDTO( + project1.getId(), project1.getName(), project1.getDescription(), + project1.getManagerId(), project1.getTeamLeadId(), + project1.getDeveloperIds(), project1.getTesterIds(), + project1.getStatus(), + project1.getCurrentMilestoneId(), project1.getMilestoneIds(), + project1.getBugReportIds(), + project1.getCreatedDate(), project1.getCreatedBy(), + project1.getUpdatedDate(), project1.getUpdatedBy() + ); + + GetProjectDTO dto2 = new GetProjectDTO( + project2.getId(), project2.getName(), project2.getDescription(), + project2.getManagerId(), project2.getTeamLeadId(), + project2.getDeveloperIds(), project2.getTesterIds(), + project2.getStatus(), + project2.getCurrentMilestoneId(), project2.getMilestoneIds(), + project2.getBugReportIds(), + project2.getCreatedDate(), project2.getCreatedBy(), + project2.getUpdatedDate(), project2.getUpdatedBy() + ); + + Mockito.when(mapper.mapToPresentation(project1, GetProjectDTO.class)).thenReturn(dto1); + Mockito.when(mapper.mapToPresentation(project2, GetProjectDTO.class)).thenReturn(dto2); + + List dtoList = List.of(dto1, dto2); + + Mockito.when(ctx.status(200)).thenReturn(ctx); + Mockito.when(ctx.json(dtoList)).thenReturn(ctx); + + adapter.listProjects(ctx); + + Mockito.verify(ctx).status(200); + Mockito.verify(ctx).json(dtoList); + } + + @Test + public void testListProjects_UserNotFound() { + Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); + + Mockito.when(useCase.execute(10)).thenThrow(new UserNotFoundException()); + + Mockito.when(ctx.status(409)).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); + + adapter.listProjects(ctx); + + Mockito.verify(ctx).status(409); + Mockito.verify(ctx).json(Map.of("error", "User doesnt exist")); + } +} From d4f9789b9bcbbd68ca7750a7ddb187398bc6cac1 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 01:03:15 +0300 Subject: [PATCH 47/67] fixes after testing employee cruds --- src/main/java/org/lab/Main.java | 7 ++-- .../employee/EmployeeCreateAdapter.java | 12 +++---- .../employee/dto/CreateEmployeeDTO.java | 3 +- .../employee/services/CreateValidator.java | 7 ---- .../use_cases/CreateEmployeeUseCase.java | 15 +++++--- .../employee/EmployeeRepository.java | 35 ++++++++++++------- .../use_cases/TestCreateEmployeeUseCase.java | 7 ++-- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 48b492d..65076f0 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -34,10 +34,7 @@ public static void main(String[] args) { new ObjectMapper(), new CreateEmployeeUseCase( new EmployeeRepository(), - new CreateValidator( - new EmployeePermissionValidator( - new EmployeeRepository() - ), + new EmployeePermissionValidator( new EmployeeRepository() ) ) @@ -110,7 +107,7 @@ public static void main(String[] args) { app.get("/", ctx -> ctx.result("Hello World")); - app.post("/employee", createEmployeeAdapter::createEmployee); + app.post("/employee/{actorId}", createEmployeeAdapter::createEmployee); app.delete("/employee/{employeeId}/{actorId}", deleteEmployeeAdapter::deleteEmployee); app.get("/employee/{employeeId}/{actorId}", getEmployeeAdapter::getEmployee); diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java index ac88e26..b93fd33 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -1,5 +1,7 @@ package org.lab.api.adapters.employee; +import java.util.Map; + import io.javalin.http.Context; import org.lab.application.employee.dto.GetEmployeeDTO; @@ -8,9 +10,6 @@ import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; -import org.lab.domain.shared.exceptions.UserAlreadyExistsException; - -import java.util.Map; public class EmployeeCreateAdapter { @@ -29,11 +28,12 @@ public Context createEmployee( Context ctx ) { try { + int actorId = Integer.parseInt(ctx.pathParam("actorId")); CreateEmployeeDTO dto = ctx.bodyAsClass(CreateEmployeeDTO.class); Employee employee = mapper.mapToDomain(dto, Employee.class); Employee createdEmployee = useCase.execute( employee, - dto.creatorId() + actorId ); GetEmployeeDTO presentationEmployee = mapper.mapToPresentation( createdEmployee, @@ -41,9 +41,6 @@ public Context createEmployee( ); return ctx.status(201).json(presentationEmployee); - } catch (UserAlreadyExistsException e) { - return ctx.status(409).json(Map.of("error", "User already exists")); - } catch (NotPermittedException e) { return ctx.status(403).json( Map.of( @@ -53,6 +50,7 @@ public Context createEmployee( ); } catch (Exception e) { + System.err.println("ERROR" + e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java b/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java index 1804c5b..7449e4a 100644 --- a/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java +++ b/src/main/java/org/lab/application/employee/dto/CreateEmployeeDTO.java @@ -6,7 +6,6 @@ public record CreateEmployeeDTO( String name, int age, - EmployeeType type, - int creatorId + EmployeeType type ) implements PresentationObject { } diff --git a/src/main/java/org/lab/application/employee/services/CreateValidator.java b/src/main/java/org/lab/application/employee/services/CreateValidator.java index a99eb46..1822a4d 100644 --- a/src/main/java/org/lab/application/employee/services/CreateValidator.java +++ b/src/main/java/org/lab/application/employee/services/CreateValidator.java @@ -33,13 +33,6 @@ public void validate( validator.validate(creatorId); return null; }); - scope.fork(() -> { - Employee employeeCreated = employeeRepository.getById(employee.getId()); - if (employeeCreated != null) { - throw new UserAlreadyExistsException("Employee already exists"); - } - return null; - }); scope.join(); scope.throwIfFailed(); diff --git a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java index ce6d2c5..03b2754 100644 --- a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.employee.use_cases; +import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.application.employee.services.CreateValidator; @@ -7,22 +8,26 @@ public class CreateEmployeeUseCase { private final EmployeeRepository employeeRepository; - private final CreateValidator createValidator; + private final EmployeePermissionValidator validator; public CreateEmployeeUseCase( EmployeeRepository employeeRepository, - CreateValidator createValidator + EmployeePermissionValidator validator ) { this.employeeRepository = employeeRepository; - this.createValidator = createValidator; + this.validator = validator; } public Employee execute( Employee employee, int creatorId ) { - createValidator.validate(employee, creatorId); - Employee createdEmployee = employeeRepository.create(employee); + validator.validate(creatorId); + Employee createdEmployee = employeeRepository.create( + employee, + creatorId + ); + System.out.println("Created employee with id " + createdEmployee); return createdEmployee; } } diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index a0e7a7f..8510ee7 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -37,18 +37,21 @@ public Employee getById(int id) { raw.put(columnName, value); } return objectMapper.mapFromRaw(raw, Employee.class); + } else { + throw new RuntimeException("Employee creation failed: no row returned"); } } } catch (SQLException e) { throw new DatabaseException(); - } - return null; - } + }} - public Employee create(Employee employee) { + public Employee create( + Employee employee, + int actorId + ) { String sql = """ - INSERT INTO employees (name, age, type, created_by, create_date) - VALUES (?, ?, ?, ?, ?) + INSERT INTO employees (name, age, type, "createdBy") + VALUES (?, ?, ?, ?) RETURNING * """; @@ -59,19 +62,24 @@ INSERT INTO employees (name, age, type, created_by, create_date) stmt.setString(1, employee.getName()); stmt.setInt(2, employee.getAge()); stmt.setString(3, employee.getType().name()); - stmt.setInt(4, employee.getCreatedBy()); - stmt.setTimestamp(5, new Timestamp( - employee.getCreatedDate().getTime()) - ); + stmt.setInt(4, actorId); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - return objectMapper.mapFromRaw(rs, Employee.class); + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("name", rs.getString("name")); + row.put("age", rs.getInt("age")); + row.put("type", rs.getString("type")); + row.put("createdBy", rs.getInt("createdBy")); + row.put("createdDate", rs.getTimestamp("createdDate")); + return objectMapper.mapFromRaw(row, Employee.class); + } else { + throw new RuntimeException("Employee creation failed: no row returned"); } } } catch (SQLException e) { - System.err.println(e.getMessage()); + throw new DatabaseException(); } - return employee; } public Object delete(int id) { @@ -81,6 +89,7 @@ public Object delete(int id) { PreparedStatement stmt = conn.prepareStatement(sql) ) { stmt.setInt(1, id); + return stmt.executeUpdate(); } catch (SQLException e) { System.err.println(e.getMessage()); } diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index da7fa0a..9ba1209 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -25,8 +25,7 @@ public class TestCreateEmployeeUseCase { public void setUp() { employeeRepository = Mockito.mock(EmployeeRepository.class); validator = new EmployeePermissionValidator(employeeRepository); - createValidator = new CreateValidator(validator, employeeRepository); - useCase = new CreateEmployeeUseCase(employeeRepository, createValidator); + useCase = new CreateEmployeeUseCase(employeeRepository, validator); } @Test @@ -40,7 +39,7 @@ public void testCreateEmployeeSuccess() { saved.setId(100); saved.setName("Tim Cock"); - Mockito.when(employeeRepository.create(input)).thenReturn(saved); + Mockito.when(employeeRepository.create(input, 12)).thenReturn(saved); Employee creator = new Employee(); creator.setId(1); @@ -51,7 +50,7 @@ public void testCreateEmployeeSuccess() { Assertions.assertEquals(100, result.getId()); - Mockito.verify(employeeRepository).create(input); + Mockito.verify(employeeRepository).create(input, 12); Mockito.verify(employeeRepository).getById(1); } From f279f470cb8babe3e4c4a205b00ffd5c8a4c32b3 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 02:07:42 +0300 Subject: [PATCH 48/67] added data extracting into project repository --- src/main/java/org/lab/Main.java | 2 +- .../project/ProjectCreateAdapter.java | 4 +- .../project/dto/CreateProjectDTO.java | 1 - .../use_cases/CreateProjectUseCase.java | 7 +- .../core/constants/project/ProjectStatus.java | 2 +- .../repository/project/ProjectRepository.java | 69 ++++++++++--------- .../ProjectRawDataExtractor.java | 68 ++++++++++++++++++ 7 files changed, 113 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 65076f0..87fd5ed 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -111,7 +111,7 @@ public static void main(String[] args) { app.delete("/employee/{employeeId}/{actorId}", deleteEmployeeAdapter::deleteEmployee); app.get("/employee/{employeeId}/{actorId}", getEmployeeAdapter::getEmployee); - app.post("/project", createProjectAdapter::createProject); + app.post("/project/{employeeId}", createProjectAdapter::createProject); app.get("/project/{projectId}/{employeeId}", projectGetAdapter::getProject); app.delete("/project/{projectId}/{employeeId}", projectDeleteAdapter::deleteProject); app.get("/project/list/{employeeId}", projectListAdapter::listProjects); diff --git a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java index 24f322f..e6dcfe6 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java @@ -28,9 +28,10 @@ public Context createProject( Context ctx ) { try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); CreateProjectDTO dto = ctx.bodyAsClass(CreateProjectDTO.class); Project project = objectMapper.mapToDomain(dto, Project.class); - Project createdProject = useCase.execute(project); + Project createdProject = useCase.execute(project, employeeId); GetProjectDTO presentationProject = objectMapper.mapToPresentation( createdProject, GetProjectDTO.class @@ -45,6 +46,7 @@ public Context createProject( ) ); } catch (Exception e) { + System.err.println("ERROR " + e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java index 5cc1d19..b45dd09 100644 --- a/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java +++ b/src/main/java/org/lab/application/project/dto/CreateProjectDTO.java @@ -7,7 +7,6 @@ public record CreateProjectDTO( String name, String description, - int managerId, Integer teamLeadId, ListdeveloperIds, List testerIds diff --git a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java index 79432ef..4a2de45 100644 --- a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java @@ -18,10 +18,11 @@ public CreateProjectUseCase( } public Project execute( - Project project + Project project, + int employeeId ) { - this.employeePermissionValidator.validate(project.getManagerId()); - Project domainProject = this.projectRepository.create(project); + this.employeePermissionValidator.validate(employeeId); + Project domainProject = this.projectRepository.create(project, employeeId); return domainProject; } } diff --git a/src/main/java/org/lab/core/constants/project/ProjectStatus.java b/src/main/java/org/lab/core/constants/project/ProjectStatus.java index bb4a2df..49a4591 100644 --- a/src/main/java/org/lab/core/constants/project/ProjectStatus.java +++ b/src/main/java/org/lab/core/constants/project/ProjectStatus.java @@ -4,5 +4,5 @@ public enum ProjectStatus { OPEN, ACTIVE, ON_HOLD, - DELTED + DELETED } diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 4b4ee21..91d614c 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -1,25 +1,26 @@ package org.lab.infra.db.repository.project; import java.sql.*; +import java.util.*; -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.core.JsonProcessingException; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.DatabaseException; import org.lab.infra.db.client.DatabaseClient; import org.lab.infra.db.spec.Specification; import org.lab.infra.db.spec.SqlSpec; +import org.lab.infra.db.repository.project.data_extractor.ProjectRawDataExtractor; public class ProjectRepository { private final DatabaseClient databaseClient; private final ObjectMapper objectMapper; + private final ProjectRawDataExtractor projectRawDataExtractor; public ProjectRepository() { databaseClient = new DatabaseClient(); objectMapper = new ObjectMapper(); + projectRawDataExtractor = new ProjectRawDataExtractor(); } public Project get( @@ -42,18 +43,21 @@ public Project get( return null; } - public Project create(Project project) { + public Project create( + Project project, + int employeeId + ) { String sql = """ INSERT INTO projects ( name, description, - managerId, - teamLeadId, - developerIds, - testerIds, - createdBy + "managerId", + "teamLeadId", + "developerIds", + "testerIds", + "createdBy" ) - VALUES (?, ?, ?, ?, ?::jsonb, ?::jsonb, ?) + VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING * """; @@ -63,24 +67,28 @@ INSERT INTO projects ( ) { stmt.setString(1, project.getName()); stmt.setString(2, project.getDescription()); - stmt.setInt(3, project.getManagerId()); - - if (project.getTeamLeadId() != null) - stmt.setInt(4, project.getTeamLeadId()); - else - stmt.setNull(4, Types.INTEGER); - - stmt.setString(5, toJson(project.getDeveloperIds())); - stmt.setString(6, toJson(project.getTesterIds())); - + stmt.setInt(3, employeeId); + stmt.setInt(4, project.getTeamLeadId()); + Array developerArray = conn.createArrayOf( + "INTEGER", + project.getDeveloperIds().toArray() + ); + stmt.setArray(5, developerArray); + + Array testerArray = conn.createArrayOf( + "INTEGER", + project.getTesterIds().toArray() + ); + stmt.setArray(6, testerArray); stmt.setInt(7, project.getCreatedBy()); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - return objectMapper.mapFromRaw(rs, Project.class); + Map row = projectRawDataExtractor.extractRawData(rs); + return objectMapper.mapFromRaw(row, Project.class); } } - } catch (SQLException | JsonProcessingException e) { + } catch (SQLException e) { System.err.println(e.getMessage()); } return null; @@ -102,7 +110,8 @@ public List list( try (ResultSet rs = stmt.executeQuery()) { List projects = new ArrayList<>(); while (rs.next()) { - projects.add(objectMapper.mapFromRaw(rs, Project.class)); + Map row = projectRawDataExtractor.extractRawData(rs); + projects.add(objectMapper.mapFromRaw(row, Project.class)); } return projects; } @@ -111,22 +120,16 @@ public List list( } } - public void delete(int projectId) { + public int delete(int projectId) { String sql = "DELETE FROM projects WHERE id = ?"; try ( Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) ) { stmt.setInt(1, projectId); + return stmt.executeUpdate(); } catch (SQLException e) { - System.err.println(e.getMessage()); + throw new DatabaseException(); } } - - private String toJson(List list) - throws - JsonProcessingException - { - return new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(list); - } } diff --git a/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java new file mode 100644 index 0000000..ec9223e --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java @@ -0,0 +1,68 @@ +package org.lab.infra.db.repository.project.data_extractor; + +import org.lab.core.constants.project.ProjectStatus; + +import java.sql.Array; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ProjectRawDataExtractor { + + public Map extractRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("name", rs.getString("name")); + row.put("description", rs.getString("description")); + row.put("managerId", rs.getInt("managerId")); + + int teamLeadId = rs.getInt("teamLeadId"); + row.put("teamLeadId", rs.wasNull() ? null : teamLeadId); + + Array developerArrayRes = rs.getArray("developerIds"); + List developerIds = developerArrayRes != null + ? Arrays.asList((Integer[]) developerArrayRes.getArray()) + : List.of(); + row.put("developerIds", developerIds); + + Array testerArrayRes = rs.getArray("testerIds"); + List testerIds = testerArrayRes != null + ? Arrays.asList((Integer[]) testerArrayRes.getArray()) + : List.of(); + row.put("testerIds", testerIds); + + row.put("status", ProjectStatus.valueOf(rs.getString("status"))); + + int currentMilestoneId = rs.getInt("currentMilestoneId"); + row.put("currentMilestoneId", rs.wasNull() ? null : currentMilestoneId); + + Array milestoneArray = rs.getArray("milestoneIds"); + List milestoneIds = milestoneArray != null + ? Arrays.asList((Integer[]) milestoneArray.getArray()) + : List.of(); + row.put("milestoneIds", milestoneIds); + + Array bugArray = rs.getArray("bugReportIds"); + List bugReportIds = bugArray != null + ? Arrays.asList((Integer[]) bugArray.getArray()) + : List.of(); + row.put("bugReportIds", bugReportIds); + + row.put("createdBy", rs.getInt("createdBy")); + row.put("createdDate", rs.getTimestamp("createdDate")); + row.put( + "updatedBy", + rs.getObject("updatedBy") != null + ? rs.getInt("updatedBy") : null + ); + row.put("updatedDate", rs.getTimestamp("updatedDate")); + return row; + } +} From 3ce04e22808607da0c2b7c712169d864749125b6 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 13:46:16 +0300 Subject: [PATCH 49/67] added data extractor to employee repository --- src/main/java/org/lab/Main.java | 1 - .../project/ProjectDeleteAdapter.java | 1 + .../use_cases/DeleteProjectUseCase.java | 2 ++ .../employee/EmployeeRepository.java | 23 +++++------------- .../EmployeeRawDataExtractor.java | 24 +++++++++++++++++++ .../repository/project/ProjectRepository.java | 23 +++++++++++------- 6 files changed, 48 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/lab/infra/db/repository/employee/data_extractor/EmployeeRawDataExtractor.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 87fd5ed..a89b75f 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -8,7 +8,6 @@ import org.lab.api.adapters.project.ProjectDeleteAdapter; import org.lab.api.adapters.project.ProjectGetAdapter; import org.lab.api.adapters.project.ProjectListAdapter; -import org.lab.application.employee.services.CreateValidator; import org.lab.application.project.services.GetValidator; import org.lab.application.project.services.UserSpecFactory; import org.lab.application.project.use_cases.CreateProjectUseCase; diff --git a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java index 7f19c3e..8258f98 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java @@ -23,6 +23,7 @@ public Context deleteProject( Context ctx ) { try { + System.out.println("hello from deleting project"); int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); int projectId = Integer.parseInt(ctx.pathParam("projectId")); this.useCase.execute(employeeId, projectId); diff --git a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java index d03e61f..55c98e3 100644 --- a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java @@ -27,9 +27,11 @@ public void execute( int employeeId, int projectId ) { + System.out.println("Getting pair " + projectId); Pair pair = this.getValidator.validate(projectId, employeeId); Employee employee = pair.employee(); Project project = pair.project(); + System.out.println("Got pair " + projectId); this.projectMembershipValidator.validate(employee, project); this.projectRepository.delete(projectId); } diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 8510ee7..09f3235 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -4,19 +4,21 @@ import org.lab.domain.shared.exceptions.DatabaseException; import org.lab.infra.db.client.DatabaseClient; import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.infra.db.repository.employee.data_extractor.EmployeeRawDataExtractor; import java.sql.*; -import java.util.HashMap; import java.util.Map; public class EmployeeRepository { private final DatabaseClient databaseClient; private final ObjectMapper objectMapper; + private final EmployeeRawDataExtractor employeeRawDataExtractor; public EmployeeRepository() { databaseClient = new DatabaseClient(); objectMapper = new ObjectMapper(); + employeeRawDataExtractor = new EmployeeRawDataExtractor(); } public Employee getById(int id) { @@ -28,15 +30,8 @@ public Employee getById(int id) { stmt.setInt(1, id); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - ResultSetMetaData metaData = rs.getMetaData(); - int columnCount = metaData.getColumnCount(); - Map raw = new HashMap<>(); - for (int i = 1; i <= columnCount; i++) { - String columnName = metaData.getColumnLabel(i); - Object value = rs.getObject(i); - raw.put(columnName, value); - } - return objectMapper.mapFromRaw(raw, Employee.class); + Map row = employeeRawDataExtractor.extractEmployeeRawData(rs); + return objectMapper.mapFromRaw(row, Employee.class); } else { throw new RuntimeException("Employee creation failed: no row returned"); } @@ -65,13 +60,7 @@ INSERT INTO employees (name, age, type, "createdBy") stmt.setInt(4, actorId); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - Map row = new HashMap<>(); - row.put("id", rs.getInt("id")); - row.put("name", rs.getString("name")); - row.put("age", rs.getInt("age")); - row.put("type", rs.getString("type")); - row.put("createdBy", rs.getInt("createdBy")); - row.put("createdDate", rs.getTimestamp("createdDate")); + Map row = employeeRawDataExtractor.extractEmployeeRawData(rs); return objectMapper.mapFromRaw(row, Employee.class); } else { throw new RuntimeException("Employee creation failed: no row returned"); diff --git a/src/main/java/org/lab/infra/db/repository/employee/data_extractor/EmployeeRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/employee/data_extractor/EmployeeRawDataExtractor.java new file mode 100644 index 0000000..4a91980 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/employee/data_extractor/EmployeeRawDataExtractor.java @@ -0,0 +1,24 @@ +package org.lab.infra.db.repository.employee.data_extractor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class EmployeeRawDataExtractor { + + public Map extractEmployeeRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("name", rs.getString("name")); + row.put("age", rs.getInt("age")); + row.put("type", rs.getString("type")); + row.put("createdBy", rs.getInt("createdBy")); + row.put("createdDate", rs.getTimestamp("createdDate")); + return row; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 91d614c..267e178 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -34,13 +34,15 @@ public Project get( stmt.setInt(1, projectId); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - return objectMapper.mapFromRaw(rs, Project.class); + Map row = projectRawDataExtractor.extractRawData(rs); + return objectMapper.mapFromRaw(row, Project.class); + } else { + throw new RuntimeException("Employee creation failed: no row returned"); } } } catch (SQLException e) { - System.err.println(e.getMessage()); + throw new DatabaseException(); } - return null; } public Project create( @@ -86,12 +88,13 @@ INSERT INTO projects ( if (rs.next()) { Map row = projectRawDataExtractor.extractRawData(rs); return objectMapper.mapFromRaw(row, Project.class); + } else { + throw new RuntimeException("Employee creation failed: no row returned"); } } } catch (SQLException e) { - System.err.println(e.getMessage()); + throw new DatabaseException(); } - return null; } public List list( @@ -109,9 +112,13 @@ public List list( } try (ResultSet rs = stmt.executeQuery()) { List projects = new ArrayList<>(); - while (rs.next()) { - Map row = projectRawDataExtractor.extractRawData(rs); - projects.add(objectMapper.mapFromRaw(row, Project.class)); + if (rs.next()) { + while (rs.next()) { + Map row = projectRawDataExtractor.extractRawData(rs); + projects.add(objectMapper.mapFromRaw(row, Project.class)); + } + } else { + throw new RuntimeException("Employee creation failed: no row returned"); } return projects; } From 34c7b034404534070c884d0afe369562f7bf6940 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 14:25:03 +0300 Subject: [PATCH 50/67] fixes after testing with projects --- src/main/java/org/lab/Main.java | 2 +- .../project/ProjectDeleteAdapter.java | 1 - .../adapters/project/ProjectGetAdapter.java | 4 +-- .../use_cases/DeleteProjectUseCase.java | 2 -- .../employee/EmployeeRepository.java | 8 ++--- .../repository/employee/spec/ManagerSpec.java | 2 +- .../repository/project/ProjectRepository.java | 29 ++++++++++--------- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index a89b75f..03f63c3 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -110,9 +110,9 @@ public static void main(String[] args) { app.delete("/employee/{employeeId}/{actorId}", deleteEmployeeAdapter::deleteEmployee); app.get("/employee/{employeeId}/{actorId}", getEmployeeAdapter::getEmployee); + app.get("/project/list/{employeeId}", projectListAdapter::listProjects); app.post("/project/{employeeId}", createProjectAdapter::createProject); app.get("/project/{projectId}/{employeeId}", projectGetAdapter::getProject); app.delete("/project/{projectId}/{employeeId}", projectDeleteAdapter::deleteProject); - app.get("/project/list/{employeeId}", projectListAdapter::listProjects); } } diff --git a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java index 8258f98..7f19c3e 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java @@ -23,7 +23,6 @@ public Context deleteProject( Context ctx ) { try { - System.out.println("hello from deleting project"); int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); int projectId = Integer.parseInt(ctx.pathParam("projectId")); this.useCase.execute(employeeId, projectId); diff --git a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java index 8b63666..5a8e1c7 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java @@ -30,8 +30,8 @@ public Context getProject(Context ctx) { int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); int projectId = Integer.parseInt(ctx.pathParam("projectId")); Project project = useCase.execute( - employeeId, - projectId + projectId, + employeeId ); GetProjectDTO presentationProject = mapper.mapToPresentation( project, diff --git a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java index 55c98e3..d03e61f 100644 --- a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java @@ -27,11 +27,9 @@ public void execute( int employeeId, int projectId ) { - System.out.println("Getting pair " + projectId); Pair pair = this.getValidator.validate(projectId, employeeId); Employee employee = pair.employee(); Project project = pair.project(); - System.out.println("Got pair " + projectId); this.projectMembershipValidator.validate(employee, project); this.projectRepository.delete(projectId); } diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 09f3235..8ba78e6 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -1,14 +1,14 @@ package org.lab.infra.db.repository.employee; +import java.sql.*; +import java.util.Map; + import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.DatabaseException; import org.lab.infra.db.client.DatabaseClient; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.infra.db.repository.employee.data_extractor.EmployeeRawDataExtractor; -import java.sql.*; -import java.util.Map; - public class EmployeeRepository { private final DatabaseClient databaseClient; @@ -33,7 +33,7 @@ public Employee getById(int id) { Map row = employeeRawDataExtractor.extractEmployeeRawData(rs); return objectMapper.mapFromRaw(row, Employee.class); } else { - throw new RuntimeException("Employee creation failed: no row returned"); + return null; } } } catch (SQLException e) { diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java index 3ce3d4c..bca2956 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/ManagerSpec.java @@ -15,7 +15,7 @@ public ManagerSpec(int managerId) { @Override public String toSql() { - return "managerId = ?"; + return "\"managerId\" = ?"; } @Override diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 267e178..be7b8e2 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -1,7 +1,14 @@ package org.lab.infra.db.repository.project; -import java.sql.*; -import java.util.*; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.PreparedStatement; +import java.sql.Array; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.project.model.Project; @@ -37,7 +44,7 @@ public Project get( Map row = projectRawDataExtractor.extractRawData(rs); return objectMapper.mapFromRaw(row, Project.class); } else { - throw new RuntimeException("Employee creation failed: no row returned"); + return null; } } } catch (SQLException e) { @@ -89,7 +96,7 @@ INSERT INTO projects ( Map row = projectRawDataExtractor.extractRawData(rs); return objectMapper.mapFromRaw(row, Project.class); } else { - throw new RuntimeException("Employee creation failed: no row returned"); + throw new RuntimeException("Project creation failed: no row returned"); } } } catch (SQLException e) { @@ -101,7 +108,7 @@ public List list( Specification specification ) { SqlSpec spec = (SqlSpec) specification; - String sql = "SELECT * FROM projects WHERE" + spec.toSql(); + String sql = "SELECT * FROM projects WHERE " + spec.toSql(); try ( Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) @@ -112,18 +119,14 @@ public List list( } try (ResultSet rs = stmt.executeQuery()) { List projects = new ArrayList<>(); - if (rs.next()) { - while (rs.next()) { - Map row = projectRawDataExtractor.extractRawData(rs); - projects.add(objectMapper.mapFromRaw(row, Project.class)); - } - } else { - throw new RuntimeException("Employee creation failed: no row returned"); + while (rs.next()) { + Map row = projectRawDataExtractor.extractRawData(rs); + projects.add(objectMapper.mapFromRaw(row, Project.class)); } return projects; } } catch (SQLException e) { - throw new RuntimeException(e); + throw new DatabaseException(); } } From 8a89daf40c809894a371d01007ab71069e3d1c9e Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 14:27:23 +0300 Subject: [PATCH 51/67] fix imports --- .../org/lab/api/adapters/employee/EmployeeDeleteAdapter.java | 4 ++-- .../org/lab/api/adapters/employee/EmployeeGetAdapter.java | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java index cedf3ad..1bddc4c 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -1,11 +1,11 @@ package org.lab.api.adapters.employee; +import java.util.Map; + import io.javalin.http.Context; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; import org.lab.domain.shared.exceptions.NotPermittedException; -import java.util.Map; - public class EmployeeDeleteAdapter { private final DeleteEmployeeUseCase useCase; diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java index 2535c4d..2c50e89 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -1,14 +1,15 @@ package org.lab.api.adapters.employee; +import java.util.Map; + import io.javalin.http.Context; + import org.lab.application.employee.dto.GetEmployeeDTO; import org.lab.application.employee.use_cases.GetEmployeeUseCase; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; -import java.util.Map; - public class EmployeeGetAdapter { private final GetEmployeeUseCase useCase; From c06e286cbeb3b53d08ce639773534587a0ba587d Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 14:30:21 +0300 Subject: [PATCH 52/67] updated specs --- .../lab/infra/db/repository/employee/spec/DeveloperSpec.java | 2 +- .../lab/infra/db/repository/employee/spec/TeamLeaderSpec.java | 2 +- .../org/lab/infra/db/repository/employee/spec/TesterSpec.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java index 0e52854..ba3218c 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java @@ -15,7 +15,7 @@ public DeveloperSpec(int developerId) { @Override public String toSql() { - return "? IN developerIds"; + return "? IN \"developerIds\""; } @Override diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java index a5ebc5b..d283504 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TeamLeaderSpec.java @@ -15,7 +15,7 @@ public TeamLeaderSpec(int teamLeaderId) { @Override public String toSql() { - return "teamLeadId = ?"; + return "\"teamLeadId\" = ?"; } @Override diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java index e125c99..45be3f3 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java @@ -15,7 +15,7 @@ public TesterSpec(int testerId) { @Override public String toSql() { - return "? IN testerIds"; + return "? IN \"testerIds\""; } @Override From 5b80197f625b3906c8a358fab83aada4c45c5268 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 15:57:19 +0300 Subject: [PATCH 53/67] ticket logic --- src/main/java/org/lab/Main.java | 31 +++++++++- .../adapters/ticket/TicketCreateAdapter.java | 57 +++++++++++++++++++ .../employee/services/CreateValidator.java | 45 --------------- .../use_cases/CreateEmployeeUseCase.java | 2 - .../project/services/GetValidator.java | 13 ++--- .../project/services/UserSpecFactory.java | 1 + .../shared/services/ProjectProvider.java | 28 +++++++++ .../ticket/dto/CreateTicketDTO.java | 11 ++++ .../application/ticket/dto/GetTicketDTO.java | 17 ++++++ .../services/TicketCreateValidator.java | 43 ++++++++++++++ .../services/TicketPermissionValidator.java | 27 +++++++++ .../ticket/use_cases/CreateTicketUseCase.java | 25 ++++++++ .../core/constants/ticket/TicketStatus.java | 8 +++ .../org/lab/domain/ticket/model/Ticket.java | 19 +++++++ .../repository/ticket/TicketRepository.java | 51 +++++++++++++++++ .../TicketRawDataExtractor.java | 4 ++ .../use_cases/TestCreateEmployeeUseCase.java | 1 - 17 files changed, 325 insertions(+), 58 deletions(-) delete mode 100644 src/main/java/org/lab/application/employee/services/CreateValidator.java create mode 100644 src/main/java/org/lab/application/shared/services/ProjectProvider.java create mode 100644 src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java create mode 100644 src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java create mode 100644 src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java create mode 100644 src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java create mode 100644 src/main/java/org/lab/core/constants/ticket/TicketStatus.java create mode 100644 src/main/java/org/lab/domain/ticket/model/Ticket.java create mode 100644 src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java create mode 100644 src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 03f63c3..1327af6 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -8,6 +8,7 @@ import org.lab.api.adapters.project.ProjectDeleteAdapter; import org.lab.api.adapters.project.ProjectGetAdapter; import org.lab.api.adapters.project.ProjectListAdapter; +import org.lab.api.adapters.ticket.TicketCreateAdapter; import org.lab.application.project.services.GetValidator; import org.lab.application.project.services.UserSpecFactory; import org.lab.application.project.use_cases.CreateProjectUseCase; @@ -19,10 +20,15 @@ import org.lab.application.employee.use_cases.CreateEmployeeUseCase; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; import org.lab.application.employee.use_cases.GetEmployeeUseCase; +import org.lab.application.shared.services.ProjectProvider; +import org.lab.application.ticket.services.TicketCreateValidator; +import org.lab.application.ticket.services.TicketPermissionValidator; +import org.lab.application.ticket.use_cases.CreateTicketUseCase; import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.project.services.ProjectMembershipValidator; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.repository.ticket.TicketRepository; public class Main { @@ -70,7 +76,9 @@ public static void main(String[] args) { ProjectGetAdapter projectGetAdapter = new ProjectGetAdapter( new GetProjectUseCase( new GetValidator( - new ProjectRepository(), + new ProjectProvider( + new ProjectRepository() + ), new EmployeeProvider( new EmployeeRepository() ) @@ -84,7 +92,9 @@ public static void main(String[] args) { new DeleteProjectUseCase( new ProjectRepository(), new GetValidator( - new ProjectRepository(), + new ProjectProvider( + new ProjectRepository() + ), new EmployeeProvider( new EmployeeRepository() ) @@ -104,6 +114,21 @@ public static void main(String[] args) { new ObjectMapper() ); + TicketCreateAdapter ticketCreateAdapter = new TicketCreateAdapter( + new CreateTicketUseCase( + new TicketRepository(), + new TicketCreateValidator( + new TicketPermissionValidator( + new EmployeeRepository() + ), + new ProjectProvider( + new ProjectRepository() + ) + ) + ), + new ObjectMapper() + ); + app.get("/", ctx -> ctx.result("Hello World")); app.post("/employee/{actorId}", createEmployeeAdapter::createEmployee); @@ -114,5 +139,7 @@ public static void main(String[] args) { app.post("/project/{employeeId}", createProjectAdapter::createProject); app.get("/project/{projectId}/{employeeId}", projectGetAdapter::getProject); app.delete("/project/{projectId}/{employeeId}", projectDeleteAdapter::deleteProject); + + app.post("/ticket/{employeeId}/{projectId}", ticketCreateAdapter::createTicket); } } diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java index 17ee441..aa10499 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java @@ -1,4 +1,61 @@ package org.lab.api.adapters.ticket; +import io.javalin.http.Context; +import org.lab.application.ticket.dto.CreateTicketDTO; +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CreateTicketUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.ticket.model.Ticket; + +import java.util.Map; + public class TicketCreateAdapter { + + private CreateTicketUseCase useCase; + private ObjectMapper objectMapper; + + public TicketCreateAdapter( + CreateTicketUseCase useCase, + ObjectMapper objectMapper + ) { + this.useCase = useCase; + this.objectMapper = objectMapper; + } + + public Context createTicket( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int projectId = Integer.parseInt(ctx.pathParam("projectId")); + CreateTicketDTO dto = ctx.bodyAsClass(CreateTicketDTO.class); + Ticket ticket = objectMapper.mapToDomain(dto, Ticket.class); + Ticket createdTicket = useCase.execute( + ticket, + employeeId, + projectId + ); + GetTicketDTO presentationTicket = objectMapper.mapToPresentation( + createdTicket, + GetTicketDTO.class + ); + return ctx.status(201).json(presentationTicket); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesnt exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } } diff --git a/src/main/java/org/lab/application/employee/services/CreateValidator.java b/src/main/java/org/lab/application/employee/services/CreateValidator.java deleted file mode 100644 index 1822a4d..0000000 --- a/src/main/java/org/lab/application/employee/services/CreateValidator.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.lab.application.employee.services; - -import java.util.concurrent.*; - -import org.lab.application.shared.services.EmployeePermissionValidator; -import org.lab.domain.emploee.model.Employee; -import org.lab.infra.db.repository.employee.EmployeeRepository; -import org.lab.domain.shared.exceptions.NotPermittedException; -import org.lab.domain.shared.exceptions.UserAlreadyExistsException; - -public class CreateValidator { - - private final EmployeePermissionValidator validator; - private final EmployeeRepository employeeRepository; - - public CreateValidator( - EmployeePermissionValidator validator, - EmployeeRepository employeeRepository - ) { - this.validator = validator; - this.employeeRepository = employeeRepository; - } - - public void validate( - Employee employee, - int creatorId - ) - throws NotPermittedException, - UserAlreadyExistsException - { - try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ - scope.fork(() -> { - validator.validate(creatorId); - return null; - }); - - scope.join(); - scope.throwIfFailed(); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java index 03b2754..7047cb3 100644 --- a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java @@ -3,7 +3,6 @@ import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.repository.employee.EmployeeRepository; -import org.lab.application.employee.services.CreateValidator; public class CreateEmployeeUseCase { @@ -27,7 +26,6 @@ public Employee execute( employee, creatorId ); - System.out.println("Created employee with id " + createdEmployee); return createdEmployee; } } diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java index 2890160..55c87c5 100644 --- a/src/main/java/org/lab/application/project/services/GetValidator.java +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -2,23 +2,23 @@ import java.util.concurrent.*; +import org.lab.application.shared.services.ProjectProvider; import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; import org.lab.domain.shared.exceptions.ProjectNotFoundException; import org.lab.domain.shared.exceptions.UserNotFoundException; -import org.lab.infra.db.repository.project.ProjectRepository; import org.lab.application.shared.services.EmployeeProvider; public class GetValidator { - private final ProjectRepository projectRepository; + private final ProjectProvider projectProvider; private final EmployeeProvider currentEmployeeProvider; public GetValidator( - ProjectRepository projectRepository, + ProjectProvider projectProvider, EmployeeProvider currentEmployeeProvider ) { - this.projectRepository = projectRepository; + this.projectProvider = projectProvider; this.currentEmployeeProvider = currentEmployeeProvider; } @@ -31,10 +31,7 @@ public Pair validate( { try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ var projectFuture = scope.fork(() -> { - Project project = this.projectRepository.get(projectId); - if (project == null) { - throw new ProjectNotFoundException(); - } + Project project = this.projectProvider.get(projectId); return project; }); var employeeFuture = scope.fork(() -> { diff --git a/src/main/java/org/lab/application/project/services/UserSpecFactory.java b/src/main/java/org/lab/application/project/services/UserSpecFactory.java index d7af73e..d961927 100644 --- a/src/main/java/org/lab/application/project/services/UserSpecFactory.java +++ b/src/main/java/org/lab/application/project/services/UserSpecFactory.java @@ -8,6 +8,7 @@ import org.lab.infra.db.spec.Specification; public class UserSpecFactory { + public Specification getForType( Employee employee ) { diff --git a/src/main/java/org/lab/application/shared/services/ProjectProvider.java b/src/main/java/org/lab/application/shared/services/ProjectProvider.java new file mode 100644 index 0000000..8e3dfa4 --- /dev/null +++ b/src/main/java/org/lab/application/shared/services/ProjectProvider.java @@ -0,0 +1,28 @@ +package org.lab.application.shared.services; + +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; + +public class ProjectProvider { + + private ProjectRepository projectRepository; + + public ProjectProvider( + ProjectRepository projectRepository + ) { + this.projectRepository = projectRepository; + } + + public Project get( + int projectId + ) throws + ProjectNotFoundException + { + Project project = this.projectRepository.get(projectId); + if (project == null) { + throw new ProjectNotFoundException(); + } + return project; + } +} diff --git a/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java b/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java new file mode 100644 index 0000000..77d208f --- /dev/null +++ b/src/main/java/org/lab/application/ticket/dto/CreateTicketDTO.java @@ -0,0 +1,11 @@ +package org.lab.application.ticket.dto; + +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.interfaces.PresentationObject; + +public record CreateTicketDTO( + int assignedTo, + String description, + TicketStatus status +) implements PresentationObject { +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java b/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java new file mode 100644 index 0000000..dbd3ea3 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java @@ -0,0 +1,17 @@ +package org.lab.application.ticket.dto; + +import java.util.Date; + +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.interfaces.PresentationObject; + +public record GetTicketDTO( + int id, + int createdBy, + int assignedTo, + String description, + TicketStatus status, + Date createdDate, + Date closedDate +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java new file mode 100644 index 0000000..f519fba --- /dev/null +++ b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java @@ -0,0 +1,43 @@ +package org.lab.application.ticket.services; + +import org.lab.application.shared.services.ProjectProvider; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.StructuredTaskScope; + +public class TicketCreateValidator { + + private final TicketPermissionValidator ticketPermissionValidator; + private final ProjectProvider projectProvider; + + public TicketCreateValidator( + TicketPermissionValidator ticketPermissionValidator, + ProjectProvider projectProvider + ) { + this.ticketPermissionValidator = ticketPermissionValidator; + this.projectProvider = projectProvider; + } + + public void validate( + int employeeId, + int projectId + ) { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()){ + scope.fork(() -> { + this.ticketPermissionValidator.validate(employeeId); + return null; + }); + scope.fork(() -> { + this.projectProvider.get(projectId); + return null; + }); + scope.join(); + scope.throwIfFailed(); + + } catch (ExecutionException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java new file mode 100644 index 0000000..98c622d --- /dev/null +++ b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java @@ -0,0 +1,27 @@ +package org.lab.application.ticket.services; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TicketPermissionValidator { + + private final EmployeeRepository employeeRepository; + + public TicketPermissionValidator( + EmployeeRepository employeeRepository + ) { + this.employeeRepository = employeeRepository; + } + + public void validate(int employeeId) { + Employee creatorEmployee = employeeRepository.getById(employeeId); + if ( + creatorEmployee.getType() != EmployeeType.MANAGER || + creatorEmployee.getType() != EmployeeType.TEAMLEAD + ) { + throw new NotPermittedException("Only manager or team lead can create tickets"); + } + } +} diff --git a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java index f8edc43..d03c004 100644 --- a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java +++ b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java @@ -1,4 +1,29 @@ package org.lab.application.ticket.use_cases; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCreateValidator; + public class CreateTicketUseCase { + + private final TicketRepository ticketRepository; + private final TicketCreateValidator ticketCreateValidator; + + public CreateTicketUseCase( + TicketRepository ticketRepository, + TicketCreateValidator ticketCreateValidator + ) { + this.ticketRepository = ticketRepository; + this.ticketCreateValidator = ticketCreateValidator; + } + + public Ticket execute( + Ticket ticket, + int employeeId, + int projectId + ) { + this.ticketCreateValidator.validate(employeeId, projectId); + Ticket createdTicket = this.ticketRepository.create(ticket); + return createdTicket; + } } diff --git a/src/main/java/org/lab/core/constants/ticket/TicketStatus.java b/src/main/java/org/lab/core/constants/ticket/TicketStatus.java new file mode 100644 index 0000000..ff35721 --- /dev/null +++ b/src/main/java/org/lab/core/constants/ticket/TicketStatus.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.ticket; + +public enum TicketStatus { + OPEN, + ACTIVE, + ON_HOLD, + CLOSED, +} diff --git a/src/main/java/org/lab/domain/ticket/model/Ticket.java b/src/main/java/org/lab/domain/ticket/model/Ticket.java new file mode 100644 index 0000000..c04524d --- /dev/null +++ b/src/main/java/org/lab/domain/ticket/model/Ticket.java @@ -0,0 +1,19 @@ +package org.lab.domain.ticket.model; + +import java.util.Date; + +import lombok.Data; + +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.interfaces.DomainObject; + +@Data +public class Ticket implements DomainObject { + private int id; + private int createdBy; + private int assignedTo; + private String description; + private TicketStatus status; + private Date createdDate; + private Date closedDate; +} \ No newline at end of file diff --git a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java new file mode 100644 index 0000000..fb6e703 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java @@ -0,0 +1,51 @@ +package org.lab.infra.db.repository.ticket; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.client.DatabaseClient; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +public class TicketRepository { + + public Ticket get(int ticketId) { + return new Ticket(); + } + + public Ticket create( + Ticket ticket, + int employeeId, + int projectId + ) { + String sql = """ + INSERT INTO tickets (createdBy, assignedTo, description, "createdBy") + VALUES (?, ?, ?, ?) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setString(1, employee.getName()); + stmt.setInt(2, employee.getAge()); + stmt.setString(3, employee.getType().name()); + stmt.setInt(4, actorId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = employeeRawDataExtractor.extractEmployeeRawData(rs); + return objectMapper.mapFromRaw(row, Employee.class); + } else { + throw new RuntimeException("Employee creation failed: no row returned"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } +} diff --git a/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java new file mode 100644 index 0000000..ce32601 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java @@ -0,0 +1,4 @@ +package org.lab.infra.db.repository.ticket.data_extractor; + +public class TicketRawDataExtractor { +} diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index 9ba1209..8443b8f 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -18,7 +18,6 @@ public class TestCreateEmployeeUseCase { private EmployeeRepository employeeRepository; private EmployeePermissionValidator validator; - private CreateValidator createValidator; private CreateEmployeeUseCase useCase; @BeforeEach From eef1dfdeb343d71171dd8d647c13c4939ae8ef24 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 16:14:01 +0300 Subject: [PATCH 54/67] added methods for getting ticket --- .../adapters/ticket/TicketCreateAdapter.java | 4 +- .../ticket/use_cases/CreateTicketUseCase.java | 6 ++- .../org/lab/domain/ticket/model/Ticket.java | 1 + .../employee/EmployeeRepository.java | 3 +- .../repository/employee/spec/TesterSpec.java | 4 +- .../repository/ticket/TicketRepository.java | 51 +++++++++++++++---- .../TicketRawDataExtractor.java | 22 ++++++++ 7 files changed, 74 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java index aa10499..a13aec9 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java @@ -1,5 +1,7 @@ package org.lab.api.adapters.ticket; +import java.util.Map; + import io.javalin.http.Context; import org.lab.application.ticket.dto.CreateTicketDTO; import org.lab.application.ticket.dto.GetTicketDTO; @@ -9,8 +11,6 @@ import org.lab.domain.shared.exceptions.ProjectNotFoundException; import org.lab.domain.ticket.model.Ticket; -import java.util.Map; - public class TicketCreateAdapter { private CreateTicketUseCase useCase; diff --git a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java index d03c004..981e106 100644 --- a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java +++ b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java @@ -23,7 +23,11 @@ public Ticket execute( int projectId ) { this.ticketCreateValidator.validate(employeeId, projectId); - Ticket createdTicket = this.ticketRepository.create(ticket); + Ticket createdTicket = this.ticketRepository.create( + ticket, + employeeId, + projectId + ); return createdTicket; } } diff --git a/src/main/java/org/lab/domain/ticket/model/Ticket.java b/src/main/java/org/lab/domain/ticket/model/Ticket.java index c04524d..698a401 100644 --- a/src/main/java/org/lab/domain/ticket/model/Ticket.java +++ b/src/main/java/org/lab/domain/ticket/model/Ticket.java @@ -12,6 +12,7 @@ public class Ticket implements DomainObject { private int id; private int createdBy; private int assignedTo; + private int projectId; private String description; private TicketStatus status; private Date createdDate; diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 8ba78e6..30ec91b 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -38,7 +38,8 @@ public Employee getById(int id) { } } catch (SQLException e) { throw new DatabaseException(); - }} + } + } public Employee create( Employee employee, diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java index 45be3f3..3011528 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java @@ -1,10 +1,10 @@ package org.lab.infra.db.repository.employee.spec; -import org.lab.infra.db.spec.SqlSpec; - import java.util.ArrayList; import java.util.List; +import org.lab.infra.db.spec.SqlSpec; + public class TesterSpec implements SqlSpec { private final int testerId; diff --git a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java index fb6e703..6ca9d9c 100644 --- a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java +++ b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java @@ -1,9 +1,10 @@ package org.lab.infra.db.repository.ticket; -import org.lab.domain.emploee.model.Employee; +import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.shared.exceptions.DatabaseException; import org.lab.domain.ticket.model.Ticket; import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.repository.ticket.data_extractor.TicketRawDataExtractor; import java.sql.Connection; import java.sql.PreparedStatement; @@ -13,8 +14,36 @@ public class TicketRepository { - public Ticket get(int ticketId) { - return new Ticket(); + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; + private final TicketRawDataExtractor ticketRawDataExtractor; + + public TicketRepository() { + databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); + ticketRawDataExtractor = new TicketRawDataExtractor(); + } + + public Ticket get( + int ticketId + ) { + String sql = "SELECT * FROM tickets where id = ?"; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, ticketId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = ticketRawDataExtractor.extractTicketRawData(rs); + return objectMapper.mapFromRaw(row, Ticket.class); + } else { + return null; + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } } public Ticket create( @@ -23,7 +52,7 @@ public Ticket create( int projectId ) { String sql = """ - INSERT INTO tickets (createdBy, assignedTo, description, "createdBy") + INSERT INTO tickets (createdBy, assignedTo, description, projectId) VALUES (?, ?, ?, ?) RETURNING * """; @@ -32,16 +61,16 @@ INSERT INTO tickets (createdBy, assignedTo, description, "createdBy") Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) ) { - stmt.setString(1, employee.getName()); - stmt.setInt(2, employee.getAge()); - stmt.setString(3, employee.getType().name()); - stmt.setInt(4, actorId); + stmt.setString(1, String.valueOf(employeeId)); + stmt.setInt(2, ticket.getAssignedTo()); + stmt.setString(3, ticket.getDescription()); + stmt.setInt(4, projectId); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { - Map row = employeeRawDataExtractor.extractEmployeeRawData(rs); - return objectMapper.mapFromRaw(row, Employee.class); + Map row = ticketRawDataExtractor.extractTicketRawData(rs); + return objectMapper.mapFromRaw(row, Ticket.class); } else { - throw new RuntimeException("Employee creation failed: no row returned"); + throw new RuntimeException("Ticket creation failed: no row returned"); } } } catch (SQLException e) { diff --git a/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java index ce32601..5f36524 100644 --- a/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java +++ b/src/main/java/org/lab/infra/db/repository/ticket/data_extractor/TicketRawDataExtractor.java @@ -1,4 +1,26 @@ package org.lab.infra.db.repository.ticket.data_extractor; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + public class TicketRawDataExtractor { + + public Map extractTicketRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("createdBy", rs.getInt("createdBy")); + row.put("assignedTo", rs.getInt("assignedTo")); + row.put("projectId", rs.getInt("projectId")); + row.put("description", rs.getString("description")); + row.put("status", rs.getString("status")); + row.put("createdDate", rs.getTimestamp("createdDate")); + row.put("closedDate", rs.getTimestamp("closedDate")); + return row; + } } From 101611c64f16b2245305240b7a8932d654baa9ae Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 16:38:01 +0300 Subject: [PATCH 55/67] added di --- build.gradle.kts | 2 + src/main/java/org/lab/Main.java | 124 ++---------------- .../employee/EmployeeCreateAdapter.java | 2 + .../employee/EmployeeDeleteAdapter.java | 2 + .../adapters/employee/EmployeeGetAdapter.java | 2 + .../project/ProjectCreateAdapter.java | 2 + .../project/ProjectDeleteAdapter.java | 2 + .../adapters/project/ProjectGetAdapter.java | 2 + .../adapters/project/ProjectListAdapter.java | 2 + .../adapters/ticket/TicketCreateAdapter.java | 3 + .../use_cases/CreateEmployeeUseCase.java | 2 + .../use_cases/DeleteEmployeeUseCase.java | 2 + .../use_cases/GetEmployeeUseCase.java | 2 + .../project/services/GetValidator.java | 2 + .../use_cases/CreateProjectUseCase.java | 2 + .../use_cases/DeleteProjectUseCase.java | 2 + .../project/use_cases/GetProjectUseCase.java | 2 + .../project/use_cases/ListProjectUseCase.java | 2 + .../services/EmployeePermissionValidator.java | 2 + .../shared/services/EmployeeProvider.java | 4 + .../shared/services/ProjectProvider.java | 2 + .../services/TicketCreateValidator.java | 2 + .../services/TicketPermissionValidator.java | 2 + .../ticket/use_cases/CreateTicketUseCase.java | 2 + src/main/java/org/lab/infra/di/AppModule.java | 54 ++++++++ .../employee/TestEmployeeCreateAdapter.java | 13 +- .../project/TestProjectCreateAdapter.java | 7 +- .../use_cases/TestCreateEmployeeUseCase.java | 1 - .../use_cases/TestGetEmployeeUseCase.java | 3 +- 29 files changed, 125 insertions(+), 126 deletions(-) create mode 100644 src/main/java/org/lab/infra/di/AppModule.java diff --git a/build.gradle.kts b/build.gradle.kts index bca8264..0f26ec0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,6 +28,8 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-databind:2.17.2") implementation("com.fasterxml.jackson.core:jackson-core:2.17.2") implementation("com.fasterxml.jackson.core:jackson-annotations:2.17.2") + implementation("com.google.inject:guice:5.1.0") + implementation("javax.inject:javax.inject:1") compileOnly("org.projectlombok:lombok:1.18.34") annotationProcessor("org.projectlombok:lombok:1.18.34") diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 1327af6..22f33b2 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -1,6 +1,9 @@ package org.lab; +import com.google.inject.Guice; +import com.google.inject.Injector; import io.javalin.Javalin; + import org.lab.api.adapters.employee.EmployeeCreateAdapter; import org.lab.api.adapters.employee.EmployeeDeleteAdapter; import org.lab.api.adapters.employee.EmployeeGetAdapter; @@ -9,125 +12,24 @@ import org.lab.api.adapters.project.ProjectGetAdapter; import org.lab.api.adapters.project.ProjectListAdapter; import org.lab.api.adapters.ticket.TicketCreateAdapter; -import org.lab.application.project.services.GetValidator; -import org.lab.application.project.services.UserSpecFactory; -import org.lab.application.project.use_cases.CreateProjectUseCase; -import org.lab.application.project.use_cases.DeleteProjectUseCase; -import org.lab.application.project.use_cases.GetProjectUseCase; -import org.lab.application.project.use_cases.ListProjectUseCase; -import org.lab.application.shared.services.EmployeeProvider; -import org.lab.application.shared.services.EmployeePermissionValidator; -import org.lab.application.employee.use_cases.CreateEmployeeUseCase; -import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; -import org.lab.application.employee.use_cases.GetEmployeeUseCase; -import org.lab.application.shared.services.ProjectProvider; -import org.lab.application.ticket.services.TicketCreateValidator; -import org.lab.application.ticket.services.TicketPermissionValidator; -import org.lab.application.ticket.use_cases.CreateTicketUseCase; -import org.lab.core.utils.mapper.ObjectMapper; -import org.lab.domain.project.services.ProjectMembershipValidator; -import org.lab.infra.db.repository.employee.EmployeeRepository; -import org.lab.infra.db.repository.project.ProjectRepository; -import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.infra.di.AppModule; public class Main { public static void main(String[] args) { + Injector injector = Guice.createInjector(new AppModule()); Javalin app = Javalin.create(config -> {}).start(7070); - EmployeeCreateAdapter createEmployeeAdapter = new EmployeeCreateAdapter( - new ObjectMapper(), - new CreateEmployeeUseCase( - new EmployeeRepository(), - new EmployeePermissionValidator( - new EmployeeRepository() - ) - ) - ); - EmployeeDeleteAdapter deleteEmployeeAdapter = new EmployeeDeleteAdapter( - new DeleteEmployeeUseCase( - new EmployeeRepository(), - new EmployeePermissionValidator( - new EmployeeRepository() - ) - ) - ); - EmployeeGetAdapter getEmployeeAdapter = new EmployeeGetAdapter( - new GetEmployeeUseCase( - new EmployeePermissionValidator( - new EmployeeRepository() - ), - new EmployeeProvider( - new EmployeeRepository() - ) - ), - new ObjectMapper() - ); - - ProjectCreateAdapter createProjectAdapter = new ProjectCreateAdapter( - new ObjectMapper(), - new CreateProjectUseCase( - new ProjectRepository(), - new EmployeePermissionValidator( - new EmployeeRepository() - ) - ) - ); - ProjectGetAdapter projectGetAdapter = new ProjectGetAdapter( - new GetProjectUseCase( - new GetValidator( - new ProjectProvider( - new ProjectRepository() - ), - new EmployeeProvider( - new EmployeeRepository() - ) - ), - new ProjectMembershipValidator() - ), - new ObjectMapper() - ); - - ProjectDeleteAdapter projectDeleteAdapter = new ProjectDeleteAdapter( - new DeleteProjectUseCase( - new ProjectRepository(), - new GetValidator( - new ProjectProvider( - new ProjectRepository() - ), - new EmployeeProvider( - new EmployeeRepository() - ) - ), - new ProjectMembershipValidator() - ) - ); + EmployeeCreateAdapter createEmployeeAdapter = injector.getInstance(EmployeeCreateAdapter.class); + EmployeeDeleteAdapter deleteEmployeeAdapter = injector.getInstance(EmployeeDeleteAdapter.class); + EmployeeGetAdapter getEmployeeAdapter = injector.getInstance(EmployeeGetAdapter.class); - ProjectListAdapter projectListAdapter = new ProjectListAdapter( - new ListProjectUseCase( - new ProjectRepository(), - new EmployeeProvider( - new EmployeeRepository() - ), - new UserSpecFactory() - ), - new ObjectMapper() - ); + ProjectCreateAdapter createProjectAdapter = injector.getInstance(ProjectCreateAdapter.class); + ProjectGetAdapter projectGetAdapter = injector.getInstance(ProjectGetAdapter.class); + ProjectDeleteAdapter projectDeleteAdapter = injector.getInstance(ProjectDeleteAdapter.class); + ProjectListAdapter projectListAdapter = injector.getInstance(ProjectListAdapter.class); - TicketCreateAdapter ticketCreateAdapter = new TicketCreateAdapter( - new CreateTicketUseCase( - new TicketRepository(), - new TicketCreateValidator( - new TicketPermissionValidator( - new EmployeeRepository() - ), - new ProjectProvider( - new ProjectRepository() - ) - ) - ), - new ObjectMapper() - ); + TicketCreateAdapter ticketCreateAdapter = injector.getInstance(TicketCreateAdapter.class); app.get("/", ctx -> ctx.result("Hello World")); diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java index b93fd33..e407369 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -2,6 +2,7 @@ import java.util.Map; +import com.google.inject.Inject; import io.javalin.http.Context; import org.lab.application.employee.dto.GetEmployeeDTO; @@ -16,6 +17,7 @@ public class EmployeeCreateAdapter { private final ObjectMapper mapper; private final CreateEmployeeUseCase useCase; + @Inject public EmployeeCreateAdapter( ObjectMapper mapper, CreateEmployeeUseCase useCase diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java index 1bddc4c..60bd14b 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeDeleteAdapter.java @@ -2,6 +2,7 @@ import java.util.Map; +import com.google.inject.Inject; import io.javalin.http.Context; import org.lab.application.employee.use_cases.DeleteEmployeeUseCase; import org.lab.domain.shared.exceptions.NotPermittedException; @@ -10,6 +11,7 @@ public class EmployeeDeleteAdapter { private final DeleteEmployeeUseCase useCase; + @Inject public EmployeeDeleteAdapter( DeleteEmployeeUseCase useCase ) { diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java index 2c50e89..651671e 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeGetAdapter.java @@ -2,6 +2,7 @@ import java.util.Map; +import com.google.inject.Inject; import io.javalin.http.Context; import org.lab.application.employee.dto.GetEmployeeDTO; @@ -15,6 +16,7 @@ public class EmployeeGetAdapter { private final GetEmployeeUseCase useCase; private final ObjectMapper mapper; + @Inject public EmployeeGetAdapter( GetEmployeeUseCase useCase, ObjectMapper mapper diff --git a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java index e6dcfe6..221afe7 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java @@ -1,5 +1,6 @@ package org.lab.api.adapters.project; +import com.google.inject.Inject; import io.javalin.http.Context; import org.lab.application.project.dto.CreateProjectDTO; @@ -16,6 +17,7 @@ public class ProjectCreateAdapter { private final ObjectMapper objectMapper; private final CreateProjectUseCase useCase; + @Inject public ProjectCreateAdapter( ObjectMapper objectMapper, CreateProjectUseCase useCase diff --git a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java index 7f19c3e..5b8ff51 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectDeleteAdapter.java @@ -2,6 +2,7 @@ import java.util.Map; +import com.google.inject.Inject; import io.javalin.http.Context; import org.lab.application.project.use_cases.DeleteProjectUseCase; @@ -13,6 +14,7 @@ public class ProjectDeleteAdapter { private final DeleteProjectUseCase useCase; + @Inject public ProjectDeleteAdapter( DeleteProjectUseCase useCase ) { diff --git a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java index 5a8e1c7..10a31c8 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectGetAdapter.java @@ -2,6 +2,7 @@ import java.util.Map; +import com.google.inject.Inject; import io.javalin.http.Context; import org.lab.application.project.dto.GetProjectDTO; @@ -17,6 +18,7 @@ public class ProjectGetAdapter { private final GetProjectUseCase useCase; private final ObjectMapper mapper; + @Inject public ProjectGetAdapter( GetProjectUseCase useCase, ObjectMapper mapper diff --git a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java index 93984b7..e32aa5a 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectListAdapter.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; +import com.google.inject.Inject; import io.javalin.http.Context; import org.lab.application.project.dto.GetProjectDTO; @@ -17,6 +18,7 @@ public class ProjectListAdapter { private final ListProjectUseCase useCase; private final ObjectMapper mapper; + @Inject public ProjectListAdapter( ListProjectUseCase useCase, ObjectMapper mapper diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java index a13aec9..4ff47ba 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java @@ -2,7 +2,9 @@ import java.util.Map; +import com.google.inject.Inject; import io.javalin.http.Context; + import org.lab.application.ticket.dto.CreateTicketDTO; import org.lab.application.ticket.dto.GetTicketDTO; import org.lab.application.ticket.use_cases.CreateTicketUseCase; @@ -16,6 +18,7 @@ public class TicketCreateAdapter { private CreateTicketUseCase useCase; private ObjectMapper objectMapper; + @Inject public TicketCreateAdapter( CreateTicketUseCase useCase, ObjectMapper objectMapper diff --git a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java index 7047cb3..782a55d 100644 --- a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.employee.use_cases; +import com.google.inject.Inject; import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.repository.employee.EmployeeRepository; @@ -9,6 +10,7 @@ public class CreateEmployeeUseCase { private final EmployeeRepository employeeRepository; private final EmployeePermissionValidator validator; + @Inject public CreateEmployeeUseCase( EmployeeRepository employeeRepository, EmployeePermissionValidator validator diff --git a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java index 901925f..cc9c0c4 100644 --- a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.employee.use_cases; +import com.google.inject.Inject; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.application.shared.services.EmployeePermissionValidator; @@ -8,6 +9,7 @@ public class DeleteEmployeeUseCase { private final EmployeeRepository employeeRepository; private final EmployeePermissionValidator validator; + @Inject public DeleteEmployeeUseCase( EmployeeRepository employeeRepository, EmployeePermissionValidator validator diff --git a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java index a7e913c..e34e4e1 100644 --- a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.employee.use_cases; +import com.google.inject.Inject; import org.lab.domain.emploee.model.Employee; import org.lab.application.shared.services.EmployeeProvider; import org.lab.application.shared.services.EmployeePermissionValidator; @@ -9,6 +10,7 @@ public class GetEmployeeUseCase { private EmployeePermissionValidator employeePermissionValidator; private EmployeeProvider employeeProvider; + @Inject public GetEmployeeUseCase( EmployeePermissionValidator employeePermissionValidator, EmployeeProvider employeeProvider diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java index 55c87c5..611bac5 100644 --- a/src/main/java/org/lab/application/project/services/GetValidator.java +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -2,6 +2,7 @@ import java.util.concurrent.*; +import com.google.inject.Inject; import org.lab.application.shared.services.ProjectProvider; import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; @@ -14,6 +15,7 @@ public class GetValidator { private final ProjectProvider projectProvider; private final EmployeeProvider currentEmployeeProvider; + @Inject public GetValidator( ProjectProvider projectProvider, EmployeeProvider currentEmployeeProvider diff --git a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java index 4a2de45..32c8a9b 100644 --- a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.project.use_cases; +import com.google.inject.Inject; import org.lab.domain.project.model.Project; import org.lab.infra.db.repository.project.ProjectRepository; import org.lab.application.shared.services.EmployeePermissionValidator; @@ -9,6 +10,7 @@ public class CreateProjectUseCase { private final ProjectRepository projectRepository; private final EmployeePermissionValidator employeePermissionValidator; + @Inject public CreateProjectUseCase( ProjectRepository projectRepository, EmployeePermissionValidator employeePermissionValidator diff --git a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java index d03e61f..a610f0f 100644 --- a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.project.use_cases; +import com.google.inject.Inject; import org.lab.application.project.services.Pair; import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; @@ -13,6 +14,7 @@ public class DeleteProjectUseCase { private final GetValidator getValidator; private ProjectMembershipValidator projectMembershipValidator; + @Inject public DeleteProjectUseCase( ProjectRepository projectRepository, GetValidator getValidator, diff --git a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java index 057c938..34dd2ba 100644 --- a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.project.use_cases; +import com.google.inject.Inject; import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; import org.lab.application.project.services.GetValidator; @@ -11,6 +12,7 @@ public class GetProjectUseCase { private final GetValidator getValidator; private final ProjectMembershipValidator projectMembershipValidator; + @Inject public GetProjectUseCase( GetValidator getValidator, ProjectMembershipValidator projectMembershipValidator diff --git a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java index ece7f2f..3c17e12 100644 --- a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java @@ -2,6 +2,7 @@ import java.util.List; +import com.google.inject.Inject; import org.lab.application.project.services.UserSpecFactory; import org.lab.application.shared.services.EmployeeProvider; import org.lab.domain.emploee.model.Employee; @@ -15,6 +16,7 @@ public class ListProjectUseCase { private final EmployeeProvider currentEmployeeProvider; private final UserSpecFactory userSpecFactory; + @Inject public ListProjectUseCase( ProjectRepository projectRepository, EmployeeProvider currentEmployeeProvider, diff --git a/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java index df0428d..0ffb739 100644 --- a/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java +++ b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java @@ -1,5 +1,6 @@ package org.lab.application.shared.services; +import com.google.inject.Inject; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; @@ -9,6 +10,7 @@ public class EmployeePermissionValidator { private final EmployeeRepository employeeRepository; + @Inject public EmployeePermissionValidator( EmployeeRepository employeeRepository ) { diff --git a/src/main/java/org/lab/application/shared/services/EmployeeProvider.java b/src/main/java/org/lab/application/shared/services/EmployeeProvider.java index ae0613e..6de0a53 100644 --- a/src/main/java/org/lab/application/shared/services/EmployeeProvider.java +++ b/src/main/java/org/lab/application/shared/services/EmployeeProvider.java @@ -1,12 +1,16 @@ package org.lab.application.shared.services; +import com.google.inject.Inject; + import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.employee.EmployeeRepository; public class EmployeeProvider { + private final EmployeeRepository employeeRepository; + @Inject public EmployeeProvider( EmployeeRepository employeeRepository ) { diff --git a/src/main/java/org/lab/application/shared/services/ProjectProvider.java b/src/main/java/org/lab/application/shared/services/ProjectProvider.java index 8e3dfa4..5d5beab 100644 --- a/src/main/java/org/lab/application/shared/services/ProjectProvider.java +++ b/src/main/java/org/lab/application/shared/services/ProjectProvider.java @@ -1,5 +1,6 @@ package org.lab.application.shared.services; +import com.google.inject.Inject; import org.lab.domain.project.model.Project; import org.lab.domain.shared.exceptions.ProjectNotFoundException; import org.lab.infra.db.repository.project.ProjectRepository; @@ -8,6 +9,7 @@ public class ProjectProvider { private ProjectRepository projectRepository; + @Inject public ProjectProvider( ProjectRepository projectRepository ) { diff --git a/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java index f519fba..14d7399 100644 --- a/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java +++ b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java @@ -1,5 +1,6 @@ package org.lab.application.ticket.services; +import com.google.inject.Inject; import org.lab.application.shared.services.ProjectProvider; import java.util.concurrent.ExecutionException; @@ -10,6 +11,7 @@ public class TicketCreateValidator { private final TicketPermissionValidator ticketPermissionValidator; private final ProjectProvider projectProvider; + @Inject public TicketCreateValidator( TicketPermissionValidator ticketPermissionValidator, ProjectProvider projectProvider diff --git a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java index 98c622d..c69f468 100644 --- a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java +++ b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java @@ -1,5 +1,6 @@ package org.lab.application.ticket.services; +import com.google.inject.Inject; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; @@ -9,6 +10,7 @@ public class TicketPermissionValidator { private final EmployeeRepository employeeRepository; + @Inject public TicketPermissionValidator( EmployeeRepository employeeRepository ) { diff --git a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java index 981e106..0c9b670 100644 --- a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java +++ b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java @@ -1,5 +1,6 @@ package org.lab.application.ticket.use_cases; +import com.google.inject.Inject; import org.lab.domain.ticket.model.Ticket; import org.lab.infra.db.repository.ticket.TicketRepository; import org.lab.application.ticket.services.TicketCreateValidator; @@ -9,6 +10,7 @@ public class CreateTicketUseCase { private final TicketRepository ticketRepository; private final TicketCreateValidator ticketCreateValidator; + @Inject public CreateTicketUseCase( TicketRepository ticketRepository, TicketCreateValidator ticketCreateValidator diff --git a/src/main/java/org/lab/infra/di/AppModule.java b/src/main/java/org/lab/infra/di/AppModule.java new file mode 100644 index 0000000..5397304 --- /dev/null +++ b/src/main/java/org/lab/infra/di/AppModule.java @@ -0,0 +1,54 @@ +package org.lab.infra.di; + +import com.google.inject.AbstractModule; +import org.lab.api.adapters.employee.*; +import org.lab.api.adapters.project.*; +import org.lab.api.adapters.ticket.*; +import org.lab.application.employee.use_cases.*; +import org.lab.application.project.use_cases.*; +import org.lab.application.ticket.use_cases.*; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.application.shared.services.*; +import org.lab.application.ticket.services.*; +import org.lab.application.project.services.*; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.infra.db.repository.employee.EmployeeRepository; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.repository.ticket.TicketRepository; + + +public class AppModule extends AbstractModule { + @Override + protected void configure() { + bind(EmployeeRepository.class).asEagerSingleton(); + bind(ProjectRepository.class).asEagerSingleton(); + bind(TicketRepository.class).asEagerSingleton(); + bind(ObjectMapper.class).asEagerSingleton(); + bind(EmployeePermissionValidator.class).asEagerSingleton(); + bind(ProjectProvider.class).asEagerSingleton(); + bind(EmployeeProvider.class).asEagerSingleton(); + bind(ProjectMembershipValidator.class).asEagerSingleton(); + bind(GetValidator.class).asEagerSingleton(); + bind(UserSpecFactory.class).asEagerSingleton(); + bind(TicketPermissionValidator.class).asEagerSingleton(); + bind(TicketCreateValidator.class).asEagerSingleton(); + + bind(CreateEmployeeUseCase.class).asEagerSingleton(); + bind(DeleteEmployeeUseCase.class).asEagerSingleton(); + bind(GetEmployeeUseCase.class).asEagerSingleton(); + bind(CreateProjectUseCase.class).asEagerSingleton(); + bind(GetProjectUseCase.class).asEagerSingleton(); + bind(DeleteProjectUseCase.class).asEagerSingleton(); + bind(ListProjectUseCase.class).asEagerSingleton(); + bind(CreateTicketUseCase.class).asEagerSingleton(); + + bind(EmployeeCreateAdapter.class).asEagerSingleton(); + bind(EmployeeDeleteAdapter.class).asEagerSingleton(); + bind(EmployeeGetAdapter.class).asEagerSingleton(); + bind(ProjectCreateAdapter.class).asEagerSingleton(); + bind(ProjectGetAdapter.class).asEagerSingleton(); + bind(ProjectDeleteAdapter.class).asEagerSingleton(); + bind(ProjectListAdapter.class).asEagerSingleton(); + bind(TicketCreateAdapter.class).asEagerSingleton(); + } +} diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java index 71ab999..1efa0ab 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java @@ -36,8 +36,7 @@ public void testCreateEmployeeSuccess() { CreateEmployeeDTO dto = new CreateEmployeeDTO( "John", 30, - EmployeeType.PROGRAMMER, - 99 + EmployeeType.PROGRAMMER ); Employee mappedEmployee = new Employee(); @@ -68,7 +67,7 @@ public void testCreateEmployeeSuccess() { Mockito.when(mapper.mapToDomain(dto, Employee.class)) .thenReturn(mappedEmployee); - Mockito.when(useCase.execute(mappedEmployee, dto.creatorId())) + Mockito.when(useCase.execute(mappedEmployee, 99)) .thenReturn(createdEmployee); Mockito.when(mapper.mapToPresentation(createdEmployee, GetEmployeeDTO.class)) @@ -88,15 +87,14 @@ public void testCreateEmployeeNotPermitted() { CreateEmployeeDTO dto = new CreateEmployeeDTO( "John", 30, - EmployeeType.PROGRAMMER, - 99 + EmployeeType.PROGRAMMER ); Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)).thenReturn(dto); Mockito.when(mapper.mapToDomain(dto, Employee.class)) .thenReturn(new Employee()); - Mockito.when(useCase.execute(Mockito.any(), Mockito.eq(dto.creatorId()))) + Mockito.when(useCase.execute(Mockito.any(), Mockito.eq(99))) .thenThrow(new NotPermittedException( "You do not have permission to perform this operation" )); @@ -117,8 +115,7 @@ public void testCreateEmployeeUnexpectedError() { CreateEmployeeDTO dto = new CreateEmployeeDTO( "John", 30, - EmployeeType.PROGRAMMER, - 99 + EmployeeType.PROGRAMMER ); Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)).thenReturn(dto); diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java index 7111520..bd44f20 100644 --- a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java +++ b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java @@ -40,7 +40,6 @@ public void testCreateProjectSuccess() { "TestProject", "Description", 10, - 20, List.of(1, 2), List.of(3, 4) ); @@ -66,7 +65,7 @@ public void testCreateProjectSuccess() { created.setCreatedDate(new Date()); created.setCreatedBy(99); - Mockito.when(useCase.execute(mapped)) + Mockito.when(useCase.execute(mapped, 20)) .thenReturn(created); GetProjectDTO presentation = new GetProjectDTO( @@ -111,7 +110,6 @@ public void testCreateProject_NotPermitted() { "TestProject", "Description", 10, - 20, List.of(), List.of() ); @@ -122,7 +120,7 @@ public void testCreateProject_NotPermitted() { Mockito.when(mapper.mapToDomain(dto, Project.class)) .thenReturn(new Project()); - Mockito.when(useCase.execute(Mockito.any())) + Mockito.when(useCase.execute(Mockito.any(), 20)) .thenThrow( new NotPermittedException( "You do not have permission to perform this operation" @@ -149,7 +147,6 @@ public void testCreateProject_UnexpectedError() { "TestProject", "Description", 10, - 20, List.of(), List.of() ); diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index 8443b8f..9954f32 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -9,7 +9,6 @@ import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.shared.exceptions.NotPermittedException; -import org.lab.application.employee.services.CreateValidator; import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.repository.employee.EmployeeRepository; diff --git a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java index 10694cd..f5173dc 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java @@ -1,8 +1,10 @@ package org.lab.application.employee.use_cases; +import org.mockito.Mockito; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.application.shared.services.EmployeeProvider; import org.lab.core.constants.employee.EmployeeType; @@ -10,7 +12,6 @@ import org.lab.domain.shared.exceptions.NotPermittedException; import org.lab.domain.shared.exceptions.UserNotFoundException; import org.lab.infra.db.repository.employee.EmployeeRepository; -import org.mockito.Mockito; public class TestGetEmployeeUseCase { From a599f3f09d8745e081ce7a8fbcc60189c1677c0e Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 16:57:35 +0300 Subject: [PATCH 56/67] bug fixes after testing --- .../java/org/lab/api/adapters/ticket/TicketCreateAdapter.java | 1 + .../java/org/lab/application/ticket/dto/GetTicketDTO.java | 1 + .../ticket/services/TicketPermissionValidator.java | 3 ++- .../org/lab/infra/db/repository/ticket/TicketRepository.java | 4 ++-- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java index 4ff47ba..74ff59e 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java @@ -58,6 +58,7 @@ public Context createTicket( return ctx.status(404).json(Map.of("error", "Project doesnt exist")); } catch (Exception e) { + System.err.println("ERROR " + e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java b/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java index dbd3ea3..6bff6d4 100644 --- a/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java +++ b/src/main/java/org/lab/application/ticket/dto/GetTicketDTO.java @@ -9,6 +9,7 @@ public record GetTicketDTO( int id, int createdBy, int assignedTo, + int projectId, String description, TicketStatus status, Date createdDate, diff --git a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java index c69f468..41a827d 100644 --- a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java +++ b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java @@ -19,8 +19,9 @@ public TicketPermissionValidator( public void validate(int employeeId) { Employee creatorEmployee = employeeRepository.getById(employeeId); + System.out.println(creatorEmployee); if ( - creatorEmployee.getType() != EmployeeType.MANAGER || + creatorEmployee.getType() != EmployeeType.MANAGER && creatorEmployee.getType() != EmployeeType.TEAMLEAD ) { throw new NotPermittedException("Only manager or team lead can create tickets"); diff --git a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java index 6ca9d9c..aaa937d 100644 --- a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java +++ b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java @@ -52,7 +52,7 @@ public Ticket create( int projectId ) { String sql = """ - INSERT INTO tickets (createdBy, assignedTo, description, projectId) + INSERT INTO tickets ("createdBy", "assignedTo", description, "projectId") VALUES (?, ?, ?, ?) RETURNING * """; @@ -61,7 +61,7 @@ INSERT INTO tickets (createdBy, assignedTo, description, projectId) Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) ) { - stmt.setString(1, String.valueOf(employeeId)); + stmt.setInt(1, employeeId); stmt.setInt(2, ticket.getAssignedTo()); stmt.setString(3, ticket.getDescription()); stmt.setInt(4, projectId); From c501b6e4d5fbe69a7f1184877b3ed776c281e07b Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 17:13:59 +0300 Subject: [PATCH 57/67] added functionality for closing ticket --- .../adapters/ticket/TicketCloseAdapter.java | 52 +++++++++++++++++++ .../ticket/services/TicketCloseValidator.java | 31 +++++++++++ .../ticket/use_cases/CloseTicketUseCase.java | 27 ++++++++++ .../repository/ticket/TicketRepository.java | 42 ++++++++++++--- src/main/java/org/lab/infra/di/AppModule.java | 3 ++ 5 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/lab/application/ticket/services/TicketCloseValidator.java diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java index b4925db..0f8bac8 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java @@ -1,4 +1,56 @@ package org.lab.api.adapters.ticket; +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CloseTicketUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; + public class TicketCloseAdapter { + + private final CloseTicketUseCase useCase; + private final ObjectMapper objectMapper; + + @Inject + public TicketCloseAdapter( + CloseTicketUseCase useCase, + ObjectMapper objectMapper + ) { + this.useCase = useCase; + this.objectMapper = objectMapper; + } + + public Context closeTicket( + Context ctx + ) { + try { + int employeeId = Integer.parseInt(ctx.pathParam("employeeId")); + int ticketId = Integer.parseInt(ctx.pathParam("ticketId")); + Ticket updatedTicket = useCase.execute( + ticketId, + employeeId + ); + GetTicketDTO presentationTicket = objectMapper.mapToPresentation( + updatedTicket, + GetTicketDTO.class + ); + return ctx.status(201).json(presentationTicket); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } } diff --git a/src/main/java/org/lab/application/ticket/services/TicketCloseValidator.java b/src/main/java/org/lab/application/ticket/services/TicketCloseValidator.java new file mode 100644 index 0000000..18f99d7 --- /dev/null +++ b/src/main/java/org/lab/application/ticket/services/TicketCloseValidator.java @@ -0,0 +1,31 @@ +package org.lab.application.ticket.services; + +import com.google.inject.Inject; + +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; + +public class TicketCloseValidator { + + private TicketRepository ticketRepository; + + @Inject + public TicketCloseValidator( + TicketRepository ticketRepository + ) { + this.ticketRepository = ticketRepository; + } + + public void validate( + int ticketId, + int employeeId + ) throws + NotPermittedException + { + Ticket ticket = this.ticketRepository.get(ticketId, employeeId); + if (ticket == null) { + throw new NotPermittedException("Ticket with id " + ticketId + " does not exist"); + } + } +} diff --git a/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java index 6a8cdaf..1dfb844 100644 --- a/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java +++ b/src/main/java/org/lab/application/ticket/use_cases/CloseTicketUseCase.java @@ -1,4 +1,31 @@ package org.lab.application.ticket.use_cases; +import com.google.inject.Inject; + +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCloseValidator; + public class CloseTicketUseCase { + + private final TicketRepository ticketRepository; + private final TicketCloseValidator ticketCloseValidator; + + @Inject + public CloseTicketUseCase( + TicketRepository ticketRepository, + TicketCloseValidator ticketCloseValidator + ) { + this.ticketRepository = ticketRepository; + this.ticketCloseValidator = ticketCloseValidator; + } + + public Ticket execute( + int ticketId, + int employeeId + ) { + this.ticketCloseValidator.validate(ticketId, employeeId); + Ticket ticket = this.ticketRepository.close(ticketId); + return ticket; + } } diff --git a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java index aaa937d..a7b28ec 100644 --- a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java +++ b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java @@ -1,17 +1,17 @@ package org.lab.infra.db.repository.ticket; -import org.lab.core.utils.mapper.ObjectMapper; -import org.lab.domain.shared.exceptions.DatabaseException; -import org.lab.domain.ticket.model.Ticket; -import org.lab.infra.db.client.DatabaseClient; -import org.lab.infra.db.repository.ticket.data_extractor.TicketRawDataExtractor; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.repository.ticket.data_extractor.TicketRawDataExtractor; + public class TicketRepository { private final DatabaseClient databaseClient; @@ -25,14 +25,16 @@ public TicketRepository() { } public Ticket get( - int ticketId + int ticketId, + int employeeId ) { - String sql = "SELECT * FROM tickets where id = ?"; + String sql = "SELECT * FROM tickets WHERE id = ? AND \"assignedTo\" = ?"; try ( Connection conn = DatabaseClient.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql) ) { stmt.setInt(1, ticketId); + stmt.setInt(2, employeeId); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { Map row = ticketRawDataExtractor.extractTicketRawData(rs); @@ -77,4 +79,28 @@ INSERT INTO tickets ("createdBy", "assignedTo", description, "projectId") throw new DatabaseException(); } } + + public Ticket close( + int ticketId + ) { + String sql = """ + UPDATE tickets SET status = 'CLOSED' WHERE id = ?"; + """; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, ticketId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = ticketRawDataExtractor.extractTicketRawData(rs); + return objectMapper.mapFromRaw(row, Ticket.class); + } else { + throw new RuntimeException("Ticket update failed: no row returned"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } } diff --git a/src/main/java/org/lab/infra/di/AppModule.java b/src/main/java/org/lab/infra/di/AppModule.java index 5397304..465f6e6 100644 --- a/src/main/java/org/lab/infra/di/AppModule.java +++ b/src/main/java/org/lab/infra/di/AppModule.java @@ -32,6 +32,7 @@ protected void configure() { bind(UserSpecFactory.class).asEagerSingleton(); bind(TicketPermissionValidator.class).asEagerSingleton(); bind(TicketCreateValidator.class).asEagerSingleton(); + bind(TicketCloseValidator.class).asEagerSingleton(); bind(CreateEmployeeUseCase.class).asEagerSingleton(); bind(DeleteEmployeeUseCase.class).asEagerSingleton(); @@ -41,6 +42,7 @@ protected void configure() { bind(DeleteProjectUseCase.class).asEagerSingleton(); bind(ListProjectUseCase.class).asEagerSingleton(); bind(CreateTicketUseCase.class).asEagerSingleton(); + bind(CloseTicketUseCase.class).asEagerSingleton(); bind(EmployeeCreateAdapter.class).asEagerSingleton(); bind(EmployeeDeleteAdapter.class).asEagerSingleton(); @@ -50,5 +52,6 @@ protected void configure() { bind(ProjectDeleteAdapter.class).asEagerSingleton(); bind(ProjectListAdapter.class).asEagerSingleton(); bind(TicketCreateAdapter.class).asEagerSingleton(); + bind(TicketCloseAdapter.class).asEagerSingleton(); } } From 1fb0c9453042110936995656efe16309f2fcd349 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 17:20:24 +0300 Subject: [PATCH 58/67] registered route for closing ticket --- src/main/java/org/lab/Main.java | 3 +++ .../java/org/lab/api/adapters/ticket/TicketCloseAdapter.java | 1 + .../org/lab/infra/db/repository/ticket/TicketRepository.java | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 22f33b2..75fe555 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -11,6 +11,7 @@ import org.lab.api.adapters.project.ProjectDeleteAdapter; import org.lab.api.adapters.project.ProjectGetAdapter; import org.lab.api.adapters.project.ProjectListAdapter; +import org.lab.api.adapters.ticket.TicketCloseAdapter; import org.lab.api.adapters.ticket.TicketCreateAdapter; import org.lab.infra.di.AppModule; @@ -30,6 +31,7 @@ public static void main(String[] args) { ProjectListAdapter projectListAdapter = injector.getInstance(ProjectListAdapter.class); TicketCreateAdapter ticketCreateAdapter = injector.getInstance(TicketCreateAdapter.class); + TicketCloseAdapter ticketCloseAdapter = injector.getInstance(TicketCloseAdapter.class); app.get("/", ctx -> ctx.result("Hello World")); @@ -43,5 +45,6 @@ public static void main(String[] args) { app.delete("/project/{projectId}/{employeeId}", projectDeleteAdapter::deleteProject); app.post("/ticket/{employeeId}/{projectId}", ticketCreateAdapter::createTicket); + app.patch("/ticket/{employeeId}/{ticketId}", ticketCloseAdapter::closeTicket); } } diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java index 0f8bac8..be126e2 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java @@ -50,6 +50,7 @@ public Context closeTicket( ); } catch (Exception e) { + System.err.println("ERROR "+ e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java index a7b28ec..6d8e722 100644 --- a/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java +++ b/src/main/java/org/lab/infra/db/repository/ticket/TicketRepository.java @@ -84,7 +84,10 @@ public Ticket close( int ticketId ) { String sql = """ - UPDATE tickets SET status = 'CLOSED' WHERE id = ?"; + UPDATE tickets + SET status = 'CLOSED' + WHERE id = ? + RETURNING * """; try ( Connection conn = DatabaseClient.getConnection(); From dcc07bd5f017f549f82c1ccf614da9c76372a36f Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 18:42:36 +0300 Subject: [PATCH 59/67] completed logic with creating error message --- src/main/java/org/lab/Main.java | 10 ++- .../ErrorMessageCloseAdapter.java | 4 ++ .../ErrorMessageCreateAdapter.java | 66 +++++++++++++++++++ .../dto/CreateErrorMessageDTO.java | 9 +++ .../error_message/dto/GetErrorMessageDTO.java | 13 ++++ .../services/CreateErrorMessageValidator.java | 40 +++++++++++ .../use_cases/CloseCreateMessageUseCase.java | 4 ++ .../use_cases/CreateErrorMessageUseCase.java | 30 +++++++++ .../shared/services/ProjectSpecProvider.java | 37 +++++++++++ .../error_message/ErrorMessageStatus.java | 8 +++ .../error_mesage/model/ErrorMessage.java | 15 +++++ .../error_message/ErrorMessageRepository.java | 56 ++++++++++++++++ .../ErrorMessageRawDataExtractor.java | 23 +++++++ .../repository/project/ProjectRepository.java | 28 ++++++++ .../ProjectRawDataExtractor.java | 4 +- src/main/java/org/lab/infra/di/AppModule.java | 10 ++- 16 files changed, 353 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java create mode 100644 src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java create mode 100644 src/main/java/org/lab/application/error_message/dto/CreateErrorMessageDTO.java create mode 100644 src/main/java/org/lab/application/error_message/dto/GetErrorMessageDTO.java create mode 100644 src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java create mode 100644 src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java create mode 100644 src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java create mode 100644 src/main/java/org/lab/application/shared/services/ProjectSpecProvider.java create mode 100644 src/main/java/org/lab/core/constants/error_message/ErrorMessageStatus.java create mode 100644 src/main/java/org/lab/domain/error_mesage/model/ErrorMessage.java create mode 100644 src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java create mode 100644 src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index 75fe555..c74e87c 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -2,11 +2,13 @@ import com.google.inject.Guice; import com.google.inject.Injector; +import com.google.inject.Stage; import io.javalin.Javalin; import org.lab.api.adapters.employee.EmployeeCreateAdapter; import org.lab.api.adapters.employee.EmployeeDeleteAdapter; import org.lab.api.adapters.employee.EmployeeGetAdapter; +import org.lab.api.adapters.error_message.ErrorMessageCreateAdapter; import org.lab.api.adapters.project.ProjectCreateAdapter; import org.lab.api.adapters.project.ProjectDeleteAdapter; import org.lab.api.adapters.project.ProjectGetAdapter; @@ -18,7 +20,7 @@ public class Main { public static void main(String[] args) { - Injector injector = Guice.createInjector(new AppModule()); + Injector injector = Guice.createInjector(Stage.DEVELOPMENT, new AppModule()); Javalin app = Javalin.create(config -> {}).start(7070); EmployeeCreateAdapter createEmployeeAdapter = injector.getInstance(EmployeeCreateAdapter.class); @@ -33,6 +35,10 @@ public static void main(String[] args) { TicketCreateAdapter ticketCreateAdapter = injector.getInstance(TicketCreateAdapter.class); TicketCloseAdapter ticketCloseAdapter = injector.getInstance(TicketCloseAdapter.class); + ErrorMessageCreateAdapter errorMessageCreateAdapter = injector.getInstance( + ErrorMessageCreateAdapter.class + ); + app.get("/", ctx -> ctx.result("Hello World")); app.post("/employee/{actorId}", createEmployeeAdapter::createEmployee); @@ -46,5 +52,7 @@ public static void main(String[] args) { app.post("/ticket/{employeeId}/{projectId}", ticketCreateAdapter::createTicket); app.patch("/ticket/{employeeId}/{ticketId}", ticketCloseAdapter::closeTicket); + + app.post("/error_message/{employeeId}", errorMessageCreateAdapter::createErrorMessage); } } diff --git a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java new file mode 100644 index 0000000..23a45d4 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java @@ -0,0 +1,4 @@ +package org.lab.api.adapters.error_message; + +public class ErrorMessageCloseAdapter { +} diff --git a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java new file mode 100644 index 0000000..941ac61 --- /dev/null +++ b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java @@ -0,0 +1,66 @@ +package org.lab.api.adapters.error_message; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.error_message.dto.CreateErrorMessageDTO; +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +import java.util.Map; + +public class ErrorMessageCreateAdapter { + + private final CreateErrorMessageUseCase useCase; + private final ObjectMapper mapper; + + @Inject + public ErrorMessageCreateAdapter( + CreateErrorMessageUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context createErrorMessage( + Context ctx + ) { + try { + int actorId = Integer.parseInt(ctx.pathParam("actorId")); + CreateErrorMessageDTO dto = ctx.bodyAsClass(CreateErrorMessageDTO.class); + ErrorMessage errorMessage = mapper.mapToDomain(dto, ErrorMessage.class); + ErrorMessage createdMessage = useCase.execute( + errorMessage, + actorId + ); + GetErrorMessageDTO presentationMessage = mapper.mapToPresentation( + createdMessage, + GetErrorMessageDTO.class + ); + return ctx.status(201).json(presentationMessage); + + } catch (UserNotFoundException e) { + return ctx.status(409).json(Map.of("error", "User doesnt exist")); + + } catch (NotPermittedException e) { + return ctx.status(403).json( + Map.of( + "error", + "You do not have permission to perform this operation" + ) + ); + + } catch (ProjectNotFoundException e) { + return ctx.status(404).json(Map.of("error", "Project doesnt exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } +} diff --git a/src/main/java/org/lab/application/error_message/dto/CreateErrorMessageDTO.java b/src/main/java/org/lab/application/error_message/dto/CreateErrorMessageDTO.java new file mode 100644 index 0000000..418e30a --- /dev/null +++ b/src/main/java/org/lab/application/error_message/dto/CreateErrorMessageDTO.java @@ -0,0 +1,9 @@ +package org.lab.application.error_message.dto; + +import org.lab.domain.interfaces.PresentationObject; + +public record CreateErrorMessageDTO( + int projectId, + String text +) implements PresentationObject { +} diff --git a/src/main/java/org/lab/application/error_message/dto/GetErrorMessageDTO.java b/src/main/java/org/lab/application/error_message/dto/GetErrorMessageDTO.java new file mode 100644 index 0000000..7bab17d --- /dev/null +++ b/src/main/java/org/lab/application/error_message/dto/GetErrorMessageDTO.java @@ -0,0 +1,13 @@ +package org.lab.application.error_message.dto; + +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.domain.interfaces.PresentationObject; + +public record GetErrorMessageDTO( + int id, + int projectId, + int createdBy, + String text, + ErrorMessageStatus status +) implements PresentationObject { +} \ No newline at end of file diff --git a/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java b/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java new file mode 100644 index 0000000..ce39215 --- /dev/null +++ b/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java @@ -0,0 +1,40 @@ +package org.lab.application.error_message.services; + +import com.google.inject.Inject; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.application.shared.services.ProjectSpecProvider; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.spec.Specification; +import org.lab.application.project.services.UserSpecFactory; + +public class CreateErrorMessageValidator { + + private final EmployeeProvider employeeProvider; + private final ProjectSpecProvider projectSpecProvider; + private final UserSpecFactory userSpecFactory; + + @Inject + public CreateErrorMessageValidator( + EmployeeProvider employeeProvider, + ProjectSpecProvider projectSpecProvider, + UserSpecFactory userSpecFactory + ) { + this.employeeProvider = employeeProvider; + this.projectSpecProvider = projectSpecProvider; + this.userSpecFactory = userSpecFactory; + } + + public void validate( + int employeeId, + int projectId + ) { + Employee employee = this.employeeProvider.get(employeeId); + if (employee.getType() != EmployeeType.TESTER) { + throw new NotPermittedException("Only tester can create error messages"); + } + Specification employeeSpec = this.userSpecFactory.getForType(employee); + this.projectSpecProvider.get(projectId, employeeSpec); + } +} diff --git a/src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java b/src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java new file mode 100644 index 0000000..ca81481 --- /dev/null +++ b/src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java @@ -0,0 +1,4 @@ +package org.lab.application.error_message.use_cases; + +public class CloseCreateMessageUseCase { +} diff --git a/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java b/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java new file mode 100644 index 0000000..c59a655 --- /dev/null +++ b/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java @@ -0,0 +1,30 @@ +package org.lab.application.error_message.use_cases; + +import com.google.inject.Inject; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; +import org.lab.application.error_message.services.CreateErrorMessageValidator; + +public class CreateErrorMessageUseCase { + + private final ErrorMessageRepository errorMessageRepository; + private final CreateErrorMessageValidator createErrorMessageValidator; + + @Inject + public CreateErrorMessageUseCase( + ErrorMessageRepository errorMessageRepository, + CreateErrorMessageValidator createErrorMessageValidator + ) { + this.errorMessageRepository = errorMessageRepository; + this.createErrorMessageValidator = createErrorMessageValidator; + } + + public ErrorMessage execute( + ErrorMessage message, + int employeeId + ) { + this.createErrorMessageValidator.validate(employeeId, message.getProjectId()); + ErrorMessage createdMessage = this.errorMessageRepository.create(message, employeeId); + return createdMessage; + } +} diff --git a/src/main/java/org/lab/application/shared/services/ProjectSpecProvider.java b/src/main/java/org/lab/application/shared/services/ProjectSpecProvider.java new file mode 100644 index 0000000..44cc602 --- /dev/null +++ b/src/main/java/org/lab/application/shared/services/ProjectSpecProvider.java @@ -0,0 +1,37 @@ +package org.lab.application.shared.services; + +import com.google.inject.Inject; + +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; + +public class ProjectSpecProvider { + + private ProjectRepository projectRepository; + + @Inject + public ProjectSpecProvider( + ProjectRepository projectRepository + ) { + this.projectRepository = projectRepository; + } + + public Project get( + int projectId, + Specification spec + ) throws + ProjectNotFoundException + { + Project project = this.projectRepository.getWithSpec( + projectId, + spec + ); + if (project == null) { + throw new ProjectNotFoundException(); + } + return project; + } +} + diff --git a/src/main/java/org/lab/core/constants/error_message/ErrorMessageStatus.java b/src/main/java/org/lab/core/constants/error_message/ErrorMessageStatus.java new file mode 100644 index 0000000..8002ff0 --- /dev/null +++ b/src/main/java/org/lab/core/constants/error_message/ErrorMessageStatus.java @@ -0,0 +1,8 @@ +package org.lab.core.constants.error_message; + +public enum ErrorMessageStatus { + OPEN, + ACTIVE, + ON_HOLD, + CLOSED, +} \ No newline at end of file diff --git a/src/main/java/org/lab/domain/error_mesage/model/ErrorMessage.java b/src/main/java/org/lab/domain/error_mesage/model/ErrorMessage.java new file mode 100644 index 0000000..7d86ef1 --- /dev/null +++ b/src/main/java/org/lab/domain/error_mesage/model/ErrorMessage.java @@ -0,0 +1,15 @@ +package org.lab.domain.error_mesage.model; + +import lombok.Data; + +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.domain.interfaces.DomainObject; + +@Data +public class ErrorMessage implements DomainObject { + private int id; + private int projectId; + private int createdBy; + private String text; + private ErrorMessageStatus status; +} diff --git a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java new file mode 100644 index 0000000..9765a37 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java @@ -0,0 +1,56 @@ +package org.lab.infra.db.repository.error_message; + +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.repository.error_message.data_extractor.ErrorMessageRawDataExtractor; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +public class ErrorMessageRepository { + + private final DatabaseClient databaseClient; + private final ObjectMapper objectMapper; + private final ErrorMessageRawDataExtractor errorMessageRawDataExtractor; + + public ErrorMessageRepository() { + databaseClient = new DatabaseClient(); + objectMapper = new ObjectMapper(); + errorMessageRawDataExtractor = new ErrorMessageRawDataExtractor(); + } + + public ErrorMessage create( + ErrorMessage message, + int employeeId + ) { + String sql = """ + INSERT INTO error_messages (projectId, createdBy, text) + VALUES (?, ?, ?,) + RETURNING * + """; + + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, message.getProjectId()); + stmt.setInt(2, employeeId); + stmt.setString(3, message.getText()); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = errorMessageRawDataExtractor.extractErrorMessageRawData(rs); + return objectMapper.mapFromRaw(row, ErrorMessage.class); + } else { + throw new RuntimeException("Employee creation failed: no row returned"); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } +} diff --git a/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java new file mode 100644 index 0000000..f1b5611 --- /dev/null +++ b/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java @@ -0,0 +1,23 @@ +package org.lab.infra.db.repository.error_message.data_extractor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +public class ErrorMessageRawDataExtractor { + + public Map extractErrorMessageRawData( + ResultSet rs + ) throws + SQLException + { + Map row = new HashMap<>(); + row.put("id", rs.getInt("id")); + row.put("projectId", rs.getString("projectId")); + row.put("createdBy", rs.getInt("createdBy")); + row.put("text", rs.getString("text")); + row.put("status", rs.getInt("status")); + return row; + } +} diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index be7b8e2..0deb786 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -52,6 +52,34 @@ public Project get( } } + public Project getWithSpec( + int projectId, + Specification spec + ) { + SqlSpec sqlSpec = (SqlSpec) spec; + String sql = "SELECT * FROM projects WHERE id = ? AND " + sqlSpec.toSql(); + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, projectId); + List params = sqlSpec.getParams(); + for (int i = 1; i < params.size(); i++) { + stmt.setObject(i+1, params.get(i)); + } + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = projectRawDataExtractor.extractRawData(rs); + return objectMapper.mapFromRaw(row, Project.class); + } else { + return null; + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + } + public Project create( Project project, int employeeId diff --git a/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java index ec9223e..1b817af 100644 --- a/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java +++ b/src/main/java/org/lab/infra/db/repository/project/data_extractor/ProjectRawDataExtractor.java @@ -1,7 +1,5 @@ package org.lab.infra.db.repository.project.data_extractor; -import org.lab.core.constants.project.ProjectStatus; - import java.sql.Array; import java.sql.ResultSet; import java.sql.SQLException; @@ -10,6 +8,8 @@ import java.util.List; import java.util.Map; +import org.lab.core.constants.project.ProjectStatus; + public class ProjectRawDataExtractor { public Map extractRawData( diff --git a/src/main/java/org/lab/infra/di/AppModule.java b/src/main/java/org/lab/infra/di/AppModule.java index 465f6e6..ec22d93 100644 --- a/src/main/java/org/lab/infra/di/AppModule.java +++ b/src/main/java/org/lab/infra/di/AppModule.java @@ -2,22 +2,26 @@ import com.google.inject.AbstractModule; import org.lab.api.adapters.employee.*; +import org.lab.api.adapters.error_message.ErrorMessageCreateAdapter; import org.lab.api.adapters.project.*; import org.lab.api.adapters.ticket.*; import org.lab.application.employee.use_cases.*; +import org.lab.application.error_message.services.CreateErrorMessageValidator; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; import org.lab.application.project.use_cases.*; import org.lab.application.ticket.use_cases.*; -import org.lab.domain.project.services.ProjectMembershipValidator; import org.lab.application.shared.services.*; import org.lab.application.ticket.services.*; import org.lab.application.project.services.*; import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.project.services.ProjectMembershipValidator; import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.infra.db.repository.project.ProjectRepository; import org.lab.infra.db.repository.ticket.TicketRepository; public class AppModule extends AbstractModule { + @Override protected void configure() { bind(EmployeeRepository.class).asEagerSingleton(); @@ -33,6 +37,8 @@ protected void configure() { bind(TicketPermissionValidator.class).asEagerSingleton(); bind(TicketCreateValidator.class).asEagerSingleton(); bind(TicketCloseValidator.class).asEagerSingleton(); + bind(CreateErrorMessageValidator.class).asEagerSingleton(); + bind(ProjectSpecProvider.class).asEagerSingleton(); bind(CreateEmployeeUseCase.class).asEagerSingleton(); bind(DeleteEmployeeUseCase.class).asEagerSingleton(); @@ -43,6 +49,7 @@ protected void configure() { bind(ListProjectUseCase.class).asEagerSingleton(); bind(CreateTicketUseCase.class).asEagerSingleton(); bind(CloseTicketUseCase.class).asEagerSingleton(); + bind(CreateErrorMessageUseCase.class).asEagerSingleton(); bind(EmployeeCreateAdapter.class).asEagerSingleton(); bind(EmployeeDeleteAdapter.class).asEagerSingleton(); @@ -53,5 +60,6 @@ protected void configure() { bind(ProjectListAdapter.class).asEagerSingleton(); bind(TicketCreateAdapter.class).asEagerSingleton(); bind(TicketCloseAdapter.class).asEagerSingleton(); + bind(ErrorMessageCreateAdapter.class).asEagerSingleton(); } } From ed5eee318933830343af851b72be8944c3fcfb76 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 18:48:32 +0300 Subject: [PATCH 60/67] fixed imports --- .../lab/api/adapters/employee/EmployeeCreateAdapter.java | 1 - .../adapters/error_message/ErrorMessageCreateAdapter.java | 4 ++-- .../org/lab/api/adapters/project/ProjectCreateAdapter.java | 5 ++--- .../org/lab/api/adapters/ticket/TicketCreateAdapter.java | 1 - .../employee/use_cases/CreateEmployeeUseCase.java | 1 + .../employee/use_cases/DeleteEmployeeUseCase.java | 1 + .../application/employee/use_cases/GetEmployeeUseCase.java | 1 + .../services/CreateErrorMessageValidator.java | 1 + .../org/lab/application/project/services/GetValidator.java | 4 +++- .../project/use_cases/CreateProjectUseCase.java | 1 + .../project/use_cases/DeleteProjectUseCase.java | 1 + .../application/project/use_cases/GetProjectUseCase.java | 1 + .../application/project/use_cases/ListProjectUseCase.java | 1 + .../shared/services/EmployeePermissionValidator.java | 1 + .../lab/application/shared/services/ProjectProvider.java | 1 + .../application/ticket/services/TicketCreateValidator.java | 7 ++++--- .../ticket/services/TicketPermissionValidator.java | 1 + .../application/ticket/use_cases/CreateTicketUseCase.java | 1 + 18 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java index e407369..f56a209 100644 --- a/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/employee/EmployeeCreateAdapter.java @@ -52,7 +52,6 @@ public Context createEmployee( ); } catch (Exception e) { - System.err.println("ERROR" + e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java index 941ac61..2cb2b40 100644 --- a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java @@ -1,5 +1,7 @@ package org.lab.api.adapters.error_message; +import java.util.Map; + import com.google.inject.Inject; import io.javalin.http.Context; @@ -12,8 +14,6 @@ import org.lab.domain.shared.exceptions.ProjectNotFoundException; import org.lab.domain.shared.exceptions.UserNotFoundException; -import java.util.Map; - public class ErrorMessageCreateAdapter { private final CreateErrorMessageUseCase useCase; diff --git a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java index 221afe7..aaaccf8 100644 --- a/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/project/ProjectCreateAdapter.java @@ -1,5 +1,7 @@ package org.lab.api.adapters.project; +import java.util.Map; + import com.google.inject.Inject; import io.javalin.http.Context; @@ -10,8 +12,6 @@ import org.lab.core.utils.mapper.ObjectMapper; import org.lab.domain.shared.exceptions.NotPermittedException; -import java.util.Map; - public class ProjectCreateAdapter { private final ObjectMapper objectMapper; @@ -48,7 +48,6 @@ public Context createProject( ) ); } catch (Exception e) { - System.err.println("ERROR " + e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java index 74ff59e..4ff47ba 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCreateAdapter.java @@ -58,7 +58,6 @@ public Context createTicket( return ctx.status(404).json(Map.of("error", "Project doesnt exist")); } catch (Exception e) { - System.err.println("ERROR " + e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java index 782a55d..b14a67d 100644 --- a/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/CreateEmployeeUseCase.java @@ -1,6 +1,7 @@ package org.lab.application.employee.use_cases; import com.google.inject.Inject; + import org.lab.application.shared.services.EmployeePermissionValidator; import org.lab.domain.emploee.model.Employee; import org.lab.infra.db.repository.employee.EmployeeRepository; diff --git a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java index cc9c0c4..e21efc1 100644 --- a/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/DeleteEmployeeUseCase.java @@ -1,6 +1,7 @@ package org.lab.application.employee.use_cases; import com.google.inject.Inject; + import org.lab.infra.db.repository.employee.EmployeeRepository; import org.lab.application.shared.services.EmployeePermissionValidator; diff --git a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java index e34e4e1..017a663 100644 --- a/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java +++ b/src/main/java/org/lab/application/employee/use_cases/GetEmployeeUseCase.java @@ -1,6 +1,7 @@ package org.lab.application.employee.use_cases; import com.google.inject.Inject; + import org.lab.domain.emploee.model.Employee; import org.lab.application.shared.services.EmployeeProvider; import org.lab.application.shared.services.EmployeePermissionValidator; diff --git a/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java b/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java index ce39215..9b8959a 100644 --- a/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java +++ b/src/main/java/org/lab/application/error_message/services/CreateErrorMessageValidator.java @@ -1,6 +1,7 @@ package org.lab.application.error_message.services; import com.google.inject.Inject; + import org.lab.application.shared.services.EmployeeProvider; import org.lab.application.shared.services.ProjectSpecProvider; import org.lab.core.constants.employee.EmployeeType; diff --git a/src/main/java/org/lab/application/project/services/GetValidator.java b/src/main/java/org/lab/application/project/services/GetValidator.java index 611bac5..dbccff3 100644 --- a/src/main/java/org/lab/application/project/services/GetValidator.java +++ b/src/main/java/org/lab/application/project/services/GetValidator.java @@ -1,8 +1,10 @@ package org.lab.application.project.services; -import java.util.concurrent.*; +import java.util.concurrent.StructuredTaskScope; +import java.util.concurrent.ExecutionException; import com.google.inject.Inject; + import org.lab.application.shared.services.ProjectProvider; import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; diff --git a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java index 32c8a9b..6ebcc8e 100644 --- a/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/CreateProjectUseCase.java @@ -1,6 +1,7 @@ package org.lab.application.project.use_cases; import com.google.inject.Inject; + import org.lab.domain.project.model.Project; import org.lab.infra.db.repository.project.ProjectRepository; import org.lab.application.shared.services.EmployeePermissionValidator; diff --git a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java index a610f0f..9fba328 100644 --- a/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/DeleteProjectUseCase.java @@ -1,6 +1,7 @@ package org.lab.application.project.use_cases; import com.google.inject.Inject; + import org.lab.application.project.services.Pair; import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; diff --git a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java index 34dd2ba..d856b7a 100644 --- a/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/GetProjectUseCase.java @@ -1,6 +1,7 @@ package org.lab.application.project.use_cases; import com.google.inject.Inject; + import org.lab.domain.emploee.model.Employee; import org.lab.domain.project.model.Project; import org.lab.application.project.services.GetValidator; diff --git a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java index 3c17e12..fb9cca9 100644 --- a/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java +++ b/src/main/java/org/lab/application/project/use_cases/ListProjectUseCase.java @@ -3,6 +3,7 @@ import java.util.List; import com.google.inject.Inject; + import org.lab.application.project.services.UserSpecFactory; import org.lab.application.shared.services.EmployeeProvider; import org.lab.domain.emploee.model.Employee; diff --git a/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java index 0ffb739..08337ea 100644 --- a/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java +++ b/src/main/java/org/lab/application/shared/services/EmployeePermissionValidator.java @@ -1,6 +1,7 @@ package org.lab.application.shared.services; import com.google.inject.Inject; + import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; diff --git a/src/main/java/org/lab/application/shared/services/ProjectProvider.java b/src/main/java/org/lab/application/shared/services/ProjectProvider.java index 5d5beab..d1332a4 100644 --- a/src/main/java/org/lab/application/shared/services/ProjectProvider.java +++ b/src/main/java/org/lab/application/shared/services/ProjectProvider.java @@ -1,6 +1,7 @@ package org.lab.application.shared.services; import com.google.inject.Inject; + import org.lab.domain.project.model.Project; import org.lab.domain.shared.exceptions.ProjectNotFoundException; import org.lab.infra.db.repository.project.ProjectRepository; diff --git a/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java index 14d7399..1ac11e1 100644 --- a/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java +++ b/src/main/java/org/lab/application/ticket/services/TicketCreateValidator.java @@ -1,11 +1,12 @@ package org.lab.application.ticket.services; -import com.google.inject.Inject; -import org.lab.application.shared.services.ProjectProvider; - import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; +import com.google.inject.Inject; + +import org.lab.application.shared.services.ProjectProvider; + public class TicketCreateValidator { private final TicketPermissionValidator ticketPermissionValidator; diff --git a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java index 41a827d..157fc07 100644 --- a/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java +++ b/src/main/java/org/lab/application/ticket/services/TicketPermissionValidator.java @@ -1,6 +1,7 @@ package org.lab.application.ticket.services; import com.google.inject.Inject; + import org.lab.core.constants.employee.EmployeeType; import org.lab.domain.emploee.model.Employee; import org.lab.domain.shared.exceptions.NotPermittedException; diff --git a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java index 0c9b670..21a35f8 100644 --- a/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java +++ b/src/main/java/org/lab/application/ticket/use_cases/CreateTicketUseCase.java @@ -1,6 +1,7 @@ package org.lab.application.ticket.use_cases; import com.google.inject.Inject; + import org.lab.domain.ticket.model.Ticket; import org.lab.infra.db.repository.ticket.TicketRepository; import org.lab.application.ticket.services.TicketCreateValidator; From e1cd8322ca0a081e62303ad1f5c1300a4b77e5c1 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 23:08:08 +0300 Subject: [PATCH 61/67] fixes after tests --- .../error_message/ErrorMessageCreateAdapter.java | 2 +- .../use_cases/CreateErrorMessageUseCase.java | 10 ++++++++-- .../infra/db/repository/employee/spec/TesterSpec.java | 2 +- .../error_message/ErrorMessageRepository.java | 6 +++--- .../data_extractor/ErrorMessageRawDataExtractor.java | 2 +- .../infra/db/repository/project/ProjectRepository.java | 4 ++-- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java index 2cb2b40..c73c2a5 100644 --- a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java +++ b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCreateAdapter.java @@ -32,7 +32,7 @@ public Context createErrorMessage( Context ctx ) { try { - int actorId = Integer.parseInt(ctx.pathParam("actorId")); + int actorId = Integer.parseInt(ctx.pathParam("employeeId")); CreateErrorMessageDTO dto = ctx.bodyAsClass(CreateErrorMessageDTO.class); ErrorMessage errorMessage = mapper.mapToDomain(dto, ErrorMessage.class); ErrorMessage createdMessage = useCase.execute( diff --git a/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java b/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java index c59a655..3e11ce7 100644 --- a/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java +++ b/src/main/java/org/lab/application/error_message/use_cases/CreateErrorMessageUseCase.java @@ -23,8 +23,14 @@ public ErrorMessage execute( ErrorMessage message, int employeeId ) { - this.createErrorMessageValidator.validate(employeeId, message.getProjectId()); - ErrorMessage createdMessage = this.errorMessageRepository.create(message, employeeId); + this.createErrorMessageValidator.validate( + employeeId, + message.getProjectId() + ); + ErrorMessage createdMessage = this.errorMessageRepository.create( + message, + employeeId + ); return createdMessage; } } diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java index 3011528..bc57571 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/TesterSpec.java @@ -15,7 +15,7 @@ public TesterSpec(int testerId) { @Override public String toSql() { - return "? IN \"testerIds\""; + return "? = ANY(\"testerIds\")"; } @Override diff --git a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java index 9765a37..2f85173 100644 --- a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java +++ b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java @@ -29,9 +29,9 @@ public ErrorMessage create( int employeeId ) { String sql = """ - INSERT INTO error_messages (projectId, createdBy, text) - VALUES (?, ?, ?,) - RETURNING * + INSERT INTO error_messages ("projectId", "createdBy", "text") + VALUES (?, ?, ?) + RETURNING * """; try ( diff --git a/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java b/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java index f1b5611..1537e47 100644 --- a/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java +++ b/src/main/java/org/lab/infra/db/repository/error_message/data_extractor/ErrorMessageRawDataExtractor.java @@ -17,7 +17,7 @@ public Map extractErrorMessageRawData( row.put("projectId", rs.getString("projectId")); row.put("createdBy", rs.getInt("createdBy")); row.put("text", rs.getString("text")); - row.put("status", rs.getInt("status")); + row.put("status", rs.getString("status")); return row; } } diff --git a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java index 0deb786..fd40020 100644 --- a/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java +++ b/src/main/java/org/lab/infra/db/repository/project/ProjectRepository.java @@ -64,8 +64,8 @@ public Project getWithSpec( ) { stmt.setInt(1, projectId); List params = sqlSpec.getParams(); - for (int i = 1; i < params.size(); i++) { - stmt.setObject(i+1, params.get(i)); + for (int i = 0; i < params.size(); i++) { + stmt.setObject(i+2, params.get(i)); } try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { From e14313dadef5ed429e7a7e9051278f999afa1445 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 6 Dec 2025 23:54:10 +0300 Subject: [PATCH 62/67] added endpoint for closing error message --- src/main/java/org/lab/Main.java | 5 ++ .../ErrorMessageCloseAdapter.java | 41 +++++++++++++++ .../services/ErrorMessageValidator.java | 25 +++++++++ .../use_cases/CloseCreateMessageUseCase.java | 4 -- .../use_cases/CloseErrorMessageUseCase.java | 29 +++++++++++ .../exceptions/MessageNotFoundException.java | 7 +++ .../employee/spec/DeveloperSpec.java | 2 +- .../error_message/ErrorMessageRepository.java | 52 ++++++++++++++++++- src/main/java/org/lab/infra/di/AppModule.java | 6 +++ 9 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/lab/application/error_message/services/ErrorMessageValidator.java delete mode 100644 src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java create mode 100644 src/main/java/org/lab/application/error_message/use_cases/CloseErrorMessageUseCase.java create mode 100644 src/main/java/org/lab/domain/shared/exceptions/MessageNotFoundException.java diff --git a/src/main/java/org/lab/Main.java b/src/main/java/org/lab/Main.java index c74e87c..1a62b20 100644 --- a/src/main/java/org/lab/Main.java +++ b/src/main/java/org/lab/Main.java @@ -8,6 +8,7 @@ import org.lab.api.adapters.employee.EmployeeCreateAdapter; import org.lab.api.adapters.employee.EmployeeDeleteAdapter; import org.lab.api.adapters.employee.EmployeeGetAdapter; +import org.lab.api.adapters.error_message.ErrorMessageCloseAdapter; import org.lab.api.adapters.error_message.ErrorMessageCreateAdapter; import org.lab.api.adapters.project.ProjectCreateAdapter; import org.lab.api.adapters.project.ProjectDeleteAdapter; @@ -38,6 +39,9 @@ public static void main(String[] args) { ErrorMessageCreateAdapter errorMessageCreateAdapter = injector.getInstance( ErrorMessageCreateAdapter.class ); + ErrorMessageCloseAdapter errorMessageCloseAdapter = injector.getInstance( + ErrorMessageCloseAdapter.class + ); app.get("/", ctx -> ctx.result("Hello World")); @@ -54,5 +58,6 @@ public static void main(String[] args) { app.patch("/ticket/{employeeId}/{ticketId}", ticketCloseAdapter::closeTicket); app.post("/error_message/{employeeId}", errorMessageCreateAdapter::createErrorMessage); + app.patch("/error_message/{messageId}", errorMessageCloseAdapter::closeMessage); } } diff --git a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java index 23a45d4..7d47b93 100644 --- a/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java +++ b/src/main/java/org/lab/api/adapters/error_message/ErrorMessageCloseAdapter.java @@ -1,4 +1,45 @@ package org.lab.api.adapters.error_message; +import java.util.Map; + +import com.google.inject.Inject; +import io.javalin.http.Context; + +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; + public class ErrorMessageCloseAdapter { + + private final CloseErrorMessageUseCase useCase; + private final ObjectMapper mapper; + + @Inject + public ErrorMessageCloseAdapter( + CloseErrorMessageUseCase useCase, + ObjectMapper mapper + ) { + this.useCase = useCase; + this.mapper = mapper; + } + + public Context closeMessage(Context ctx) { + try { + int messageId = Integer.parseInt(ctx.pathParam("messageId")); + ErrorMessage createdMessage = useCase.execute(messageId); + GetErrorMessageDTO presentationMessage = mapper.mapToPresentation( + createdMessage, + GetErrorMessageDTO.class + ); + return ctx.status(201).json(presentationMessage); + + } catch (MessageNotFoundException e) { + return ctx.status(409).json(Map.of("error", "Message doesnt exist")); + + } catch (Exception e) { + return ctx.status(500).json(Map.of("error", "Internal server error")); + } + } } diff --git a/src/main/java/org/lab/application/error_message/services/ErrorMessageValidator.java b/src/main/java/org/lab/application/error_message/services/ErrorMessageValidator.java new file mode 100644 index 0000000..c49b944 --- /dev/null +++ b/src/main/java/org/lab/application/error_message/services/ErrorMessageValidator.java @@ -0,0 +1,25 @@ +package org.lab.application.error_message.services; + +import com.google.inject.Inject; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +public class ErrorMessageValidator { + + private final ErrorMessageRepository errorMessageRepository; + + @Inject + public ErrorMessageValidator( + ErrorMessageRepository errorMessageRepository + ) { + this.errorMessageRepository = errorMessageRepository; + } + + public void validate(int messageId) { + ErrorMessage message = this.errorMessageRepository.get(messageId); + if (message == null) { + throw new MessageNotFoundException(); + } + } +} diff --git a/src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java b/src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java deleted file mode 100644 index ca81481..0000000 --- a/src/main/java/org/lab/application/error_message/use_cases/CloseCreateMessageUseCase.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.lab.application.error_message.use_cases; - -public class CloseCreateMessageUseCase { -} diff --git a/src/main/java/org/lab/application/error_message/use_cases/CloseErrorMessageUseCase.java b/src/main/java/org/lab/application/error_message/use_cases/CloseErrorMessageUseCase.java new file mode 100644 index 0000000..178858d --- /dev/null +++ b/src/main/java/org/lab/application/error_message/use_cases/CloseErrorMessageUseCase.java @@ -0,0 +1,29 @@ +package org.lab.application.error_message.use_cases; + +import com.google.inject.Inject; +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +public class CloseErrorMessageUseCase { + + private final ErrorMessageValidator errorMessageValidator; + private final ErrorMessageRepository errorMessageRepository; + + @Inject + public CloseErrorMessageUseCase( + ErrorMessageValidator errorMessageValidator, + ErrorMessageRepository errorMessageRepository + ) { + this.errorMessageValidator = errorMessageValidator; + this.errorMessageRepository = errorMessageRepository; + } + + public ErrorMessage execute( + int messageId + ) { + this.errorMessageValidator.validate(messageId); + ErrorMessage message = this.errorMessageRepository.close(messageId); + return message; + } +} diff --git a/src/main/java/org/lab/domain/shared/exceptions/MessageNotFoundException.java b/src/main/java/org/lab/domain/shared/exceptions/MessageNotFoundException.java new file mode 100644 index 0000000..0aa4362 --- /dev/null +++ b/src/main/java/org/lab/domain/shared/exceptions/MessageNotFoundException.java @@ -0,0 +1,7 @@ +package org.lab.domain.shared.exceptions; + +public class MessageNotFoundException extends RuntimeException { + public MessageNotFoundException() { + super("Error message not found"); + } +} diff --git a/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java index ba3218c..b21a4f8 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java +++ b/src/main/java/org/lab/infra/db/repository/employee/spec/DeveloperSpec.java @@ -15,7 +15,7 @@ public DeveloperSpec(int developerId) { @Override public String toSql() { - return "? IN \"developerIds\""; + return "? = ANY(\"developerIds\")"; } @Override diff --git a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java index 2f85173..248f3d4 100644 --- a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java +++ b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java @@ -24,6 +24,56 @@ public ErrorMessageRepository() { errorMessageRawDataExtractor = new ErrorMessageRawDataExtractor(); } + public ErrorMessage get( + int messageId + ) { + String sql = """ + SELECT * FROM error_messages + WHERE id = ? + """; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, messageId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = errorMessageRawDataExtractor.extractErrorMessageRawData(rs); + return objectMapper.mapFromRaw(row, ErrorMessage.class); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + return null; + } + + public ErrorMessage close( + int messageId + ) { + String sql = """ + UPDATE error_messages + SET status = 'CLOSED' + WHERE id = ? + RETURNING * + """; + try ( + Connection conn = DatabaseClient.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql) + ) { + stmt.setInt(1, messageId); + try (ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + Map row = errorMessageRawDataExtractor.extractErrorMessageRawData(rs); + return objectMapper.mapFromRaw(row, ErrorMessage.class); + } + } + } catch (SQLException e) { + throw new DatabaseException(); + } + return null; + } + public ErrorMessage create( ErrorMessage message, int employeeId @@ -46,7 +96,7 @@ INSERT INTO error_messages ("projectId", "createdBy", "text") Map row = errorMessageRawDataExtractor.extractErrorMessageRawData(rs); return objectMapper.mapFromRaw(row, ErrorMessage.class); } else { - throw new RuntimeException("Employee creation failed: no row returned"); + throw new RuntimeException("Message creation failed"); } } } catch (SQLException e) { diff --git a/src/main/java/org/lab/infra/di/AppModule.java b/src/main/java/org/lab/infra/di/AppModule.java index ec22d93..98467a1 100644 --- a/src/main/java/org/lab/infra/di/AppModule.java +++ b/src/main/java/org/lab/infra/di/AppModule.java @@ -2,11 +2,14 @@ import com.google.inject.AbstractModule; import org.lab.api.adapters.employee.*; +import org.lab.api.adapters.error_message.ErrorMessageCloseAdapter; import org.lab.api.adapters.error_message.ErrorMessageCreateAdapter; import org.lab.api.adapters.project.*; import org.lab.api.adapters.ticket.*; import org.lab.application.employee.use_cases.*; import org.lab.application.error_message.services.CreateErrorMessageValidator; +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; import org.lab.application.project.use_cases.*; import org.lab.application.ticket.use_cases.*; @@ -39,6 +42,7 @@ protected void configure() { bind(TicketCloseValidator.class).asEagerSingleton(); bind(CreateErrorMessageValidator.class).asEagerSingleton(); bind(ProjectSpecProvider.class).asEagerSingleton(); + bind(ErrorMessageValidator.class).asEagerSingleton(); bind(CreateEmployeeUseCase.class).asEagerSingleton(); bind(DeleteEmployeeUseCase.class).asEagerSingleton(); @@ -50,6 +54,7 @@ protected void configure() { bind(CreateTicketUseCase.class).asEagerSingleton(); bind(CloseTicketUseCase.class).asEagerSingleton(); bind(CreateErrorMessageUseCase.class).asEagerSingleton(); + bind(CloseErrorMessageUseCase.class).asEagerSingleton(); bind(EmployeeCreateAdapter.class).asEagerSingleton(); bind(EmployeeDeleteAdapter.class).asEagerSingleton(); @@ -61,5 +66,6 @@ protected void configure() { bind(TicketCreateAdapter.class).asEagerSingleton(); bind(TicketCloseAdapter.class).asEagerSingleton(); bind(ErrorMessageCreateAdapter.class).asEagerSingleton(); + bind(ErrorMessageCloseAdapter.class).asEagerSingleton(); } } From afb079865efff42eb90f8574ad6dd8cdcb79c4af Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 7 Dec 2025 01:12:28 +0300 Subject: [PATCH 63/67] some tests for functionality --- .../adapters/ticket/TicketCloseAdapter.java | 1 - .../lab/core/utils/mapper/ObjectMapper.java | 1 - .../error_message/ErrorMessageRepository.java | 12 +- .../org/lab/infra/db/spec/Specification.java | 8 +- .../TestErrorMessageCloseAdapter.java | 93 +++++++++ .../TestErrorMessageCreateAdapter.java | 185 ++++++++++++++++++ .../ticket/TestTicketCloseAdapter.java | 106 ++++++++++ .../ticket/TestTicketCreateAdapter.java | 171 ++++++++++++++++ .../TestCreateErrorMessageValidator.java | 69 +++++++ .../services/TestErrorMessageValidator.java | 43 ++++ .../TestCloseErrorMessageUseCase.java | 51 +++++ .../TestCreateErrorMessageUseCase.java | 72 +++++++ .../shared/services/DummySpec.java | 18 ++ .../TestEmployeePermissionValidator.java | 56 ++++++ .../shared/services/TestEmployeeProvider.java | 46 +++++ .../shared/services/TestProjectProvider.java | 50 +++++ .../services/TestProjectSpecProvider.java | 52 +++++ .../core/utils/mapper/TestObjectMapper.java | 77 ++++++++ .../TestProjectMembershipValidator.java | 102 ++++++++++ 19 files changed, 1204 insertions(+), 9 deletions(-) create mode 100644 src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCloseAdapter.java create mode 100644 src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCreateAdapter.java create mode 100644 src/test/java/org/lab/api/adapters/ticket/TestTicketCloseAdapter.java create mode 100644 src/test/java/org/lab/api/adapters/ticket/TestTicketCreateAdapter.java create mode 100644 src/test/java/org/lab/application/error_messages/services/TestCreateErrorMessageValidator.java create mode 100644 src/test/java/org/lab/application/error_messages/services/TestErrorMessageValidator.java create mode 100644 src/test/java/org/lab/application/error_messages/use_cases/TestCloseErrorMessageUseCase.java create mode 100644 src/test/java/org/lab/application/error_messages/use_cases/TestCreateErrorMessageUseCase.java create mode 100644 src/test/java/org/lab/application/shared/services/DummySpec.java create mode 100644 src/test/java/org/lab/application/shared/services/TestEmployeePermissionValidator.java create mode 100644 src/test/java/org/lab/application/shared/services/TestEmployeeProvider.java create mode 100644 src/test/java/org/lab/application/shared/services/TestProjectProvider.java create mode 100644 src/test/java/org/lab/application/shared/services/TestProjectSpecProvider.java create mode 100644 src/test/java/org/lab/core/utils/mapper/TestObjectMapper.java create mode 100644 src/test/java/org/lab/domain/project/services/TestProjectMembershipValidator.java diff --git a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java index be126e2..0f8bac8 100644 --- a/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java +++ b/src/main/java/org/lab/api/adapters/ticket/TicketCloseAdapter.java @@ -50,7 +50,6 @@ public Context closeTicket( ); } catch (Exception e) { - System.err.println("ERROR "+ e.getMessage()); return ctx.status(500).json(Map.of("error", "Internal server error")); } } diff --git a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java index 1b89183..ac42a33 100644 --- a/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java +++ b/src/main/java/org/lab/core/utils/mapper/ObjectMapper.java @@ -3,7 +3,6 @@ import org.lab.domain.interfaces.DomainObject; import org.lab.domain.interfaces.PresentationObject; - public class ObjectMapper { private final com.fasterxml.jackson.databind.ObjectMapper mapper = diff --git a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java index 248f3d4..602e0ea 100644 --- a/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java +++ b/src/main/java/org/lab/infra/db/repository/error_message/ErrorMessageRepository.java @@ -1,17 +1,17 @@ package org.lab.infra.db.repository.error_message; -import org.lab.core.utils.mapper.ObjectMapper; -import org.lab.domain.error_mesage.model.ErrorMessage; -import org.lab.domain.shared.exceptions.DatabaseException; -import org.lab.infra.db.client.DatabaseClient; -import org.lab.infra.db.repository.error_message.data_extractor.ErrorMessageRawDataExtractor; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.DatabaseException; +import org.lab.infra.db.client.DatabaseClient; +import org.lab.infra.db.repository.error_message.data_extractor.ErrorMessageRawDataExtractor; + public class ErrorMessageRepository { private final DatabaseClient databaseClient; diff --git a/src/main/java/org/lab/infra/db/spec/Specification.java b/src/main/java/org/lab/infra/db/spec/Specification.java index f42244a..6f008e8 100644 --- a/src/main/java/org/lab/infra/db/spec/Specification.java +++ b/src/main/java/org/lab/infra/db/spec/Specification.java @@ -1,4 +1,10 @@ package org.lab.infra.db.spec; -public sealed interface Specification permits SqlSpec { +import java.util.List; + +public sealed interface Specification + permits SqlSpec +{ + String toSql(); + List getParams(); } diff --git a/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCloseAdapter.java b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCloseAdapter.java new file mode 100644 index 0000000..21f372e --- /dev/null +++ b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCloseAdapter.java @@ -0,0 +1,93 @@ +package org.lab.api.adapters.error_message; + +import java.util.Map; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; + +public class TestErrorMessageCloseAdapter { + + private CloseErrorMessageUseCase useCase; + private ObjectMapper mapper; + private ErrorMessageCloseAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CloseErrorMessageUseCase.class); + mapper = mock(ObjectMapper.class); + adapter = new ErrorMessageCloseAdapter(useCase, mapper); + ctx = mock(Context.class); + } + + @Test + void closeMessage_success() { + when(ctx.pathParam("messageId")).thenReturn("1"); + + ErrorMessage msg = new ErrorMessage(); + msg.setId(1); + msg.setProjectId(100); + msg.setText("Some text"); + msg.setStatus(ErrorMessageStatus.CLOSED); + msg.setCreatedBy(999); + + GetErrorMessageDTO dto = new GetErrorMessageDTO( + 1, + 100, + 999, + "Some text", + ErrorMessageStatus.CLOSED + ); + + when(useCase.execute(1)).thenReturn(msg); + when(mapper.mapToPresentation(msg, GetErrorMessageDTO.class)).thenReturn(dto); + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + Context result = adapter.closeMessage(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + assertEquals(ctx, result); + } + + @Test + void closeMessage_messageNotFound() { + when(ctx.pathParam("messageId")).thenReturn("5"); + + when(useCase.execute(5)).thenThrow(new MessageNotFoundException()); + + when(ctx.status(409)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeMessage(ctx); + + verify(ctx).status(409); + verify(ctx).json(Map.of("error", "Message doesnt exist")); + } + + @Test + void closeMessage_internalServerError() { + when(ctx.pathParam("messageId")).thenReturn("10"); + + when(useCase.execute(10)).thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeMessage(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCreateAdapter.java b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCreateAdapter.java new file mode 100644 index 0000000..4377b56 --- /dev/null +++ b/src/test/java/org/lab/api/adapters/error_message/TestErrorMessageCreateAdapter.java @@ -0,0 +1,185 @@ +package org.lab.api.adapters.error_message; + +import java.util.Map; + +import static org.mockito.Mockito.*; +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.error_message.dto.CreateErrorMessageDTO; +import org.lab.application.error_message.dto.GetErrorMessageDTO; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; +import org.lab.core.constants.error_message.ErrorMessageStatus; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class TestErrorMessageCreateAdapter { + + private CreateErrorMessageUseCase useCase; + private ObjectMapper mapper; + private ErrorMessageCreateAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CreateErrorMessageUseCase.class); + mapper = mock(ObjectMapper.class); + adapter = new ErrorMessageCreateAdapter(useCase, mapper); + ctx = mock(Context.class); + } + + @Test + void createErrorMessage_success() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + + CreateErrorMessageDTO bodyDto = new CreateErrorMessageDTO(1, "text"); + ErrorMessage domainMsg = new ErrorMessage(); + domainMsg.setId(5); + domainMsg.setProjectId(1); + domainMsg.setCreatedBy(10); + domainMsg.setText("text"); + domainMsg.setStatus(ErrorMessageStatus.OPEN); + + ErrorMessage created = new ErrorMessage(); + created.setId(5); + created.setProjectId(1); + created.setCreatedBy(10); + created.setText("text"); + created.setStatus(ErrorMessageStatus.OPEN); + GetErrorMessageDTO dto = new GetErrorMessageDTO( + 5, + 1, + 10, + "text", + ErrorMessageStatus.OPEN + ); + + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)).thenReturn(bodyDto); + when(mapper.mapToDomain(bodyDto, ErrorMessage.class)).thenReturn(domainMsg); + when(useCase.execute(domainMsg, 10)).thenReturn(created); + when(mapper.mapToPresentation(created, GetErrorMessageDTO.class)).thenReturn(dto); + + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + } + + @Test + void createErrorMessage_userNotFound() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))).thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))).thenThrow(new UserNotFoundException()); + + when(ctx.status(409)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(409); + verify(ctx).json(Map.of("error", "User doesnt exist")); + } + + @Test + void createErrorMessage_notPermitted() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))).thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))) + .thenThrow(new NotPermittedException("not permitted")); + + when(ctx.status(403)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(403); + verify(ctx).json(Map.of( + "error", "You do not have permission to perform this operation" + )); + } + + @Test + void createErrorMessage_projectNotFound() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))) + .thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))) + .thenThrow(new ProjectNotFoundException()); + + when(ctx.status(404)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(404); + verify(ctx).json(Map.of("error", "Project doesnt exist")); + } + + @Test + void createErrorMessage_internalServerError() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.bodyAsClass(CreateErrorMessageDTO.class)) + .thenReturn(new CreateErrorMessageDTO(1,"x")); + + ErrorMessage expcMessage = new ErrorMessage(); + expcMessage.setId(0); + expcMessage.setProjectId(1); + expcMessage.setCreatedBy(10); + expcMessage.setText("x"); + expcMessage.setStatus(ErrorMessageStatus.OPEN); + + when(mapper.mapToDomain(any(), eq(ErrorMessage.class))) + .thenReturn(expcMessage); + + when(useCase.execute(any(), eq(10))) + .thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createErrorMessage(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/ticket/TestTicketCloseAdapter.java b/src/test/java/org/lab/api/adapters/ticket/TestTicketCloseAdapter.java new file mode 100644 index 0000000..8ecb81e --- /dev/null +++ b/src/test/java/org/lab/api/adapters/ticket/TestTicketCloseAdapter.java @@ -0,0 +1,106 @@ +package org.lab.api.adapters.ticket; + +import java.util.Date; +import java.util.Map; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; + +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CloseTicketUseCase; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; + +public class TestTicketCloseAdapter { + + private CloseTicketUseCase useCase; + private ObjectMapper objectMapper; + private TicketCloseAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CloseTicketUseCase.class); + objectMapper = mock(ObjectMapper.class); + adapter = new TicketCloseAdapter(useCase, objectMapper); + ctx = mock(Context.class); + } + + @Test + void closeTicket_success() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("ticketId")).thenReturn("5"); + + Ticket domainTicket = new Ticket(); + domainTicket.setId(5); + domainTicket.setCreatedBy(10); + domainTicket.setAssignedTo(11); + domainTicket.setProjectId(1); + domainTicket.setDescription("Fix bug"); + domainTicket.setStatus(TicketStatus.CLOSED); + domainTicket.setCreatedDate(new Date()); + domainTicket.setClosedDate(new Date()); + + GetTicketDTO dto = new GetTicketDTO( + 5, + 10, + 11, + 1, + "Fix bug", + TicketStatus.CLOSED, + domainTicket.getCreatedDate(), + domainTicket.getClosedDate() + ); + + when(useCase.execute(5, 10)).thenReturn(domainTicket); + when(objectMapper.mapToPresentation(domainTicket, GetTicketDTO.class)).thenReturn(dto); + + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + adapter.closeTicket(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + } + + @Test + void closeTicket_notPermitted() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("ticketId")).thenReturn("5"); + + when(useCase.execute(5, 10)) + .thenThrow(new NotPermittedException("you are not permitted")); + + when(ctx.status(403)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeTicket(ctx); + + verify(ctx).status(403); + verify(ctx).json(Map.of( + "error", + "You do not have permission to perform this operation" + )); + } + + @Test + void closeTicket_internalServerError() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("ticketId")).thenReturn("5"); + + when(useCase.execute(5, 10)).thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.closeTicket(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/api/adapters/ticket/TestTicketCreateAdapter.java b/src/test/java/org/lab/api/adapters/ticket/TestTicketCreateAdapter.java new file mode 100644 index 0000000..111ad4a --- /dev/null +++ b/src/test/java/org/lab/api/adapters/ticket/TestTicketCreateAdapter.java @@ -0,0 +1,171 @@ +package org.lab.api.adapters.ticket; + +import java.util.Date; +import java.util.Map; + +import io.javalin.http.Context; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; + +import org.lab.application.ticket.dto.CreateTicketDTO; +import org.lab.application.ticket.dto.GetTicketDTO; +import org.lab.application.ticket.use_cases.CreateTicketUseCase; +import org.lab.core.constants.ticket.TicketStatus; +import org.lab.core.utils.mapper.ObjectMapper; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.ticket.model.Ticket; + +public class TestTicketCreateAdapter { + + private CreateTicketUseCase useCase; + private ObjectMapper objectMapper; + private TicketCreateAdapter adapter; + private Context ctx; + + @BeforeEach + void setup() { + useCase = mock(CreateTicketUseCase.class); + objectMapper = mock(ObjectMapper.class); + adapter = new TicketCreateAdapter(useCase, objectMapper); + ctx = mock(Context.class); + } + + @Test + void createTicket_success() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + CreateTicketDTO bodyDto = new CreateTicketDTO( + 20, + "Fix memory leak", + TicketStatus.OPEN + ); + + Ticket domainTicket = new Ticket(); + domainTicket.setId(0); + domainTicket.setAssignedTo(20); + domainTicket.setCreatedBy(10); + domainTicket.setProjectId(3); + domainTicket.setDescription("Fix memory leak"); + domainTicket.setStatus(TicketStatus.OPEN); + + Ticket created = new Ticket(); + created.setId(7); + created.setAssignedTo(20); + created.setCreatedBy(10); + created.setProjectId(3); + created.setDescription("Fix memory leak"); + created.setStatus(TicketStatus.OPEN); + created.setCreatedDate(new Date()); + + GetTicketDTO dto = new GetTicketDTO( + 7, + 10, + 20, + 3, + "Fix memory leak", + TicketStatus.OPEN, + created.getCreatedDate(), + null + ); + + when(ctx.bodyAsClass(CreateTicketDTO.class)).thenReturn(bodyDto); + when(objectMapper.mapToDomain(bodyDto, Ticket.class)).thenReturn(domainTicket); + when(useCase.execute(domainTicket, 10, 3)).thenReturn(created); + when(objectMapper.mapToPresentation(created, GetTicketDTO.class)).thenReturn(dto); + + when(ctx.status(201)).thenReturn(ctx); + when(ctx.json(dto)).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(201); + verify(ctx).json(dto); + } + + @Test + void createTicket_notPermitted() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + when(ctx.bodyAsClass(CreateTicketDTO.class)) + .thenReturn(new CreateTicketDTO( + 20, + "Fix bug", + TicketStatus.OPEN) + ); + + when(objectMapper.mapToDomain(any(), eq(Ticket.class))) + .thenReturn(new Ticket()); + + when(useCase.execute(any(), eq(10), eq(3))) + .thenThrow(new NotPermittedException("not permitted")); + + when(ctx.status(403)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(403); + verify(ctx).json(Map.of( + "error", + "You do not have permission to perform this operation" + )); + } + + @Test + void createTicket_projectNotFound() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + when(ctx.bodyAsClass(CreateTicketDTO.class)) + .thenReturn(new CreateTicketDTO( + 20, + "Fix bug", + TicketStatus.OPEN) + ); + + when(objectMapper.mapToDomain(any(), eq(Ticket.class))) + .thenReturn(new Ticket()); + + when(useCase.execute(any(), eq(10), eq(3))) + .thenThrow(new ProjectNotFoundException()); + + when(ctx.status(404)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(404); + verify(ctx).json(Map.of("error", "Project doesnt exist")); + } + + @Test + void createTicket_internalServerError() { + when(ctx.pathParam("employeeId")).thenReturn("10"); + when(ctx.pathParam("projectId")).thenReturn("3"); + + when(ctx.bodyAsClass(CreateTicketDTO.class)) + .thenReturn(new CreateTicketDTO( + 20, + "Fix bug", + TicketStatus.OPEN) + ); + + when(objectMapper.mapToDomain(any(), eq(Ticket.class))) + .thenReturn(new Ticket()); + + when(useCase.execute(any(), eq(10), eq(3))) + .thenThrow(new RuntimeException("boom")); + + when(ctx.status(500)).thenReturn(ctx); + when(ctx.json(any())).thenReturn(ctx); + + adapter.createTicket(ctx); + + verify(ctx).status(500); + verify(ctx).json(Map.of("error", "Internal server error")); + } +} diff --git a/src/test/java/org/lab/application/error_messages/services/TestCreateErrorMessageValidator.java b/src/test/java/org/lab/application/error_messages/services/TestCreateErrorMessageValidator.java new file mode 100644 index 0000000..8ed67af --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/services/TestCreateErrorMessageValidator.java @@ -0,0 +1,69 @@ +package org.lab.application.error_messages.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.application.error_message.services.CreateErrorMessageValidator; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.application.shared.services.ProjectSpecProvider; +import org.lab.application.project.services.UserSpecFactory; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.spec.TesterSpec; +import org.lab.infra.db.spec.Specification; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +public class TestCreateErrorMessageValidator { + + private EmployeeProvider employeeProvider; + private ProjectSpecProvider projectSpecProvider; + private UserSpecFactory userSpecFactory; + private CreateErrorMessageValidator validator; + + @BeforeEach + void setup() { + employeeProvider = mock(EmployeeProvider.class); + projectSpecProvider = mock(ProjectSpecProvider.class); + userSpecFactory = mock(UserSpecFactory.class); + validator = new CreateErrorMessageValidator( + employeeProvider, + projectSpecProvider, + userSpecFactory + ); + } + + @Test + void validate_onlyTesterAllowed() { + Employee employee = new Employee(); + employee.setId(1); + employee.setType(EmployeeType.PROGRAMMER); + when(employeeProvider.get(1)).thenReturn(employee); + + NotPermittedException ex = assertThrows(NotPermittedException.class, () -> { + validator.validate(1, 10); + }); + + assertEquals("Only tester can create error messages", ex.getMessage()); + verifyNoInteractions(userSpecFactory, projectSpecProvider); + } + + @Test + void validate_testerCallsProjectSpecProvider() { + Employee employee = new Employee(); + employee.setId(2); + employee.setType(EmployeeType.TESTER); + + Specification spec = mock(TesterSpec.class); + + when(employeeProvider.get(2)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenReturn(spec); + + validator.validate(2, 10); + + verify(userSpecFactory).getForType(employee); + verify(projectSpecProvider).get(10, spec); + } +} diff --git a/src/test/java/org/lab/application/error_messages/services/TestErrorMessageValidator.java b/src/test/java/org/lab/application/error_messages/services/TestErrorMessageValidator.java new file mode 100644 index 0000000..a5f366c --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/services/TestErrorMessageValidator.java @@ -0,0 +1,43 @@ +package org.lab.application.error_messages.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +class TestErrorMessageValidator { + + private ErrorMessageRepository repository; + private ErrorMessageValidator validator; + + @BeforeEach + void setup() { + repository = mock(ErrorMessageRepository.class); + validator = new ErrorMessageValidator(repository); + } + + @Test + void validate_messageExists() { + ErrorMessage message = new ErrorMessage(); + when(repository.get(1)).thenReturn(message); + + assertDoesNotThrow(() -> validator.validate(1)); + + verify(repository).get(1); + } + + @Test + void validate_messageNotFound() { + when(repository.get(2)).thenReturn(null); + + assertThrows(MessageNotFoundException.class, () -> validator.validate(2)); + + verify(repository).get(2); + } +} + diff --git a/src/test/java/org/lab/application/error_messages/use_cases/TestCloseErrorMessageUseCase.java b/src/test/java/org/lab/application/error_messages/use_cases/TestCloseErrorMessageUseCase.java new file mode 100644 index 0000000..3268d30 --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/use_cases/TestCloseErrorMessageUseCase.java @@ -0,0 +1,51 @@ +package org.lab.application.error_messages.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.application.error_message.use_cases.CloseErrorMessageUseCase; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.domain.shared.exceptions.MessageNotFoundException; +import org.lab.application.error_message.services.ErrorMessageValidator; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; + +class TestCloseErrorMessageUseCase { + + private ErrorMessageValidator validator; + private ErrorMessageRepository repository; + private CloseErrorMessageUseCase useCase; + + @BeforeEach + void setup() { + validator = mock(ErrorMessageValidator.class); + repository = mock(ErrorMessageRepository.class); + useCase = new CloseErrorMessageUseCase(validator, repository); + } + + @Test + void execute_success() { + int messageId = 1; + ErrorMessage closedMessage = new ErrorMessage(); + when(repository.close(messageId)).thenReturn(closedMessage); + + ErrorMessage result = useCase.execute(messageId); + + verify(validator).validate(messageId); + verify(repository).close(messageId); + assertEquals(closedMessage, result); + } + + @Test + void execute_messageNotFound() { + int messageId = 2; + doThrow(new MessageNotFoundException()).when(validator).validate(messageId); + + assertThrows(MessageNotFoundException.class, () -> useCase.execute(messageId)); + + verify(validator).validate(messageId); + verifyNoInteractions(repository); + } +} + diff --git a/src/test/java/org/lab/application/error_messages/use_cases/TestCreateErrorMessageUseCase.java b/src/test/java/org/lab/application/error_messages/use_cases/TestCreateErrorMessageUseCase.java new file mode 100644 index 0000000..a9c7eba --- /dev/null +++ b/src/test/java/org/lab/application/error_messages/use_cases/TestCreateErrorMessageUseCase.java @@ -0,0 +1,72 @@ +package org.lab.application.error_messages.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.application.error_message.services.CreateErrorMessageValidator; +import org.lab.application.error_message.use_cases.CreateErrorMessageUseCase; +import org.lab.domain.error_mesage.model.ErrorMessage; +import org.lab.infra.db.repository.error_message.ErrorMessageRepository; +import org.lab.domain.shared.exceptions.NotPermittedException; + +class CreateErrorMessageUseCaseTest { + + private ErrorMessageRepository repository; + private CreateErrorMessageValidator validator; + private CreateErrorMessageUseCase useCase; + + @BeforeEach + void setup() { + repository = mock(ErrorMessageRepository.class); + validator = mock(CreateErrorMessageValidator.class); + useCase = new CreateErrorMessageUseCase(repository, validator); + } + + @Test + void execute_success() { + int employeeId = 10; + ErrorMessage message = new ErrorMessage(); + message.setProjectId(1); + + ErrorMessage createdMessage = new ErrorMessage(); + when(repository.create(message, employeeId)).thenReturn(createdMessage); + + ErrorMessage result = useCase.execute(message, employeeId); + + verify(validator).validate(employeeId, message.getProjectId()); + verify(repository).create(message, employeeId); + assertEquals(createdMessage, result); + } + + @Test + void execute_notPermitted() { + int employeeId = 10; + ErrorMessage message = new ErrorMessage(); + message.setProjectId(1); + + doThrow(new NotPermittedException("not permitted")) + .when(validator) + .validate(employeeId, message.getProjectId()); + + assertThrows(NotPermittedException.class, () -> useCase.execute(message, employeeId)); + verify(validator).validate(employeeId, message.getProjectId()); + verifyNoInteractions(repository); + } + + @Test + void execute_runtimeExceptionFromRepository() { + int employeeId = 10; + ErrorMessage message = new ErrorMessage(); + message.setProjectId(1); + + doNothing().when(validator).validate(employeeId, message.getProjectId()); + when(repository.create(message, employeeId)).thenThrow(new RuntimeException("boom")); + + assertThrows(RuntimeException.class, () -> useCase.execute(message, employeeId)); + verify(validator).validate(employeeId, message.getProjectId()); + verify(repository).create(message, employeeId); + } +} + diff --git a/src/test/java/org/lab/application/shared/services/DummySpec.java b/src/test/java/org/lab/application/shared/services/DummySpec.java new file mode 100644 index 0000000..b5e7bf1 --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/DummySpec.java @@ -0,0 +1,18 @@ +package org.lab.application.shared.services; + +import org.lab.infra.db.spec.SqlSpec; + +import java.util.List; + +public final class DummySpec implements SqlSpec { + @Override + public String toSql() { + return "1=1"; + } + + @Override + public List getParams() { + return List.of(); + } +} + diff --git a/src/test/java/org/lab/application/shared/services/TestEmployeePermissionValidator.java b/src/test/java/org/lab/application/shared/services/TestEmployeePermissionValidator.java new file mode 100644 index 0000000..650ce0d --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestEmployeePermissionValidator.java @@ -0,0 +1,56 @@ +package org.lab.application.shared.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +class TestEmployeePermissionValidator { + + private EmployeeRepository employeeRepository; + private EmployeePermissionValidator validator; + + @BeforeEach + void setup() { + employeeRepository = mock(EmployeeRepository.class); + validator = new EmployeePermissionValidator(employeeRepository); + } + + @Test + void validate_manager_passes() { + Employee manager = new Employee(); + manager.setId(1); + manager.setType(EmployeeType.MANAGER); + + when(employeeRepository.getById(1)).thenReturn(manager); + + validator.validate(1); + verify(employeeRepository).getById(1); + } + + @Test + void validate_nonManager_throwsException() { + Employee programmer = new Employee(); + programmer.setId(2); + programmer.setType(EmployeeType.PROGRAMMER); + + when(employeeRepository.getById(2)).thenReturn(programmer); + + assertThrows(NotPermittedException.class, () -> validator.validate(2)); + verify(employeeRepository).getById(2); + } + + @Test + void validate_nullEmployee_throwsException() { + when(employeeRepository.getById(3)).thenReturn(null); + + assertThrows(NullPointerException.class, () -> validator.validate(3)); + verify(employeeRepository).getById(3); + } +} + diff --git a/src/test/java/org/lab/application/shared/services/TestEmployeeProvider.java b/src/test/java/org/lab/application/shared/services/TestEmployeeProvider.java new file mode 100644 index 0000000..a50f9f5 --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestEmployeeProvider.java @@ -0,0 +1,46 @@ +package org.lab.application.shared.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +class TestEmployeeProvider { + + private EmployeeRepository repository; + private EmployeeProvider provider; + + @BeforeEach + void setup() { + repository = mock(EmployeeRepository.class); + provider = new EmployeeProvider(repository); + } + + @Test + void get_existingEmployee_returnsEmployee() throws UserNotFoundException { + int employeeId = 1; + Employee employee = new Employee(); + employee.setId(employeeId); + + when(repository.getById(employeeId)).thenReturn(employee); + + Employee result = provider.get(employeeId); + + assertEquals(employee, result); + verify(repository).getById(employeeId); + } + + @Test + void get_nonExistingEmployee_throwsException() { + int employeeId = 1; + + when(repository.getById(employeeId)).thenReturn(null); + + assertThrows(UserNotFoundException.class, () -> provider.get(employeeId)); + verify(repository).getById(employeeId); + } +} diff --git a/src/test/java/org/lab/application/shared/services/TestProjectProvider.java b/src/test/java/org/lab/application/shared/services/TestProjectProvider.java new file mode 100644 index 0000000..52124bd --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestProjectProvider.java @@ -0,0 +1,50 @@ +package org.lab.application.shared.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; + +class TestProjectProvider { + + private ProjectRepository repository; + private ProjectProvider provider; + + @BeforeEach + void setup() { + repository = mock(ProjectRepository.class); + provider = new ProjectProvider(repository); + } + + @Test + void get_existingEmployee_returnsEmployee() + throws + UserNotFoundException + { + int projectId = 1; + Project project = new Project(); + project.setId(projectId); + + when(repository.get(projectId)).thenReturn(project); + + Project result = provider.get(projectId); + + assertEquals(project, result); + verify(repository).get(projectId); + } + + @Test + void get_nonExistingProject_throwsException() { + int projectId = 1; + + when(repository.get(projectId)).thenReturn(null); + + assertThrows(ProjectNotFoundException.class, () -> provider.get(projectId)); + verify(repository).get(projectId); + } +} \ No newline at end of file diff --git a/src/test/java/org/lab/application/shared/services/TestProjectSpecProvider.java b/src/test/java/org/lab/application/shared/services/TestProjectSpecProvider.java new file mode 100644 index 0000000..f07e245 --- /dev/null +++ b/src/test/java/org/lab/application/shared/services/TestProjectSpecProvider.java @@ -0,0 +1,52 @@ +package org.lab.application.shared.services; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; + +class TestProjectSpecProvider { + + private ProjectRepository repository; + private ProjectSpecProvider provider; + private Specification spec; + + @BeforeEach + void setup() { + repository = mock(ProjectRepository.class); + provider = new ProjectSpecProvider(repository); + spec = mock(DummySpec.class); + } + + @Test + void get_existingProject_returnsProject() + throws + ProjectNotFoundException + { + int projectId = 1; + Project project = new Project(); + project.setId(projectId); + + when(repository.getWithSpec(projectId, spec)).thenReturn(project); + + Project result = provider.get(projectId, spec); + + assertEquals(project, result); + verify(repository).getWithSpec(projectId, spec); + } + + @Test + void get_nonExistingProject_throwsException() { + int projectId = 1; + + when(repository.getWithSpec(projectId, spec)).thenReturn(null); + + assertThrows(ProjectNotFoundException.class, () -> provider.get(projectId, spec)); + verify(repository).getWithSpec(projectId, spec); + } +} diff --git a/src/test/java/org/lab/core/utils/mapper/TestObjectMapper.java b/src/test/java/org/lab/core/utils/mapper/TestObjectMapper.java new file mode 100644 index 0000000..7b7b5d3 --- /dev/null +++ b/src/test/java/org/lab/core/utils/mapper/TestObjectMapper.java @@ -0,0 +1,77 @@ +package org.lab.core.utils.mapper; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.domain.interfaces.DomainObject; +import org.lab.domain.interfaces.PresentationObject; + +public class TestObjectMapper { + private ObjectMapper mapper; + + interface TestDomain extends DomainObject {} + interface TestPresentation extends PresentationObject {} + + static class DomainImpl implements TestDomain { + public int id; + public String name; + + public DomainImpl() {} + public DomainImpl(int id, String name) { + this.id = id; + this.name = name; + } + } + + static class PresentationImpl implements TestPresentation { + public int id; + public String name; + + public PresentationImpl() {} + public PresentationImpl(int id, String name) { + this.id = id; + this.name = name; + } + } + + @BeforeEach + void setup() { + mapper = new ObjectMapper(); + } + + @Test + void mapToDomain_shouldConvertPresentationToDomain() { + PresentationImpl presentation = new PresentationImpl(1, "Alice"); + + DomainImpl domain = mapper.mapToDomain(presentation, DomainImpl.class); + + assertEquals(1, domain.id); + assertEquals("Alice", domain.name); + } + + @Test + void mapToPresentation_shouldConvertDomainToPresentation() { + DomainImpl domain = new DomainImpl(2, "Bob"); + + PresentationImpl presentation = mapper.mapToPresentation(domain, PresentationImpl.class); + + assertEquals(2, presentation.id); + assertEquals("Bob", presentation.name); + } + + @Test + void mapFromRaw_shouldConvertMapToDomain() { + Map raw = Map.of( + "id", 3, + "name", "Charlie" + ); + + DomainImpl domain = mapper.mapFromRaw(raw, DomainImpl.class); + + assertEquals(3, domain.id); + assertEquals("Charlie", domain.name); + } +} \ No newline at end of file diff --git a/src/test/java/org/lab/domain/project/services/TestProjectMembershipValidator.java b/src/test/java/org/lab/domain/project/services/TestProjectMembershipValidator.java new file mode 100644 index 0000000..a915ca2 --- /dev/null +++ b/src/test/java/org/lab/domain/project/services/TestProjectMembershipValidator.java @@ -0,0 +1,102 @@ +package org.lab.domain.project.services; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class TestProjectMembershipValidator { + + private ProjectMembershipValidator validator; + private Employee employee; + private Project project; + + @BeforeEach + void setup() { + validator = new ProjectMembershipValidator(); + + project = new Project(); + project.setManagerId(1); + project.setTeamLeadId(2); + project.setDeveloperIds(List.of(3, 4, 5)); + project.setTesterIds(List.of(6, 7)); + } + + @Test + void programmer_allowed() { + employee = new Employee(); + employee.setId(3); + employee.setType(EmployeeType.PROGRAMMER); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void programmer_denied() { + employee = new Employee(); + employee.setId(99); + employee.setType(EmployeeType.PROGRAMMER); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } + + @Test + void tester_allowed() { + employee = new Employee(); + employee.setId(6); + employee.setType(EmployeeType.TESTER); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void tester_denied() { + employee = new Employee(); + employee.setId(123); + employee.setType(EmployeeType.TESTER); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } + + @Test + void manager_allowed() { + employee = new Employee(); + employee.setId(1); + employee.setType(EmployeeType.MANAGER); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void manager_denied() { + employee = new Employee(); + employee.setId(55); + employee.setType(EmployeeType.MANAGER); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } + + @Test + void teamlead_allowed() { + employee = new Employee(); + employee.setId(2); + employee.setType(EmployeeType.TEAMLEAD); + + assertDoesNotThrow(() -> validator.validate(employee, project)); + } + + @Test + void teamlead_denied() { + employee = new Employee(); + employee.setId(666); + employee.setType(EmployeeType.TEAMLEAD); + + assertThrows(NotPermittedException.class, () -> validator.validate(employee, project)); + } +} From c41f3150572137768051f392132f0fac0028c72f Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 7 Dec 2025 01:26:23 +0300 Subject: [PATCH 64/67] tests for tickets --- .../services/TestTicketCloseValidator.java | 45 ++++++++++++ .../services/TestTicketCreateValidator.java | 68 +++++++++++++++++++ .../TestTicketPermissionValidator.java | 56 +++++++++++++++ .../use_cases/TestCloseTicketUseCase.java | 54 +++++++++++++++ .../use_cases/TestCreateTicketUseCase.java | 60 ++++++++++++++++ 5 files changed, 283 insertions(+) create mode 100644 src/test/java/org/lab/application/ticket/services/TestTicketCloseValidator.java create mode 100644 src/test/java/org/lab/application/ticket/services/TestTicketCreateValidator.java create mode 100644 src/test/java/org/lab/application/ticket/services/TestTicketPermissionValidator.java create mode 100644 src/test/java/org/lab/application/ticket/use_cases/TestCloseTicketUseCase.java create mode 100644 src/test/java/org/lab/application/ticket/use_cases/TestCreateTicketUseCase.java diff --git a/src/test/java/org/lab/application/ticket/services/TestTicketCloseValidator.java b/src/test/java/org/lab/application/ticket/services/TestTicketCloseValidator.java new file mode 100644 index 0000000..943425f --- /dev/null +++ b/src/test/java/org/lab/application/ticket/services/TestTicketCloseValidator.java @@ -0,0 +1,45 @@ +package org.lab.application.ticket.services; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; + +public class TestTicketCloseValidator { + + private TicketRepository ticketRepository; + private TicketCloseValidator validator; + + @BeforeEach + void setup() { + ticketRepository = mock(TicketRepository.class); + validator = new TicketCloseValidator(ticketRepository); + } + + @Test + void validate_existingTicket_passes() throws NotPermittedException { + Ticket ticket = new Ticket(); + ticket.setId(1); + ticket.setAssignedTo(10); + + when(ticketRepository.get(1, 10)).thenReturn(ticket); + + validator.validate(1, 10); + verify(ticketRepository).get(1, 10); + } + + @Test + void validate_nonExistingTicket_throwsException() { + when(ticketRepository.get(2, 10)).thenReturn(null); + + assertThrows( + NotPermittedException.class, + () -> validator.validate(2, 10) + ); + verify(ticketRepository).get(2, 10); + } +} diff --git a/src/test/java/org/lab/application/ticket/services/TestTicketCreateValidator.java b/src/test/java/org/lab/application/ticket/services/TestTicketCreateValidator.java new file mode 100644 index 0000000..2be4d95 --- /dev/null +++ b/src/test/java/org/lab/application/ticket/services/TestTicketCreateValidator.java @@ -0,0 +1,68 @@ +package org.lab.application.ticket.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.lab.application.shared.services.ProjectProvider; +import org.lab.domain.shared.exceptions.NotPermittedException; + +public class TestTicketCreateValidator { + + private TicketPermissionValidator ticketPermissionValidator; + private ProjectProvider projectProvider; + private TicketCreateValidator validator; + + @BeforeEach + void setup() { + ticketPermissionValidator = mock(TicketPermissionValidator.class); + projectProvider = mock(ProjectProvider.class); + validator = new TicketCreateValidator(ticketPermissionValidator, projectProvider); + } + + @Test + void validate_success_noException() { + int employeeId = 10; + int projectId = 5; + + validator.validate(employeeId, projectId); + verify(ticketPermissionValidator).validate(employeeId); + verify(projectProvider).get(projectId); + } + + @Test + void validate_ticketPermissionThrows_exception() { + int employeeId = 10; + int projectId = 5; + + doThrow(new NotPermittedException("not allowed")) + .when(ticketPermissionValidator) + .validate(employeeId); + + assertThrows( + RuntimeException.class, + () -> validator.validate(employeeId, projectId) + ); + + verify(ticketPermissionValidator).validate(employeeId); + } + + @Test + void validate_projectProviderThrows_exception() { + int employeeId = 10; + int projectId = 5; + + doThrow(new RuntimeException("project not found")) + .when(projectProvider) + .get(projectId); + + assertThrows( + RuntimeException.class, + () -> validator.validate(employeeId, projectId) + ); + + verify(ticketPermissionValidator).validate(employeeId); + verify(projectProvider).get(projectId); + } +} diff --git a/src/test/java/org/lab/application/ticket/services/TestTicketPermissionValidator.java b/src/test/java/org/lab/application/ticket/services/TestTicketPermissionValidator.java new file mode 100644 index 0000000..27a4acd --- /dev/null +++ b/src/test/java/org/lab/application/ticket/services/TestTicketPermissionValidator.java @@ -0,0 +1,56 @@ +package org.lab.application.ticket.services; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.employee.EmployeeRepository; + +public class TestTicketPermissionValidator { + + private EmployeeRepository employeeRepository; + private TicketPermissionValidator validator; + + @BeforeEach + void setup() { + employeeRepository = mock(EmployeeRepository.class); + validator = new TicketPermissionValidator(employeeRepository); + } + + @Test + void validate_managerAllowed() { + Employee manager = new Employee(); + manager.setType(EmployeeType.MANAGER); + when(employeeRepository.getById(1)).thenReturn(manager); + + validator.validate(1); + verify(employeeRepository).getById(1); + } + + @Test + void validate_teamLeadAllowed() { + Employee teamLead = new Employee(); + teamLead.setType(EmployeeType.TEAMLEAD); + when(employeeRepository.getById(2)).thenReturn(teamLead); + + validator.validate(2); + verify(employeeRepository).getById(2); + } + + @Test + void validate_notAllowed_throws() { + Employee tester = new Employee(); + tester.setType(EmployeeType.TESTER); + when(employeeRepository.getById(3)).thenReturn(tester); + + assertThrows( + NotPermittedException.class, + () -> validator.validate(3) + ); + verify(employeeRepository).getById(3); + } +} diff --git a/src/test/java/org/lab/application/ticket/use_cases/TestCloseTicketUseCase.java b/src/test/java/org/lab/application/ticket/use_cases/TestCloseTicketUseCase.java new file mode 100644 index 0000000..66dcd9a --- /dev/null +++ b/src/test/java/org/lab/application/ticket/use_cases/TestCloseTicketUseCase.java @@ -0,0 +1,54 @@ +package org.lab.application.ticket.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCloseValidator; + +class TestCloseTicketUseCase { + + private TicketRepository ticketRepository; + private TicketCloseValidator ticketCloseValidator; + private CloseTicketUseCase useCase; + + @BeforeEach + void setup() { + ticketRepository = mock(TicketRepository.class); + ticketCloseValidator = mock(TicketCloseValidator.class); + useCase = new CloseTicketUseCase(ticketRepository, ticketCloseValidator); + } + + @Test + void execute_success() { + Ticket ticket = new Ticket(); + ticket.setId(1); + when(ticketRepository.close(1)).thenReturn(ticket); + + Ticket result = useCase.execute(1, 10); + + verify(ticketCloseValidator).validate(1, 10); + verify(ticketRepository).close(1); + assertEquals(ticket, result); + } + + @Test + void execute_validatorThrows_notPermitted() { + doThrow( + new NotPermittedException("not permitted") + ).when(ticketCloseValidator).validate(1, 10); + + assertThrows( + NotPermittedException.class, + () -> useCase.execute(1, 10) + ); + + verify(ticketCloseValidator).validate(1, 10); + verify(ticketRepository, never()).close(anyInt()); + } +} diff --git a/src/test/java/org/lab/application/ticket/use_cases/TestCreateTicketUseCase.java b/src/test/java/org/lab/application/ticket/use_cases/TestCreateTicketUseCase.java new file mode 100644 index 0000000..51029fd --- /dev/null +++ b/src/test/java/org/lab/application/ticket/use_cases/TestCreateTicketUseCase.java @@ -0,0 +1,60 @@ +package org.lab.application.ticket.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import org.lab.domain.ticket.model.Ticket; +import org.lab.infra.db.repository.ticket.TicketRepository; +import org.lab.application.ticket.services.TicketCreateValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; + +class TestCreateTicketUseCase { + + private TicketRepository ticketRepository; + private TicketCreateValidator ticketCreateValidator; + private CreateTicketUseCase useCase; + + @BeforeEach + void setup() { + ticketRepository = mock(TicketRepository.class); + ticketCreateValidator = mock(TicketCreateValidator.class); + useCase = new CreateTicketUseCase(ticketRepository, ticketCreateValidator); + } + + @Test + void execute_success() { + Ticket ticket = new Ticket(); + Ticket createdTicket = new Ticket(); + createdTicket.setId(1); + + when(ticketRepository.create(ticket, 10, 5)) + .thenReturn(createdTicket); + + Ticket result = useCase.execute(ticket, 10, 5); + + verify(ticketCreateValidator).validate(10, 5); + verify(ticketRepository).create(ticket, 10, 5); + assertEquals(createdTicket, result); + } + + @Test + void execute_validatorThrows_notPermitted() { + Ticket ticket = new Ticket(); + doThrow( + new NotPermittedException("Not permitted") + ) + .when(ticketCreateValidator) + .validate(10, 5); + + assertThrows( + NotPermittedException.class, + () -> useCase.execute(ticket, 10, 5) + ); + + verify(ticketCreateValidator).validate(10, 5); + verify(ticketRepository, never()).create(any(), anyInt(), anyInt()); + } +} From e940ab4501d0d8367fed9166083adb0798a064e4 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 7 Dec 2025 02:12:02 +0300 Subject: [PATCH 65/67] tests done --- .../employee/TestEmployeeCreateAdapter.java | 40 ++++--- .../project/TestProjectCreateAdapter.java | 61 ++++------ .../project/TestProjectGetAdapter.java | 26 ++-- .../use_cases/TestCreateEmployeeUseCase.java | 32 ++--- .../project/services/TestGetValidator.java | 86 ++++++++++++++ .../project/services/TestUserSpecFactory.java | 71 +++++++++++ .../use_cases/TestCreateProjectUseCase.java | 58 +++++++++ .../use_cases/TestDeleteProjectUseCase.java | 90 ++++++++++++++ .../use_cases/TestGetProjectUseCase.java | 104 ++++++++++++++++ .../use_cases/TestListProjectUseCase.java | 111 ++++++++++++++++++ 10 files changed, 595 insertions(+), 84 deletions(-) create mode 100644 src/test/java/org/lab/application/project/services/TestGetValidator.java create mode 100644 src/test/java/org/lab/application/project/services/TestUserSpecFactory.java create mode 100644 src/test/java/org/lab/application/project/use_cases/TestCreateProjectUseCase.java create mode 100644 src/test/java/org/lab/application/project/use_cases/TestDeleteProjectUseCase.java create mode 100644 src/test/java/org/lab/application/project/use_cases/TestGetProjectUseCase.java create mode 100644 src/test/java/org/lab/application/project/use_cases/TestListProjectUseCase.java diff --git a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java index 1efa0ab..e093095 100644 --- a/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java +++ b/src/test/java/org/lab/api/adapters/employee/TestEmployeeCreateAdapter.java @@ -33,6 +33,8 @@ public void setUp() { @Test public void testCreateEmployeeSuccess() { + Mockito.when(ctx.pathParam("actorId")).thenReturn("99"); + CreateEmployeeDTO dto = new CreateEmployeeDTO( "John", 30, @@ -61,20 +63,23 @@ public void testCreateEmployeeSuccess() { createdEmployee.getCreatedDate() ); - Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)) - .thenReturn(dto); - - Mockito.when(mapper.mapToDomain(dto, Employee.class)) - .thenReturn(mappedEmployee); - - Mockito.when(useCase.execute(mappedEmployee, 99)) - .thenReturn(createdEmployee); - - Mockito.when(mapper.mapToPresentation(createdEmployee, GetEmployeeDTO.class)) - .thenReturn(presentation); - - Mockito.when(ctx.status(201)).thenReturn(ctx); - Mockito.when(ctx.json(presentation)).thenReturn(ctx); + Mockito.when( + ctx.bodyAsClass(CreateEmployeeDTO.class) + ).thenReturn(dto); + Mockito.when( + mapper.mapToDomain(dto, Employee.class) + ).thenReturn(mappedEmployee); + Mockito.when( + useCase.execute(mappedEmployee, 99) + ).thenReturn(createdEmployee); + Mockito.when( + mapper.mapToPresentation( + createdEmployee, + GetEmployeeDTO.class) + ).thenReturn(presentation); + + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.createEmployee(ctx); @@ -84,6 +89,8 @@ public void testCreateEmployeeSuccess() { @Test public void testCreateEmployeeNotPermitted() { + Mockito.when(ctx.pathParam("actorId")).thenReturn("99"); + CreateEmployeeDTO dto = new CreateEmployeeDTO( "John", 30, @@ -91,15 +98,14 @@ public void testCreateEmployeeNotPermitted() { ); Mockito.when(ctx.bodyAsClass(CreateEmployeeDTO.class)).thenReturn(dto); - Mockito.when(mapper.mapToDomain(dto, Employee.class)) - .thenReturn(new Employee()); + Mockito.when(mapper.mapToDomain(dto, Employee.class)).thenReturn(new Employee()); Mockito.when(useCase.execute(Mockito.any(), Mockito.eq(99))) .thenThrow(new NotPermittedException( "You do not have permission to perform this operation" )); - Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.createEmployee(ctx); diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java index bd44f20..bc5253a 100644 --- a/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java +++ b/src/test/java/org/lab/api/adapters/project/TestProjectCreateAdapter.java @@ -35,7 +35,7 @@ public void setUp() { } @Test - public void testCreateProjectSuccess() { + void testCreateProjectSuccess() { CreateProjectDTO dto = new CreateProjectDTO( "TestProject", "Description", @@ -43,13 +43,11 @@ public void testCreateProjectSuccess() { List.of(1, 2), List.of(3, 4) ); - - Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)) - .thenReturn(dto); + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)).thenReturn(dto); + Mockito.when(ctx.pathParam("employeeId")).thenReturn("20"); Project mapped = new Project(); - Mockito.when(mapper.mapToDomain(dto, Project.class)) - .thenReturn(mapped); + Mockito.when(mapper.mapToDomain(dto, Project.class)).thenReturn(mapped); Project created = new Project(); created.setId(111); @@ -59,47 +57,38 @@ public void testCreateProjectSuccess() { created.setTeamLeadId(20); created.setDeveloperIds(List.of(1, 2)); created.setTesterIds(List.of(3, 4)); - created.setStatus(ProjectStatus.OPEN); - created.setMilestoneIds(List.of()); - created.setBugReportIds(List.of()); - created.setCreatedDate(new Date()); - created.setCreatedBy(99); - Mockito.when(useCase.execute(mapped, 20)) - .thenReturn(created); + Mockito.when( + useCase.execute(Mockito.eq(mapped), + Mockito.eq(20)) + ).thenReturn(created); GetProjectDTO presentation = new GetProjectDTO( created.getId(), created.getName(), created.getDescription(), - created.getManagerId(), created.getTeamLeadId(), - created.getDeveloperIds(), created.getTesterIds(), - created.getStatus(), - created.getCurrentMilestoneId(), created.getMilestoneIds(), - created.getBugReportIds(), - created.getCreatedDate(), created.getCreatedBy(), created.getUpdatedDate(), created.getUpdatedBy() ); + Mockito.when( + mapper.mapToPresentation( + created, + GetProjectDTO.class) + ).thenReturn(presentation); - Mockito.when(mapper.mapToPresentation(created, GetProjectDTO.class)) - .thenReturn(presentation); - - Mockito.when(ctx.status(201)).thenReturn(ctx); - Mockito.when(ctx.json(presentation)).thenReturn(ctx); - + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.createProject(ctx); - Mockito.verify(ctx).status(201); Mockito.verify(ctx).json(presentation); } @@ -114,20 +103,18 @@ public void testCreateProject_NotPermitted() { List.of() ); - Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)) - .thenReturn(dto); + Mockito.when(ctx.bodyAsClass(CreateProjectDTO.class)).thenReturn(dto); + Mockito.when(ctx.pathParam("employeeId")).thenReturn("20"); - Mockito.when(mapper.mapToDomain(dto, Project.class)) - .thenReturn(new Project()); + Project mappedProject = new Project(); + Mockito.when(mapper.mapToDomain(dto, Project.class)).thenReturn(mappedProject); - Mockito.when(useCase.execute(Mockito.any(), 20)) - .thenThrow( - new NotPermittedException( - "You do not have permission to perform this operation" - ) - ); + Mockito.when(useCase.execute(Mockito.eq(mappedProject), Mockito.eq(20))) + .thenThrow(new NotPermittedException( + "You do not have permission to perform this operation" + )); - Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.createProject(ctx); diff --git a/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java b/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java index 76e0fe2..56eb0ce 100644 --- a/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java +++ b/src/test/java/org/lab/api/adapters/project/TestProjectGetAdapter.java @@ -51,7 +51,12 @@ public void testGetProjectSuccess() { project.setCreatedDate(new Date()); project.setCreatedBy(99); - Mockito.when(useCase.execute(10, 50)).thenReturn(project); + Mockito.when( + useCase.execute( + Mockito.eq(50), + Mockito.eq(10) + ) + ).thenReturn(project); GetProjectDTO dto = new GetProjectDTO( project.getId(), @@ -71,11 +76,10 @@ public void testGetProjectSuccess() { project.getUpdatedBy() ); - Mockito.when(mapper.mapToPresentation(project, GetProjectDTO.class)) - .thenReturn(dto); + Mockito.when(mapper.mapToPresentation(project, GetProjectDTO.class)).thenReturn(dto); - Mockito.when(ctx.status(200)).thenReturn(ctx); - Mockito.when(ctx.json(dto)).thenReturn(ctx); + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); + Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.getProject(ctx); @@ -88,10 +92,10 @@ public void testGetProject_UserNotFound() { Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); - Mockito.when(useCase.execute(10, 50)) + Mockito.when(useCase.execute(Mockito.eq(50), Mockito.eq(10))) .thenThrow(new UserNotFoundException()); - Mockito.when(ctx.status(409)).thenReturn(ctx); + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.getProject(ctx); @@ -105,10 +109,10 @@ public void testGetProject_NotPermitted() { Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); - Mockito.when(useCase.execute(10, 50)) + Mockito.when(useCase.execute(Mockito.eq(50), Mockito.eq(10))) .thenThrow(new NotPermittedException("denied")); - Mockito.when(ctx.status(403)).thenReturn(ctx); + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.getProject(ctx); @@ -124,10 +128,10 @@ public void testGetProject_ProjectNotFound() { Mockito.when(ctx.pathParam("employeeId")).thenReturn("10"); Mockito.when(ctx.pathParam("projectId")).thenReturn("50"); - Mockito.when(useCase.execute(10, 50)) + Mockito.when(useCase.execute(Mockito.eq(50), Mockito.eq(10))) .thenThrow(new ProjectNotFoundException()); - Mockito.when(ctx.status(404)).thenReturn(ctx); + Mockito.when(ctx.status(Mockito.anyInt())).thenReturn(ctx); Mockito.when(ctx.json(Mockito.any())).thenReturn(ctx); adapter.getProject(ctx); diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index 9954f32..18b4ec2 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -37,7 +37,11 @@ public void testCreateEmployeeSuccess() { saved.setId(100); saved.setName("Tim Cock"); - Mockito.when(employeeRepository.create(input, 12)).thenReturn(saved); + Mockito.when( + employeeRepository.create(Mockito.any(Employee.class), + Mockito.eq(1)) + ) + .thenReturn(saved); Employee creator = new Employee(); creator.setId(1); @@ -46,9 +50,13 @@ public void testCreateEmployeeSuccess() { Employee result = useCase.execute(input, 1); + Assertions.assertNotNull(result); Assertions.assertEquals(100, result.getId()); - Mockito.verify(employeeRepository).create(input, 12); + Mockito.verify(employeeRepository) + .create(Mockito.any(Employee.class), + Mockito.eq(1) + ); Mockito.verify(employeeRepository).getById(1); } @@ -63,18 +71,11 @@ public void testCreateEmployeeRaisesNotPermittedException() { creator.setType(EmployeeType.PROGRAMMER); Mockito.when(employeeRepository.getById(1)).thenReturn(creator); - Mockito.when(employeeRepository.getById(123)).thenReturn(null); - RuntimeException thrown = Assertions.assertThrows( - RuntimeException.class, + Assertions.assertThrows( + NotPermittedException.class, () -> useCase.execute(input, 1) ); - - Assertions.assertTrue( - thrown.getCause() instanceof ExecutionException && - ((ExecutionException) thrown.getCause()).getCause() instanceof NotPermittedException - ); - Mockito.verify(employeeRepository).getById(123); Mockito.verify(employeeRepository).getById(1); } @@ -89,18 +90,11 @@ public void testCreateEmployeeRaisesRuntimeException() { creator.setType(EmployeeType.PROGRAMMER); Mockito.when(employeeRepository.getById(1)).thenThrow(new RuntimeException()); - Mockito.when(employeeRepository.getById(123)).thenThrow(new RuntimeException()); - RuntimeException thrown = Assertions.assertThrows( + Assertions.assertThrows( RuntimeException.class, () -> useCase.execute(input, 1) ); - - Assertions.assertTrue( - thrown.getCause() instanceof ExecutionException && - ((ExecutionException) thrown.getCause()).getCause() instanceof RuntimeException - ); - Mockito.verify(employeeRepository).getById(123); Mockito.verify(employeeRepository).getById(1); } } diff --git a/src/test/java/org/lab/application/project/services/TestGetValidator.java b/src/test/java/org/lab/application/project/services/TestGetValidator.java new file mode 100644 index 0000000..bc814d6 --- /dev/null +++ b/src/test/java/org/lab/application/project/services/TestGetValidator.java @@ -0,0 +1,86 @@ +package org.lab.application.project.services; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.application.shared.services.ProjectProvider; + +import java.util.concurrent.ExecutionException; + +class TestGetValidator { + + private ProjectProvider projectProvider; + private EmployeeProvider employeeProvider; + private GetValidator validator; + + @BeforeEach + void setup() { + projectProvider = mock(ProjectProvider.class); + employeeProvider = mock(EmployeeProvider.class); + validator = new GetValidator(projectProvider, employeeProvider); + } + + @Test + void validate_success() throws Exception { + Project project = new Project(); + Employee employee = new Employee(); + + when(projectProvider.get(5)).thenReturn(project); + when(employeeProvider.get(10)).thenReturn(employee); + + Pair result = validator.validate(5, 10); + + verify(projectProvider).get(5); + verify(employeeProvider).get(10); + assertSame(project, result.project()); + assertSame(employee, result.employee()); + } + + @Test + void validate_projectNotFound_throwsException() throws Exception { + when(projectProvider.get(5)).thenThrow(new ProjectNotFoundException()); + when(employeeProvider.get(10)).thenReturn(new Employee()); + + RuntimeException thrown = Assertions.assertThrows( + RuntimeException.class, + () -> validator.validate(5, 10) + ); + + Assertions.assertTrue( + thrown.getCause() instanceof ExecutionException && + ((ExecutionException) thrown.getCause() + ).getCause() instanceof ProjectNotFoundException + ); + verify(projectProvider).get(5); + verify(employeeProvider).get(10); + } + + @Test + void validate_userNotFound_throwsException() throws Exception { + when(projectProvider.get(5)).thenReturn(new Project()); + when(employeeProvider.get(10)).thenThrow(new UserNotFoundException()); + + RuntimeException thrown = Assertions.assertThrows( + RuntimeException.class, + () -> validator.validate(5, 10) + ); + + Assertions.assertTrue( + thrown.getCause() instanceof ExecutionException && + ((ExecutionException) thrown.getCause() + ).getCause() instanceof UserNotFoundException + ); + + verify(projectProvider).get(5); + verify(employeeProvider).get(10); + } +} diff --git a/src/test/java/org/lab/application/project/services/TestUserSpecFactory.java b/src/test/java/org/lab/application/project/services/TestUserSpecFactory.java new file mode 100644 index 0000000..2142efd --- /dev/null +++ b/src/test/java/org/lab/application/project/services/TestUserSpecFactory.java @@ -0,0 +1,71 @@ +package org.lab.application.project.services; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.core.constants.employee.EmployeeType; +import org.lab.domain.emploee.model.Employee; +import org.lab.infra.db.repository.employee.spec.DeveloperSpec; +import org.lab.infra.db.repository.employee.spec.ManagerSpec; +import org.lab.infra.db.repository.employee.spec.TeamLeaderSpec; +import org.lab.infra.db.repository.employee.spec.TesterSpec; +import org.lab.infra.db.spec.Specification; + +class TestUserSpecFactory { + + private UserSpecFactory factory; + + @BeforeEach + void setup() { + factory = new UserSpecFactory(); + } + + @Test + void getForType_manager_returnsManagerSpec() { + Employee emp = new Employee(); + emp.setId(1); + emp.setType(EmployeeType.MANAGER); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof ManagerSpec); + assertEquals(1, ((ManagerSpec) spec).getParams().get(0)); + } + + @Test + void getForType_teamLead_returnsTeamLeaderSpec() { + Employee emp = new Employee(); + emp.setId(2); + emp.setType(EmployeeType.TEAMLEAD); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof TeamLeaderSpec); + assertEquals(2, ((TeamLeaderSpec) spec).getParams().get(0)); + } + + @Test + void getForType_programmer_returnsDeveloperSpec() { + Employee emp = new Employee(); + emp.setId(3); + emp.setType(EmployeeType.PROGRAMMER); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof DeveloperSpec); + assertEquals(3, ((DeveloperSpec) spec).getParams().get(0)); + } + + @Test + void getForType_tester_returnsTesterSpec() { + Employee emp = new Employee(); + emp.setId(4); + emp.setType(EmployeeType.TESTER); + + Specification spec = factory.getForType(emp); + + assertTrue(spec instanceof TesterSpec); + assertEquals(4, ((TesterSpec) spec).getParams().get(0)); + } +} diff --git a/src/test/java/org/lab/application/project/use_cases/TestCreateProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestCreateProjectUseCase.java new file mode 100644 index 0000000..2a1bec5 --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestCreateProjectUseCase.java @@ -0,0 +1,58 @@ +package org.lab.application.project.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.domain.project.model.Project; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.application.shared.services.EmployeePermissionValidator; + +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +class TestCreateProjectUseCase { + + private ProjectRepository projectRepository; + private EmployeePermissionValidator employeePermissionValidator; + private CreateProjectUseCase useCase; + + @BeforeEach + void setup() { + projectRepository = mock(ProjectRepository.class); + employeePermissionValidator = mock(EmployeePermissionValidator.class); + useCase = new CreateProjectUseCase( + projectRepository, + employeePermissionValidator + ); + } + + @Test + void execute_success_createsProject() { + Project project = new Project(); + project.setId(1); + int employeeId = 10; + + when(projectRepository.create(project, employeeId)).thenReturn(project); + + Project result = useCase.execute(project, employeeId); + + verify(employeePermissionValidator).validate(employeeId); + verify(projectRepository).create(project, employeeId); + assertEquals(project, result); + } + + @Test + void execute_notPermitted_throwsException() { + Project project = new Project(); + int employeeId = 5; + + doThrow(new NotPermittedException("Not allowed")) + .when(employeePermissionValidator).validate(employeeId); + + assertThrows(NotPermittedException.class, () -> useCase.execute(project, employeeId)); + + verify(employeePermissionValidator).validate(employeeId); + verifyNoInteractions(projectRepository); + } +} + diff --git a/src/test/java/org/lab/application/project/use_cases/TestDeleteProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestDeleteProjectUseCase.java new file mode 100644 index 0000000..930814a --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestDeleteProjectUseCase.java @@ -0,0 +1,90 @@ +package org.lab.application.project.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.application.project.services.GetValidator; +import org.lab.application.project.services.Pair; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; + +class TestDeleteProjectUseCase { + + private ProjectRepository projectRepository; + private GetValidator getValidator; + private ProjectMembershipValidator projectMembershipValidator; + private DeleteProjectUseCase useCase; + + @BeforeEach + void setup() { + projectRepository = mock(ProjectRepository.class); + getValidator = mock(GetValidator.class); + projectMembershipValidator = mock(ProjectMembershipValidator.class); + useCase = new DeleteProjectUseCase( + projectRepository, + getValidator, + projectMembershipValidator + ); + } + + @Test + void execute_success_deletesProject() throws Exception { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + + useCase.execute(employeeId, projectId); + + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + verify(projectRepository).delete(projectId); + } + + @Test + void execute_validatorThrows_exception() throws Exception { + int employeeId = 10; + int projectId = 5; + + when(getValidator.validate(projectId, employeeId)) + .thenThrow(new ProjectNotFoundException()); + + assertThrows( + ProjectNotFoundException.class, + () -> useCase.execute(employeeId, projectId) + ); + + verify(getValidator).validate(projectId, employeeId); + verifyNoInteractions(projectMembershipValidator); + verifyNoInteractions(projectRepository); + } + + @Test + void execute_membershipValidatorThrows_exception() throws Exception { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + doThrow( + new NotPermittedException("Not permitted") + ).when(projectMembershipValidator).validate(employee, project); + + assertThrows(NotPermittedException.class, () -> useCase.execute(employeeId, projectId)); + + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + verifyNoInteractions(projectRepository); + } +} diff --git a/src/test/java/org/lab/application/project/use_cases/TestGetProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestGetProjectUseCase.java new file mode 100644 index 0000000..36e22c0 --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestGetProjectUseCase.java @@ -0,0 +1,104 @@ +package org.lab.application.project.use_cases; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.*; + +import org.lab.application.project.services.GetValidator; +import org.lab.application.project.services.Pair; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.domain.project.services.ProjectMembershipValidator; +import org.lab.domain.shared.exceptions.NotPermittedException; +import org.lab.domain.shared.exceptions.ProjectNotFoundException; +import org.lab.domain.shared.exceptions.UserNotFoundException; + +public class TestGetProjectUseCase { + private GetValidator getValidator; + private ProjectMembershipValidator projectMembershipValidator; + private GetProjectUseCase useCase; + + @BeforeEach + void setup() { + getValidator = mock(GetValidator.class); + projectMembershipValidator = mock(ProjectMembershipValidator.class); + useCase = new GetProjectUseCase( + getValidator, + projectMembershipValidator + ); + } + + @Test + void execute_success_returnsProject() { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + + Project result = useCase.execute(projectId, employeeId); + + assertEquals(project, result); + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + } + + @Test + void execute_validatorThrowsProjectNotFoundException() { + int employeeId = 10; + int projectId = 5; + + when(getValidator.validate(projectId, employeeId)) + .thenThrow(new ProjectNotFoundException()); + + assertThrows( + ProjectNotFoundException.class, + () -> useCase.execute(projectId, employeeId) + ); + + verify(getValidator).validate(projectId, employeeId); + verifyNoInteractions(projectMembershipValidator); + } + + @Test + void execute_validatorThrowsUserNotFoundException() { + int employeeId = 10; + int projectId = 5; + + when(getValidator.validate(projectId, employeeId)) + .thenThrow(new UserNotFoundException()); + + assertThrows( + UserNotFoundException.class, + () -> useCase.execute(projectId, employeeId) + ); + + verify(getValidator).validate(projectId, employeeId); + verifyNoInteractions(projectMembershipValidator); + } + + @Test + void execute_membershipValidatorThrowsNotPermittedException() { + int employeeId = 10; + int projectId = 5; + Employee employee = new Employee(); + Project project = new Project(); + Pair pair = new Pair(project, employee); + + when(getValidator.validate(projectId, employeeId)).thenReturn(pair); + doThrow( + new NotPermittedException("not permitted") + ).when(projectMembershipValidator).validate(employee, project); + + assertThrows( + NotPermittedException.class, + () -> useCase.execute(projectId, employeeId) + ); + + verify(getValidator).validate(projectId, employeeId); + verify(projectMembershipValidator).validate(employee, project); + } +} diff --git a/src/test/java/org/lab/application/project/use_cases/TestListProjectUseCase.java b/src/test/java/org/lab/application/project/use_cases/TestListProjectUseCase.java new file mode 100644 index 0000000..91f5c79 --- /dev/null +++ b/src/test/java/org/lab/application/project/use_cases/TestListProjectUseCase.java @@ -0,0 +1,111 @@ +package org.lab.application.project.use_cases; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.lab.application.project.services.UserSpecFactory; +import org.lab.application.shared.services.EmployeeProvider; +import org.lab.domain.emploee.model.Employee; +import org.lab.domain.project.model.Project; +import org.lab.infra.db.repository.project.ProjectRepository; +import org.lab.infra.db.spec.Specification; +import org.lab.infra.db.spec.SqlSpec; + +public class TestListProjectUseCase { + + private ProjectRepository projectRepository; + private EmployeeProvider employeeProvider; + private UserSpecFactory userSpecFactory; + private ListProjectUseCase useCase; + + @BeforeEach + void setup() { + projectRepository = mock(ProjectRepository.class); + employeeProvider = mock(EmployeeProvider.class); + userSpecFactory = mock(UserSpecFactory.class); + useCase = new ListProjectUseCase( + projectRepository, + employeeProvider, + userSpecFactory + ); + } + + @Test + void execute_success_returnsProjects() { + int employeeId = 10; + Employee employee = new Employee(); + Specification spec = mock(SqlSpec.class); + Project project1 = new Project(); + Project project2 = new Project(); + List projects = List.of(project1, project2); + + when(employeeProvider.get(employeeId)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenReturn(spec); + when(projectRepository.list(spec)).thenReturn(projects); + + List result = useCase.execute(employeeId); + + assertEquals(projects, result); + verify(employeeProvider).get(employeeId); + verify(userSpecFactory).getForType(employee); + verify(projectRepository).list(spec); + } + + @Test + void execute_employeeProviderThrowsException_propagates() { + int employeeId = 10; + when(employeeProvider.get(employeeId)).thenThrow(new RuntimeException("boom")); + + RuntimeException ex = assertThrows( + RuntimeException.class, + () -> useCase.execute(employeeId) + ); + assertEquals("boom", ex.getMessage()); + + verify(employeeProvider).get(employeeId); + verifyNoInteractions(userSpecFactory); + verifyNoInteractions(projectRepository); + } + + @Test + void execute_userSpecFactoryThrowsException_propagates() { + int employeeId = 10; + Employee employee = new Employee(); + when(employeeProvider.get(employeeId)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenThrow(new RuntimeException("boom")); + + RuntimeException ex = assertThrows( + RuntimeException.class, + () -> useCase.execute(employeeId) + ); + assertEquals("boom", ex.getMessage()); + + verify(employeeProvider).get(employeeId); + verify(userSpecFactory).getForType(employee); + verifyNoInteractions(projectRepository); + } + + @Test + void execute_projectRepositoryThrowsException_propagates() { + int employeeId = 10; + Employee employee = new Employee(); + Specification spec = mock(SqlSpec.class); + when(employeeProvider.get(employeeId)).thenReturn(employee); + when(userSpecFactory.getForType(employee)).thenReturn(spec); + when(projectRepository.list(spec)).thenThrow(new RuntimeException("boom")); + + RuntimeException ex = assertThrows( + RuntimeException.class, + () -> useCase.execute(employeeId) + ); + assertEquals("boom", ex.getMessage()); + + verify(employeeProvider).get(employeeId); + verify(userSpecFactory).getForType(employee); + verify(projectRepository).list(spec); + } +} From 64d7c920a9e9257de413f131a6133bbef9ce0571 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 7 Dec 2025 02:18:24 +0300 Subject: [PATCH 66/67] removed * --- .../lab/infra/db/repository/employee/EmployeeRepository.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java index 30ec91b..c269db3 100644 --- a/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java +++ b/src/main/java/org/lab/infra/db/repository/employee/EmployeeRepository.java @@ -1,6 +1,9 @@ package org.lab.infra.db.repository.employee; -import java.sql.*; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.Map; import org.lab.domain.emploee.model.Employee; From f34743ee214673fd6e0f97934f57848faa1835ed Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 7 Dec 2025 02:29:27 +0300 Subject: [PATCH 67/67] rm redundant improts --- src/main/java/org/lab/infra/db/client/DatabaseClient.java | 6 +++--- .../employee/use_cases/TestCreateEmployeeUseCase.java | 2 -- .../employee/use_cases/TestDeleteEmployeeUseCase.java | 2 +- .../employee/use_cases/TestGetEmployeeUseCase.java | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/lab/infra/db/client/DatabaseClient.java b/src/main/java/org/lab/infra/db/client/DatabaseClient.java index b5a620c..61e3ae6 100644 --- a/src/main/java/org/lab/infra/db/client/DatabaseClient.java +++ b/src/main/java/org/lab/infra/db/client/DatabaseClient.java @@ -1,11 +1,11 @@ package org.lab.infra.db.client; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; - import java.sql.Connection; import java.sql.SQLException; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + public class DatabaseClient { private static HikariDataSource dataSource; diff --git a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java index 18b4ec2..1e1cba1 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestCreateEmployeeUseCase.java @@ -1,7 +1,5 @@ package org.lab.application.employee.use_cases; -import java.util.concurrent.ExecutionException; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java index 1235cde..e70382f 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestDeleteEmployeeUseCase.java @@ -44,7 +44,7 @@ public void testDeleteEmployeeRaisesNotPermittedError() { Mockito.when(employeeRepository.getById(1)).thenReturn(manager); - RuntimeException thrown = Assertions.assertThrows( + Assertions.assertThrows( NotPermittedException.class, () -> useCase.execute(2, 1) ); diff --git a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java index f5173dc..b2726e5 100644 --- a/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java +++ b/src/test/java/org/lab/application/employee/use_cases/TestGetEmployeeUseCase.java @@ -52,7 +52,7 @@ public void testGetEmployeeRaisesNotPermittedError() { Mockito.when(employeeRepository.getById(1)).thenReturn(manager); - RuntimeException thrown = Assertions.assertThrows( + Assertions.assertThrows( NotPermittedException.class, () -> useCase.execute(2, 1) ); @@ -67,7 +67,7 @@ public void testGetEmployeeRaisesEmployeeNotFoundError() { Mockito.when(employeeRepository.getById(1)).thenReturn(manager); Mockito.when(employeeRepository.getById(2)).thenReturn(null); - RuntimeException thrown = Assertions.assertThrows( + Assertions.assertThrows( UserNotFoundException.class, () -> useCase.execute(2, 1) );