diff --git a/pom.xml b/pom.xml
index 17fd7ad..57032fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,63 +1,78 @@
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.1.3.RELEASE
-
-
- com.zoomcare
- java-candidate-challenge
- 0.0.1-SNAPSHOT
- candidate-challenge
- ZoomCare Candidate Code Challenge
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.5
+
+
+ com.example
+ ZoomCareCodeChallenge
+ 0.0.1-SNAPSHOT
+ ZoomCareCodeChallenge
+ ZoomCareCodeChallenge
+
+ 17
+
+
+
+ org.hamcrest
+ hamcrest
+ 2.2
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ junit
+ junit
+ test
+
+
-
- 1.8
-
-
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
-
- org.springframework.boot
- spring-boot-starter-data-jdbc
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
- org.flywaydb
- flyway-core
- runtime
-
-
-
- com.h2database
- h2
- runtime
-
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
diff --git a/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java
new file mode 100644
index 0000000..ac9fd14
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java
@@ -0,0 +1,34 @@
+package com.zoomcare.candidatechallenge.controller;
+
+import com.zoomcare.candidatechallenge.dto.EmployeeDTO;
+import com.zoomcare.candidatechallenge.service.EmployeeService;
+import lombok.AllArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/employees")
+public class EmployeeController {
+
+ private final EmployeeService employeeService;
+
+
+ @GetMapping("/")
+ public ResponseEntity> getTopLevelEmployees() {
+ List topLevelEmployees = employeeService.getTopLevelEmployees();
+ return new ResponseEntity<>(topLevelEmployees, HttpStatus.OK);
+ }
+
+ @GetMapping("/{id}")
+ public ResponseEntity getEmployee(@PathVariable Long id) {
+ EmployeeDTO employee = employeeService.getEmployee(id);
+ return new ResponseEntity<>(employee, HttpStatus.OK);
+ }
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java
new file mode 100644
index 0000000..8e0ed01
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java
@@ -0,0 +1,24 @@
+package com.zoomcare.candidatechallenge.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+public class EmployeeDTO {
+
+ private Long id;
+ private List properties;
+ private List reports;
+
+ public EmployeeDTO() {
+
+ }
+
+ public EmployeeDTO(long l) {
+ }
+
+ // Getters and setters
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/PropertiesDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertiesDTO.java
new file mode 100644
index 0000000..ab4d590
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertiesDTO.java
@@ -0,0 +1,17 @@
+package com.zoomcare.candidatechallenge.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class PropertiesDTO {
+
+ private String key;
+ private String value;
+
+
+ // Getters and setters
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/exceptions/EmployeeNotFoundException.java b/src/main/java/com/zoomcare/candidatechallenge/exceptions/EmployeeNotFoundException.java
new file mode 100644
index 0000000..863e8c5
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/exceptions/EmployeeNotFoundException.java
@@ -0,0 +1,11 @@
+package com.zoomcare.candidatechallenge.exceptions;
+
+public class EmployeeNotFoundException extends RuntimeException {
+
+ public EmployeeNotFoundException(Long id) {
+ super("Could not find employee with id: " + id);
+ }
+
+ }
+
+
diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java
new file mode 100644
index 0000000..fa6de52
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java
@@ -0,0 +1,40 @@
+package com.zoomcare.candidatechallenge.model;
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+@Data
+@AllArgsConstructor
+@Entity
+@Table(name = "EMPLOYEE")
+public class Employee {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "SUPERVISOR_ID")
+ private Employee supervisor;
+
+ @OneToMany(mappedBy = "supervisor", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ private List reports = new ArrayList<>();
+
+ @OneToMany(mappedBy = "employee", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ private List properties = new ArrayList<>();
+
+ public Employee() {
+
+ }
+
+ public Employee(long l, Object o, Object o1) {
+ }
+
+ public Employee(long l) {
+ }
+
+ // Getters and setters
+}
\ No newline at end of file
diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java b/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java
new file mode 100644
index 0000000..b1c8a2a
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/model/Properties.java
@@ -0,0 +1,40 @@
+package com.zoomcare.candidatechallenge.model;
+
+
+import jakarta.persistence.*;
+import lombok.Data;
+
+@Data
+@Entity
+@Table(name = "PROPERTIES")
+public class Properties {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "EMPLOYEE_ID")
+ private Employee employee;
+
+
+
+ @Column(name = "SUPERVISOR_ID")
+ private Long supervisorId;
+ @Column(name = "property_key")
+ private String key;
+ @Column(name = "property_value")
+ private String value;
+
+ public Properties(String key1, String value1) {
+ }
+
+ public Properties() {
+
+ }
+
+
+ // Getters and setters
+
+}
+
diff --git a/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java
new file mode 100644
index 0000000..48d6be1
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java
@@ -0,0 +1,14 @@
+package com.zoomcare.candidatechallenge.repository;
+
+import com.zoomcare.candidatechallenge.model.Employee;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface EmployeeRepository extends JpaRepository {
+
+ List findBySupervisorIsNull();
+
+}
diff --git a/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java
new file mode 100644
index 0000000..2b1e396
--- /dev/null
+++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java
@@ -0,0 +1,68 @@
+package com.zoomcare.candidatechallenge.service;
+
+import com.zoomcare.candidatechallenge.dto.EmployeeDTO;
+import com.zoomcare.candidatechallenge.dto.PropertiesDTO;
+import com.zoomcare.candidatechallenge.exceptions.EmployeeNotFoundException;
+import com.zoomcare.candidatechallenge.model.Employee;
+import com.zoomcare.candidatechallenge.model.Properties;
+import com.zoomcare.candidatechallenge.repository.EmployeeRepository;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class EmployeeService {
+
+ private final EmployeeRepository employeeRepository;
+
+ public EmployeeService(EmployeeRepository employeeRepository) {
+ this.employeeRepository = employeeRepository;
+ }
+
+ public List getTopLevelEmployees() {
+ List topLevelEmployees = employeeRepository.findBySupervisorIsNull();
+ return convertToDTO(topLevelEmployees);
+ }
+
+ public EmployeeDTO getEmployee(Long id) {
+ Optional employee = employeeRepository.findById(id);
+ if (employee.isPresent()) {
+ return convertToDTO(employee.get());
+ } else {
+ throw new EmployeeNotFoundException(id);
+ }
+ }
+
+ private List convertToDTO(List employees) {
+ List employeeDTOs = new ArrayList<>();
+ for (Employee employee : employees) {
+ employeeDTOs.add(convertToDTO(employee));
+ }
+ return employeeDTOs;
+ }
+
+ public EmployeeDTO convertToDTO(Employee employee) {
+ EmployeeDTO employeeDTO = new EmployeeDTO();
+ employeeDTO.setId(employee.getId());
+ employeeDTO.setProperties(convertPropertiesToDTO(employee.getProperties()));
+ employeeDTO.setReports((List) convertToDTO(employee.getReports()));
+ return employeeDTO;
+ }
+
+ public List convertPropertiesToDTO(List properties) {
+ List propertiesDTOs = new ArrayList<>();
+ for (Properties property : properties) {
+ propertiesDTOs.add(convertToDTO(property));
+ }
+ return propertiesDTOs;
+ }
+
+ private PropertiesDTO convertToDTO(Properties property) {
+ PropertiesDTO propertiesDTO = new PropertiesDTO();
+ propertiesDTO.setKey(property.getKey());
+ propertiesDTO.setValue(property.getValue());
+ return propertiesDTO;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
new file mode 100644
index 0000000..83cae53
--- /dev/null
+++ b/src/main/resources/application.properties
@@ -0,0 +1,14 @@
+server.port=8890
+spring.datasource.url=jdbc:h2:mem:testdb
+spring.datasource.driverClassName=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.show-sql=true
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
+spring.jpa.hibernate.ddl-auto=create-drop
+spring.jpa.defer-datasource-initialization=true
+
+spring.h2.console.enabled=true
+spring.h2.console.path=/h2-console
+
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
deleted file mode 100644
index 4408d17..0000000
--- a/src/main/resources/application.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-spring:
- h2:
- console:
- enabled: true
-management:
- endpoints:
- web:
- exposure:
- include: "*"
- endpoint:
- health:
- show-details: always
\ No newline at end of file
diff --git a/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeControllerTest.java b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeControllerTest.java
new file mode 100644
index 0000000..717a122
--- /dev/null
+++ b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeControllerTest.java
@@ -0,0 +1,91 @@
+package com.example.zoomcarecodechallenge;
+
+import com.example.zoomcarecodechallenge.controller.EmployeeController;
+import com.example.zoomcarecodechallenge.dto.EmployeeDTO;
+import com.example.zoomcarecodechallenge.dto.PropertiesDTO;
+import com.example.zoomcarecodechallenge.model.Employee;
+import com.example.zoomcarecodechallenge.model.Properties;
+import com.example.zoomcarecodechallenge.repository.EmployeeRepository;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.hamcrest.MockitoHamcrest;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.RequestBuilder;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.web.server.ResponseStatusException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.hamcrest.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import com.example.zoomcarecodechallenge.dto.EmployeeDTO;
+import com.example.zoomcarecodechallenge.service.EmployeeService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class EmployeeControllerTest {
+
+ @Mock
+ private EmployeeService employeeService;
+ @InjectMocks
+ private EmployeeController employeeController;
+
+ @Test
+ public void testGetTopLevelEmployees() {
+ EmployeeDTO employeeDTO1 = new EmployeeDTO();
+ employeeDTO1.setId(1L);
+
+ EmployeeDTO employeeDTO2 = new EmployeeDTO();
+ employeeDTO2.setId(2L);
+
+ List expectedEmployees = Arrays.asList(employeeDTO1, employeeDTO2);
+
+ when(employeeService.getTopLevelEmployees())
+ .thenReturn(expectedEmployees);
+
+ ResponseEntity> responseEntity = employeeController.getTopLevelEmployees();
+
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertEquals(expectedEmployees, responseEntity.getBody());
+ }
+
+ @Test
+ public void testGetEmployee() {
+ EmployeeDTO expectedEmployee = new EmployeeDTO();
+ expectedEmployee.setId(1L);
+
+ when(employeeService.getEmployee(1L))
+ .thenReturn(expectedEmployee);
+
+ ResponseEntity responseEntity = employeeController.getEmployee(1L);
+
+ assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
+ assertEquals(expectedEmployee, responseEntity.getBody());
+ }
+}
diff --git a/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeServiceTest.java b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeServiceTest.java
new file mode 100644
index 0000000..cece2f0
--- /dev/null
+++ b/src/main/test/java/com/example/zoomcarecodechallenge/EmployeeServiceTest.java
@@ -0,0 +1,130 @@
+package com.example.zoomcarecodechallenge;
+
+import com.example.zoomcarecodechallenge.dto.EmployeeDTO;
+import com.example.zoomcarecodechallenge.dto.PropertiesDTO;
+import com.example.zoomcarecodechallenge.exceptions.EmployeeNotFoundException;
+import com.example.zoomcarecodechallenge.model.Employee;
+import com.example.zoomcarecodechallenge.model.Properties;
+import com.example.zoomcarecodechallenge.repository.EmployeeRepository;
+import com.example.zoomcarecodechallenge.service.EmployeeService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+
+public class EmployeeServiceTest {
+
+ @Mock
+ private EmployeeRepository employeeRepository;
+
+ private EmployeeService employeeService;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+ employeeService = new EmployeeService(employeeRepository);
+ }
+
+ @Test
+ public void testGetTopLevelEmployees() {
+ Employee employee1 = new Employee();
+ employee1.setId(1L);
+ employee1.setSupervisor(null);
+ employee1.setReports(Arrays.asList(new Employee(), new Employee()));
+
+ Employee employee2 = new Employee();
+ employee2.setId(2L);
+ employee2.setSupervisor(null);
+ employee2.setReports(Arrays.asList(new Employee()));
+
+ when(employeeRepository.findBySupervisorIsNull())
+ .thenReturn(Arrays.asList(employee1, employee2));
+
+ List topLevelEmployees = employeeService.getTopLevelEmployees();
+
+ assertEquals(2, topLevelEmployees.size());
+ assertEquals(1L, topLevelEmployees.get(0).getId());
+ assertEquals(2L, topLevelEmployees.get(1).getId());
+ }
+
+ @Test
+ public void testGetEmployee() {
+ Employee employee = new Employee();
+ employee.setId(1L);
+ employee.setSupervisor(new Employee());
+ employee.setReports(new ArrayList<>());
+ employee.setProperties(Arrays.asList(new Properties(), new Properties()));
+
+ when(employeeRepository.findById(1L))
+ .thenReturn(Optional.of(employee));
+
+ EmployeeDTO employeeDTO = employeeService.getEmployee(1L);
+
+ assertEquals(1L, employeeDTO.getId());
+ assertEquals(2, employeeDTO.getProperties().size());
+ }
+
+ @Test
+ public void testGetEmployeeNotFound() {
+ when(employeeRepository.findById(1L))
+ .thenReturn(Optional.empty());
+
+ assertThrows(EmployeeNotFoundException.class, () -> employeeService.getEmployee(1L));
+ }
+
+ @Test
+ public void testConvertToDTO() {
+ Employee employee = new Employee();
+ employee.setId(1L);
+ employee.setSupervisor(new Employee());
+ employee.setReports(new ArrayList<>());
+ employee.setProperties(Arrays.asList(new Properties(), new Properties()));
+
+ EmployeeDTO employeeDTO = employeeService.convertToDTO(employee);
+
+ assertEquals(1L, employeeDTO.getId());
+ assertEquals(2, employeeDTO.getProperties().size());
+ }
+
+ @Test
+ public void testConvertPropertiesToDTO() {
+ Properties property1 = new Properties();
+ property1.setKey("key1");
+ property1.setValue("value1");
+
+ Properties property2 = new Properties();
+ property2.setKey("key2");
+ property2.setValue("value2");
+
+ List properties = Arrays.asList(property1, property2);
+
+ List propertiesDTOs = employeeService.convertPropertiesToDTO(properties);
+
+ assertEquals(2, propertiesDTOs.size());
+ assertEquals("key1", propertiesDTOs.get(0).getKey());
+ assertEquals("value2", propertiesDTOs.get(1).getValue());
+ }
+
+ @Test
+ public void testConvertToDTOWithNullSupervisor() {
+ Employee employee = new Employee();
+ employee.setId(1L);
+ employee.setSupervisor(null);
+ employee.setReports(Arrays.asList(new Employee(), new Employee()));
+ employee.setProperties(Arrays.asList(new Properties(), new Properties()));
+
+ EmployeeDTO employeeDTO = employeeService.convertToDTO(employee);
+
+ assertEquals(1L, employeeDTO.getId());
+ assertEquals(2, employeeDTO.getProperties().size());
+ assertEquals(2, employeeDTO.getReports().size());}
+}
+