diff --git a/pom.xml b/pom.xml index 17fd7ad..f2ff7f1 100644 --- a/pom.xml +++ b/pom.xml @@ -23,10 +23,20 @@ org.springframework.boot spring-boot-starter-actuator + + + + org.springframework.boot - spring-boot-starter-data-jdbc + spring-boot-starter-data-jpa + + org.projectlombok + lombok + provided + + org.springframework.boot spring-boot-starter-web 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..e2c792b --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java @@ -0,0 +1,24 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.model.EmployeeWithReports; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +public interface EmployeeController { + + /** + * Returns a list of all top-level employees. + * + * @return the list of employees + */ + ResponseEntity> getAll(); + + /** + * Returns the employee with the given ID or a 404 response if no such employee exists. + * + * @param id the employee ID + * @return the employee + */ + ResponseEntity getById(Long id); +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeControllerImpl.java b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeControllerImpl.java new file mode 100644 index 0000000..a1232a3 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeControllerImpl.java @@ -0,0 +1,35 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.model.EmployeeWithReports; +import com.zoomcare.candidatechallenge.service.EmployeeService; +import lombok.AllArgsConstructor; +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; +import java.util.Optional; + + +@RestController +@RequestMapping("/employees") +@AllArgsConstructor +public class EmployeeControllerImpl implements EmployeeController { + + private EmployeeService employeeService; + + @GetMapping("/all") + public ResponseEntity> getAll() { + return ResponseEntity.ok(employeeService.getAll()); + } + + @GetMapping("/id/{id}") + public ResponseEntity getById(@PathVariable Long id) { + return Optional.ofNullable(employeeService.getById(id)) + .map(ResponseEntity::ok) // if exists, return 200 with body + .orElse(ResponseEntity.notFound().build()); // otherwise return a 404 + } +} + 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..729a22e --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java @@ -0,0 +1,23 @@ +package com.zoomcare.candidatechallenge.model; + +import lombok.Data; + +import javax.persistence.*; +import java.util.HashMap; +import java.util.Map; + +@Entity +@Data +public class Employee { + + @Id + public Long id; + + public Long supervisorId; + + @ElementCollection + @MapKeyColumn(name = "key") + @Column(name = "value") + @CollectionTable(name = "property", joinColumns = @JoinColumn(name = "employee_id")) + public Map properties = new HashMap<>(); +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/EmployeeWithReports.java b/src/main/java/com/zoomcare/candidatechallenge/model/EmployeeWithReports.java new file mode 100644 index 0000000..66cdc50 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/EmployeeWithReports.java @@ -0,0 +1,18 @@ +package com.zoomcare.candidatechallenge.model; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class EmployeeWithReports { + + private Long id; + + private Long supervisorId; + + private Map properties; + + private List reports; +} 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..a96ea75 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java @@ -0,0 +1,13 @@ +package com.zoomcare.candidatechallenge.repository; + +import com.zoomcare.candidatechallenge.model.Employee; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface EmployeeRepository extends JpaRepository { + + List findBySupervisorId(Long supervisorId); + + List findBySupervisorIdIsNull(); +} 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..3165b84 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java @@ -0,0 +1,24 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.model.EmployeeWithReports; +import org.springframework.web.bind.annotation.PathVariable; + +import java.util.List; + +public interface EmployeeService { + + /** + * Returns all top-level employees. + * + * @return the list of employees + */ + List getAll(); + + /** + * Returns the employee with the given ID, or null if no such employee exists. + * + * @param id the employee ID + * @return the employee + */ + EmployeeWithReports getById(@PathVariable Long id); +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImpl.java b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImpl.java new file mode 100644 index 0000000..c907d68 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeServiceImpl.java @@ -0,0 +1,65 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.model.Employee; +import com.zoomcare.candidatechallenge.model.EmployeeWithReports; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.PathVariable; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@AllArgsConstructor +public class EmployeeServiceImpl implements EmployeeService { + + private EmployeeRepository employeeRepository; + + public List getAll() { + return employeeRepository + .findBySupervisorIdIsNull() + .stream() + .map(this::getEmployeeWithReports) + .collect(Collectors.toList()); + } + + public EmployeeWithReports getById(@PathVariable Long id) { + if (!employeeRepository.existsById(id)) { + return null; + } + return employeeRepository + .findById(id) + .map(this::getEmployeeWithReports) + .orElse(null); + } + + /** + * Creates a new EmployeeWithReports object. + * + * @param employee the DAU instance to copy. + * @return an EmployeeWithReports object. + */ + private EmployeeWithReports getEmployeeWithReports(Employee employee) { + EmployeeWithReports result = new EmployeeWithReports(); + result.setId(employee.getId()); + result.setSupervisorId(employee.getSupervisorId()); + result.setProperties(employee.getProperties()); + result.setReports(collectReportsFor(employee.getId())); + return result; + } + + /** + * Recursively collects reports via findBySupervisorId. + * + * @param id the id of the superior employee. + * @return a list of reports. + */ + private List collectReportsFor(Long id) { + return employeeRepository + .findBySupervisorId(id) + .stream() + .map(this::getEmployeeWithReports) + .collect(Collectors.toList()); + } +}