mirror of
https://github.com/OkaeriPoland/okaeri-timings.git
synced 2026-01-17 19:22:16 +01:00
Create backend parser concept
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package eu.okaeri.timings.api;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
public class IndexController {
|
||||
|
||||
@RequestMapping
|
||||
public String index() {
|
||||
return "redirect:/swagger-ui";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package eu.okaeri.timings.api.security;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.httpBasic().disable();
|
||||
http.csrf().disable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
config.setAllowedOrigins(List.of("*"));
|
||||
config.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization"));
|
||||
config.setAllowedMethods(List.of("*"));
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package eu.okaeri.timings.api.v1;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/v1/parse")
|
||||
public class ParseController {
|
||||
|
||||
@SneakyThrows
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<?> parse(@RequestPart("file") MultipartFile file) {
|
||||
|
||||
if (!"text/csv".equals(file.getContentType())) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", "Expected text/csv, got: " + file.getContentType()));
|
||||
}
|
||||
|
||||
String content = new String(file.getBytes(), StandardCharsets.UTF_8);
|
||||
String[] lines = content.split("\r?\n");
|
||||
|
||||
Map<String, String> metadata = new LinkedHashMap<>();
|
||||
List<String> header = null;
|
||||
List<List<BigDecimal>> records = new ArrayList<>();
|
||||
|
||||
for (String line : lines) {
|
||||
|
||||
// metadata
|
||||
if (line.startsWith("#")) {
|
||||
if (!line.contains(":")) {
|
||||
continue;
|
||||
}
|
||||
String[] parts = line.substring(1).split(":", 2);
|
||||
if (parts.length != 2) {
|
||||
throw new RuntimeException("Cannot parse metadata: '" + line + "'");
|
||||
}
|
||||
String key = parts[0].trim().toLowerCase(Locale.ROOT);
|
||||
String value = parts[1].trim();
|
||||
metadata.put(key, value);
|
||||
continue;
|
||||
}
|
||||
|
||||
// header
|
||||
if (header == null) {
|
||||
String[] parts = line.split(",");
|
||||
if (parts.length < 2) {
|
||||
throw new RuntimeException("Cannot parse header: '" + line + "'");
|
||||
}
|
||||
header = Arrays.asList(parts);
|
||||
continue;
|
||||
}
|
||||
|
||||
// data
|
||||
String[] parts = line.split(",");
|
||||
if (parts.length != header.size()) {
|
||||
throw new RuntimeException("Cannot parse record: '" + line + "'");
|
||||
}
|
||||
|
||||
records.add(Arrays.stream(parts)
|
||||
.map(value -> {
|
||||
try {
|
||||
return new BigDecimal(value);
|
||||
} catch (Exception exception) {
|
||||
throw new RuntimeException("Cannot parse value: '" + value + "' from record '" + line + "'");
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
if (header == null || records.size() < 2) {
|
||||
throw new RuntimeException("Invalid report");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"meta", metadata,
|
||||
"header", header,
|
||||
"data", records
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -20,9 +20,11 @@ springdoc:
|
||||
default-produces-media-type: "application/json"
|
||||
|
||||
spring:
|
||||
data:
|
||||
rest:
|
||||
base-path: /rest
|
||||
servlet:
|
||||
multipart:
|
||||
enabled: true
|
||||
max-file-size: 1MB
|
||||
max-request-size: 1MB
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
|
||||
Reference in New Issue
Block a user