Add Swagger annotations and API documentation across all controllers while integrating API key-based authentication with CORS configuration.

This commit is contained in:
Jan 2025-11-03 15:43:27 +01:00
parent d06531599f
commit dfbc1e00bd
10 changed files with 385 additions and 27 deletions

View 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);
}
}

View 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 {
}

View file

@ -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;
}
}

View file

@ -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());
@ -32,4 +53,4 @@ public class CertificateController {
return certificateRepository.findAll(); return certificateRepository.findAll();
} }
} }

View file

@ -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,8 +23,19 @@ 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();
} }
} }

View file

@ -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);
@ -39,4 +79,4 @@ public class GeoController {
return Collections.emptyList(); return Collections.emptyList();
} }
} }

View file

@ -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,18 +33,51 @@ 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();
} }
} }

View file

@ -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;
} }
}
}

View file

@ -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);
} }
}
}

View file

@ -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