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;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.http.HttpMethod;
|
||||
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.configurers.AbstractHttpConfigurer;
|
||||
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
|
||||
@EnableMethodSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final ApiKeyFilter apiKeyFilter;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
.requestMatchers("/actuator/health").permitAll()
|
||||
.requestMatchers("/actuator/**").hasRole("SERVICE")
|
||||
.requestMatchers("/api/**").permitAll()
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
.requestMatchers("/actuator/health").permitAll()
|
||||
.requestMatchers("/actuator/**").authenticated()
|
||||
.requestMatchers("/api/**").authenticated()
|
||||
.requestMatchers("/doc/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
).addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.csrf(AbstractHttpConfigurer::disable);
|
||||
|
||||
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.repository.CertificateRepository;
|
||||
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.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
|
@ -13,6 +20,7 @@ import java.util.List;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/certificates")
|
||||
@Tag(name = "Certificates", description = "API endpoints for managing and retrieving certificates")
|
||||
public class CertificateController {
|
||||
|
||||
private final CertificateRepository certificateRepository;
|
||||
|
|
@ -23,8 +31,21 @@ public class CertificateController {
|
|||
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
|
||||
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) {
|
||||
return certificateTypeRepository.findByCertificateTypeCode(certificateTypeCode).map(type -> certificateRepository.findByCertificateType(AggregateReference.to(type.getId()))).orElse(List.of());
|
||||
|
|
@ -32,4 +53,4 @@ public class CertificateController {
|
|||
|
||||
return certificateRepository.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,19 @@ package de.avatic.taric.controller;
|
|||
|
||||
import de.avatic.taric.model.ConditionType;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/conditions")
|
||||
@Tag(name = "Conditions", description = "API endpoints for retrieving condition types")
|
||||
public class ConditionController {
|
||||
|
||||
private final ConditionTypeRepository conditionTypeRepository;
|
||||
|
|
@ -16,8 +23,19 @@ public class ConditionController {
|
|||
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
|
||||
public Iterable<ConditionType> getConditions() {
|
||||
return conditionTypeRepository.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,13 @@ package de.avatic.taric.controller;
|
|||
import de.avatic.taric.model.Geo;
|
||||
import de.avatic.taric.model.GeoGroup;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
|
@ -14,22 +21,55 @@ import java.util.Optional;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/geo")
|
||||
@Tag(name = "Geography", description = "API endpoints for geographical data including countries and country groups")
|
||||
public class GeoController {
|
||||
|
||||
|
||||
private final GeoService geoService;
|
||||
|
||||
public GeoController(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("")
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
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)
|
||||
return geoService.getGeoGroupByCountryCode(countryCode);
|
||||
|
|
@ -39,4 +79,4 @@ public class GeoController {
|
|||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,12 @@ import de.avatic.taric.model.MeasureSeries;
|
|||
import de.avatic.taric.repository.MeasureActionRepository;
|
||||
import de.avatic.taric.repository.MeasureRepository;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -14,6 +20,7 @@ import java.util.List;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/measures")
|
||||
@Tag(name = "Measures", description = "API endpoints for trade measures, measure series, and measure actions")
|
||||
public class MeasureController {
|
||||
|
||||
private final MeasureRepository measureRepository;
|
||||
|
|
@ -26,18 +33,51 @@ public class MeasureController {
|
|||
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
|
||||
public Iterable<Measure> getMeasures() {
|
||||
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")
|
||||
public Iterable<MeasureSeries> getMeasuresSeries() {
|
||||
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")
|
||||
public Iterable<MeasureAction> getMeasuresAction() {
|
||||
return measureActionRepository.findAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,13 @@ package de.avatic.taric.controller;
|
|||
|
||||
import de.avatic.taric.model.Nomenclature;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
|
@ -12,6 +19,7 @@ import java.util.Optional;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/nomenclature")
|
||||
@Tag(name = "Nomenclature", description = "API endpoints for HS code nomenclature and tariff classification hierarchy")
|
||||
public class NomenclatureController {
|
||||
|
||||
private final NomenclatureService nomenclatureService;
|
||||
|
|
@ -20,20 +28,63 @@ public class NomenclatureController {
|
|||
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("")
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
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);
|
||||
return found;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,14 @@ package de.avatic.taric.controller;
|
|||
|
||||
import de.avatic.taric.model.AppliedMeasure;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
|
@ -12,18 +20,44 @@ import java.util.Map;
|
|||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/tariff")
|
||||
@Tag(name = "Tariff", description = "API endpoints for calculating and retrieving applied tariff rates and measures")
|
||||
public class TariffController {
|
||||
|
||||
|
||||
private final TariffService tariffService;
|
||||
|
||||
public TariffController(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("")
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
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