diff --git a/api/build.gradle.kts b/api/build.gradle.kts index bdc70f2a0c..52409bc561 100644 --- a/api/build.gradle.kts +++ b/api/build.gradle.kts @@ -5,6 +5,10 @@ val mockOauth2ServerVersion: String by project val tokenSupportVersion: String by project val valiktorVersion: String by project +plugins { + id("org.hidetake.swagger.generator") version "2.19.2" +} + tasks { test { environment("IDPORTEN_WELL_KNOWN_URL", "http://localhost:6666/idporten-issuer/.well-known/openid-configuration") @@ -13,12 +17,21 @@ tasks { } } +swaggerSources { + register("simba") { + setInputFile(file("src/main/resources/openapi/documentation.yaml")) + } +} + dependencies { + swaggerUI("org.webjars:swagger-ui:5.6.1") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-server-core:$ktorVersion") implementation("io.ktor:ktor-server-netty:$ktorVersion") implementation("io.ktor:ktor-server-status-pages:$ktorVersion") + implementation("io.ktor:ktor-server-swagger:$ktorVersion") implementation("io.lettuce:lettuce-core:$lettuceVersion") implementation("no.nav.helsearbeidsgiver:altinn-client:$altinnVersion") implementation("no.nav.security:token-client-core:$tokenSupportVersion") @@ -29,6 +42,4 @@ dependencies { testImplementation("io.ktor:ktor-client-core:$ktorVersion") testImplementation("io.ktor:ktor-server-test-host:$ktorVersion") testImplementation("no.nav.security:mock-oauth2-server:$mockOauth2ServerVersion") - testImplementation(project(":felles-test")) - testImplementation(kotlin("test")) } diff --git a/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/App.kt b/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/App.kt index 2fe445b472..baa95c2fd3 100644 --- a/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/App.kt +++ b/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/App.kt @@ -10,6 +10,7 @@ import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.statuspages.StatusPages +import io.ktor.server.plugins.swagger.swaggerUI import io.ktor.server.response.respondText import io.ktor.server.routing.get import io.ktor.server.routing.route @@ -58,6 +59,9 @@ fun startServer(env: Map = System.getenv()) { } fun Application.apiModule(rapid: RapidsConnection) { + val redisPoller = RedisPoller() + val tilgangCache = LocalCache(60.minutes, 100) + customAuthentication() install(ContentNegotiation) { @@ -76,23 +80,17 @@ fun Application.apiModule(rapid: RapidsConnection) { HelsesjekkerRouting() routing { - get("/") { - call.respondText("helsearbeidsgiver inntektsmelding") - } - - val redisPoller = RedisPoller() - - val tilgangCache = LocalCache(60.minutes, 100) - authenticate { route(Routes.PREFIX) { + trengerRoute(rapid, redisPoller, tilgangCache) routeExtra(rapid, redisPoller, tilgangCache) { - trengerRoute() inntektRoute() innsendingRoute() kvitteringRoute() } } } + + swaggerUI(path = "swagger", swaggerFile = "openapi/documentation.yaml") } } diff --git a/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRoute.kt b/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRoute.kt index cc3cc6e7ff..817dbd89da 100644 --- a/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRoute.kt +++ b/api/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRoute.kt @@ -3,11 +3,17 @@ package no.nav.helsearbeidsgiver.inntektsmelding.api.trenger import io.ktor.http.HttpStatusCode import io.ktor.server.application.application import io.ktor.server.application.call +import io.ktor.server.request.receive +import io.ktor.server.response.respond +import io.ktor.server.routing.Route import io.ktor.server.routing.post import io.ktor.server.routing.route import io.prometheus.client.Summary import kotlinx.serialization.builtins.serializer +import no.nav.helse.rapids_rivers.RapidsConnection +import no.nav.helsearbeidsgiver.felles.Tilgang import no.nav.helsearbeidsgiver.felles.TrengerData +import no.nav.helsearbeidsgiver.inntektsmelding.api.RedisPoller import no.nav.helsearbeidsgiver.inntektsmelding.api.RedisPollerTimeoutException import no.nav.helsearbeidsgiver.inntektsmelding.api.Routes import no.nav.helsearbeidsgiver.inntektsmelding.api.auth.ManglerAltinnRettigheterException @@ -17,29 +23,30 @@ import no.nav.helsearbeidsgiver.inntektsmelding.api.logger import no.nav.helsearbeidsgiver.inntektsmelding.api.response.RedisTimeoutResponse import no.nav.helsearbeidsgiver.inntektsmelding.api.sikkerLogger import no.nav.helsearbeidsgiver.inntektsmelding.api.tilgang.TilgangProducer -import no.nav.helsearbeidsgiver.inntektsmelding.api.utils.RouteExtra import no.nav.helsearbeidsgiver.inntektsmelding.api.utils.receive import no.nav.helsearbeidsgiver.inntektsmelding.api.utils.respond -import no.nav.helsearbeidsgiver.inntektsmelding.api.utils.respondBadRequest -import no.nav.helsearbeidsgiver.inntektsmelding.api.utils.respondForbidden -import no.nav.helsearbeidsgiver.inntektsmelding.api.utils.respondInternalServerError import no.nav.helsearbeidsgiver.inntektsmelding.api.validation.ValidationError import no.nav.helsearbeidsgiver.inntektsmelding.api.validation.ValidationResponse +import no.nav.helsearbeidsgiver.utils.cache.LocalCache import no.nav.helsearbeidsgiver.utils.json.fromJson -fun RouteExtra.trengerRoute() { - val trengerProducer = TrengerProducer(connection) - val tilgangProducer = TilgangProducer(connection) +fun Route.trengerRoute( + rapid: RapidsConnection, + redis: RedisPoller, + tilgangCache: LocalCache +) { + val trengerProducer = TrengerProducer(rapid) + val tilgangProducer = TilgangProducer(rapid) val requestLatency = Summary.build() .name("simba_trenger_latency_seconds") .help("trenger endpoint latency in seconds") .register() - route.route(Routes.TRENGER) { + route(Routes.TRENGER) { post { val requestTimer = requestLatency.startTimer() runCatching { - receive(TrengerRequest.serializer()) + call.receive() } .onSuccess { request -> logger.info("Henter data for uuid: ${request.uuid}") @@ -58,12 +65,12 @@ fun RouteExtra.trengerRoute() { val status = if (trengerResponse.feilReport == null) { HttpStatusCode.Created } else if (trengerResponse.feilReport.status() < 0) HttpStatusCode.ServiceUnavailable else HttpStatusCode.Created - respond(status, trengerResponse, TrengerResponse.serializer()) + call.respond(status, trengerResponse) } catch (e: ManglerAltinnRettigheterException) { - respondForbidden("Du har ikke rettigheter for organisasjon.", String.serializer()) + call.respond(HttpStatusCode.Forbidden, "Du har ikke rettigheter for organisasjon.") } catch (_: RedisPollerTimeoutException) { logger.info("Fikk timeout for ${request.uuid}") - respondInternalServerError(RedisTimeoutResponse(request.uuid), RedisTimeoutResponse.serializer()) + call.respond(HttpStatusCode.InternalServerError, RedisTimeoutResponse(request.uuid)) } }.also { requestTimer.observeDuration() @@ -79,7 +86,7 @@ fun RouteExtra.trengerRoute() { ) ) ) - respondBadRequest(response, ValidationResponse.serializer()) + call.respond(HttpStatusCode.BadRequest, response) } } } diff --git a/api/src/main/resources/openapi/documentation.yaml b/api/src/main/resources/openapi/documentation.yaml new file mode 100644 index 0000000000..05ff1cc559 --- /dev/null +++ b/api/src/main/resources/openapi/documentation.yaml @@ -0,0 +1,290 @@ +openapi: "3.0.3" +info: + title: "helsearbeidsgiver_inntektsmelding API" + description: "helsearbeidsgiver_inntektsmelding API" + version: "1.0.0" +servers: +- url: "https://helsearbeidsgiver_inntektsmelding" +paths: + /api/v1/trenger: + post: + description: "" + parameters: + - name: "Authorization" + in: "header" + required: false + schema: + type: "string" + requestBody: + content: + '*/*': + schema: + $ref: "#/components/schemas/TrengerRequest" + required: true + responses: + "500": + description: "Internal Server Error" + content: + '*/*': + schema: + $ref: "#/components/schemas/RedisTimeoutResponse" + text/plain: + schema: + type: "string" + examples: + Example#1: + value: "" + Example#2: + value: "" + Example#3: + value: "" + Example#4: + value: "" + "201": + description: "Created" + content: + '*/*': + schema: + $ref: "#/components/schemas/TrengerResponse" + "503": + description: "Service Unavailable" + content: + '*/*': + schema: + $ref: "#/components/schemas/TrengerResponse" + "403": + description: "Forbidden" + content: + '*/*': + schema: + type: "string" + examples: + Example#1: + value: "Du har ikke rettigheter for organisasjon." + "400": + description: "Bad Request" + content: + '*/*': + schema: + $ref: "#/components/schemas/ValidationResponse" + examples: + Example#1: + description: "" + value: + errors: + - property: "null" + error: "null" + value: "" + /isalive: + get: + description: "" + responses: + "200": + description: "OK" + content: + text/plain: + schema: + type: "string" + examples: + Example#1: + value: "I'm alive" + /isready: + get: + description: "" + responses: + "200": + description: "OK" + content: + text/plain: + schema: + type: "string" + examples: + Example#1: + value: "I'm ready" + /metrics: + get: + description: "" + parameters: + - name: "name[]" + in: "query" + required: false + schema: + type: "array" + items: + type: "string" +components: + schemas: + TrengerRequest: + type: "object" + properties: + uuid: + type: "string" + format: "uuid" + Periode: + type: "object" + properties: + fom: + type: "string" + format: "date" + tom: + type: "string" + format: "date" + YearMonth: + type: "object" + properties: + year: + type: "integer" + format: "int32" + month: + type: "integer" + format: "int32" + InntektPerMaaned: + type: "object" + properties: + maaned: + $ref: "#/components/schemas/YearMonth" + inntekt: + type: "number" + format: "double" + Arbeidsgiverperiode: + type: "object" + properties: + paakrevd: + type: "boolean" + ForslagInntekt: + type: "object" + properties: {} + Inntekt: + type: "object" + properties: + paakrevd: + type: "boolean" + forslag: + $ref: "#/components/schemas/ForslagInntekt" + ForslagRefusjon: + type: "object" + properties: + perioder: + type: "array" + items: + $ref: "#/components/schemas/Periode" + opphoersdato: + type: "string" + format: "date" + Refusjon: + type: "object" + properties: + paakrevd: + type: "boolean" + forslag: + $ref: "#/components/schemas/ForslagRefusjon" + ForespurtData: + type: "object" + properties: + arbeidsgiverperiode: + $ref: "#/components/schemas/Arbeidsgiverperiode" + inntekt: + $ref: "#/components/schemas/Inntekt" + refusjon: + $ref: "#/components/schemas/Refusjon" + Feilmelding: + type: "object" + properties: + melding: + type: "string" + status: + type: "integer" + format: "int32" + datafelt: + type: "string" + enum: + - "VIRKSOMHET" + - "ARBEIDSTAKER_INFORMASJON" + - "ARBEIDSGIVER_INFORMASJON" + - "INNTEKTSMELDING_DOKUMENT" + - "ARBEIDSFORHOLD" + - "SAK_ID" + - "PERSISTERT_SAK_ID" + - "OPPGAVE_ID" + - "ORGNRUNDERENHET" + - "FORESPOERSEL_ID" + - "INNTEKTSMELDING" + - "FORESPOERSEL_SVAR" + - "TRENGER_INNTEKT" + - "INNTEKT" + - "FNR" + - "ARBEIDSGIVER_FNR" + - "SKJAERINGSTIDSPUNKT" + - "TILGANG" + - "SPINN_INNTEKTSMELDING_ID" + - "EKSTERN_INNTEKTSMELDING" + FeilReport: + type: "object" + properties: + feil: + type: "array" + items: + $ref: "#/components/schemas/Feilmelding" + TrengerResponse: + type: "object" + properties: + navn: + type: "string" + orgNavn: + type: "string" + innsenderNavn: + type: "string" + identitetsnummer: + type: "string" + orgnrUnderenhet: + type: "string" + fravaersperioder: + type: "array" + items: + $ref: "#/components/schemas/Periode" + egenmeldingsperioder: + type: "array" + items: + $ref: "#/components/schemas/Periode" + bruttoinntekt: + type: "number" + format: "double" + tidligereinntekter: + type: "array" + items: + $ref: "#/components/schemas/InntektPerMaaned" + behandlingsperiode: + $ref: "#/components/schemas/Periode" + behandlingsdager: + type: "array" + items: + type: "string" + format: "date" + forespurtData: + $ref: "#/components/schemas/ForespurtData" + feilReport: + $ref: "#/components/schemas/FeilReport" + RedisTimeoutResponse: + type: "object" + properties: + uuid: + type: "string" + format: "uuid" + error: + type: "string" + ValidationError: + type: "object" + properties: + property: + type: "string" + error: + type: "string" + value: + type: "string" + ValidationResponse: + type: "object" + properties: + errors: + type: "array" + items: + $ref: "#/components/schemas/ValidationError" \ No newline at end of file diff --git a/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/innsending/InnsendingValidateKtTest.kt b/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/innsending/InnsendingValidateKtTest.kt index b6f6ee9322..ebd04a3f5e 100644 --- a/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/innsending/InnsendingValidateKtTest.kt +++ b/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/innsending/InnsendingValidateKtTest.kt @@ -22,12 +22,12 @@ import no.nav.helsearbeidsgiver.felles.test.mock.DELVIS_INNSENDING_REQUEST import no.nav.helsearbeidsgiver.felles.test.mock.GYLDIG_INNSENDING_REQUEST import no.nav.helsearbeidsgiver.inntektsmelding.api.TestData import no.nav.helsearbeidsgiver.inntektsmelding.api.validation.validationResponseMapper +import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertDoesNotThrow import org.junit.jupiter.api.assertThrows import org.valiktor.ConstraintViolationException import java.time.LocalDate -import kotlin.test.assertEquals class InnsendingValidateKtTest { diff --git a/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRouteKtTest.kt b/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRouteKtTest.kt index d3c1b9d33b..01d6246ac2 100644 --- a/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRouteKtTest.kt +++ b/api/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/api/trenger/TrengerRouteKtTest.kt @@ -41,9 +41,9 @@ import no.nav.helsearbeidsgiver.utils.test.date.januar import no.nav.helsearbeidsgiver.utils.test.date.mai import no.nav.helsearbeidsgiver.utils.test.date.mars import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Test import java.util.UUID -import kotlin.test.assertNotNull private const val PATH = Routes.PREFIX + Routes.TRENGER diff --git a/integrasjonstest/build.gradle.kts b/integrasjonstest/build.gradle.kts index 470deeaf25..296ea70c76 100644 --- a/integrasjonstest/build.gradle.kts +++ b/integrasjonstest/build.gradle.kts @@ -55,7 +55,6 @@ dependencies { testApi(project(":api")) - testImplementation(project(":felles-test")) testImplementation("com.redis.testcontainers:testcontainers-redis-junit:$testcontainersRedisJunitVersion") testImplementation("org.testcontainers:kafka:$testcontainersVersion") testImplementation("org.testcontainers:postgresql:$testcontainersVersion")