diff --git a/pom.xml b/pom.xml index 04d3200..21a3a2d 100644 --- a/pom.xml +++ b/pom.xml @@ -22,6 +22,7 @@ + org.springframework.boot spring-boot-starter-web @@ -30,18 +31,12 @@ org.springframework.boot spring-boot-starter-data-jdbc - + - com.h2database - h2 - runtime + com.fasterxml.jackson.dataformat + jackson-dataformat-csv - - org.projectlombok - lombok - true - - + org.springdoc springdoc-openapi-ui @@ -52,7 +47,18 @@ jackson-databind-nullable 0.2.6 - + + + com.h2database + h2 + runtime + + + + org.projectlombok + lombok + true + @@ -60,6 +66,11 @@ spring-boot-starter-test test + + org.mockito + mockito-core + test + @@ -79,7 +90,7 @@ spring de.etecture.ga.api - de.etecture.ga.model + de.etecture.ga.dto ApiUtil.java diff --git a/src/main/java/de/etecture/ga/api/GarageApiController.java b/src/main/java/de/etecture/ga/api/GarageApiController.java new file mode 100644 index 0000000..6110bdc --- /dev/null +++ b/src/main/java/de/etecture/ga/api/GarageApiController.java @@ -0,0 +1,46 @@ +/** + * + */ +package de.etecture.ga.api; + +import java.util.List; + +import org.springframework.http.ResponseEntity; + +import de.etecture.ga.dto.Termin; +import de.etecture.ga.dto.TerminRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; + +/** + * + */ +public class GarageApiController implements WerkstattApi { + + @Override + public ResponseEntity getTermin(String werkstattId, String terminId) { + // TODO Auto-generated method stub + return WerkstattApi.super.getTermin(werkstattId, terminId); + } + + @Override + public ResponseEntity> getTermine(String werkstattId, @Valid String von, @Valid String bis, + @Valid String leistungsId) { + // TODO Auto-generated method stub + return WerkstattApi.super.getTermine(werkstattId, von, bis, leistungsId); + } + + @Override + public ResponseEntity> getTerminvorschlaege(String werkstattId, @NotNull @Valid String leistungsId, + @Valid String von, @Valid String bis) { + // TODO Auto-generated method stub + return WerkstattApi.super.getTerminvorschlaege(werkstattId, leistungsId, von, bis); + } + + @Override + public ResponseEntity postTermin(String werkstattId, @Valid TerminRequest termin) { + // TODO Auto-generated method stub + return WerkstattApi.super.postTermin(werkstattId, termin); + } + +} diff --git a/src/main/java/de/etecture/ga/config/Setup.java b/src/main/java/de/etecture/ga/config/Setup.java new file mode 100644 index 0000000..645b766 --- /dev/null +++ b/src/main/java/de/etecture/ga/config/Setup.java @@ -0,0 +1,29 @@ +package de.etecture.ga.config; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import de.etecture.ga.model.Garage; +import de.etecture.ga.service.GarageImportService; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Component +public class Setup { + + @Autowired + private GarageImportService importService; + + @PostConstruct + private void setupData() { + + List garages = importService.importGarageData(); + + // Daten in DB übertragen + + } + +} diff --git a/src/main/java/de/etecture/ga/model/Appointment.java b/src/main/java/de/etecture/ga/model/Appointment.java new file mode 100644 index 0000000..34d9559 --- /dev/null +++ b/src/main/java/de/etecture/ga/model/Appointment.java @@ -0,0 +1,25 @@ +package de.etecture.ga.model; + +import java.time.Duration; +import java.util.Date; + +import org.springframework.data.annotation.Id; + +import lombok.Data; + +@Data +public class Appointment { + + @Id + private Long id; + + private Garage garage; + + private String serviceCode; + + private String serviceName; + + private Date appointmentTime; + + private Duration duration; +} diff --git a/src/main/java/de/etecture/ga/model/Garage.java b/src/main/java/de/etecture/ga/model/Garage.java new file mode 100644 index 0000000..4afbed7 --- /dev/null +++ b/src/main/java/de/etecture/ga/model/Garage.java @@ -0,0 +1,29 @@ +package de.etecture.ga.model; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.annotation.Id; + +import lombok.Data; + +@Data +public class Garage { + + @Id + private Long id; + + private String name; + + private List appointments; + + public Garage addAppointment(Appointment appointment) { + + if(this.appointments == null) { + this.appointments = new ArrayList<>(); + } + + this.appointments.add(appointment); + return this; + } +} diff --git a/src/main/java/de/etecture/ga/model/MDService.java b/src/main/java/de/etecture/ga/model/MDService.java new file mode 100644 index 0000000..2981a4c --- /dev/null +++ b/src/main/java/de/etecture/ga/model/MDService.java @@ -0,0 +1,20 @@ +package de.etecture.ga.model; + +import java.time.Duration; + +import org.springframework.data.annotation.Id; + +import lombok.Data; + +@Data +public class MDService { + + @Id + private Long id; + + private String code; + + private String name; + + private Duration duration; +} diff --git a/src/main/java/de/etecture/ga/service/GarageImportService.java b/src/main/java/de/etecture/ga/service/GarageImportService.java new file mode 100644 index 0000000..f82a923 --- /dev/null +++ b/src/main/java/de/etecture/ga/service/GarageImportService.java @@ -0,0 +1,105 @@ +package de.etecture.ga.service; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import org.springframework.stereotype.Service; + +import com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.dataformat.csv.CsvMapper; +import com.fasterxml.jackson.dataformat.csv.CsvSchema; + +import de.etecture.ga.model.Appointment; +import de.etecture.ga.model.Garage; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +public class GarageImportService { + + private static final String IMPORT_FOLDER = "import"; + + + public List importGarageData() { + + List importData = new ArrayList<>(); + + try (Stream files = Files + .list(Paths.get(getClass().getClassLoader().getResource(IMPORT_FOLDER).toURI()))) { + + files.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".csv")) + .map(this::loadGarageData).mapMulti(Optional::ifPresent).forEach(importData::add); + + } catch (IOException | URISyntaxException e) { + log.error("Can't read file", e); + } + + return importData; + } + + public Optional loadGarageData(final Path file) { + + Garage garage = new Garage(); + garage.setName(getGarageNameFromFile(file)); + + List appointments = loadObjectsFromFile(file); + appointments.stream().map(data -> this.fromCSVData(data, garage)).filter(Objects::nonNull) + .forEach(garage::addAppointment); + + return Optional.of(garage); + } + + private String getGarageNameFromFile(final Path fileName) { + + String name = fileName.getFileName().toString(); + + int pos = name.lastIndexOf("."); + if (pos > 0 && pos < (name.length() - 1)) { + name = name.substring(0, pos); + } + + return name; + } + + private List loadObjectsFromFile(final Path file) { + + try { + CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader(); + CsvMapper mapper = new CsvMapper(); + MappingIterator readValues = mapper.readerFor(CSVData.class).with(bootstrapSchema) + .readValues(file.toFile()); + + return readValues.readAll(); + + } catch (Exception e) { + log.error("Error occurred while loading object list from file {}", file, e); + return Collections.emptyList(); + } + } + + private Appointment fromCSVData(CSVData data, Garage forGarage) { + + if (data == null) + return null; + + Appointment appointment = new Appointment(); + appointment.setGarage(forGarage); + appointment.setAppointmentTime(data.APP_DATE); + appointment.setServiceCode(data.SERVICE); + + return appointment; + } + + private record CSVData(Date APP_DATE, String SERVICE) { + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7aa7eca..4d495be 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,10 @@ spring.application.name=Garage appointment management + +# Datasource settings +spring.datasource.url=jdbc:h2:mem:etecture +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password + +# init DB by script +spring.sql.init.mode=always \ No newline at end of file diff --git a/src/main/resources/termine/autohaus-schmidt.csv b/src/main/resources/import/autohaus-schmidt.csv similarity index 100% rename from src/main/resources/termine/autohaus-schmidt.csv rename to src/main/resources/import/autohaus-schmidt.csv diff --git a/src/main/resources/termine/meisterbetrieb-bachstraße.csv b/src/main/resources/import/meisterbetrieb-bachstraße.csv similarity index 100% rename from src/main/resources/termine/meisterbetrieb-bachstraße.csv rename to src/main/resources/import/meisterbetrieb-bachstraße.csv diff --git a/src/test/java/de/etecture/ga/api/GarageApiControllerTest.java b/src/test/java/de/etecture/ga/api/GarageApiControllerTest.java new file mode 100644 index 0000000..6b27d58 --- /dev/null +++ b/src/test/java/de/etecture/ga/api/GarageApiControllerTest.java @@ -0,0 +1,53 @@ +package de.etecture.ga.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +/** + * Integrationtest for the {@link WerkstattApi} + */ +@SpringBootTest +@AutoConfigureMockMvc +class GarageApiControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void testGetTermin() throws Exception { + + MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.get("/werkstatt/{werkstattId}/termin/{terminId}", "1", "1")) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk()) +// .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("Hello World!!!")) + .andReturn(); + + assertEquals("application/json;charset=UTF-8", mvcResult.getResponse().getContentType()); + } + + @Test + void testGetTermine() { + fail("Not yet implemented"); + } + + @Test + void testGetTerminvorschlaege() { + fail("Not yet implemented"); + } + + @Test + void testPostTermin() { + fail("Not yet implemented"); + } + +} diff --git a/src/test/java/de/etecture/ga/service/GarageImportServiceTest.java b/src/test/java/de/etecture/ga/service/GarageImportServiceTest.java new file mode 100644 index 0000000..b165d6f --- /dev/null +++ b/src/test/java/de/etecture/ga/service/GarageImportServiceTest.java @@ -0,0 +1,40 @@ +package de.etecture.ga.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URISyntaxException; +import java.util.List; + +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +import de.etecture.ga.model.Appointment; +import de.etecture.ga.model.Garage; +import lombok.extern.slf4j.Slf4j; + +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@Slf4j +class GarageImportServiceTest { + + @InjectMocks + private GarageImportService service; + + + @Test + void testImportGarageData() throws URISyntaxException { + + List garageData = service.importGarageData(); + + assertThat(garageData).isNotEmpty().hasOnlyElementsOfType(Garage.class).hasSize(1); + + Garage garageToTest = garageData.get(0); + assertThat(garageToTest.getName()).isEqualTo("test_data"); + assertThat(garageToTest.getAppointments()).hasOnlyElementsOfType(Appointment.class).hasSize(19); + } + +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..2cc4925 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,10 @@ +spring.application.name=Garage appointment management - test + +# Datasource settings +spring.datasource.url=jdbc:h2:mem:etecture_test +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password + +# init DB by script +spring.sql.init.mode=always \ No newline at end of file diff --git a/src/test/resources/import/test_data.csv b/src/test/resources/import/test_data.csv new file mode 100644 index 0000000..f3b382e --- /dev/null +++ b/src/test/resources/import/test_data.csv @@ -0,0 +1,20 @@ +APP_DATE,SERVICE +2019-01-02T13:00Z,WHE +2019-01-02T15:00Z,WHE +2019-01-04T08:15Z,MOT +2019-01-04T09:00Z,OIL +2019-01-04T09:00Z,OIL +2019-01-05T10:15Z,MOT +2019-01-05T13:00Z,WHE +2019-01-07T08:15Z,OIL +2019-01-07T08:45Z,OIL +2019-01-08T09:15Z,MOT +2019-01-08T09:30Z,MOT +2019-01-08T08:00Z,WHE +2019-01-09T08:30Z,WHE +2019-01-09T10:45Z,OIL +2019-01-09T13:00Z,OIL +2019-01-10T15:15Z,WHE +2019-01-10T14:10Z,MOT +2019-01-11T12:15Z,OIL +2019-01-11T08:15Z,WHE