diff --git a/.nais/app-dev.yml b/.nais/app-dev.yml index 2356b4b..9b7e42d 100644 --- a/.nais/app-dev.yml +++ b/.nais/app-dev.yml @@ -26,7 +26,7 @@ spec: enabled: true maskinporten: enabled: true - scopes: + scopes: # Når du legger til en konsument her, husk å legge inn i Consumers-object exposes: - name: "afpprivat.read" enabled: true diff --git a/.nais/app-prod.yml b/.nais/app-prod.yml index fda0e14..69aa707 100644 --- a/.nais/app-prod.yml +++ b/.nais/app-prod.yml @@ -26,7 +26,7 @@ spec: enabled: true maskinporten: enabled: true - scopes: + scopes: # Når du legger til en konsument her, husk å legge inn i Consumers-object exposes: - name: "afpprivat.read" enabled: true diff --git a/README.md b/README.md index 678e5fb..d91275c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Når Kelvin er ferdig vil data begynne å flyte derfra, men tanken er at dette i ## URL som benyttes Base-URL for AAP-API er: - For test: https://aap-api.ekstern.dev.nav.no/ -- For prod: Ikke i prod enda +- For prod: https://aap-api.nav.no/ ## Beskrivelse av uttrekk Beskrivelse av uttrekk kan finnes på vår [Swagger dokumentasjon](https://aap-api.ekstern.dev.nav.no/swagger). diff --git a/app/main/api/App.kt b/app/main/api/App.kt index 9b8ff35..b426061 100644 --- a/app/main/api/App.kt +++ b/app/main/api/App.kt @@ -72,6 +72,6 @@ fun Application.api() { actuator(prometheus) swaggerUI(path = "swagger", swaggerFile = "openapi.yaml") - afp(config, arenaRestClient, sporingsloggKafkaClient, prometheus) + afp(config.sporingslogg.enabled, arenaRestClient, sporingsloggKafkaClient, prometheus) } } diff --git a/app/main/api/afp/Routes.kt b/app/main/api/afp/Routes.kt index ed8d302..f0a16ec 100644 --- a/app/main/api/afp/Routes.kt +++ b/app/main/api/afp/Routes.kt @@ -4,16 +4,30 @@ import api.arena.ArenaoppslagRestClient import api.auth.MASKINPORTEN_AFP_OFFENTLIG import api.auth.MASKINPORTEN_AFP_PRIVAT import api.sporingslogg.SporingsloggKafkaClient -import api.util.Config import api.auth.hentConsumerId +import api.sporingslogg.Spor +import api.sporingslogg.SporingsloggException +import api.util.Consumers.getConsumerTag +import api.util.httpCallCounter +import api.util.httpFailedCallCounter +import api.util.sporingsloggFailCounter +import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.auth.* +import io.ktor.server.request.* +import io.ktor.server.response.* import io.ktor.server.routing.* import io.micrometer.prometheus.PrometheusMeterRegistry +import org.slf4j.LoggerFactory +import java.lang.Exception +import java.util.* + +private val secureLog = LoggerFactory.getLogger("secureLog") +private val logger = LoggerFactory.getLogger("App") fun Route.afp( - config: Config, + brukSporingslogg: Boolean, arenaoppslagRestClient: ArenaoppslagRestClient, sporingsloggClient: SporingsloggKafkaClient, prometheus: PrometheusMeterRegistry @@ -21,27 +35,49 @@ fun Route.afp( route("/afp") { authenticate(MASKINPORTEN_AFP_PRIVAT) { post("/fellesordningen") { - Afp.doWorkFellesOrdningen(call, config, arenaoppslagRestClient, sporingsloggClient, prometheus) + hentPerioder(call, brukSporingslogg, arenaoppslagRestClient, sporingsloggClient, prometheus) } } authenticate(MASKINPORTEN_AFP_OFFENTLIG) { post("/offentlig") { - Afp.doWorkOffentlig( - call, - config, - arenaoppslagRestClient, - sporingsloggClient, - prometheus, - orgnr = call.hentConsumerId() - ) + hentPerioder(call, brukSporingslogg, arenaoppslagRestClient, sporingsloggClient, prometheus) } } } +} + +private suspend fun hentPerioder( + call: ApplicationCall, + brukSporingslogg: Boolean, + arenaoppslagRestClient: ArenaoppslagRestClient, + sporingsloggClient: SporingsloggKafkaClient, + prometheus: PrometheusMeterRegistry +) { + val orgnr = call.hentConsumerId() + val consumerTag = getConsumerTag(orgnr) - authenticate(MASKINPORTEN_AFP_PRIVAT) { - post("/fellesordning-for-afp") { - Afp.doWorkFellesOrdningen(call, config, arenaoppslagRestClient, sporingsloggClient, prometheus) + prometheus.httpCallCounter(consumerTag, call.request.path()).increment() + val body = call.receive() + val callId = requireNotNull(call.request.header("x-callid")) { "x-callid ikke satt" } + runCatching { + arenaoppslagRestClient.hentVedtakFellesordning(UUID.fromString(callId), body) + }.onFailure { ex -> + prometheus.httpFailedCallCounter(consumerTag, call.request.path()).increment() + secureLog.error("Klarte ikke hente vedtak fra Arena", ex) + throw ex + }.onSuccess { res -> + if (brukSporingslogg) { + try { + sporingsloggClient.send(Spor.opprett(body.personidentifikator, res, orgnr)) + call.respond(res) + } catch (e: Exception) { + prometheus.sporingsloggFailCounter(consumerTag).increment() + throw SporingsloggException(e) + } + } else { + logger.warn("Sporingslogg er skrudd av, returnerer data uten å sende til kafka") + call.respond(res) } } } diff --git a/app/main/api/afp/Worker.kt b/app/main/api/afp/Worker.kt deleted file mode 100644 index 85ba324..0000000 --- a/app/main/api/afp/Worker.kt +++ /dev/null @@ -1,93 +0,0 @@ -package api.afp - -import api.arena.ArenaoppslagRestClient -import api.sporingslogg.Spor -import api.sporingslogg.SporingsloggKafkaClient -import api.util.Config -import api.util.httpCallCounter -import api.util.httpFailedCallCounter -import api.util.sporingsloggFailCounter -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.micrometer.prometheus.PrometheusMeterRegistry -import org.slf4j.LoggerFactory -import java.lang.Exception -import java.util.* - -private val secureLog = LoggerFactory.getLogger("secureLog") -private val logger = LoggerFactory.getLogger("App") -private const val AFP_FELLERORDNINGEN_ORGNR = "987414502" - -private val consumerTags = mapOf( - AFP_FELLERORDNINGEN_ORGNR to "fellesordningen" -) - -object Afp { - private fun getConsumerTag(orgnr: String) = consumerTags[orgnr] ?: orgnr - - suspend fun doWorkOffentlig( - call: ApplicationCall, - config: Config, - arenaoppslagRestClient: ArenaoppslagRestClient, - sporingsloggClient: SporingsloggKafkaClient, - prometheus: PrometheusMeterRegistry, - orgnr: String - ) { - doWork( - call, config, arenaoppslagRestClient, sporingsloggClient, prometheus, orgnr - ) - } - - suspend fun doWorkFellesOrdningen( - call: ApplicationCall, - config: Config, - arenaoppslagRestClient: ArenaoppslagRestClient, - sporingsloggClient: SporingsloggKafkaClient, - prometheus: PrometheusMeterRegistry - ) { - doWork( - call, config, arenaoppslagRestClient, sporingsloggClient, prometheus, AFP_FELLERORDNINGEN_ORGNR - ) - } - - private suspend fun doWork( - call: ApplicationCall, - config: Config, - arenaoppslagRestClient: ArenaoppslagRestClient, - sporingsloggClient: SporingsloggKafkaClient, - prometheus: PrometheusMeterRegistry, - orgnr: String - ) { - val consumerTag = getConsumerTag(orgnr) - - prometheus.httpCallCounter(consumerTag, call.request.path()).increment() - val body = call.receive() - val callId = requireNotNull(call.request.header("x-callid")) { "x-callid ikke satt" } - runCatching { - arenaoppslagRestClient.hentVedtakFellesordning(UUID.fromString(callId), body) - }.onFailure { ex -> - prometheus.httpFailedCallCounter(consumerTag, call.request.path()).increment() - secureLog.error("Klarte ikke hente vedtak fra Arena", ex) - throw ex - }.onSuccess { res -> - if (config.sporingslogg.enabled) { - try { - sporingsloggClient.send(Spor.opprett(body.personidentifikator, res, orgnr)) - call.respond(res) - } catch (e: Exception) { - prometheus.sporingsloggFailCounter(consumerTag).increment() - secureLog.error("Klarte ikke produsere til kafka sporingslogg og kan derfor ikke returnere data", e) - call.respond( - HttpStatusCode.ServiceUnavailable, - "Feilet sporing av oppslag, kan derfor ikke returnere data. Feilen er på vår side, prøv igjen senere." - ) - } - } else { - logger.warn("Sporingslogg er skrudd av, returnerer data uten å sende til kafka") - call.respond(res) - } - } - } -} diff --git a/app/main/api/sporingslogg/SporingsloggKafkaClient.kt b/app/main/api/sporingslogg/SporingsloggKafkaClient.kt index 08d58f1..5da52c4 100644 --- a/app/main/api/sporingslogg/SporingsloggKafkaClient.kt +++ b/app/main/api/sporingslogg/SporingsloggKafkaClient.kt @@ -11,6 +11,8 @@ import org.apache.kafka.clients.producer.RecordMetadata import java.time.LocalDateTime import java.util.* +class SporingsloggException(cause: Throwable) : Exception(cause) + class SporingsloggKafkaClient(kafkaConf: KafkaConfig, private val sporingConf: SporingsloggConfig) { private val producer = KafkaFactory.createProducer("aap-api-producer-${sporingConf.topic}", kafkaConf) diff --git a/app/main/api/util/Consumers.kt b/app/main/api/util/Consumers.kt new file mode 100644 index 0000000..abedf83 --- /dev/null +++ b/app/main/api/util/Consumers.kt @@ -0,0 +1,19 @@ +package api.util + +import org.slf4j.LoggerFactory + +private val secureLog = LoggerFactory.getLogger("secureLog") + +object Consumers { + private const val NAV_ORGNR = "889640782" + private const val AFP_FELLERORDNINGEN_ORGNR = "987414502" + + private val consumerTags = mapOf( + NAV_ORGNR to "NAV", + AFP_FELLERORDNINGEN_ORGNR to "fellesordningen" + ) + + fun getConsumerTag(orgnr: String) = consumerTags[orgnr] ?: orgnr.also { + secureLog.warn("Prøver å lage prometheus tag for $orgnr, men den er ikke mappet opp.") + } +} \ No newline at end of file diff --git a/app/main/api/util/ErrorHandling.kt b/app/main/api/util/ErrorHandling.kt index e06fcbb..b757e10 100644 --- a/app/main/api/util/ErrorHandling.kt +++ b/app/main/api/util/ErrorHandling.kt @@ -1,6 +1,7 @@ package api.util import api.auth.SamtykkeIkkeGittException +import api.sporingslogg.SporingsloggException import io.ktor.http.* import io.ktor.server.plugins.statuspages.* import io.ktor.server.request.* @@ -8,6 +9,13 @@ import io.ktor.server.response.* import org.slf4j.Logger fun StatusPagesConfig.feilhåndtering(logger: Logger) { + exception { call, cause -> + logger.error("Klarte ikke produsere til kafka sporingslogg og kan derfor ikke returnere data", cause) + call.respondText( + text = "Feilet sporing av oppslag, kan derfor ikke returnere data. Feilen er på vår side, prøv igjen senere.", + status = HttpStatusCode.ServiceUnavailable + ) + } exception { call, cause -> logger.warn("Samtykke ikke gitt", cause) call.respondText(text = "Samtykke ikke gitt", status = HttpStatusCode.Forbidden) diff --git a/app/main/openapi.yaml b/app/main/openapi.yaml index b7d2137..0b11476 100644 --- a/app/main/openapi.yaml +++ b/app/main/openapi.yaml @@ -18,8 +18,12 @@ info: name: MIT url: https://opensource.org/license/mit/ tags: + - name: afp + description: AFP (avtalefestet pensjon) - name: fellesordningen description: Fellesordningen for AFP (afp.no) + - name: afpoffentlig + description: AFP i offentlig sektor servers: - url: https://aap-api.ekstern.dev.nav.no description: Test server @@ -29,8 +33,9 @@ paths: summary: Hent perioder med AAP tags: - fellesordningen + - afp description: |- - Henter perioder med vedtak for en person innen gitte datointerval. Denne er identisk til "/fellesordning-for-afp", men vi ønsker å bytte route for å forenkle vedlikehold. + Henter perioder med vedtak for en person innen gitte datointerval. Scope: `nav:aap:afpprivat.read` security: @@ -47,55 +52,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/FellesordningRequest' + $ref: '#/components/schemas/PeriodeRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/FellesordningResponse' - '400': - description: Feil i request - '401': - description: Mangler / feil maskinporten token eller samtykke-token - '403': - description: Feil scope i maskinporten token - '404': - description: Manglende URL / Person ikke funnet - '500': - description: Feil på tjeneste, meld fra og send med call-id - /fellesordning-for-afp: - post: - summary: Hent perioder med AAP - tags: - - fellesordningen - description: |- - Henter perioder med vedtak for en person innen gitte datointerval. - - Scope: `nav:aap:afpprivat.read` - security: - - maskinporten: [] - parameters: - - in: header - name: x-callid - description: En UUID konsumenten sender inn som brukes til videre logging og feilsøking. - schema: - type: string - format: uuid - required: true - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/FellesordningRequest' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/FellesordningResponse' + $ref: '#/components/schemas/PeriodeResponse' '400': description: Feil i request '401': @@ -110,7 +74,8 @@ paths: post: summary: Hent perioder med AAP tags: - - fellesordningen + - afpoffentlig + - afp description: |- Henter perioder med vedtak for en person innen gitte datointerval. @@ -129,14 +94,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/FellesordningRequest' + $ref: '#/components/schemas/PeriodeRequest' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/FellesordningResponse' + $ref: '#/components/schemas/PeriodeResponse' '400': description: Feil i request '401': @@ -149,15 +114,15 @@ paths: description: Feil på tjeneste, meld fra og send med call-id components: schemas: - FellesordningResponse: + PeriodeResponse: type: object properties: perioder: type: array description: Perioder med vedtak items: - $ref: '#/components/schemas/FellesordningPeriode' - FellesordningPeriode: + $ref: '#/components/schemas/Periode' + Periode: type: object properties: fraOgMedDato: @@ -171,7 +136,7 @@ components: nullable: true description: Til dato for perioden example: 2021-01-31 - FellesordningRequest: + PeriodeRequest: type: object properties: personidentifikator: