some fixes, added api key as param

This commit is contained in:
Jan 2025-11-05 14:33:57 +01:00
parent dfbc1e00bd
commit 3c19a9191b
10 changed files with 74 additions and 100 deletions

View file

@ -21,6 +21,7 @@ import java.util.List;
public class ApiKeyFilter extends OncePerRequestFilter { public class ApiKeyFilter extends OncePerRequestFilter {
private static final String API_KEY_HEADER = "X-API-Key"; private static final String API_KEY_HEADER = "X-API-Key";
private static final String API_KEY_PARAM = "apiKey";
private static final List<String> PUBLIC_PATHS = List.of( private static final List<String> PUBLIC_PATHS = List.of(
"/actuator/health", "/actuator/health",
@ -29,7 +30,7 @@ public class ApiKeyFilter extends OncePerRequestFilter {
"/v3/api-docs" "/v3/api-docs"
); );
@Value("${taric.api-key}") @Value("${taric.api.key}")
private String validApiKey; private String validApiKey;
@Override @Override
@ -39,7 +40,6 @@ public class ApiKeyFilter extends OncePerRequestFilter {
String path = request.getRequestURI(); String path = request.getRequestURI();
if ("OPTIONS".equals(request.getMethod())) { if ("OPTIONS".equals(request.getMethod())) {
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
return; return;
@ -50,15 +50,9 @@ public class ApiKeyFilter extends OncePerRequestFilter {
return; return;
} }
String apiKey = request.getHeader(API_KEY_HEADER); String apiKey = extractApiKey(request);
if (apiKey == null) {
apiKey = request.getHeader(API_KEY_HEADER.toLowerCase());
}
if (validApiKey != null && validApiKey.equals(apiKey)) { if (validApiKey != null && validApiKey.equals(apiKey)) {
UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken( new UsernamePasswordAuthenticationToken(
"api-user", "api-user",
@ -76,6 +70,21 @@ public class ApiKeyFilter extends OncePerRequestFilter {
} }
} }
private String extractApiKey(HttpServletRequest request) {
String apiKey = request.getHeader(API_KEY_HEADER);
if (apiKey == null) {
apiKey = request.getHeader(API_KEY_HEADER.toLowerCase());
}
if (apiKey == null) {
apiKey = request.getParameter(API_KEY_PARAM);
}
return apiKey;
}
private boolean isPublicPath(String path) { private boolean isPublicPath(String path) {
return PUBLIC_PATHS.stream().anyMatch(path::startsWith); return PUBLIC_PATHS.stream().anyMatch(path::startsWith);
} }

View file

@ -7,6 +7,7 @@ import de.avatic.taric.repository.MeasureActionRepository;
import de.avatic.taric.repository.MeasureRepository; import de.avatic.taric.repository.MeasureRepository;
import de.avatic.taric.repository.MeasureSeriesRepository; import de.avatic.taric.repository.MeasureSeriesRepository;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -14,10 +15,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController @RestController
@RequestMapping("/api/v1/measures") @RequestMapping("/api/v1/measures")
@Tag(name = "Measures", description = "API endpoints for trade measures, measure series, and measure actions") @Tag(name = "Measures", description = "API endpoints for trade measures, measure series, and measure actions")
@ -45,7 +45,12 @@ public class MeasureController {
) )
}) })
@GetMapping @GetMapping
public Iterable<Measure> getMeasures() { public Iterable<Measure> getMeasures(@Parameter(description = "Optional series to filter measures by series")
@RequestParam(required = false) String series) {
if( series != null )
return measureRepository.findByMeasureSeries(series);
return measureRepository.findAll(); return measureRepository.findAll();
} }

View file

@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.Min;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
@ -65,8 +66,10 @@ public class NomenclatureController {
@GetMapping("/declarable") @GetMapping("/declarable")
public List<Nomenclature> getDeclarable( public List<Nomenclature> getDeclarable(
@Parameter(description = "Parent HS code to retrieve declarable children from", required = true) @Parameter(description = "Parent HS code to retrieve declarable children from", required = true)
@RequestParam String hscode) { @RequestParam String hscode,
return nomenclatureService.getDeclarableChildren(hscode); @Parameter(description = "Limit (default =10)", required = true)
@RequestParam(required = false, defaultValue = "10") @Min(1) Integer limit) {
return nomenclatureService.getDeclarableChildren(hscode, limit);
} }
@Operation( @Operation(
@ -84,7 +87,6 @@ public class NomenclatureController {
public List<Nomenclature> getCascade( public List<Nomenclature> getCascade(
@Parameter(description = "HS code to retrieve the complete hierarchy for", required = true) @Parameter(description = "HS code to retrieve the complete hierarchy for", required = true)
@RequestParam String hscode) { @RequestParam String hscode) {
var found = nomenclatureService.getNomenclatureCascade(hscode); return nomenclatureService.getNomenclatureCascade(hscode);
return found;
} }
} }

View file

@ -55,9 +55,9 @@ public class TariffController {
@GetMapping("") @GetMapping("")
public Map<String, Map<String, List<AppliedMeasure>>> getTariffRate( public Map<String, Map<String, List<AppliedMeasure>>> getTariffRate(
@Parameter(description = "Harmonized System code (e.g., 0101210000)", required = true, example = "0101210000") @Parameter(description = "Harmonized System code (e.g., 0101210000)", required = true, example = "0101210000")
@RequestParam String hsCode, @RequestParam String hscode,
@Parameter(description = "ISO country code (e.g., US, CN, GB)", required = true, example = "US") @Parameter(description = "ISO country code (e.g., US, CN, GB)", required = true, example = "US")
@RequestParam String countryCode) { @RequestParam String countryCode) {
return tariffService.getAppliedMeasures(hsCode, countryCode); return tariffService.getAppliedMeasures(hscode, countryCode);
} }
} }

View file

@ -2,11 +2,15 @@ package de.avatic.taric.model;
import java.time.LocalDate; import java.time.LocalDate;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
@Getter
@Setter
public class MonetaryUnitDesc { public class MonetaryUnitDesc {
@Id @Id
@ -19,43 +23,4 @@ public class MonetaryUnitDesc {
private LocalDate descStartDate; private LocalDate descStartDate;
public MonetaryUnitDesc(String lang, String desc, LocalDate descStartDate) {
this.lang = lang;
this.desc = desc;
this.descStartDate = descStartDate;
}
public Integer getId() {
return id;
}
public void setId(final Integer id) {
this.id = id;
}
public String getLang() {
return lang;
}
public void setLang(final String lang) {
this.lang = lang;
}
public String getDesc() {
return desc;
}
public void setDescc(final String desc) {
this.desc = desc;
}
public LocalDate getDescStartDate() {
return descStartDate;
}
public void setDescStartDate(final LocalDate descStartDate) {
this.descStartDate = descStartDate;
}
} }

View file

@ -1,5 +1,6 @@
package de.avatic.taric.repository; package de.avatic.taric.repository;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import de.avatic.taric.model.Measure; import de.avatic.taric.model.Measure;
@ -7,4 +8,6 @@ import de.avatic.taric.model.Measure;
public interface MeasureRepository extends CrudRepository<Measure, Integer> { public interface MeasureRepository extends CrudRepository<Measure, Integer> {
@Query("SELECT * FROM measure m LEFT JOIN measure_series s ON m.measure_series_id = s.id WHERE s.measure_series_code = :series")
Iterable<Measure> findByMeasureSeries(String series);
} }

View file

@ -1,20 +1,25 @@
package de.avatic.taric.repository; package de.avatic.taric.repository;
import de.avatic.taric.model.Nomenclature; import de.avatic.taric.model.Nomenclature;
import org.springframework.data.domain.Limit;
import org.springframework.data.jdbc.repository.query.Query; import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@Repository @Repository
public interface NomenclatureRepository extends CrudRepository<Nomenclature, Integer> { public interface NomenclatureRepository extends CrudRepository<Nomenclature, Integer> {
@Query("SELECT * FROM nomenclature WHERE hscode = :hscode AND (end_date IS NULL OR end_date >= CURRENT_DATE)") @Query("SELECT * FROM nomenclature WHERE hscode = :hscode AND (end_date IS NULL OR end_date >= CURRENT_DATE)")
Optional<Nomenclature> findByHscode(String hscode); Optional<Nomenclature> findByHscode(String hscode);
@Query("SELECT * FROM nomenclature WHERE is_leaf = 1 AND hscode LIKE CONCAT(:code, '%') AND (end_date IS NULL OR end_date >= CURRENT_DATE)") @Query("SELECT * FROM nomenclature WHERE is_leaf = 1 AND hscode LIKE CONCAT(:code, '%') AND (end_date IS NULL OR end_date >= CURRENT_DATE)")
List<Nomenclature> findDeclarableChildren(@Param("code") String code); List<Nomenclature> findDeclarableChildren(@Param("code") String code);
} }

View file

@ -25,26 +25,32 @@ public class DescSetSerializer extends JsonSerializer<Set<Description>> {
@Override @Override
public void serialize(Set<Description> value, JsonGenerator gen, SerializerProvider serializer) throws IOException { public void serialize(Set<Description> value, JsonGenerator gen, SerializerProvider serializer) throws IOException {
if (value != null) { if (value == null || value.isEmpty()) {
var filtered = value; gen.writeNull();
return;
}
String language = LANGUAGE_CONTEXT.get(); String language = LANGUAGE_CONTEXT.get();
if (language != null) {
filtered = value.stream()
.filter(desc -> language.equalsIgnoreCase(desc.getLang()))
.collect(Collectors.toSet());
}
if (filtered.isEmpty()) { Set<Description> filtered = Set.of();
filtered = value.stream() if (language != null) {
.filter(desc -> "EN".equalsIgnoreCase(desc.getLang())) filtered = value.stream()
.collect(Collectors.toSet()); .filter(desc -> language.equalsIgnoreCase(desc.getLang()))
} .collect(Collectors.toSet());
}
if(filtered.size() == 1) if (filtered.isEmpty()) {
gen.writeObject(filtered.iterator().next()); filtered = value.stream()
else .filter(desc -> "EN".equalsIgnoreCase(desc.getLang()))
gen.writeObject(filtered); .collect(Collectors.toSet());
}
if (filtered.isEmpty() && !value.isEmpty()) {
filtered = Set.of(value.iterator().next());
}
if (!filtered.isEmpty()) {
gen.writeObject(filtered.iterator().next());
} else { } else {
gen.writeNull(); gen.writeNull();
} }

View file

@ -2,6 +2,7 @@ package de.avatic.taric.service;
import de.avatic.taric.model.Nomenclature; import de.avatic.taric.model.Nomenclature;
import de.avatic.taric.repository.NomenclatureRepository; import de.avatic.taric.repository.NomenclatureRepository;
import org.springframework.data.domain.Limit;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.*; import java.util.*;
@ -20,16 +21,11 @@ public class NomenclatureService {
return nomenclatureRepository.findByHscode(normalize(hscode.replaceAll("[^0-9]", ""))); return nomenclatureRepository.findByHscode(normalize(hscode.replaceAll("[^0-9]", "")));
} }
public boolean isDeclarable(String hscode) { public List<Nomenclature> getDeclarableChildren(String hscode, Integer limit) {
var nomenclature = getNomenclature(hscode); // var normalized = normalize(hscode.replaceAll("[^0-9]", ""));
if (nomenclature.isEmpty()) return false; // var level = getHierarchyLevel(normalized);
return nomenclature.get().getIsLeaf();
}
public List<Nomenclature> getDeclarableChildren(String hscode) { return nomenclatureRepository.findDeclarableChildren(hscode).stream().limit(limit).toList();
var normalized = normalize(hscode.replaceAll("[^0-9]", ""));
var level = getHierarchyLevel(normalized);
return nomenclatureRepository.findDeclarableChildren(normalized.substring(0, level));
} }
public List<Nomenclature> getNomenclatureCascade(String hscode) { public List<Nomenclature> getNomenclatureCascade(String hscode) {

View file

@ -63,23 +63,6 @@ public class TariffService {
} }
private Optional<Double> findTariff(Collection<AppliedMeasure> measures, Measure appl) {
var code = appl.getMeasureCode();
//TODO return all measures...
List<AppliedMeasure> filteredMeasures = measures.stream()
.filter(meas -> meas.getAmount() != null && meas.getAmount().trim().matches("\\d+\\.\\d+\\s*%"))
.toList();
AppliedMeasure customTariff = filteredMeasures.stream().filter(meas -> meas.getMeasure().getId().equals(appl.getId())).findAny().orElse(filteredMeasures.isEmpty() ? null : filteredMeasures.getFirst());
if (!filteredMeasures.isEmpty())
return Optional.of(customTariff).map(meas -> Double.parseDouble(meas.getAmount().trim().replace("%", "").trim()) / 100);
return Optional.empty();
}
public List<AppliedMeasure> findAppliedMeasureByGeo(Nomenclature nomenclature, Geo geo) { public List<AppliedMeasure> findAppliedMeasureByGeo(Nomenclature nomenclature, Geo geo) {
var foundImport = importRepository.findByNomenclatureAndGeo(nomenclature.getId(), geo.getId()); var foundImport = importRepository.findByNomenclatureAndGeo(nomenclature.getId(), geo.getId());