feat: Add GTIService to call GTI
This commit is contained in:
parent
8d1e5ab3c6
commit
99136e5401
|
@ -7,13 +7,14 @@
|
|||
<module name="routing.server.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="de.hbt.routing.MainApplication" />
|
||||
<extension name="net.ashald.envfile">
|
||||
<option name="IS_ENABLED" value="false" />
|
||||
<option name="IS_SUBST" value="false" />
|
||||
<option name="IS_ENABLED" value="true" />
|
||||
<option name="IS_SUBST" value="true" />
|
||||
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||
<ENTRIES>
|
||||
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
||||
<ENTRY IS_ENABLED="true" PARSER="env" IS_EXECUTABLE="false" PATH=".env" />
|
||||
</ENTRIES>
|
||||
</extension>
|
||||
<method v="2">
|
||||
|
|
|
@ -16,4 +16,6 @@ dependencies {
|
|||
implementation(libs.plugin.gradle.versions)
|
||||
implementation(libs.plugin.version.catalog)
|
||||
implementation(libs.plugin.jib)
|
||||
implementation(libs.plugin.openapi.generator)
|
||||
implementation(libs.swagger.parser)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
|
||||
|
||||
plugins {
|
||||
id("org.openapi.generator")
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
openApiGenerate {
|
||||
generatorName.set("kotlin")
|
||||
inputSpec.set("$rootDir/module-server/src/main/resources/gtiApiDoc.yaml")
|
||||
outputDir.set("${layout.buildDirectory.get()}/generated")
|
||||
apiPackage.set("de.hbt.geofox.gti.api")
|
||||
invokerPackage.set("de.hbt.geofox.gti.invoker")
|
||||
modelPackage.set("de.hbt.geofox.gti.model")
|
||||
configOptions.set(mapOf("serializationLibrary" to "jackson", "library" to "jvm-ktor"))
|
||||
globalProperties.set(mapOf("models" to ""))
|
||||
generateModelDocumentation.set(false)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
kotlin.srcDir("${layout.buildDirectory.get()}/generated/src/main/kotlin")
|
||||
}
|
||||
}
|
||||
|
||||
openApiValidate {
|
||||
inputSpec.set("$rootDir/module-server/src/main/resources/gtiApiDoc.yaml")
|
||||
}
|
||||
|
||||
tasks.named("compileKotlin") {
|
||||
dependsOn("openApiGenerate")
|
||||
}
|
||||
|
||||
tasks.withType<KaptGenerateStubsTask>().configureEach {
|
||||
dependsOn("openApiGenerate")
|
||||
}
|
|
@ -16,6 +16,7 @@ junit = "5.11.0"
|
|||
assertj = "3.26.3"
|
||||
mockito = "5.13.0"
|
||||
keycloak = "25.0.4"
|
||||
swagger-parser = "2.1.22"
|
||||
kotlin-logging = "3.0.5"
|
||||
kotlin-reflect = "2.0.20"
|
||||
kotlin-lombok = "1.9.0"
|
||||
|
@ -24,6 +25,7 @@ plugin-lombok = "8.0.1"
|
|||
plugin-gradle-versions = "0.46.0"
|
||||
plugin-version-catalog = "0.8.0"
|
||||
plugin-kotlin-gradle = "1.9.21"
|
||||
plugin-openapi-generator = "6.6.0"
|
||||
plugin-jib = "3.4.3"
|
||||
|
||||
[libraries]
|
||||
|
@ -44,6 +46,7 @@ spring-cloud-starter-bus-kafka = { module = "org.springframework.cloud:spring-cl
|
|||
spring-cloud-starter-config = { module = "org.springframework.cloud:spring-cloud-starter-config" }
|
||||
spring-cloud-config-server = { module = "org.springframework.cloud:spring-cloud-config-server" }
|
||||
spring-cloud-leader-election = { module = "org.springframework.cloud:spring-cloud-kubernetes-fabric8-leader" }
|
||||
swagger-parser = { module = "io.swagger.parser.v3:swagger-parser", version.ref = "swagger-parser" }
|
||||
spring-boot-starter = { module = "org.springframework.boot:spring-boot-starter" }
|
||||
spring-grpc = { module = "net.devh:grpc-spring-boot-starter", version.ref = "spring-grpc" }
|
||||
spring-ui = { module = "org.springdoc:springdoc-openapi-starter-webmvc-ui", version.ref = "spring-doc" }
|
||||
|
@ -89,6 +92,7 @@ plugin-lombok = { module = "io.freefair.gradle:lombok-plugin", version.ref = "pl
|
|||
plugin-gradle-versions = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "plugin-gradle-versions" }
|
||||
plugin-version-catalog = { module = "nl.littlerobots.vcu:plugin", version.ref = "plugin-version-catalog" }
|
||||
plugin-kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "plugin-kotlin-gradle" }
|
||||
plugin-openapi-generator = { module = "org.openapitools:openapi-generator-gradle-plugin", version.ref = "plugin-openapi-generator" }
|
||||
plugin-jib = { module = "com.google.cloud.tools:jib-gradle-plugin", version.ref = "plugin-jib" }
|
||||
|
||||
[bundles]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
id("hbt.spring-boot-cloud-application")
|
||||
id("hbt.kotlin")
|
||||
id("hbt.openapi")
|
||||
kotlin("kapt")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
package de.hbt.routing
|
||||
|
||||
import de.hbt.routing.service.GTIWrapper
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.web.client.RestTemplate
|
||||
|
||||
@RestController
|
||||
open class GTIProxyController(private val restTemplate: RestTemplate,
|
||||
private val gtiWrapper: GTIWrapper) {
|
||||
|
||||
@PostMapping(path = ["/gti/{requestMethod}"])
|
||||
fun requestGti(@PathVariable("requestMethod") method: String, @RequestBody body: String, @RequestHeader("api-key") key: String): String {
|
||||
val entity: HttpEntity<String> = gtiWrapper.sign(body, key)
|
||||
val result = restTemplate.postForEntity("https://gti.geofox.de/gti/public/${method}", entity, String::class.java)
|
||||
return result.body ?: ""
|
||||
}
|
||||
}
|
|
@ -1,15 +1,13 @@
|
|||
package de.hbt.routing.configuration
|
||||
|
||||
import de.hbt.routing.property.ServiceProperties
|
||||
import de.hbt.support.property.HealthCheckProperties
|
||||
import de.hbt.support.property.RestTemplateTimeoutProperties
|
||||
import de.hbt.support.property.StatusProbeProperties
|
||||
import de.hbt.support.property.TimeProperties
|
||||
import de.hbt.support.property.*
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(RestTemplateTimeoutProperties::class,
|
||||
StatusProbeProperties::class, TimeProperties::class,
|
||||
HealthCheckProperties::class, ServiceProperties::class)
|
||||
HealthCheckProperties::class, ServiceProperties::class,
|
||||
GtiProperties::class)
|
||||
open class PropertyConfiguration
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package de.hbt.routing.gti
|
||||
|
||||
import de.hbt.geofox.gti.model.*
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.client.RestClient
|
||||
|
||||
private const val GTI_PREFIX = "https://gti.geofox.de/gti/public"
|
||||
|
||||
@Service
|
||||
open class GTIService(private val restClient: RestClient) {
|
||||
|
||||
fun getRoute(start: SDName, destination: SDName, dateTime: GTITime): GRResponse {
|
||||
val request = GRRequest(
|
||||
start = start,
|
||||
dest = destination,
|
||||
time = dateTime,
|
||||
schedulesBefore = 0,
|
||||
schedulesAfter = 0,
|
||||
version = 58
|
||||
)
|
||||
val result = restClient.post()
|
||||
.uri("$GTI_PREFIX/getRoute")
|
||||
.body(request)
|
||||
.retrieve()
|
||||
return result.body(GRResponse::class.java)!!
|
||||
}
|
||||
|
||||
fun init(): InitResponse {
|
||||
val request = InitRequest()
|
||||
val result = restClient.post()
|
||||
.uri("$GTI_PREFIX/init")
|
||||
.body(request)
|
||||
.retrieve()
|
||||
return result.body(InitResponse::class.java)!!
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package de.hbt.routing.gti
|
||||
|
||||
import de.hbt.geofox.gti.model.InitResponse
|
||||
import io.swagger.v3.oas.annotations.Hidden
|
||||
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.tags.Tag
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = ["/gti"])
|
||||
@Tag(name = "Gti", description = "all requests that proxy GTI")
|
||||
class GtiProxyController(private val gtiService: GTIService) {
|
||||
|
||||
@Operation(
|
||||
summary = "Perform GTI init request",
|
||||
responses = [ApiResponse(
|
||||
responseCode = "200",
|
||||
description = "Init request successful",
|
||||
content = [Content(
|
||||
schema = Schema(implementation = InitResponse::class)
|
||||
)]
|
||||
)]
|
||||
)
|
||||
@Hidden
|
||||
@GetMapping("/init")
|
||||
fun init(): ResponseEntity<InitResponse> {
|
||||
val initResponse = gtiService.init()
|
||||
return ResponseEntity.ok(initResponse)
|
||||
}
|
||||
}
|
|
@ -82,6 +82,9 @@ resttemplate:
|
|||
connectionRestTemplateTimeoutInMillis: 5000
|
||||
|
||||
de.hbt.support.health.greeting: "Good morning"
|
||||
de.hbt.support.gti:
|
||||
user: hbt47
|
||||
secret: ${GTI_CLIENT_SECRET}
|
||||
|
||||
time:
|
||||
defaultTimeZone: Europe/Berlin
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,9 @@
|
|||
package de.hbt.support.configuration
|
||||
|
||||
import de.hbt.support.interceptor.GTIInterceptor
|
||||
import de.hbt.support.interceptor.HeaderInterceptorRest
|
||||
import de.hbt.support.interceptor.LoggingInterceptorRest
|
||||
import de.hbt.support.property.GtiProperties
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import java.time.Clock
|
||||
|
@ -17,4 +19,9 @@ open class InterceptorConfiguration {
|
|||
open fun headerInterceptorRest(): HeaderInterceptorRest {
|
||||
return HeaderInterceptorRest()
|
||||
}
|
||||
|
||||
@Bean
|
||||
open fun gtiInterceptor(properties: GtiProperties): GTIInterceptor {
|
||||
return GTIInterceptor(properties)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package de.hbt.support.configuration
|
||||
|
||||
import de.hbt.support.interceptor.GTIInterceptor
|
||||
import de.hbt.support.interceptor.HeaderInterceptorRest
|
||||
import de.hbt.support.interceptor.LoggingInterceptorRest
|
||||
import de.hbt.support.property.RestTemplateTimeoutProperties
|
||||
|
@ -14,11 +15,12 @@ open class RestClientConfiguration {
|
|||
open fun restClient(
|
||||
headerInterceptorRest: HeaderInterceptorRest,
|
||||
loggingInterceptor: LoggingInterceptorRest,
|
||||
gtiInterceptor: GTIInterceptor,
|
||||
restTemplateTimeoutProperties: RestTemplateTimeoutProperties
|
||||
): RestClient {
|
||||
return RestClient.builder()
|
||||
.messageConverters { it.add(Jaxb2RootElementHttpMessageConverter()) }
|
||||
.requestInterceptors { listOf(headerInterceptorRest, loggingInterceptor) }
|
||||
.requestInterceptors { it.addAll(listOf(headerInterceptorRest, gtiInterceptor, loggingInterceptor)) }
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package de.hbt.support.configuration
|
||||
|
||||
import de.hbt.support.interceptor.GTIInterceptor
|
||||
import de.hbt.support.interceptor.HeaderInterceptorRest
|
||||
import de.hbt.support.interceptor.LoggingInterceptorRest
|
||||
import de.hbt.support.property.RestTemplateTimeoutProperties
|
||||
|
@ -14,10 +15,11 @@ open class RestTemplateConfiguration {
|
|||
open fun restTemplate(
|
||||
headerInterceptorRest: HeaderInterceptorRest,
|
||||
loggingInterceptor: LoggingInterceptorRest,
|
||||
gtiInterceptor: GTIInterceptor,
|
||||
restTemplateTimeoutProperties: RestTemplateTimeoutProperties
|
||||
): RestTemplate {
|
||||
return RestTemplateBuilder()
|
||||
.additionalInterceptors(headerInterceptorRest, loggingInterceptor)
|
||||
.additionalInterceptors(headerInterceptorRest, loggingInterceptor, gtiInterceptor)
|
||||
.setConnectTimeout(restTemplateTimeoutProperties.connectionRestTemplateTimeoutInMillis)
|
||||
.setReadTimeout(restTemplateTimeoutProperties.readTimeoutRestTemplateInMillis)
|
||||
.build()
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.nio.file.Paths
|
|||
import java.util.jar.JarFile
|
||||
|
||||
@Controller
|
||||
@RequestMapping(value = ["/timetable"])
|
||||
@RequestMapping(value = ["/routing"])
|
||||
class VersionHtmlController {
|
||||
@GetMapping(path = ["/version"])
|
||||
fun version(): String {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package de.hbt.routing.service
|
||||
package de.hbt.support.interceptor
|
||||
|
||||
import de.hbt.support.property.GtiProperties
|
||||
import mu.KotlinLogging
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.http.HttpRequest
|
||||
import org.springframework.http.client.ClientHttpRequestExecution
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor
|
||||
import org.springframework.http.client.ClientHttpResponse
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.InvalidKeyException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
|
@ -12,26 +13,20 @@ import java.util.*
|
|||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
class GTIInterceptor(private val properties: GtiProperties) : ClientHttpRequestInterceptor {
|
||||
|
||||
@Service
|
||||
class GTIWrapper {
|
||||
|
||||
fun sign(body: String, key: String): HttpEntity<String> {
|
||||
val headers = HttpHeaders()
|
||||
headers.add("geofox-auth-user", "hbt47")
|
||||
headers.add("geofox-auth-signature", computeHmacSHA1(body, key))
|
||||
headers.contentType = MediaType.APPLICATION_JSON
|
||||
val entity: HttpEntity<String> = HttpEntity(body, headers)
|
||||
return entity
|
||||
override fun intercept(request: HttpRequest, body: ByteArray, execution: ClientHttpRequestExecution): ClientHttpResponse {
|
||||
request.headers.set("geofox-auth-user", properties.user)
|
||||
request.headers.set("geofox-auth-signature", computeHmacSHA1(body, properties.secret))
|
||||
return execution.execute(request, body)
|
||||
}
|
||||
|
||||
private fun computeHmacSHA1(body: String, key: String): String {
|
||||
private fun computeHmacSHA1(body: ByteArray, key: String): String {
|
||||
try {
|
||||
val mac = Mac.getInstance("HmacSHA1")
|
||||
val secretKeySpec = SecretKeySpec(key.toByteArray(StandardCharsets.UTF_8), "HmacSHA1")
|
||||
mac.init(secretKeySpec)
|
||||
val digest = mac.doFinal(body.toByteArray(StandardCharsets.UTF_8))
|
||||
val digest = mac.doFinal(body)
|
||||
return Base64.getEncoder().encodeToString(digest)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
logger.error("Error computing hmac", e)
|
||||
|
@ -47,4 +42,8 @@ class GTIWrapper {
|
|||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package de.hbt.support.property
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding
|
||||
|
||||
@ConfigurationProperties(prefix = "de.hbt.support.gti")
|
||||
@Schema(description = "Properties, to configure GTI service")
|
||||
data class GtiProperties @ConstructorBinding constructor(
|
||||
val user: String,
|
||||
val secret: String
|
||||
)
|
Loading…
Reference in New Issue