Add Swagger annotations and API documentation across all controllers while integrating API key-based authentication with CORS configuration.
This commit is contained in:
parent
d06531599f
commit
dfbc1e00bd
10 changed files with 385 additions and 27 deletions
82
src/main/java/de/avatic/taric/config/ApiKeyFilter.java
Normal file
82
src/main/java/de/avatic/taric/config/ApiKeyFilter.java
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package de.avatic.taric.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ApiKeyFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private static final String API_KEY_HEADER = "X-API-Key";
|
||||||
|
|
||||||
|
private static final List<String> PUBLIC_PATHS = List.of(
|
||||||
|
"/actuator/health",
|
||||||
|
"/doc",
|
||||||
|
"/swagger-ui",
|
||||||
|
"/v3/api-docs"
|
||||||
|
);
|
||||||
|
|
||||||
|
@Value("${taric.api-key}")
|
||||||
|
private String validApiKey;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
|
||||||
|
String path = request.getRequestURI();
|
||||||
|
|
||||||
|
|
||||||
|
if ("OPTIONS".equals(request.getMethod())) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPublicPath(path)) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String apiKey = request.getHeader(API_KEY_HEADER);
|
||||||
|
|
||||||
|
if (apiKey == null) {
|
||||||
|
apiKey = request.getHeader(API_KEY_HEADER.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validApiKey != null && validApiKey.equals(apiKey)) {
|
||||||
|
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(
|
||||||
|
"api-user",
|
||||||
|
null,
|
||||||
|
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
|
||||||
|
);
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} else {
|
||||||
|
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
response.setContentType("application/json");
|
||||||
|
response.getWriter().write("{\"message\": \"Invalid or missing API Key\"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPublicPath(String path) {
|
||||||
|
return PUBLIC_PATHS.stream().anyMatch(path::startsWith);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/java/de/avatic/taric/config/OpenApiConfig.java
Normal file
45
src/main/java/de/avatic/taric/config/OpenApiConfig.java
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
package de.avatic.taric.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn;
|
||||||
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
||||||
|
import io.swagger.v3.oas.annotations.info.Contact;
|
||||||
|
import io.swagger.v3.oas.annotations.info.Info;
|
||||||
|
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||||
|
import io.swagger.v3.oas.annotations.servers.Server;
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@OpenAPIDefinition(
|
||||||
|
info = @Info(
|
||||||
|
title = "TARIC API",
|
||||||
|
version = "1.0",
|
||||||
|
description = "REST API for accessing TARIC (Integrated Tariff of the European Union) data. " +
|
||||||
|
"This API provides endpoints for retrieving tariff information, nomenclature, geographical data, " +
|
||||||
|
"certificates, conditions, and measures.",
|
||||||
|
contact = @Contact(
|
||||||
|
name = "API Support",
|
||||||
|
email = "support@avatic.de"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
servers = {
|
||||||
|
@Server(
|
||||||
|
description = "Production Server",
|
||||||
|
url = "https://taric.avatic.de"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@SecurityScheme(
|
||||||
|
name = "apiKey",
|
||||||
|
type = SecuritySchemeType.APIKEY,
|
||||||
|
in = SecuritySchemeIn.HEADER,
|
||||||
|
paramName = "X-API-Key"
|
||||||
|
)
|
||||||
|
public class OpenApiConfig {
|
||||||
|
}
|
||||||
|
|
@ -1,28 +1,54 @@
|
||||||
package de.avatic.taric.config;
|
package de.avatic.taric.config;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Profile;
|
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final ApiKeyFilter apiKeyFilter;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http.authorizeHttpRequests(auth -> auth
|
http.authorizeHttpRequests(auth -> auth
|
||||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||||
.requestMatchers("/actuator/health").permitAll()
|
.requestMatchers("/actuator/health").permitAll()
|
||||||
.requestMatchers("/actuator/**").hasRole("SERVICE")
|
.requestMatchers("/actuator/**").authenticated()
|
||||||
.requestMatchers("/api/**").permitAll()
|
.requestMatchers("/api/**").authenticated()
|
||||||
.anyRequest().permitAll()
|
.requestMatchers("/doc/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
);
|
.anyRequest().authenticated()
|
||||||
|
).addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
|
||||||
|
.csrf(AbstractHttpConfigurer::disable);
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
configuration.setAllowedOrigins(List.of("*"));
|
||||||
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||||
|
configuration.setAllowedHeaders(List.of("*"));
|
||||||
|
configuration.setExposedHeaders(List.of("X-API-Key"));
|
||||||
|
configuration.setAllowCredentials(false);
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,13 @@ package de.avatic.taric.controller;
|
||||||
import de.avatic.taric.model.Certificate;
|
import de.avatic.taric.model.Certificate;
|
||||||
import de.avatic.taric.repository.CertificateRepository;
|
import de.avatic.taric.repository.CertificateRepository;
|
||||||
import de.avatic.taric.repository.CertificateTypeRepository;
|
import de.avatic.taric.repository.CertificateTypeRepository;
|
||||||
|
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.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
import org.springframework.data.jdbc.core.mapping.AggregateReference;
|
||||||
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;
|
||||||
|
|
@ -13,6 +20,7 @@ import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/certificates")
|
@RequestMapping("/api/v1/certificates")
|
||||||
|
@Tag(name = "Certificates", description = "API endpoints for managing and retrieving certificates")
|
||||||
public class CertificateController {
|
public class CertificateController {
|
||||||
|
|
||||||
private final CertificateRepository certificateRepository;
|
private final CertificateRepository certificateRepository;
|
||||||
|
|
@ -23,8 +31,21 @@ public class CertificateController {
|
||||||
this.certificateTypeRepository = certificateTypeRepository;
|
this.certificateTypeRepository = certificateTypeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get certificates",
|
||||||
|
description = "Retrieves all certificates or filters by certificate type code. Returns an empty list if the certificate type code is not found."
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved certificates",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Certificate.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public Iterable<Certificate> getCertificates(@RequestParam(required = false) String certificateTypeCode) {
|
public Iterable<Certificate> getCertificates(
|
||||||
|
@Parameter(description = "Optional certificate type code to filter certificates by type")
|
||||||
|
@RequestParam(required = false) String certificateTypeCode) {
|
||||||
|
|
||||||
if (certificateTypeCode != null) {
|
if (certificateTypeCode != null) {
|
||||||
return certificateTypeRepository.findByCertificateTypeCode(certificateTypeCode).map(type -> certificateRepository.findByCertificateType(AggregateReference.to(type.getId()))).orElse(List.of());
|
return certificateTypeRepository.findByCertificateTypeCode(certificateTypeCode).map(type -> certificateRepository.findByCertificateType(AggregateReference.to(type.getId()))).orElse(List.of());
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,19 @@ package de.avatic.taric.controller;
|
||||||
|
|
||||||
import de.avatic.taric.model.ConditionType;
|
import de.avatic.taric.model.ConditionType;
|
||||||
import de.avatic.taric.repository.ConditionTypeRepository;
|
import de.avatic.taric.repository.ConditionTypeRepository;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
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.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/conditions")
|
@RequestMapping("/api/v1/conditions")
|
||||||
|
@Tag(name = "Conditions", description = "API endpoints for retrieving condition types")
|
||||||
public class ConditionController {
|
public class ConditionController {
|
||||||
|
|
||||||
private final ConditionTypeRepository conditionTypeRepository;
|
private final ConditionTypeRepository conditionTypeRepository;
|
||||||
|
|
@ -16,6 +23,17 @@ public class ConditionController {
|
||||||
this.conditionTypeRepository = conditionTypeRepository;
|
this.conditionTypeRepository = conditionTypeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get all condition types",
|
||||||
|
description = "Retrieves a complete list of all available condition types in the system"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved condition types",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = ConditionType.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public Iterable<ConditionType> getConditions() {
|
public Iterable<ConditionType> getConditions() {
|
||||||
return conditionTypeRepository.findAll();
|
return conditionTypeRepository.findAll();
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ package de.avatic.taric.controller;
|
||||||
import de.avatic.taric.model.Geo;
|
import de.avatic.taric.model.Geo;
|
||||||
import de.avatic.taric.model.GeoGroup;
|
import de.avatic.taric.model.GeoGroup;
|
||||||
import de.avatic.taric.service.GeoService;
|
import de.avatic.taric.service.GeoService;
|
||||||
|
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.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
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.RequestParam;
|
||||||
|
|
@ -14,22 +21,55 @@ import java.util.Optional;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/geo")
|
@RequestMapping("/api/v1/geo")
|
||||||
|
@Tag(name = "Geography", description = "API endpoints for geographical data including countries and country groups")
|
||||||
public class GeoController {
|
public class GeoController {
|
||||||
|
|
||||||
|
|
||||||
private final GeoService geoService;
|
private final GeoService geoService;
|
||||||
|
|
||||||
public GeoController(GeoService geoService) {
|
public GeoController(GeoService geoService) {
|
||||||
this.geoService = geoService;
|
this.geoService = geoService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get geographical information by country code",
|
||||||
|
description = "Retrieves detailed geographical information for a specific country using its country code"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved geographical information",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Geo.class))
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "Country not found",
|
||||||
|
content = @Content
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("")
|
@GetMapping("")
|
||||||
public Optional<Geo> getGeo(@RequestParam String countryCode) {
|
public Optional<Geo> getGeo(
|
||||||
|
@Parameter(description = "ISO country code (e.g., DE, US, FR)", required = true)
|
||||||
|
@RequestParam String countryCode) {
|
||||||
return geoService.getGeo(countryCode);
|
return geoService.getGeo(countryCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get geographical groups",
|
||||||
|
description = "Retrieves geographical groups either by country code or by group abbreviation. Only one parameter should be provided at a time."
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved geographical groups",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = GeoGroup.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("/group")
|
@GetMapping("/group")
|
||||||
public List<GeoGroup> getGeoGroup(@RequestParam(required = false) String countryCode, @RequestParam(required = false) String abbr) {
|
public List<GeoGroup> getGeoGroup(
|
||||||
|
@Parameter(description = "ISO country code to find all groups containing this country")
|
||||||
|
@RequestParam(required = false) String countryCode,
|
||||||
|
@Parameter(description = "Group abbreviation to retrieve a specific geographical group")
|
||||||
|
@RequestParam(required = false) String abbr) {
|
||||||
|
|
||||||
if (countryCode != null && abbr == null)
|
if (countryCode != null && abbr == null)
|
||||||
return geoService.getGeoGroupByCountryCode(countryCode);
|
return geoService.getGeoGroupByCountryCode(countryCode);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@ import de.avatic.taric.model.MeasureSeries;
|
||||||
import de.avatic.taric.repository.MeasureActionRepository;
|
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.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
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.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
@ -14,6 +20,7 @@ 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")
|
||||||
public class MeasureController {
|
public class MeasureController {
|
||||||
|
|
||||||
private final MeasureRepository measureRepository;
|
private final MeasureRepository measureRepository;
|
||||||
|
|
@ -26,16 +33,49 @@ public class MeasureController {
|
||||||
this.measureActionRepository = measureActionRepository;
|
this.measureActionRepository = measureActionRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get all measures",
|
||||||
|
description = "Retrieves a complete list of all trade measures in the system"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved measures",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Measure.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public Iterable<Measure> getMeasures() {
|
public Iterable<Measure> getMeasures() {
|
||||||
return measureRepository.findAll();
|
return measureRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get all measure series",
|
||||||
|
description = "Retrieves a complete list of all measure series that group related measures together"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved measure series",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = MeasureSeries.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("/series")
|
@GetMapping("/series")
|
||||||
public Iterable<MeasureSeries> getMeasuresSeries() {
|
public Iterable<MeasureSeries> getMeasuresSeries() {
|
||||||
return measureSeriesRepository.findAll();
|
return measureSeriesRepository.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get all measure actions",
|
||||||
|
description = "Retrieves a complete list of all measure actions that define possible actions on measures"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved measure actions",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = MeasureAction.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("/action")
|
@GetMapping("/action")
|
||||||
public Iterable<MeasureAction> getMeasuresAction() {
|
public Iterable<MeasureAction> getMeasuresAction() {
|
||||||
return measureActionRepository.findAll();
|
return measureActionRepository.findAll();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@ package de.avatic.taric.controller;
|
||||||
|
|
||||||
import de.avatic.taric.model.Nomenclature;
|
import de.avatic.taric.model.Nomenclature;
|
||||||
import de.avatic.taric.service.NomenclatureService;
|
import de.avatic.taric.service.NomenclatureService;
|
||||||
|
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.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
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.RequestParam;
|
||||||
|
|
@ -12,6 +19,7 @@ import java.util.Optional;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/nomenclature")
|
@RequestMapping("/api/v1/nomenclature")
|
||||||
|
@Tag(name = "Nomenclature", description = "API endpoints for HS code nomenclature and tariff classification hierarchy")
|
||||||
public class NomenclatureController {
|
public class NomenclatureController {
|
||||||
|
|
||||||
private final NomenclatureService nomenclatureService;
|
private final NomenclatureService nomenclatureService;
|
||||||
|
|
@ -20,20 +28,63 @@ public class NomenclatureController {
|
||||||
this.nomenclatureService = nomenclatureService;
|
this.nomenclatureService = nomenclatureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get nomenclature by HS code",
|
||||||
|
description = "Retrieves a specific nomenclature entry by its Harmonized System (HS) code"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved nomenclature",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Nomenclature.class))
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "HS code not found",
|
||||||
|
content = @Content
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("")
|
@GetMapping("")
|
||||||
public Optional<Nomenclature> getNomenclature(@RequestParam String hscode) {
|
public Optional<Nomenclature> getNomenclature(
|
||||||
|
@Parameter(description = "Harmonized System code (e.g., 0101, 010121)", required = true)
|
||||||
|
@RequestParam String hscode) {
|
||||||
return nomenclatureService.getNomenclature(hscode);
|
return nomenclatureService.getNomenclature(hscode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get declarable child nomenclatures",
|
||||||
|
description = "Retrieves all declarable (leaf-level) child nomenclatures under a given HS code. These are codes that can be used for customs declarations."
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved declarable nomenclatures",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Nomenclature.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("/declarable")
|
@GetMapping("/declarable")
|
||||||
public List<Nomenclature> getDeclarable(@RequestParam String hscode) {
|
public List<Nomenclature> getDeclarable(
|
||||||
|
@Parameter(description = "Parent HS code to retrieve declarable children from", required = true)
|
||||||
|
@RequestParam String hscode) {
|
||||||
return nomenclatureService.getDeclarableChildren(hscode);
|
return nomenclatureService.getDeclarableChildren(hscode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get nomenclature cascade",
|
||||||
|
description = "Retrieves the complete hierarchical cascade of a nomenclature, including all parent and child relationships in the tariff classification tree"
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved nomenclature cascade",
|
||||||
|
content = @Content(mediaType = "application/json", schema = @Schema(implementation = Nomenclature.class))
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("/cascade")
|
@GetMapping("/cascade")
|
||||||
public List<Nomenclature> getCascade(@RequestParam String hscode) {
|
public List<Nomenclature> getCascade(
|
||||||
|
@Parameter(description = "HS code to retrieve the complete hierarchy for", required = true)
|
||||||
|
@RequestParam String hscode) {
|
||||||
var found = nomenclatureService.getNomenclatureCascade(hscode);
|
var found = nomenclatureService.getNomenclatureCascade(hscode);
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,14 @@ package de.avatic.taric.controller;
|
||||||
|
|
||||||
import de.avatic.taric.model.AppliedMeasure;
|
import de.avatic.taric.model.AppliedMeasure;
|
||||||
import de.avatic.taric.service.TariffService;
|
import de.avatic.taric.service.TariffService;
|
||||||
|
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.ExampleObject;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
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.RequestParam;
|
||||||
|
|
@ -12,18 +20,44 @@ import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/tariff")
|
@RequestMapping("/api/v1/tariff")
|
||||||
|
@Tag(name = "Tariff", description = "API endpoints for calculating and retrieving applied tariff rates and measures")
|
||||||
public class TariffController {
|
public class TariffController {
|
||||||
|
|
||||||
|
|
||||||
private final TariffService tariffService;
|
private final TariffService tariffService;
|
||||||
|
|
||||||
public TariffController(TariffService tariffService) {
|
public TariffController(TariffService tariffService) {
|
||||||
this.tariffService = tariffService;
|
this.tariffService = tariffService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(
|
||||||
|
summary = "Get applied tariff measures",
|
||||||
|
description = "Retrieves all applicable tariff measures for a specific HS code and country combination. " +
|
||||||
|
"Returns a nested map structure grouping measures by measure type and additional code."
|
||||||
|
)
|
||||||
|
@ApiResponses(value = {
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "200",
|
||||||
|
description = "Successfully retrieved applied measures",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = "application/json",
|
||||||
|
schema = @Schema(implementation = Map.class),
|
||||||
|
examples = @ExampleObject(
|
||||||
|
value = "{ \"103\": { \"default\": [ {...} ] }, \"142\": { \"C999\": [ {...} ] } }"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
@ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
|
description = "HS code or country not found",
|
||||||
|
content = @Content
|
||||||
|
)
|
||||||
|
})
|
||||||
@GetMapping("")
|
@GetMapping("")
|
||||||
public Map<String, Map<String, List<AppliedMeasure>>> getTariffRate(@RequestParam String hsCode, @RequestParam String countryCode) {
|
public Map<String, Map<String, List<AppliedMeasure>>> getTariffRate(
|
||||||
|
@Parameter(description = "Harmonized System code (e.g., 0101210000)", required = true, example = "0101210000")
|
||||||
|
@RequestParam String hsCode,
|
||||||
|
@Parameter(description = "ISO country code (e.g., US, CN, GB)", required = true, example = "US")
|
||||||
|
@RequestParam String countryCode) {
|
||||||
return tariffService.getAppliedMeasures(hsCode, countryCode);
|
return tariffService.getAppliedMeasures(hsCode, countryCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
spring.application.name=taric
|
spring.application.name=taric
|
||||||
logging.level.org.springframework.data.jdbc.core.convert.RowDocumentResultSetExtractor=ERROR
|
logging.level.org.springframework.data.jdbc.core.convert.RowDocumentResultSetExtractor=ERROR
|
||||||
|
springdoc.swagger-ui.path=/doc
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue