Import von bestehenden CSV-Dateien
- CSV-Dateien im Verzeichniss "import" werden automatisch eingelesen
This commit is contained in:
35
pom.xml
35
pom.xml
@@ -22,6 +22,7 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- Spring libs -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
@@ -30,18 +31,12 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
<artifactId>spring-boot-starter-data-jdbc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- CSV handling -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>jackson-dataformat-csv</artifactId>
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<!-- OpenAPI generation -->
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-ui</artifactId>
|
<artifactId>springdoc-openapi-ui</artifactId>
|
||||||
@@ -52,7 +47,18 @@
|
|||||||
<artifactId>jackson-databind-nullable</artifactId>
|
<artifactId>jackson-databind-nullable</artifactId>
|
||||||
<version>0.2.6</version>
|
<version>0.2.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- DB driver - change to other driver on DB change -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- confinience -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- testing -->
|
<!-- testing -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -60,6 +66,11 @@
|
|||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -79,7 +90,7 @@
|
|||||||
</inputSpec>
|
</inputSpec>
|
||||||
<generatorName>spring</generatorName>
|
<generatorName>spring</generatorName>
|
||||||
<apiPackage>de.etecture.ga.api</apiPackage>
|
<apiPackage>de.etecture.ga.api</apiPackage>
|
||||||
<modelPackage>de.etecture.ga.model</modelPackage>
|
<modelPackage>de.etecture.ga.dto</modelPackage>
|
||||||
<supportingFilesToGenerate>
|
<supportingFilesToGenerate>
|
||||||
ApiUtil.java
|
ApiUtil.java
|
||||||
</supportingFilesToGenerate>
|
</supportingFilesToGenerate>
|
||||||
|
|||||||
46
src/main/java/de/etecture/ga/api/GarageApiController.java
Normal file
46
src/main/java/de/etecture/ga/api/GarageApiController.java
Normal file
@@ -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<Termin> getTermin(String werkstattId, String terminId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return WerkstattApi.super.getTermin(werkstattId, terminId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<List<Termin>> 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<List<Termin>> 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<Termin> postTermin(String werkstattId, @Valid TerminRequest termin) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return WerkstattApi.super.postTermin(werkstattId, termin);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
29
src/main/java/de/etecture/ga/config/Setup.java
Normal file
29
src/main/java/de/etecture/ga/config/Setup.java
Normal file
@@ -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<Garage> garages = importService.importGarageData();
|
||||||
|
|
||||||
|
// Daten in DB übertragen
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
src/main/java/de/etecture/ga/model/Appointment.java
Normal file
25
src/main/java/de/etecture/ga/model/Appointment.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
29
src/main/java/de/etecture/ga/model/Garage.java
Normal file
29
src/main/java/de/etecture/ga/model/Garage.java
Normal file
@@ -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<Appointment> appointments;
|
||||||
|
|
||||||
|
public Garage addAppointment(Appointment appointment) {
|
||||||
|
|
||||||
|
if(this.appointments == null) {
|
||||||
|
this.appointments = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.appointments.add(appointment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/de/etecture/ga/model/MDService.java
Normal file
20
src/main/java/de/etecture/ga/model/MDService.java
Normal file
@@ -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;
|
||||||
|
}
|
||||||
105
src/main/java/de/etecture/ga/service/GarageImportService.java
Normal file
105
src/main/java/de/etecture/ga/service/GarageImportService.java
Normal file
@@ -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<Garage> importGarageData() {
|
||||||
|
|
||||||
|
List<Garage> importData = new ArrayList<>();
|
||||||
|
|
||||||
|
try (Stream<Path> files = Files
|
||||||
|
.list(Paths.get(getClass().getClassLoader().getResource(IMPORT_FOLDER).toURI()))) {
|
||||||
|
|
||||||
|
files.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".csv"))
|
||||||
|
.map(this::loadGarageData).<Garage>mapMulti(Optional::ifPresent).forEach(importData::add);
|
||||||
|
|
||||||
|
} catch (IOException | URISyntaxException e) {
|
||||||
|
log.error("Can't read file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return importData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Garage> loadGarageData(final Path file) {
|
||||||
|
|
||||||
|
Garage garage = new Garage();
|
||||||
|
garage.setName(getGarageNameFromFile(file));
|
||||||
|
|
||||||
|
List<CSVData> 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<CSVData> loadObjectsFromFile(final Path file) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader();
|
||||||
|
CsvMapper mapper = new CsvMapper();
|
||||||
|
MappingIterator<CSVData> 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,10 @@
|
|||||||
spring.application.name=Garage appointment management
|
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
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Garage> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
src/test/resources/application.properties
Normal file
10
src/test/resources/application.properties
Normal file
@@ -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
|
||||||
20
src/test/resources/import/test_data.csv
Normal file
20
src/test/resources/import/test_data.csv
Normal file
@@ -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
|
||||||
|
Reference in New Issue
Block a user