From ed320ff16395dbf92ba758f73a16e14a28f1bd0b Mon Sep 17 00:00:00 2001 From: andresgomez Date: Wed, 5 Jul 2023 22:22:07 -0500 Subject: [PATCH 1/3] Adding getEmployeeById endpoint --- pom.xml | 23 +++++++ .../config/CandidateChallengeConfig.java | 20 ++++++ .../controller/EmployeeController.java | 27 ++++++++ .../candidatechallenge/dto/EmployeeDTO.java | 14 +++++ .../candidatechallenge/dto/PropertyDTO.java | 11 ++++ .../candidatechallenge/dto/PropertyPKDTO.java | 10 +++ .../candidatechallenge/model/Employee.java | 61 +++++++++++++++++++ .../candidatechallenge/model/Property.java | 28 +++++++++ .../candidatechallenge/model/PropertyPK.java | 30 +++++++++ .../repository/EmployeeRepository.java | 11 ++++ .../service/EmployeeService.java | 28 +++++++++ src/main/resources/application.yml | 2 + 12 files changed, 265 insertions(+) create mode 100644 src/main/java/com/zoomcare/candidatechallenge/config/CandidateChallengeConfig.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/dto/PropertyDTO.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/dto/PropertyPKDTO.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/model/Employee.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/model/Property.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java create mode 100644 src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java diff --git a/pom.xml b/pom.xml index 17fd7ad..351e267 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-data-jpa + org.flywaydb @@ -49,6 +53,25 @@ spring-boot-starter-test test + + + org.modelmapper + modelmapper + 3.1.1 + + + + org.projectlombok + lombok + 1.18.28 + provided + + + + org.springframework.boot + spring-boot-devtools + + diff --git a/src/main/java/com/zoomcare/candidatechallenge/config/CandidateChallengeConfig.java b/src/main/java/com/zoomcare/candidatechallenge/config/CandidateChallengeConfig.java new file mode 100644 index 0000000..5369d8b --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/config/CandidateChallengeConfig.java @@ -0,0 +1,20 @@ +package com.zoomcare.candidatechallenge.config; + +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import com.zoomcare.candidatechallenge.service.EmployeeService; +import org.modelmapper.ModelMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CandidateChallengeConfig { + @Bean + public ModelMapper modelMapper() { + return new ModelMapper(); + } + + @Bean + public EmployeeService employeeService(ModelMapper modelMapper, EmployeeRepository employeeRepository) { + return new EmployeeService(employeeRepository, modelMapper); + } +} 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..fa4923b --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java @@ -0,0 +1,27 @@ +package com.zoomcare.candidatechallenge.controller; + +import com.zoomcare.candidatechallenge.dto.EmployeeDTO; +import com.zoomcare.candidatechallenge.service.EmployeeService; +import java.util.Optional; +import org.springframework.beans.factory.annotation.Autowired; +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; + +@RestController +@RequestMapping("/api/employee") +public class EmployeeController { + @Autowired + private EmployeeService employeeService; + + @GetMapping("/{id}") + ResponseEntity getById(@PathVariable Long id) { + Optional employeeDTO = employeeService.findById(id); + if (!employeeDTO.isPresent()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(employeeDTO.get()); + } +} 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..8a7535a --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java @@ -0,0 +1,14 @@ +package com.zoomcare.candidatechallenge.dto; + +import java.util.List; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class EmployeeDTO { + private Long id; + private Long supervisorId; + private List properties; + private List directReports; +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/PropertyDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertyDTO.java new file mode 100644 index 0000000..7dfbc00 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertyDTO.java @@ -0,0 +1,11 @@ +package com.zoomcare.candidatechallenge.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class PropertyDTO { + private PropertyPKDTO id; + private String value; +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/PropertyPKDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertyPKDTO.java new file mode 100644 index 0000000..fcda728 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/dto/PropertyPKDTO.java @@ -0,0 +1,10 @@ +package com.zoomcare.candidatechallenge.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class PropertyPKDTO { + private String key; +} 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..f93687d --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java @@ -0,0 +1,61 @@ +package com.zoomcare.candidatechallenge.model; + +import java.io.Serializable; +import java.util.List; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToMany; + +@Entity() +public class Employee implements Serializable { + @Id + @GeneratedValue + private Long id; + + @Column(name = "supervisor_id") + private Long supervisorId; + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "employee_id") + private List properties; + + @OneToMany(fetch = FetchType.LAZY) + @JoinColumn(name = "supervisor_id") + private List directReports; + + public Long getSupervisorId() { + return supervisorId; + } + + public void setSupervisorId(Long supervisorId) { + this.supervisorId = supervisorId; + } + + public List getDirectReports() { + return directReports; + } + + public void setDirectReports(List directReports) { + this.directReports = directReports; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Property.java b/src/main/java/com/zoomcare/candidatechallenge/model/Property.java new file mode 100644 index 0000000..0a78611 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Property.java @@ -0,0 +1,28 @@ +package com.zoomcare.candidatechallenge.model; + +import java.io.Serializable; +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; + +@Entity +public class Property implements Serializable { + @EmbeddedId + private PropertyPK id; + private String value; + + public PropertyPK getId() { + return id; + } + + public void setId(PropertyPK id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java b/src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java new file mode 100644 index 0000000..2b09747 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java @@ -0,0 +1,30 @@ +package com.zoomcare.candidatechallenge.model; + +import java.io.Serializable; +import javax.persistence.Column; +import javax.persistence.Embeddable; + +@Embeddable +public class PropertyPK implements Serializable { + + private String key; + + @Column(name = "employee_id") + private Long employeeId; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Long getEmployeeId() { + return employeeId; + } + + public void setEmployeeId(Long employeeId) { + this.employeeId = employeeId; + } +} 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..8694f0a --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/repository/EmployeeRepository.java @@ -0,0 +1,11 @@ +package com.zoomcare.candidatechallenge.repository; + +import com.zoomcare.candidatechallenge.model.Employee; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface EmployeeRepository extends JpaRepository { + Optional findById(Long id); + List findAllBySupervisorIdIsNull(); +} 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..48f4ef9 --- /dev/null +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java @@ -0,0 +1,28 @@ +package com.zoomcare.candidatechallenge.service; + +import com.zoomcare.candidatechallenge.dto.EmployeeDTO; +import com.zoomcare.candidatechallenge.model.Employee; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import java.util.Optional; +import org.modelmapper.ModelMapper; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeService { + private final EmployeeRepository employeeRepository; + private final ModelMapper modelMapper; + + public EmployeeService(EmployeeRepository employeeRepository, ModelMapper modelMapper) { + this.employeeRepository = employeeRepository; + this.modelMapper = modelMapper; + } + + public Optional findById(Long id) { + return employeeRepository.findById(id).map(this::convertToDto); + } + + public EmployeeDTO convertToDto(Employee employee) { + return modelMapper.map(employee, EmployeeDTO.class); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4408d17..69ee7c7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,6 @@ spring: + main: + allow-bean-definition-overriding: true h2: console: enabled: true From 37e839fc10eac975d9abb2006458048a17691e43 Mon Sep 17 00:00:00 2001 From: andresgomez Date: Sun, 9 Jul 2023 15:34:51 -0500 Subject: [PATCH 2/3] Adding topLevel employee endpoint --- .../controller/EmployeeController.java | 12 +++++++++++- .../candidatechallenge/service/EmployeeService.java | 8 ++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java index fa4923b..04b26bc 100644 --- a/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java +++ b/src/main/java/com/zoomcare/candidatechallenge/controller/EmployeeController.java @@ -2,6 +2,7 @@ import com.zoomcare.candidatechallenge.dto.EmployeeDTO; import com.zoomcare.candidatechallenge.service.EmployeeService; +import java.util.List; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -11,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/api/employee") +@RequestMapping("/api/employees") public class EmployeeController { @Autowired private EmployeeService employeeService; @@ -24,4 +25,13 @@ ResponseEntity getById(@PathVariable Long id) { } return ResponseEntity.ok(employeeDTO.get()); } + + @GetMapping("/topLevel") + ResponseEntity> getTopLevelEmployees() { + List employeeDTOs = employeeService.getTopLevelEmployee(); + if (employeeDTOs.isEmpty()) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.ok(employeeDTOs); + } } diff --git a/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java index 48f4ef9..a2c1818 100644 --- a/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java +++ b/src/main/java/com/zoomcare/candidatechallenge/service/EmployeeService.java @@ -3,7 +3,9 @@ import com.zoomcare.candidatechallenge.dto.EmployeeDTO; import com.zoomcare.candidatechallenge.model.Employee; import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; @@ -21,6 +23,12 @@ public Optional findById(Long id) { return employeeRepository.findById(id).map(this::convertToDto); } + public List getTopLevelEmployee() { + return employeeRepository.findAllBySupervisorIdIsNull().stream() + .map(this::convertToDto) + .collect(Collectors.toList()); + } + public EmployeeDTO convertToDto(Employee employee) { return modelMapper.map(employee, EmployeeDTO.class); } From 671b6c712086e7129e632266b9dae2f35ca677a5 Mon Sep 17 00:00:00 2001 From: andresgomez Date: Sun, 9 Jul 2023 17:07:12 -0500 Subject: [PATCH 3/3] Adding unit testing --- .../candidatechallenge/dto/EmployeeDTO.java | 4 + .../candidatechallenge/model/Employee.java | 8 +- .../candidatechallenge/model/Property.java | 2 + .../candidatechallenge/model/PropertyPK.java | 2 + .../service/EmployeeServiceTest.java | 110 ++++++++++++++++++ 5 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceTest.java diff --git a/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java index 8a7535a..3eeaf1a 100644 --- a/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java +++ b/src/main/java/com/zoomcare/candidatechallenge/dto/EmployeeDTO.java @@ -1,11 +1,15 @@ package com.zoomcare.candidatechallenge.dto; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data +@AllArgsConstructor @NoArgsConstructor +@Builder public class EmployeeDTO { private Long id; private Long supervisorId; diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java index f93687d..efa3a91 100644 --- a/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Employee.java @@ -9,8 +9,10 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; +import lombok.Builder; -@Entity() +@Builder +@Entity public class Employee implements Serializable { @Id @GeneratedValue @@ -47,10 +49,6 @@ public Long getId() { return id; } - public void setId(Long id) { - this.id = id; - } - public List getProperties() { return properties; } diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/Property.java b/src/main/java/com/zoomcare/candidatechallenge/model/Property.java index 0a78611..d93a408 100644 --- a/src/main/java/com/zoomcare/candidatechallenge/model/Property.java +++ b/src/main/java/com/zoomcare/candidatechallenge/model/Property.java @@ -3,8 +3,10 @@ import java.io.Serializable; import javax.persistence.EmbeddedId; import javax.persistence.Entity; +import lombok.Builder; @Entity +@Builder public class Property implements Serializable { @EmbeddedId private PropertyPK id; diff --git a/src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java b/src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java index 2b09747..db193e8 100644 --- a/src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java +++ b/src/main/java/com/zoomcare/candidatechallenge/model/PropertyPK.java @@ -3,8 +3,10 @@ import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Embeddable; +import lombok.Builder; @Embeddable +@Builder public class PropertyPK implements Serializable { private String key; diff --git a/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceTest.java b/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceTest.java new file mode 100644 index 0000000..b31af1c --- /dev/null +++ b/src/test/java/com/zoomcare/candidatechallenge/service/EmployeeServiceTest.java @@ -0,0 +1,110 @@ +package com.zoomcare.candidatechallenge.service; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import com.zoomcare.candidatechallenge.dto.EmployeeDTO; +import com.zoomcare.candidatechallenge.model.Employee; +import com.zoomcare.candidatechallenge.model.Property; +import com.zoomcare.candidatechallenge.model.PropertyPK; +import com.zoomcare.candidatechallenge.repository.EmployeeRepository; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.modelmapper.ModelMapper; + +@RunWith(MockitoJUnitRunner.class) +public class EmployeeServiceTest { + private final Long EMPLOYEE_ID = 2L; + private final Long SUPERVISOR_ID = 1L; + private final String PROPERTY_KEY = "key"; + private Employee employee; + private Employee supervisorEmployee; + + @Mock + private EmployeeRepository employeeRepository; + + private PropertyPK propertyPK; + private Property property; + private ModelMapper modelMapper; + + private EmployeeService employeeService; + + @Before + public void setUp() { + modelMapper = new ModelMapper(); + employeeService = new EmployeeService(employeeRepository, modelMapper); + + propertyPK = PropertyPK.builder() + .employeeId(EMPLOYEE_ID) + .key(PROPERTY_KEY) + .build(); + + property = Property.builder() + .id(propertyPK) + .value("value") + .build(); + + employee = Employee.builder() + .id(EMPLOYEE_ID) + .supervisorId(SUPERVISOR_ID) + .properties(Arrays.asList(property)) + .build(); + + supervisorEmployee = Employee.builder() + .id(1l) + .directReports(Arrays.asList(employee)) + .build(); + + } + + @Test + public void findByIdFindsEmployeeTest() { + when(employeeRepository.findById(EMPLOYEE_ID)).thenReturn(Optional.of(employee)); + + Optional result = employeeService.findById(EMPLOYEE_ID); + + assertTrue(result.isPresent()); + EmployeeDTO employeeDTO = result.get(); + assertEquals(EMPLOYEE_ID, employeeDTO.getId()); + assertEquals(propertyPK.getKey(), employeeDTO.getProperties().get(0).getId().getKey()); + assertEquals(property.getValue(), employeeDTO.getProperties().get(0).getValue()); + assertEquals(employee.getSupervisorId(), employeeDTO.getSupervisorId()); + assertEquals(employee.getProperties().get(0).getValue(), employeeDTO.getProperties().get(0).getValue()); + } + + @Test + public void findByIdEmployeeNotFoundTest() { + Optional result = employeeService.findById(3L); + + assertFalse(result.isPresent()); + } + + @Test + public void getTopLevelEmployeeFoundTest() { + when(employeeRepository.findAllBySupervisorIdIsNull()).thenReturn(Arrays.asList(supervisorEmployee)); + + List supervisors = employeeService.getTopLevelEmployee(); + + assertFalse(supervisors.isEmpty()); + EmployeeDTO supervisor = supervisors.get(0); + assertEquals(SUPERVISOR_ID, supervisor.getId()); + assertEquals(supervisorEmployee.getSupervisorId(), supervisor.getSupervisorId()); + assertEquals(supervisorEmployee.getDirectReports().size(), supervisor.getDirectReports().size()); + assertEquals(EMPLOYEE_ID, supervisor.getDirectReports().get(0).getId()); + } + + @Test + public void getTopLevelEmployeeNotFoundTest() { + List supervisors = employeeService.getTopLevelEmployee(); + + assertTrue(supervisors.isEmpty()); + } +} \ No newline at end of file