diff --git a/.nais/app.yaml b/.nais/app.yaml index 1c9cf56..cddd428 100644 --- a/.nais/app.yaml +++ b/.nais/app.yaml @@ -73,3 +73,7 @@ spec: value: api://dev-gcp.aap.behandlingsflyt/.default - name: INTEGRASJON_BEHANDLINGSFLYT_AZP value: dev-gcp:aap:behandlingsflyt + - name: INTEGRASJON_BREV_SANITY_PROXY_URL + value: http://brev-sanity-proxy + - name: INTEGRASJON_BREV_SANITY_PROXY_SCOPE + value: api://dev-gcp.aap.brev-sanity-proxy/.default diff --git a/app/src/main/kotlin/no/nav/aap/brev/App.kt b/app/src/main/kotlin/no/nav/aap/brev/App.kt index 7279aa8..64831f7 100644 --- a/app/src/main/kotlin/no/nav/aap/brev/App.kt +++ b/app/src/main/kotlin/no/nav/aap/brev/App.kt @@ -1,7 +1,6 @@ package no.nav.aap.brev import com.papsign.ktor.openapigen.route.apiRouting -import com.papsign.ktor.openapigen.route.path.normal.post import com.papsign.ktor.openapigen.route.response.respond import com.papsign.ktor.openapigen.route.route import com.zaxxer.hikari.HikariConfig @@ -17,6 +16,12 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import io.micrometer.prometheusmetrics.PrometheusConfig import io.micrometer.prometheusmetrics.PrometheusMeterRegistry +import no.nav.aap.brev.api.BestillBrevRequest +import no.nav.aap.brev.api.BestillBrevResponse +import no.nav.aap.brev.api.ErrorRespons +import no.nav.aap.brev.domene.BrevbestillingReferanse +import no.nav.aap.brev.innhold.BrevinnholdService +import no.nav.aap.brev.innhold.SanityBrevinnholdGateway import no.nav.aap.komponenter.commonKtorModule import no.nav.aap.komponenter.config.requiredConfigForKey import no.nav.aap.komponenter.dbmigrering.Migrering @@ -33,10 +38,22 @@ class App fun main() { Thread.currentThread().setUncaughtExceptionHandler { _, e -> SECURE_LOGGER.error("Uhåndtert feil", e) } - embeddedServer(Netty, port = 8080) { server(DbConfig()) }.start(wait = true) + + val brevinnholdGateway = SanityBrevinnholdGateway() + val brevinnholdService = BrevinnholdService(brevinnholdGateway) + + embeddedServer(Netty, port = 8080) { + server( + DbConfig(), + brevinnholdService, + ) + }.start(wait = true) } -internal fun Application.server(dbConfig: DbConfig) { +internal fun Application.server( + dbConfig: DbConfig, + brevinnholdService: BrevinnholdService, +) { val prometheus = PrometheusMeterRegistry(PrometheusConfig.DEFAULT) commonKtorModule(prometheus, AzureConfig(), "AAP - Brev") @@ -65,7 +82,15 @@ internal fun Application.server(dbConfig: DbConfig) { route("/api") { route("/bestill") { authorizedPostWithApprovedList(behandlingsflytAzp) { _, request -> - respond(BestillBrevResponse(UUID.randomUUID()), HttpStatusCode.Created) + brevinnholdService.behandleBrevbestilling( + request.behandlingReferanse, + request.brevtype, + request.language, + ) + respond( + response = BestillBrevResponse(BrevbestillingReferanse(UUID.randomUUID())), + statusCode = HttpStatusCode.Created + ) } } } diff --git a/app/src/main/kotlin/no/nav/aap/brev/BestillBrevRequest.kt b/app/src/main/kotlin/no/nav/aap/brev/BestillBrevRequest.kt deleted file mode 100644 index 67b9a39..0000000 --- a/app/src/main/kotlin/no/nav/aap/brev/BestillBrevRequest.kt +++ /dev/null @@ -1,3 +0,0 @@ -package no.nav.aap.brev - -data class BestillBrevRequest(val behandlingId: String) diff --git a/app/src/main/kotlin/no/nav/aap/brev/BestillBrevResponse.kt b/app/src/main/kotlin/no/nav/aap/brev/BestillBrevResponse.kt deleted file mode 100644 index 1000c39..0000000 --- a/app/src/main/kotlin/no/nav/aap/brev/BestillBrevResponse.kt +++ /dev/null @@ -1,5 +0,0 @@ -package no.nav.aap.brev - -import java.util.UUID - -data class BestillBrevResponse(val bestillingsId: UUID) diff --git a/app/src/main/kotlin/no/nav/aap/brev/api/BestillBrevRequest.kt b/app/src/main/kotlin/no/nav/aap/brev/api/BestillBrevRequest.kt new file mode 100644 index 0000000..62ad7be --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/api/BestillBrevRequest.kt @@ -0,0 +1,11 @@ +package no.nav.aap.brev.api + +import no.nav.aap.brev.domene.BehandlingReferanse +import no.nav.aap.brev.domene.Brevtype +import no.nav.aap.brev.domene.Språk + +data class BestillBrevRequest( + val behandlingReferanse: BehandlingReferanse, + val brevtype: Brevtype, + val language: Språk, +) diff --git a/app/src/main/kotlin/no/nav/aap/brev/api/BestillBrevResponse.kt b/app/src/main/kotlin/no/nav/aap/brev/api/BestillBrevResponse.kt new file mode 100644 index 0000000..36dc658 --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/api/BestillBrevResponse.kt @@ -0,0 +1,6 @@ +package no.nav.aap.brev.api + +import no.nav.aap.brev.domene.BrevbestillingReferanse + + +data class BestillBrevResponse(val brevbestillingReferanse: BrevbestillingReferanse) diff --git a/app/src/main/kotlin/no/nav/aap/brev/ErrorRespons.kt b/app/src/main/kotlin/no/nav/aap/brev/api/ErrorRespons.kt similarity index 62% rename from app/src/main/kotlin/no/nav/aap/brev/ErrorRespons.kt rename to app/src/main/kotlin/no/nav/aap/brev/api/ErrorRespons.kt index 534dc5d..518054a 100644 --- a/app/src/main/kotlin/no/nav/aap/brev/ErrorRespons.kt +++ b/app/src/main/kotlin/no/nav/aap/brev/api/ErrorRespons.kt @@ -1,3 +1,3 @@ -package no.nav.aap.brev +package no.nav.aap.brev.api data class ErrorRespons(val message: String?) diff --git a/app/src/main/kotlin/no/nav/aap/brev/domene/BehandlingReferanse.kt b/app/src/main/kotlin/no/nav/aap/brev/domene/BehandlingReferanse.kt new file mode 100644 index 0000000..b2134f6 --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/domene/BehandlingReferanse.kt @@ -0,0 +1,4 @@ +package no.nav.aap.brev.domene + +@JvmInline +value class BehandlingReferanse(val referanse: String) \ No newline at end of file diff --git a/app/src/main/kotlin/no/nav/aap/brev/domene/BrevbestillingReferanse.kt b/app/src/main/kotlin/no/nav/aap/brev/domene/BrevbestillingReferanse.kt new file mode 100644 index 0000000..7f40b21 --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/domene/BrevbestillingReferanse.kt @@ -0,0 +1,6 @@ +package no.nav.aap.brev.domene + +import java.util.UUID + +@JvmInline +value class BrevbestillingReferanse(val referanse: UUID) \ No newline at end of file diff --git a/app/src/main/kotlin/no/nav/aap/brev/domene/Brevinnhold.kt b/app/src/main/kotlin/no/nav/aap/brev/domene/Brevinnhold.kt new file mode 100644 index 0000000..439e188 --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/domene/Brevinnhold.kt @@ -0,0 +1,3 @@ +package no.nav.aap.brev.domene + +data class Brevinnhold(val innhold: String) diff --git a/app/src/main/kotlin/no/nav/aap/brev/domene/Brevtype.kt b/app/src/main/kotlin/no/nav/aap/brev/domene/Brevtype.kt new file mode 100644 index 0000000..9862c0c --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/domene/Brevtype.kt @@ -0,0 +1,6 @@ +package no.nav.aap.brev.domene + +enum class Brevtype { + INNVILGELSE, + AVSLAG, +} \ No newline at end of file diff --git "a/app/src/main/kotlin/no/nav/aap/brev/domene/Spr\303\245k.kt" "b/app/src/main/kotlin/no/nav/aap/brev/domene/Spr\303\245k.kt" new file mode 100644 index 0000000..7d0d3c0 --- /dev/null +++ "b/app/src/main/kotlin/no/nav/aap/brev/domene/Spr\303\245k.kt" @@ -0,0 +1,5 @@ +package no.nav.aap.brev.domene + +enum class Språk { + EN, NB, NN +} \ No newline at end of file diff --git a/app/src/main/kotlin/no/nav/aap/brev/innhold/BrevinnholdGateway.kt b/app/src/main/kotlin/no/nav/aap/brev/innhold/BrevinnholdGateway.kt new file mode 100644 index 0000000..6843fc3 --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/innhold/BrevinnholdGateway.kt @@ -0,0 +1,9 @@ +package no.nav.aap.brev.innhold + +import no.nav.aap.brev.domene.Brevinnhold +import no.nav.aap.brev.domene.Brevtype +import no.nav.aap.brev.domene.Språk + +interface BrevinnholdGateway { + fun hentBrev(brevtype: Brevtype, språk: Språk): Brevinnhold +} \ No newline at end of file diff --git a/app/src/main/kotlin/no/nav/aap/brev/innhold/BrevinnholdService.kt b/app/src/main/kotlin/no/nav/aap/brev/innhold/BrevinnholdService.kt new file mode 100644 index 0000000..d32c978 --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/innhold/BrevinnholdService.kt @@ -0,0 +1,21 @@ +package no.nav.aap.brev.innhold + +import no.nav.aap.brev.domene.BehandlingReferanse +import no.nav.aap.brev.domene.Brevtype +import no.nav.aap.brev.domene.Språk +import org.slf4j.LoggerFactory + +class BrevinnholdService(private val brevinnholdGateway: BrevinnholdGateway) { + + private val log = LoggerFactory.getLogger(BrevinnholdService::class.java) + + fun behandleBrevbestilling( + behandlingReferanse: BehandlingReferanse, + brevtype: Brevtype, + språk: Språk, + ) { + log.info("Henter brevinnhold for behandlingReferanse=$behandlingReferanse brevtype=$brevtype språk=$språk") + val brevinnhold = brevinnholdGateway.hentBrev(brevtype, språk) + log.info("Hentet brevinnhold: ${brevinnhold.innhold}") + } +} diff --git a/app/src/main/kotlin/no/nav/aap/brev/innhold/SanityBrevinnholdGateway.kt b/app/src/main/kotlin/no/nav/aap/brev/innhold/SanityBrevinnholdGateway.kt new file mode 100644 index 0000000..a369a01 --- /dev/null +++ b/app/src/main/kotlin/no/nav/aap/brev/innhold/SanityBrevinnholdGateway.kt @@ -0,0 +1,39 @@ +package no.nav.aap.brev.innhold + +import no.nav.aap.brev.domene.Brevinnhold +import no.nav.aap.brev.domene.Brevtype +import no.nav.aap.brev.domene.Språk +import no.nav.aap.komponenter.config.requiredConfigForKey +import no.nav.aap.komponenter.httpklient.httpclient.ClientConfig +import no.nav.aap.komponenter.httpklient.httpclient.Header +import no.nav.aap.komponenter.httpklient.httpclient.RestClient +import no.nav.aap.komponenter.httpklient.httpclient.request.GetRequest +import no.nav.aap.komponenter.httpklient.httpclient.tokenprovider.azurecc.ClientCredentialsTokenProvider +import no.nav.aap.komponenter.httpklient.json.DefaultJsonMapper +import java.net.URI + +class SanityBrevinnholdGateway : BrevinnholdGateway { + + private val baseUri = URI.create(requiredConfigForKey("integrasjon.brev_sanity_proxy.url")) + val config = ClientConfig(scope = requiredConfigForKey("integrasjon.brev_sanity_proxy.scope")) + private val client = RestClient.withDefaultResponseHandler( + config = config, + tokenProvider = ClientCredentialsTokenProvider + ) + + override fun hentBrev( + brevtype: Brevtype, + språk: Språk + ): Brevinnhold { + val uri = baseUri.resolve("/api/brev?brevtype=$brevtype&language=$språk") + val httpRequest = GetRequest( + additionalHeaders = listOf( + Header("Accept", "application/json") + ) + ) + + return requireNotNull(client.get(uri = uri, request = httpRequest, mapper = { body, _ -> + DefaultJsonMapper.fromJson(body) + })) + } +} diff --git a/app/src/test/kotlin/no/nav/aap/brev/AppTest.kt b/app/src/test/kotlin/no/nav/aap/brev/AppTest.kt index 0c794cd..52cf750 100644 --- a/app/src/test/kotlin/no/nav/aap/brev/AppTest.kt +++ b/app/src/test/kotlin/no/nav/aap/brev/AppTest.kt @@ -1,25 +1,30 @@ package no.nav.aap.brev -import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* +import no.nav.aap.brev.api.BestillBrevRequest +import no.nav.aap.brev.api.BestillBrevResponse +import no.nav.aap.brev.domene.BehandlingReferanse +import no.nav.aap.brev.domene.Brevtype +import no.nav.aap.brev.domene.Språk +import no.nav.aap.brev.innhold.BrevinnholdService +import no.nav.aap.brev.innhold.SanityBrevinnholdGateway import no.nav.aap.brev.no.nav.aap.brev.test.Fakes import no.nav.aap.komponenter.httpklient.httpclient.ClientConfig import no.nav.aap.komponenter.httpklient.httpclient.RestClient import no.nav.aap.komponenter.httpklient.httpclient.error.DefaultResponseHandler +import no.nav.aap.komponenter.httpklient.httpclient.request.PostRequest import no.nav.aap.komponenter.httpklient.httpclient.tokenprovider.azurecc.ClientCredentialsTokenProvider +import no.nav.aap.komponenter.httpklient.json.DefaultJsonMapper import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Test -import java.net.URI -import org.assertj.core.api.Assertions.assertThat import org.testcontainers.containers.PostgreSQLContainer import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpResponse +import java.net.URI import java.time.Duration import java.time.temporal.ChronoUnit +import kotlin.test.assertNotNull class AppTest { @@ -30,7 +35,9 @@ class AppTest { jdbcUrl = "${postgres.jdbcUrl}&user=${postgres.username}&password=${postgres.password}", ) - val httpClient = HttpClient.newBuilder().build() + val brevinnholdGateway = SanityBrevinnholdGateway() + val brevinnholdService = BrevinnholdService(brevinnholdGateway) + private val restClient = RestClient( config = ClientConfig(scope = "brev"), tokenProvider = ClientCredentialsTokenProvider, @@ -39,7 +46,10 @@ class AppTest { // Starter server private val server = embeddedServer(Netty, port = 8080) { - server(dbConfig = dbConfig) + server( + dbConfig = dbConfig, + brevinnholdService = brevinnholdService, + ) module(fakes) }.start() @@ -53,16 +63,17 @@ class AppTest { } @Test - fun `appen er ready`() { - val res = httpClient.send( - HttpRequest.newBuilder( - URI.create("http://localhost:8080/") - .resolve("/actuator/ready"), - ).GET() - .build(), - HttpResponse.BodyHandlers.ofString() + fun `bestiller brev`() { + val res: BestillBrevResponse? = restClient.post( + uri = URI.create("http://localhost:8080/").resolve("/api/bestill"), + request = PostRequest( + body = BestillBrevRequest(BehandlingReferanse("123"), Brevtype.INNVILGELSE, Språk.NB) + ), + mapper = { body, _ -> + DefaultJsonMapper.fromJson(body) + } ) - assertThat(res.statusCode()).isEqualTo(HttpStatusCode.OK.value) + assertNotNull(res) } } diff --git a/app/src/test/kotlin/no/nav/aap/brev/TestApp.kt b/app/src/test/kotlin/no/nav/aap/brev/TestApp.kt index 7566ea6..0e2d0d4 100644 --- a/app/src/test/kotlin/no/nav/aap/brev/TestApp.kt +++ b/app/src/test/kotlin/no/nav/aap/brev/TestApp.kt @@ -4,6 +4,8 @@ import io.ktor.server.application.Application import io.ktor.server.application.ApplicationStopped import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty +import no.nav.aap.brev.innhold.BrevinnholdService +import no.nav.aap.brev.innhold.SanityBrevinnholdGateway import no.nav.aap.brev.no.nav.aap.brev.test.Fakes import org.testcontainers.containers.PostgreSQLContainer import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy @@ -19,11 +21,15 @@ fun main() { val dbConfig = DbConfig( jdbcUrl = "${postgres.jdbcUrl}&user=${postgres.username}&password=${postgres.password}", ) + val brevinnholdGateway = SanityBrevinnholdGateway() + val brevinnholdService = BrevinnholdService(brevinnholdGateway) + // Useful for connecting to the test database locally // jdbc URL contains the host and port and database name. println("jdbcUrl: ${postgres.jdbcUrl}. Password: ${postgres.password}. Username: ${postgres.username}.") server( - dbConfig + dbConfig = dbConfig, + brevinnholdService = brevinnholdService, ) module(fakes) }.start(wait = true) diff --git a/lib-test/src/main/kotlin/no/nav/aap/brev/test/Fakes.kt b/lib-test/src/main/kotlin/no/nav/aap/brev/test/Fakes.kt index b1c81cf..5b30c79 100644 --- a/lib-test/src/main/kotlin/no/nav/aap/brev/test/Fakes.kt +++ b/lib-test/src/main/kotlin/no/nav/aap/brev/test/Fakes.kt @@ -11,7 +11,8 @@ import io.ktor.server.request.receive import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.coroutines.runBlocking -import no.nav.aap.brev.ErrorRespons +import no.nav.aap.brev.api.ErrorRespons +import no.nav.aap.brev.domene.Brevinnhold import no.nav.aap.brev.test.AZURE_JWKS import no.nav.aap.brev.test.AzureTokenGen import org.slf4j.Logger @@ -24,6 +25,7 @@ class Fakes(azurePort: Int = 0) : AutoCloseable { private val azure = embeddedServer(Netty, port = azurePort, module = { azureFake() }).start() private val behandlingsflyt = embeddedServer(Netty, port = 0, module = { behandlingsflytFake() }).apply { start() } private val tilgang = embeddedServer(Netty, port = 0, module = { tilgangFake() }).apply { start() } + private val brevSanityProxy = embeddedServer(Netty, port = 0, module = { brevSanityProxyFake() }).apply { start() } init { Thread.currentThread().setUncaughtExceptionHandler { _, e -> log.error("Uhåndtert feil", e) } @@ -43,12 +45,18 @@ class Fakes(azurePort: Int = 0) : AutoCloseable { System.setProperty("integrasjon.tilgang.url", "http://localhost:${tilgang.port()}") System.setProperty("integrasjon.tilgang.scope", "scope") System.setProperty("integrasjon.tilgang.azp", "azp") + + // Brev sanity proxy + System.setProperty("integrasjon.brev_sanity_proxy.url", "http://localhost:${brevSanityProxy.port()}") + System.setProperty("integrasjon.brev_sanity_proxy.scope", "scope") + System.setProperty("integrasjon.brev_sanity_proxy.azp", "azp") } override fun close() { azure.stop(0L, 0L) behandlingsflyt.stop(0L, 0L) tilgang.stop(0L, 0L) + brevSanityProxy.stop(0L, 0L) } private fun NettyApplicationEngine.port(): Int = @@ -117,6 +125,27 @@ class Fakes(azurePort: Int = 0) : AutoCloseable { } } + fun Application.brevSanityProxyFake() { + install(ContentNegotiation) { + jackson() + } + install(StatusPages) { + exception { call, cause -> + this@brevSanityProxyFake.log.info( + "BREV_SANITY_PROXY :: Ukjent feil ved kall til '{}'", + call.request.local.uri, + cause + ) + call.respond(status = HttpStatusCode.InternalServerError, message = ErrorRespons(cause.message)) + } + } + routing { + get("/api/brev") { + call.respond(Brevinnhold("brevinnhold")) + } + } + } + internal data class TestToken( val access_token: String,