Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start med arbeidsgiver #8

Merged
merged 14 commits into from
Mar 26, 2025
2 changes: 1 addition & 1 deletion .github/workflows/deploy-rekrutteringstreff-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
uses: ./.github/workflows/deploy-template.yaml
with:
app_name: ${{ github.workflow }}
deploy_dev_branch: refs/heads/logback-absloute-path
deploy_dev_branch: refs/heads/arbeidsgiver-1
secrets: inherit
permissions:
contents: read
Expand Down
6 changes: 3 additions & 3 deletions apps/rekrutteringstreff-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ val testContainerVersion = "1.20.4"
val swaggerParserVersion = "2.1.12"
val nimbusVersion = "10.0.1"
val wiremockVersion = "3.12.0"
val jacksonDatatypeJsr310Version = "2.18.2" // JavaTimeModule, se https://github.com/FasterXML/jackson-modules-java8

dependencies {
implementation("org.flywaydb:flyway-core:$flywayVersion")
Expand All @@ -21,7 +22,7 @@ dependencies {
implementation("com.zaxxer:HikariCP:$hikariVersion")
implementation("com.github.kittinunf.fuel:fuel:$fuelVersion")
implementation("com.github.kittinunf.fuel:fuel-jackson:$fuelVersion")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDatatypeJsr310Version")
implementation("io.javalin:javalin:$javalinVersion")

kapt("io.javalin.community.openapi:openapi-annotation-processor:$javalinVersion")
Expand All @@ -37,5 +38,4 @@ dependencies {
testImplementation("org.testcontainers:postgresql:$testContainerVersion")
testImplementation("no.nav.security:mock-oauth2-server:$mockOAuth2ServerVersion")
testImplementation("org.wiremock:wiremock-standalone:$wiremockVersion")

}
}
3 changes: 3 additions & 0 deletions apps/rekrutteringstreff-api/src/main/kotlin/no/nav/toi/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import io.javalin.openapi.plugin.OpenApiPlugin
import io.javalin.openapi.plugin.swagger.SwaggerPlugin
import no.nav.toi.SecureLogLogger.Companion.secure
import no.nav.toi.rekrutteringstreff.RekrutteringstreffRepository
import no.nav.toi.arbeidsgiver.ArbeidsgiverRepository
import no.nav.toi.arbeidsgiver.handleArbeidsgiver
import no.nav.toi.rekrutteringstreff.handleRekrutteringstreff
import org.flywaydb.core.Flyway
import java.time.Instant
Expand Down Expand Up @@ -41,6 +43,7 @@ class App(
javalin.handleHealth()
javalin.leggTilAutensieringPåRekrutteringstreffEndepunkt(authConfigs)
javalin.handleRekrutteringstreff(RekrutteringstreffRepository(dataSource))
javalin.handleArbeidsgiver(ArbeidsgiverRepository(dataSource))
javalin.start(port)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package no.nav.toi.arbeidsgiver

import no.nav.toi.rekrutteringstreff.TreffId

data class Orgnr(private val orgnr: String) {
init {
if (!(orgnr.length == 9 && orgnr.all(Char::isDigit))) throw IllegalArgumentException("Orgnr må være 9 siffer. Mottok [$orgnr].")
}

val asString = orgnr
override fun toString(): String = asString

}

data class Orgnavn(private val orgnavn: String) {
init {
if (orgnavn.isEmpty()) throw IllegalArgumentException("Orgnavn må være ikke-tomt.")
}

val asString = orgnavn
override fun toString(): String = asString
}


data class LeggTilArbeidsgiver(
val orgnr: Orgnr,
val orgnavn: Orgnavn,
)

data class Arbeidsgiver(
val treffId: TreffId,
val orgnr: Orgnr,
val orgnavn: Orgnavn,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package no.nav.toi.arbeidsgiver


import io.javalin.Javalin
import io.javalin.http.Context
import io.javalin.http.bodyAsClass
import io.javalin.openapi.*
import no.nav.toi.rekrutteringstreff.TreffId
import no.nav.toi.rekrutteringstreff.endepunktRekrutteringstreff
import java.util.*


private const val pathParamTreffId = "id"
private const val arbeidsgiverPath = "$endepunktRekrutteringstreff/{$pathParamTreffId}/arbeidsgiver"

private data class LeggTilArbeidsgiverDto(
val organisasjonsummer: String,
val navn: String
) {
fun somLeggTilArbeidsgiver() = LeggTilArbeidsgiver(Orgnr(organisasjonsummer), Orgnavn(navn))
}

data class ArbeidsgiverOutboundDto(
val orgnaisasjonsnummer: String,
val navn: String,
val status: String = "TODO" // TODO Are: Default bør ikke settes her
)

@OpenApi(
summary = "Legg til ny arbeidsgiver til et rekrutteringstreff",
operationId = "leggTilArbeidsgiver",
security = [OpenApiSecurity(name = "BearerAuth")],
pathParams = [OpenApiParam(name = pathParamTreffId, type = UUID::class)],
responses = [OpenApiResponse(
status = "201"
)],
path = arbeidsgiverPath,
methods = [HttpMethod.POST]
)
private fun leggTilArbeidsgiverHandler(repo: ArbeidsgiverRepository): (Context) -> Unit = { ctx ->
val dto: LeggTilArbeidsgiverDto = ctx.bodyAsClass<LeggTilArbeidsgiverDto>()
val treff = TreffId(ctx.pathParam(pathParamTreffId))
repo.leggTil(dto.somLeggTilArbeidsgiver(), treff)
ctx.status(201)
}


@OpenApi(
summary = "Hent alle arbeidsgivere for et rekrutteringstreff",
operationId = "hentArbeidsgivere",
security = [OpenApiSecurity(name = "BearerAuth")],
responses = [OpenApiResponse(
status = "200",
content = [OpenApiContent(
from = Array<ArbeidsgiverOutboundDto>::class,
example = """[
{
"TODO": "TODO",
}
]"""
)]
)],
path = arbeidsgiverPath,
methods = [HttpMethod.GET]
)
private fun hentArbeidsgivere(repo: ArbeidsgiverRepository): (Context) -> Unit = { ctx ->
val treff = TreffId(ctx.pathParam(pathParamTreffId))
val arbeidsgivere = repo.hentArbeidsgivere(treff)
ctx.status(200).json(arbeidsgivere.toOutboundDto())
}

private fun List<Arbeidsgiver>.toOutboundDto(): List<ArbeidsgiverOutboundDto> =
map {
ArbeidsgiverOutboundDto(
orgnaisasjonsnummer = it.orgnr.asString,
navn = it.orgnavn.asString
)
}


fun Javalin.handleArbeidsgiver(repo: ArbeidsgiverRepository) {
post(arbeidsgiverPath, leggTilArbeidsgiverHandler(repo))
get(arbeidsgiverPath, hentArbeidsgivere(repo))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package no.nav.toi.arbeidsgiver

import no.nav.toi.rekrutteringstreff.TreffId
import java.sql.Connection
import javax.sql.DataSource

class ArbeidsgiverRepository(
private val dataSource: DataSource,
) {

private fun hentTreffDbId(connection: Connection, treff: TreffId): Long? {
connection.prepareStatement(
"SELECT db_id FROM rekrutteringstreff WHERE id = ?"
).use { stmt ->
stmt.setObject(1, treff.somUuid)
stmt.executeQuery().use { rs ->
if (rs.next()) {
return rs.getLong("db_id")
} else return null
}
}
}

private fun finnesIDb(connection: Connection, treffId: TreffId): Boolean =
hentTreffDbId(connection, treffId) != null


fun leggTil(arbeidsgiver: LeggTilArbeidsgiver, treff: TreffId) {

fun leggTilArbeidsgiver(connection: Connection, arbeidsgiver: LeggTilArbeidsgiver, treffDbId: Long) {
connection.prepareStatement(
"INSERT INTO arbeidsgiver (treff_db_id, orgnr, orgnavn) VALUES (?, ?, ?)"
).use { stmt ->
stmt.setLong(1, treffDbId)
stmt.setString(2, arbeidsgiver.orgnr.toString())
stmt.setString(3, arbeidsgiver.orgnavn.toString())
stmt.executeUpdate()
}
}

dataSource.connection.use { connection ->
val treffDbId: Long = hentTreffDbId(connection, treff)
?: throw IllegalArgumentException("Kan ikke legge til arbeidsgiver på treffet fordi det ikke finnes noe treff med id ${treff.somUuid}. arbeidsgiver=$arbeidsgiver")
leggTilArbeidsgiver(connection, arbeidsgiver, treffDbId)
}
}

fun hentArbeidsgivere(treff: TreffId): List<Arbeidsgiver> {
dataSource.connection.use { connection ->
if (!finnesIDb(connection, treff))
throw IllegalArgumentException("Kan ikke hente arbeidsgivere fordi det ikek finnes noe rekrutteringstreff med id $treff.")

connection.prepareStatement(
"""
SELECT ag.orgnr, ag.orgnavn, rt.id as treff_id
FROM arbeidsgiver ag
JOIN rekrutteringstreff rt ON ag.treff_db_id = rt.db_id
WHERE rt.id = ?
ORDER BY ag.db_id ASC;
""".trimIndent()
).use { stmt ->
stmt.setObject(1, treff.somUuid)
stmt.executeQuery().use { rs ->
val arbeidsgivere = mutableListOf<Arbeidsgiver>()
while (rs.next()) {
arbeidsgivere.add(
Arbeidsgiver(
treffId = TreffId(rs.getString("treff_id")),
orgnr = Orgnr(rs.getString("orgnr")),
orgnavn = Orgnavn(rs.getString("orgnavn"))
)
)
}
return arbeidsgivere
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ private fun hentRekrutteringstreffHandler(repo: RekrutteringstreffRepository): (
private fun oppdaterRekrutteringstreffHandler(repo: RekrutteringstreffRepository): (Context) -> Unit = { ctx ->
val id = TreffId(ctx.pathParam("id"))
val dto = ctx.bodyAsClass<OppdaterRekrutteringstreffDto>()
val navIdent = ctx.extractNavIdent()
repo.oppdater(id, dto, navIdent)
ctx.extractNavIdent()
repo.oppdater(id, dto)
val updated = repo.hent(id) ?: throw NotFoundResponse("Rekrutteringstreff ikke funnet etter oppdatering")
ctx.status(200).json(updated.tilRekrutteringstreffDTO())
}
Expand Down Expand Up @@ -206,7 +206,7 @@ private fun slettRekrutteringstreffHandler(repo: RekrutteringstreffRepository):
path = "$endepunktRekrutteringstreff/valider",
methods = [HttpMethod.POST]
)
private fun validerRekrutteringstreffHandler(repo: RekrutteringstreffRepository): (Context) -> Unit = { ctx ->
private fun validerRekrutteringstreffHandler(): (Context) -> Unit = { ctx ->
val dto = ctx.bodyAsClass<ValiderRekrutteringstreffDto>()
val validationResult = OpenAiClient.validateRekrutteringstreff(dto)
ctx.status(200).json(validationResult)
Expand All @@ -218,7 +218,7 @@ fun Javalin.handleRekrutteringstreff(repo: RekrutteringstreffRepository) {
get("$endepunktRekrutteringstreff/{id}", hentRekrutteringstreffHandler(repo))
put("$endepunktRekrutteringstreff/{id}", oppdaterRekrutteringstreffHandler(repo))
delete("$endepunktRekrutteringstreff/{id}", slettRekrutteringstreffHandler(repo))
post("$endepunktRekrutteringstreff/valider", validerRekrutteringstreffHandler(repo))
post("$endepunktRekrutteringstreff/valider", validerRekrutteringstreffHandler())
handleEiere(repo.eierRepository)
}

Expand All @@ -239,7 +239,7 @@ data class OpprettRekrutteringstreffDto(
val opprettetAvNavkontorEnhetId: String,
)

data class OpprettRekrutteringstreffInternalDto(
data class OpprettRekrutteringstreffInternalDto( // TODO Are: Finne et bedre navn? Ligger den i feil klasse?
val tittel: String,
val opprettetAvPersonNavident: String,
val opprettetAvNavkontorEnhetId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import javax.sql.DataSource

class RekrutteringstreffRepository(private val dataSource: DataSource) {

fun opprett(dto: OpprettRekrutteringstreffInternalDto): UUID {
val gerernertId = UUID.randomUUID()
fun opprett(dto: OpprettRekrutteringstreffInternalDto): TreffId {
val nyTreffId = TreffId(UUID.randomUUID())
dataSource.connection.use { connection ->
connection.prepareStatement(
"""
Expand All @@ -22,7 +22,7 @@ class RekrutteringstreffRepository(private val dataSource: DataSource) {
""".trimIndent()
).use { stmt ->
var c = 0
stmt.setObject(++c, gerernertId)
stmt.setObject(++c, nyTreffId.somUuid)
stmt.setString(++c, dto.tittel)
stmt.setString(++c, Status.Utkast.name)
stmt.setString(++c, dto.opprettetAvPersonNavident)
Expand All @@ -32,10 +32,10 @@ class RekrutteringstreffRepository(private val dataSource: DataSource) {
stmt.executeUpdate()
}
}
return gerernertId
return nyTreffId
}

fun oppdater(treff: TreffId, dto: OppdaterRekrutteringstreffDto, navIdent: String) {
fun oppdater(treff: TreffId, dto: OppdaterRekrutteringstreffDto) {
dataSource.connection.use { connection ->
connection.prepareStatement(
"""
Expand Down
Loading
Loading