Skip to content

Commit

Permalink
Merge pull request #17 from navikt/bulk-endpoint
Browse files Browse the repository at this point in the history
IS-2619: Bulk endpoint
  • Loading branch information
geir-waagboe authored Aug 27, 2024
2 parents c788c91 + 6c722cd commit 9ee0938
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import no.nav.syfo.api.model.NewVurderingRequestDTO
import no.nav.syfo.api.model.NewVurderingResponseDTO
import no.nav.syfo.api.model.VurderingResponseDTO
import no.nav.syfo.api.model.*
import no.nav.syfo.application.VurderingService
import no.nav.syfo.domain.Personident
import no.nav.syfo.infrastructure.NAV_PERSONIDENT_HEADER
import no.nav.syfo.infrastructure.clients.veiledertilgang.VeilederTilgangskontrollClient
import no.nav.syfo.infrastructure.clients.veiledertilgang.validateVeilederAccess
import no.nav.syfo.util.getBearerHeader
import no.nav.syfo.util.getCallId
import no.nav.syfo.util.getNAVIdent
import no.nav.syfo.util.getPersonident
Expand Down Expand Up @@ -73,8 +73,34 @@ fun Route.registerManglendeMedvirkningEndpoints(
}

post("/get-vurderinger") {
// TODO: Implement
call.respond(HttpStatusCode.NotImplemented)
val token = call.getBearerHeader()
?: throw IllegalArgumentException("Failed to get vurderinger for personer. No Authorization header supplied.")
val requestDTOList = call.receive<VurderingerRequestDTO>()

val personerVeilederHasAccessTo = veilederTilgangskontrollClient.veilederPersonerAccess(
personidenter = requestDTOList.personidenter.map { Personident(it) },
token = token,
callId = call.getCallId(),
)

val vurderinger = if (personerVeilederHasAccessTo.isNullOrEmpty()) {
emptyMap()
} else {
vurderingService.getLatestVurderingForPersoner(
personidenter = personerVeilederHasAccessTo,
)
}

if (vurderinger.isEmpty()) {
call.respond(HttpStatusCode.NoContent)
} else {
val responseDTO = VurderingerResponseDTO(
vurderinger = vurderinger.map {
it.key.value to VurderingResponseDTO.fromVurdering(it.value)
}.associate { it },
)
call.respond(responseDTO)
}
}
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/no/nav/syfo/api/model/VurderingerDTO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package no.nav.syfo.api.model

data class VurderingerRequestDTO(
val personidenter: List<String>
)

data class VurderingerResponseDTO(
val vurderinger: Map<String, VurderingResponseDTO>
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ interface IVurderingRepository {
fun updatePublishedAt(vurderingUuid: UUID)

fun getVurderinger(personident: Personident): List<ManglendeMedvirkningVurdering>

fun getLatestVurderingForPersoner(personidenter: List<Personident>): Map<Personident, ManglendeMedvirkningVurdering>
}
6 changes: 6 additions & 0 deletions src/main/kotlin/no/nav/syfo/application/VurderingService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class VurderingService(
vurderingProducer.publishVurdering(savedVurdering)
.map { vurderingRepository.updatePublishedAt(it.uuid) }
.onFailure { log.error("Failed to publish vurdering with uuid: ${savedVurdering.uuid}, and message: ${it.message}") }

return savedVurdering
}

Expand Down Expand Up @@ -71,6 +72,11 @@ class VurderingService(
}
}

fun getLatestVurderingForPersoner(
personidenter: List<Personident>,
): Map<Personident, ManglendeMedvirkningVurdering> =
vurderingRepository.getLatestVurderingForPersoner(personidenter)

