Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
<version>2.6.7</version>
<relativePath/>
</parent>

<groupId>com.zoomcare</groupId>
<artifactId>java-candidate-challenge</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.1</version>
<name>candidate-challenge</name>
<description>ZoomCare Candidate Code Challenge</description>
<description>Habib De León Baños - ZoomCare Candidate Code Challenge</description>

<properties>
<java.version>1.8</java.version>
<java.version>11</java.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>

<dependencies>
Expand All @@ -25,7 +28,7 @@
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
Expand All @@ -49,6 +52,19 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.12</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.zoomcare.candidatechallenge.controller;

import com.zoomcare.candidatechallenge.exception.DataNotFoundException;
import com.zoomcare.candidatechallenge.exception.UnexpectedException;
import com.zoomcare.candidatechallenge.model.dto.CommonResponse;
import com.zoomcare.candidatechallenge.model.enums.ResponseCode;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class ControllerExceptionHandler {

@ExceptionHandler({Exception.class})
public ResponseEntity<CommonResponse> handleException(Exception exception) {

if (exception instanceof DataNotFoundException) {
return handleDataNotFound((DataNotFoundException) exception);
} else if (exception instanceof UnexpectedException) {
return handleUnexpectedException((UnexpectedException) exception);
} else {
return handleUnexpectedException(new UnexpectedException("Something went wrong", exception));
}
}

private ResponseEntity<CommonResponse> handleDataNotFound(DataNotFoundException exception) {
return createBasicResponse(ResponseCode.DATA_NOT_FOUND, exception, HttpStatus.NOT_FOUND);
}

private ResponseEntity<CommonResponse> handleUnexpectedException(UnexpectedException exception) {
return createBasicResponse(ResponseCode.UNEXPECTED_ERROR, exception, HttpStatus.INTERNAL_SERVER_ERROR);
}

private ResponseEntity<CommonResponse> createBasicResponse(ResponseCode code, Exception exception,
HttpStatus status) {
return new ResponseEntity<>(new CommonResponse(code, exception.getMessage()), status);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.zoomcare.candidatechallenge.controller;

import com.zoomcare.candidatechallenge.model.dto.CommonResponse;
import com.zoomcare.candidatechallenge.model.dto.EmployeeDto;
import com.zoomcare.candidatechallenge.model.entity.Employee;
import com.zoomcare.candidatechallenge.model.enums.ResponseCode;
import com.zoomcare.candidatechallenge.service.EmployeeService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.util.stream.Collectors;

@RestController
@RequestMapping("employees")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class EmployeeController {

private final EmployeeService employeeService;

@GetMapping("/top-level")
@Operation(summary = "Get the nested organization structure starting from the top level employees")
public ResponseEntity<CommonResponse> getAllTopLevelEmployees() {
final List<Employee> employees = employeeService.getAllTopLevelEmployees();

final List<EmployeeDto> result = employees.stream().map(e -> employeeToDto(e)).collect(Collectors.toList());

return new ResponseEntity<>(
new CommonResponse(
ResponseCode.OPERATION_SUCCESSFUL, "Nested organization structure", result
), HttpStatus.OK
);
}

@GetMapping("/{employeeId}")
@Operation(summary = "Get the nested organization structure starting from the specified employee")
public ResponseEntity<CommonResponse> getEmployeeById(@PathVariable("employeeId") Long employeeId) {
final Employee employee = employeeService.findById(employeeId);

final EmployeeDto result = employeeToDto(employee);

return new ResponseEntity<>(
new CommonResponse(
ResponseCode.OPERATION_SUCCESSFUL, "Found employee with id " + employeeId, result
), HttpStatus.OK
);
}


private EmployeeDto employeeToDto(Employee employee) {
if (employee == null) {
return null;
}

final EmployeeDto employeeDto = new EmployeeDto();

final Employee supervisor = employee.getSupervisor();
final Long supervisorId;
if (supervisor != null) {
supervisorId = supervisor.getId();
} else {
supervisorId = null;
}

employeeDto.setSupervisorId(supervisorId);
employeeDto.setEmployeeId(employee.getId());
employeeDto.setProperties(employee.getProperties());

final List<Employee> directReports = employee.getDirectReports();
final List<EmployeeDto> directReportsDtos;

if (directReports != null && !directReports.isEmpty()) {
directReportsDtos = directReports.stream().map(dr -> employeeToDto(dr)).collect(Collectors.toList());
} else {
directReportsDtos = null;
}

employeeDto.setDirectReports(directReportsDtos);

return employeeDto;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.zoomcare.candidatechallenge.exception;

public abstract class BusinessException extends RuntimeException {

public BusinessException() {
}

public BusinessException(String message) {
super(message);
}

public BusinessException(Throwable cause) {
super(cause);
}

public BusinessException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.zoomcare.candidatechallenge.exception;

public class DataNotFoundException extends BusinessException {

public DataNotFoundException() {
}

public DataNotFoundException(String message) {
super(message);
}

public DataNotFoundException(Throwable cause) {
super(cause);
}

public DataNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.zoomcare.candidatechallenge.exception;

public class UnexpectedException extends BusinessException{

public UnexpectedException() {
}

public UnexpectedException(String message) {
super(message);
}

public UnexpectedException(Throwable cause) {
super(cause);
}

public UnexpectedException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.zoomcare.candidatechallenge.model.dto;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.zoomcare.candidatechallenge.model.enums.ResponseCode;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@JsonPropertyOrder(value = {"code", "message", "data"})
public class CommonResponse<T> {
private final ResponseCode code;
private final String message;
private final T data;

public CommonResponse(ResponseCode code, String message) {
this.code = code;
this.message = message;
this.data = null;
}

@JsonGetter("code")
public String getJsonCode() {
return code.toString();
}

@JsonIgnore
public ResponseCode getCode() {
return code;
}

public String getMessage() {
return message;
}

public T getData() {
return data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.zoomcare.candidatechallenge.model.dto;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data;

import java.util.List;
import java.util.Map;

@Data
@JsonPropertyOrder(value = {"employeeId", "supervisorId", "properties", "directReports"})
public class EmployeeDto {

private Long employeeId;
private Long supervisorId;
private Map<String, String> properties;
private List<EmployeeDto> directReports;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.zoomcare.candidatechallenge.model.entity;

import lombok.Data;

import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.OneToMany;
import java.util.List;
import java.util.Map;

@Entity
@Data
public class Employee {

@Id
@GeneratedValue
private Long id;

@ManyToOne
@JoinColumn(name = "supervisor_id")
private Employee supervisor;

@ElementCollection
@MapKeyColumn(name = "key")
@Column(name = "value")
@CollectionTable
(
name = "property",
joinColumns = {@JoinColumn(name = "employee_id", referencedColumnName = "id")}
)
private Map<String, String> properties;

@OneToMany(fetch = FetchType.LAZY, mappedBy = "supervisor")
private List<Employee> directReports;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.zoomcare.candidatechallenge.model.enums;

public enum ResponseCode {
OPERATION_SUCCESSFUL("challenge-0001"),
UNEXPECTED_ERROR("challenge-0002"),
DATA_NOT_FOUND("challenge-0003");

private final String code;

ResponseCode(String code) {
this.code = code;
}

@Override
public String toString() {
return code;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.zoomcare.candidatechallenge.repository;

import com.zoomcare.candidatechallenge.model.entity.Employee;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface EmployeeRepository extends CrudRepository<Employee, Long> {

@Query("SELECT e FROM Employee e WHERE e.supervisor IS NULL")
List<Employee> getAllTopLevelEmployees();
}
Loading