diff --git a/.nais/dev.yml b/.nais/dev.yml index f398d63..297a60f 100644 --- a/.nais/dev.yml +++ b/.nais/dev.yml @@ -61,6 +61,8 @@ spec: rules: - application: veilarboppfolging namespace: poao + - application: syfooversiktsrv + namespace: teamsykefravr inbound: rules: - application: rpa-medlemskap-og-avgift @@ -85,6 +87,10 @@ spec: value: "http://veilarboppfolging.poao/veilarboppfolging/api" - name: OPPFOLGING_SCOPE value: "dev-gcp:poao:veilarboppfolging" + - name: SYFO_URL + value: "http://syfooversiktsrv.teamsykefravr/syfooversiktsrv/api" + - name: SYFO_SCOPE + value: "dev-gcp:teamsykefravr:syfooversiktsrv" - name: NOM_URL value: "https://nom-api.intern.dev.nav.no" - name: NOM_SCOPE diff --git a/.nais/prod.yml b/.nais/prod.yml index 457960d..fa0340b 100644 --- a/.nais/prod.yml +++ b/.nais/prod.yml @@ -61,6 +61,8 @@ spec: rules: - application: veilarboppfolging namespace: poao + - application: syfooversiktsrv + namespace: teamsykefravr inbound: rules: - application: rpa-medlemskap-og-avgift @@ -85,6 +87,10 @@ spec: value: "http://veilarboppfolging.poao/veilarboppfolging/api" - name: OPPFOLGING_SCOPE value: "prod-gcp:poao:veilarboppfolging" + - name: SYFO_URL + value: "http://syfooversiktsrv.teamsykefravr/syfooversiktsrv/api" + - name: SYFO_SCOPE + value: "prod-gcp:teamsykefravr:syfooversiktsrv" - name: NOM_URL value: "https://nom-api.intern.nav.no" - name: NOM_SCOPE diff --git a/src/main/kotlin/no/nav/Application.kt b/src/main/kotlin/no/nav/Application.kt index 758a67c..a277728 100644 --- a/src/main/kotlin/no/nav/Application.kt +++ b/src/main/kotlin/no/nav/Application.kt @@ -11,6 +11,7 @@ import no.nav.api.kontonummer.configureKontonummerRegisterRoutes import no.nav.api.oppfolging.configureOppfolgingRoutes import no.nav.api.pdl.configurePdlRoutes import no.nav.api.skrivestotte.configureSkrivestotteRoutes +import no.nav.api.syfo.configureSyfoRoutes import no.nav.api.utbetalinger.configureUtbetalingerRoutes import no.nav.plugins.* @@ -39,6 +40,7 @@ fun startApplication( configureDigdirRoutes(services.digdirService) configureSkrivestotteRoutes(services.skrivestotteService) configureUtbetalingerRoutes(services.utbetalingerService) + configureSyfoRoutes(services.syfoService) } } } diff --git a/src/main/kotlin/no/nav/Consumers.kt b/src/main/kotlin/no/nav/Consumers.kt index 92b3e95..03b9c44 100644 --- a/src/main/kotlin/no/nav/Consumers.kt +++ b/src/main/kotlin/no/nav/Consumers.kt @@ -8,6 +8,7 @@ import no.nav.api.oppfolging.Nom import no.nav.api.oppfolging.OppfolgingClient import no.nav.api.pdl.PdlClient import no.nav.api.skrivestotte.SkrivestotteClient +import no.nav.api.syfo.SyfoClient import no.nav.api.utbetalinger.UtbetalingerClient import no.nav.common.client.nom.NomClient import no.nav.common.token_client.builder.AzureAdTokenClientBuilder @@ -19,6 +20,7 @@ interface Consumers { val tokenclient: MachineToMachineTokenClient val oboTokenClient: OnBehalfOfTokenClient val oppfolgingClient: OppfolgingClient + val syfoClient: SyfoClient val nom: NomClient val skrivestotteClient: SkrivestotteClient val pdlClient: PdlClient @@ -45,6 +47,7 @@ class ConsumersImpl( .buildMachineToMachineTokenClient() override val oppfolgingClient: OppfolgingClient = OppfolgingClient(env.oppfolgingUrl, oboTokenClient.bindTo(env.oppfolgingScope)) + override val syfoClient: SyfoClient = SyfoClient(env.syfoUrl, oboTokenClient.bindTo(env.syfoScope)) override val nom: NomClient = Nom(env.nomUrl, tokenclient.bindTo(env.nomScope)).client override val skrivestotteClient: SkrivestotteClient = SkrivestotteClient(env.skrivestotteUrl) override val pdlClient: PdlClient = PdlClient(env.pdlUrl, oboTokenClient.bindTo(env.pdlScope)) diff --git a/src/main/kotlin/no/nav/Env.kt b/src/main/kotlin/no/nav/Env.kt index 448070c..e709541 100644 --- a/src/main/kotlin/no/nav/Env.kt +++ b/src/main/kotlin/no/nav/Env.kt @@ -12,6 +12,8 @@ interface Env { val jwksUrl: String val oppfolgingUrl: String val oppfolgingScope: DownstreamApi + val syfoUrl: String + val syfoScope: DownstreamApi val nomUrl: String val nomScope: DownstreamApi val pdlUrl: String @@ -34,6 +36,8 @@ class EnvImpl : Env { override val jwksUrl: String = getRequiredConfig("AZURE_OPENID_CONFIG_JWKS_URI") override val oppfolgingUrl: String = getRequiredConfig("OPPFOLGING_URL") override val oppfolgingScope: DownstreamApi = getRequiredConfig("OPPFOLGING_SCOPE").toDownstreamApi() + override val syfoUrl: String = getRequiredConfig("SYFO_URL") + override val syfoScope: DownstreamApi = getRequiredConfig("SYFO_SCOPE").toDownstreamApi() override val nomUrl: String = getRequiredConfig("NOM_URL") override val nomScope: DownstreamApi = getRequiredConfig("NOM_SCOPE").toDownstreamApi() override val pdlUrl: String = getRequiredConfig("PDL_URL") diff --git a/src/main/kotlin/no/nav/Services.kt b/src/main/kotlin/no/nav/Services.kt index 65804f3..54b0a79 100644 --- a/src/main/kotlin/no/nav/Services.kt +++ b/src/main/kotlin/no/nav/Services.kt @@ -7,10 +7,12 @@ import no.nav.api.digdir.DigdirService import no.nav.api.oppfolging.OppfolgingService import no.nav.api.pdl.PdlService import no.nav.api.skrivestotte.SkrivestotteService +import no.nav.api.syfo.SyfoService import no.nav.api.utbetalinger.UtbetalingerService interface Services { val oppfolgingService: OppfolgingService + val syfoService: SyfoService val skrivestotteService: SkrivestotteService val digdirService: DigdirService val pdlService: PdlService @@ -28,6 +30,7 @@ class ServicesImpl( consumers.oppfolgingClient, consumers.nom, ) + override val syfoService = SyfoService(consumers.syfoClient, consumers.nom) override val skrivestotteService = SkrivestotteService(consumers.skrivestotteClient) override val digdirService = DigdirService(consumers.digdirClient) override val pdlService = PdlService(consumers.pdlClient) diff --git a/src/main/kotlin/no/nav/api/syfo/SyfoClient.kt b/src/main/kotlin/no/nav/api/syfo/SyfoClient.kt new file mode 100644 index 0000000..9a4cf58 --- /dev/null +++ b/src/main/kotlin/no/nav/api/syfo/SyfoClient.kt @@ -0,0 +1,64 @@ +package no.nav.api.syfo + +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.request.* +import io.ktor.http.* +import kotlinx.serialization.Serializable +import no.nav.utils.* + +class SyfoClient( + private val syfoUrl: String, + private val oboTokenProvider: BoundedOnBehalfOfTokenClient, +) { + @Serializable + class PersonIdentValue( + val value: String, + ) + + @Serializable + class SyfoTildeling( + val personIdent: PersonIdentValue, + val tildeltVeilederIdent: String?, + val tildeltenhet: String?, + ) + + private val client = + HttpClient(OkHttp) { + installContentNegotiationAndIgnoreUnknownKeys() + engine { + addInterceptor(XCorrelationIdInterceptor()) + addInterceptor( + LoggingInterceptor( + name = "isyfooversikt", + callIdExtractor = { getCallId() }, + ), + ) + addInterceptor( + HeadersInterceptor { + mapOf( + "Nav-Consumer-Id" to navConsumerId, + ) + }, + ) + } + } + + suspend fun hentSyfoVeileder( + fnr: String, + token: String, + ): SyfoTildeling? = + externalServiceCall { + val response = + client.get("$syfoUrl/v2/persontildeling/personer/single") { + header("nav-personident", fnr) + header("Authorization", "Bearer ${oboTokenProvider.exchangeOnBehalfOfToken(token)}") + } + when (response.status) { + HttpStatusCode.NoContent -> null + HttpStatusCode.OK -> response.body() + else -> error("Ukjent status code: ${response.status}") + } + } +} diff --git a/src/main/kotlin/no/nav/api/syfo/SyfoRoutes.kt b/src/main/kotlin/no/nav/api/syfo/SyfoRoutes.kt new file mode 100644 index 0000000..122d570 --- /dev/null +++ b/src/main/kotlin/no/nav/api/syfo/SyfoRoutes.kt @@ -0,0 +1,45 @@ +package no.nav.api.syfo + +import io.bkbn.kompendium.core.metadata.PostInfo +import io.bkbn.kompendium.core.plugin.NotarizedRoute +import io.ktor.http.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import no.nav.api.CommonModels +import no.nav.models.FnrRequest +import no.nav.models.deserializeFnr +import no.nav.utils.getJWT +import kotlin.reflect.typeOf + +fun Route.configureSyfoRoutes(syfoService: SyfoService) { + route("syfo/veileder") { + install(NotarizedRoute()) { + post = ApiV1.veileder + } + post { + val payload = call.getJWT() + val fnr = call.deserializeFnr() ?: return@post call.respond(HttpStatusCode.BadRequest) + val veileder = syfoService.hentVeileder(fnr, payload) + call.respond(veileder ?: HttpStatusCode.NoContent) + } + } +} + +private object ApiV1 { + val veileder = + PostInfo.builder { + summary("Brukers sykefraværsoppfølgingveileder") + description("Hentes fra isyfo") + request { + requestType(typeOf()) + description("Brukers ident") + } + response { + responseType(typeOf()) + responseCode(HttpStatusCode.OK) + description("Navn og ident til brukers veileder dersom bruker er tildelt veileder") + } + tags("Syfo") + canRespond(CommonModels.standardResponses) + } +} diff --git a/src/main/kotlin/no/nav/api/syfo/SyfoService.kt b/src/main/kotlin/no/nav/api/syfo/SyfoService.kt new file mode 100644 index 0000000..a098d53 --- /dev/null +++ b/src/main/kotlin/no/nav/api/syfo/SyfoService.kt @@ -0,0 +1,36 @@ +package no.nav.api.syfo + +import kotlinx.serialization.Serializable +import no.nav.common.client.nom.NomClient +import no.nav.common.types.identer.NavIdent +import no.nav.utils.externalServiceCall + +class SyfoService( + private val syfoClient: SyfoClient, + private val nom: NomClient, +) { + @Serializable + class Veileder( + val ident: String, + val fornavn: String, + val etternavn: String, + ) + + suspend fun hentVeileder( + fnr: String, + token: String, + ): Veileder? = + externalServiceCall { + val veileder = syfoClient.hentSyfoVeileder(fnr, token) + veileder + ?.tildeltVeilederIdent + ?.let { nom.finnNavn(NavIdent(it)) } + ?.let { + Veileder( + ident = it.navIdent.get(), + fornavn = it.fornavn, + etternavn = it.etternavn, + ) + } + } +} diff --git a/src/test/kotlin/no/nav/mock/MockConsumers.kt b/src/test/kotlin/no/nav/mock/MockConsumers.kt index bbdbc62..136dbc4 100644 --- a/src/test/kotlin/no/nav/mock/MockConsumers.kt +++ b/src/test/kotlin/no/nav/mock/MockConsumers.kt @@ -26,6 +26,7 @@ import no.nav.api.oppfolging.OppfolgingClient import no.nav.api.pdl.PdlClient import no.nav.api.skrivestotte.SkrivestotteClient import no.nav.api.skrivestotte.SkrivestotteClient.* +import no.nav.api.syfo.SyfoClient import no.nav.api.utbetalinger.UtbetalingerClient import no.nav.api.utbetalinger.utbetalinger import no.nav.common.client.nom.NomClient @@ -41,6 +42,7 @@ object MockConsumers : Consumers { override val tokenclient = tokenClientMock override val oboTokenClient = oboTokenClientMock override val oppfolgingClient = oppfolgingClientMock + override val syfoClient = syfoClientMock override val kontonummerRegister = kontonummerRegisterMock override val nom: NomClient = nomClientMock override val skrivestotteClient = skrivestotteClientMock @@ -73,6 +75,16 @@ private val oppfolgingClientMock = ) } +private val syfoClientMock = + mockOf { client -> + coEvery { client.hentSyfoVeileder(any(), any()) } returns + SyfoClient.SyfoTildeling( + personIdent = SyfoClient.PersonIdentValue("10108000398"), + tildeltVeilederIdent = "Z123456", + tildeltenhet = "4403", + ) + } + private val kontonummerRegisterMock = mockOf { kontonummerRegister -> coEvery { kontonummerRegister.hentKontonummer(any(), any(), any()) } returns diff --git a/src/test/kotlin/no/nav/mock/MockEnv.kt b/src/test/kotlin/no/nav/mock/MockEnv.kt index b3ff611..b923b22 100644 --- a/src/test/kotlin/no/nav/mock/MockEnv.kt +++ b/src/test/kotlin/no/nav/mock/MockEnv.kt @@ -10,6 +10,8 @@ object MockEnv : Env { override val kontonummerRegisterScope = DownstreamApi.parse("::") override val oppfolgingUrl: String = "" override val oppfolgingScope: DownstreamApi = DownstreamApi.parse("::") + override val syfoUrl: String = "" + override val syfoScope: DownstreamApi = DownstreamApi.parse("::") override val nomUrl: String = "" override val nomScope: DownstreamApi = DownstreamApi.parse("::") override val pdlUrl: String = ""