companion object {
private val log = LoggerFactory.getLogger(VurderingService::class.java)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class VeilederTilgangskontrollClient(
private val httpClient: HttpClient = httpClientDefault()
) {
private val tilgangskontrollPersonUrl = "${clientEnvironment.baseUrl}$TILGANGSKONTROLL_PERSON_PATH"
private val tilgangskontrollBrukereUrl = "${clientEnvironment.baseUrl}$TILGANGSKONTROLL_BRUKERE_PATH"

suspend fun hasAccess(
callId: String,
Expand Down Expand Up @@ -55,6 +56,44 @@ class VeilederTilgangskontrollClient(
}
}

suspend fun veilederPersonerAccess(
personidenter: List<Personident>,
token: String,
callId: String,
): List<Personident>? {
val oboToken = azureAdClient.getOnBehalfOfToken(
scopeClientId = clientEnvironment.clientId,
token = token
)?.accessToken
?: throw RuntimeException("Failed to request access to list of persons: Failed to get OBO token")

val identer = personidenter.map { it.value }
return try {
val response: HttpResponse = httpClient.post(tilgangskontrollBrukereUrl) {
header(HttpHeaders.Authorization, bearerHeader(oboToken))
header(NAV_CALL_ID_HEADER, callId)
accept(ContentType.Application.Json)
contentType(ContentType.Application.Json)
setBody(identer)
}
Metrics.COUNT_CALL_TILGANGSKONTROLL_PERSONS_SUCCESS.increment()
response.body<List<String>>().map { Personident(it) }
} catch (e: ClientRequestException) {
if (e.response.status == HttpStatusCode.Forbidden) {
log.warn("Forbidden to request access to list of person from istilgangskontroll")
null
} else {
Metrics.COUNT_CALL_TILGANGSKONTROLL_PERSONS_FAIL.increment()
log.error("Error while requesting access to list of person from istilgangskontroll: ${e.message}", e)
null
}
} catch (e: ServerResponseException) {
Metrics.COUNT_CALL_TILGANGSKONTROLL_PERSONS_FAIL.increment()
log.error("Error while requesting access to list of person from istilgangskontroll: ${e.message}", e)
null
}
}

private fun handleUnexpectedResponseException(
response: HttpResponse,
callId: String
Expand All @@ -71,6 +110,7 @@ class VeilederTilgangskontrollClient(
private val log = LoggerFactory.getLogger(VeilederTilgangskontrollClient::class.java)

const val TILGANGSKONTROLL_PERSON_PATH = "/api/tilgang/navident/person"
const val TILGANGSKONTROLL_BRUKERE_PATH = "/api/tilgang/navident/brukere"
}
}

Expand All @@ -80,6 +120,9 @@ private class Metrics {
const val CALL_TILGANGSKONTROLL_PERSON_SUCCESS = "${CALL_TILGANGSKONTROLL_PERSON_BASE}_success_count"
const val CALL_TILGANGSKONTROLL_PERSON_FAIL = "${CALL_TILGANGSKONTROLL_PERSON_BASE}_fail_count"
const val CALL_TILGANGSKONTROLL_PERSON_FORBIDDEN = "${CALL_TILGANGSKONTROLL_PERSON_BASE}_forbidden_count"
const val CALL_TILGANGSKONTROLL_PERSONS_BASE = "${METRICS_NS}_call_tilgangskontroll_persons"
const val CALL_TILGANGSKONTROLL_PERSONS_SUCCESS = "${CALL_TILGANGSKONTROLL_PERSONS_BASE}_success_count"
const val CALL_TILGANGSKONTROLL_PERSONS_FAIL = "${CALL_TILGANGSKONTROLL_PERSONS_BASE}_fail_count"

val COUNT_CALL_TILGANGSKONTROLL_PERSON_SUCCESS: Counter =
Counter.builder(CALL_TILGANGSKONTROLL_PERSON_SUCCESS)
Expand All @@ -93,5 +136,13 @@ private class Metrics {
Counter.builder(CALL_TILGANGSKONTROLL_PERSON_FORBIDDEN)
.description("Counts the number of forbidden calls to istilgangskontroll - person")
.register(METRICS_REGISTRY)
val COUNT_CALL_TILGANGSKONTROLL_PERSONS_SUCCESS: Counter =
Counter.builder(CALL_TILGANGSKONTROLL_PERSONS_SUCCESS)
.description("Counts the number of successful calls to tilgangskontroll - persons")
.register(METRICS_REGISTRY)
val COUNT_CALL_TILGANGSKONTROLL_PERSONS_FAIL: Counter =
Counter.builder(CALL_TILGANGSKONTROLL_PERSONS_FAIL)
.description("Counts the number of failed calls to tilgangskontroll - persons")
.register(METRICS_REGISTRY)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.nav.syfo.infrastructure.database.repository

import no.nav.syfo.domain.Varsel
import java.time.LocalDate
import java.time.OffsetDateTime
import java.util.UUID
Expand All @@ -12,4 +13,11 @@ data class PVarsel(
val updatedAt: OffsetDateTime,
val svarfrist: LocalDate,
val publishedAt: OffsetDateTime?,
)
) {
fun toVarsel() =
Varsel(
uuid = uuid,
createdAt = createdAt,
svarfrist = svarfrist,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package no.nav.syfo.infrastructure.database.repository
import no.nav.syfo.domain.JournalpostId
import no.nav.syfo.domain.ManglendeMedvirkningVurdering
import no.nav.syfo.domain.Personident
import no.nav.syfo.domain.Varsel
import no.nav.syfo.domain.Veilederident
import no.nav.syfo.domain.VurderingType
import no.nav.syfo.domain.DocumentComponent
Expand Down Expand Up @@ -34,11 +33,7 @@ data class PVurdering(
begrunnelse = begrunnelse,
document = document,
journalpostId = journalpostId,
varsel = Varsel(
uuid = pVarsel!!.uuid,
createdAt = pVarsel.createdAt,
svarfrist = pVarsel.svarfrist,
),
varsel = pVarsel!!.toVarsel(),
)
VurderingType.OPPFYLT -> ManglendeMedvirkningVurdering.Oppfylt(
uuid = uuid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,33 @@ class VurderingRepository(private val database: DatabaseInterface) : IVurderingR
}
}

override fun getLatestVurderingForPersoner(
personidenter: List<Personident>,
): Map<Personident, ManglendeMedvirkningVurdering> =
database.connection.use { connection ->
connection.prepareStatement(GET_VURDERINGER).use { preparedStatement ->
preparedStatement.setString(1, personidenter.joinToString(",") { it.value })
preparedStatement.executeQuery().toList {
toPVurdering().toManglendeMedvirkningVurdering(
if (getString("varsel_uuid") != null) {
PVarsel(
id = getInt("varsel_id"),
uuid = UUID.fromString(getString("varsel_uuid")),
createdAt = getObject("varsel_created_at", OffsetDateTime::class.java),
updatedAt = getObject("varsel_updated_at", OffsetDateTime::class.java),
vurderingId = getInt("id"),
publishedAt = getObject("varsel_published_at", OffsetDateTime::class.java),
svarfrist = getDate("varsel_svarfrist").toLocalDate(),
)
} else null
)
}
}.associateBy {
// Den nyeste vurderingen blir valgt her siden lista er sortert med den nyeste til slutt
it.personident
}
}

private fun Connection.saveVurdering(vurdering: ManglendeMedvirkningVurdering): PVurdering {
val pVurdering = this.prepareStatement(INSERT_INTO_VURDERING).use {
it.setString(1, vurdering.uuid.toString())
Expand Down Expand Up @@ -213,6 +240,20 @@ class VurderingRepository(private val database: DatabaseInterface) : IVurderingR
UPDATE VURDERING SET updated_at=?, published_at=? WHERE uuid=?
"""

private const val GET_VURDERINGER =
"""
SELECT vu.*,
va.id as varsel_id,
va.uuid as varsel_uuid,
va.created_at as varsel_created_at,
va.updated_at as varsel_updated_at,
va.svarfrist as varsel_svarfrist,
va.published_at as varsel_published_at
FROM VURDERING vu LEFT OUTER JOIN VARSEL va ON (vu.id = va.vurdering_id)
WHERE vu.personident = ANY (string_to_array(?, ','))
ORDER BY vu.created_at ASC
"""

private const val UPDATE_JOURNALPOST_ID =
"""
UPDATE VURDERING
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ import no.nav.syfo.UserConstants.PDF_FORHANDSVARSEL
import no.nav.syfo.UserConstants.PDF_VURDERING
import no.nav.syfo.UserConstants.VEILEDER_IDENT
import no.nav.syfo.api.generateJWT
import no.nav.syfo.api.model.NewVurderingRequestDTO
import no.nav.syfo.api.model.NewVurderingResponseDTO
import no.nav.syfo.api.model.VurderingResponseDTO
import no.nav.syfo.api.model.*
import no.nav.syfo.api.testApiModule
import no.nav.syfo.api.testDeniedPersonAccess
import no.nav.syfo.api.testMissingToken
Expand All @@ -26,6 +24,7 @@ import no.nav.syfo.infrastructure.bearerHeader
import no.nav.syfo.infrastructure.database.dropData
import no.nav.syfo.infrastructure.database.repository.VurderingRepository
import no.nav.syfo.util.configuredJacksonMapper
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
Expand Down Expand Up @@ -247,14 +246,102 @@ object ManglendeMedvirkningEndpointsSpek : Spek({
}

describe("POST /get-vurderinger") {
val personidenter = listOf(personIdent)
val requestDTO = VurderingerRequestDTO(personidenter)
it("Successfully retrieves an empty group of vurderinger") {
with(
handleRequest(HttpMethod.Post, "$urlVurderinger/get-vurderinger") {
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
addHeader(HttpHeaders.Authorization, bearerHeader(validToken))
setBody(objectMapper.writeValueAsString(requestDTO))
}
) {
response.status() shouldBeEqualTo HttpStatusCode.NoContent
}
}
it("Successfully retrieves a group of vurderinger") {
val forhandsvarsel = generateVurdering(
personident = Personident(personIdent),
type = VurderingType.FORHANDSVARSEL,
)
vurderingRepository.saveManglendeMedvirkningVurdering(
vurdering = forhandsvarsel,
vurderingPdf = PDF_FORHANDSVARSEL,
)
with(
handleRequest(HttpMethod.Post, "$urlVurderinger/get-vurderinger") {
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
addHeader(HttpHeaders.Authorization, bearerHeader(validToken))
setBody(objectMapper.writeValueAsString(requestDTO))
}
) {
response.status() shouldBeEqualTo HttpStatusCode.NotImplemented
response.status() shouldBeEqualTo HttpStatusCode.OK
val responseDTO = objectMapper.readValue<VurderingerResponseDTO>(response.content!!)
val vurderingResponseDTO = responseDTO.vurderinger.get(personIdent)
vurderingResponseDTO!!.personident shouldBeEqualTo personIdent
vurderingResponseDTO!!.type shouldBeEqualTo VurderingType.FORHANDSVARSEL
}
}
it("Retrieves latest vurderinger if more than one") {
val forhandsvarsel = generateVurdering(
personident = Personident(personIdent),
type = VurderingType.FORHANDSVARSEL,
)
vurderingRepository.saveManglendeMedvirkningVurdering(
vurdering = forhandsvarsel,
vurderingPdf = PDF_FORHANDSVARSEL,
)
val oppfylt = generateVurdering(
personident = Personident(personIdent),
type = VurderingType.OPPFYLT,
createdAt = forhandsvarsel.createdAt.plusSeconds(1),
)
vurderingRepository.saveManglendeMedvirkningVurdering(
vurdering = oppfylt,
vurderingPdf = PDF_FORHANDSVARSEL,
)
with(
handleRequest(HttpMethod.Post, "$urlVurderinger/get-vurderinger") {
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
addHeader(HttpHeaders.Authorization, bearerHeader(validToken))
setBody(objectMapper.writeValueAsString(requestDTO))
}
) {
response.status() shouldBeEqualTo HttpStatusCode.OK
val responseDTO = objectMapper.readValue<VurderingerResponseDTO>(response.content!!)
val vurderingResponseDTO = responseDTO.vurderinger.get(personIdent)
vurderingResponseDTO!!.personident shouldBeEqualTo personIdent
vurderingResponseDTO!!.type shouldBeEqualTo VurderingType.OPPFYLT
}
}
it("Only retrieves vurderinger for which the user has access") {
val forhandsvarsel = generateVurdering(
personident = Personident(personIdent),
type = VurderingType.FORHANDSVARSEL,
)
val forhandsvarselNoAccess = generateVurdering(
personident = Personident(personIdentNoAccess),
type = VurderingType.FORHANDSVARSEL,
)
vurderingRepository.saveManglendeMedvirkningVurdering(
vurdering = forhandsvarselNoAccess,
vurderingPdf = PDF_FORHANDSVARSEL,
)
vurderingRepository.saveManglendeMedvirkningVurdering(
vurdering = forhandsvarsel,
vurderingPdf = PDF_FORHANDSVARSEL,
)
with(
handleRequest(HttpMethod.Post, "$urlVurderinger/get-vurderinger") {
addHeader(HttpHeaders.ContentType, ContentType.Application.Json.toString())
addHeader(HttpHeaders.Authorization, bearerHeader(validToken))
setBody(objectMapper.writeValueAsString(VurderingerRequestDTO(listOf(personIdent, personIdentNoAccess))))
}
) {
response.status() shouldBeEqualTo HttpStatusCode.OK
val responseDTO = objectMapper.readValue<VurderingerResponseDTO>(response.content!!)
responseDTO.vurderinger.keys.contains(personIdent) shouldBe true
responseDTO.vurderinger.keys.contains(personIdentNoAccess) shouldBe false
}
}
}
Expand Down
Loading

0 comments on commit 9ee0938

Please sign in to comment.