Skip to content

Commit

Permalink
Feature/fhir r3.0.0 (#23)
Browse files Browse the repository at this point in the history
- Unterstützung FHIR VZD FdV und Search Schnittstellen, s. `vzd-cli fhir`
- Neues Befehl für übergreifenden login: `vzd-cli login`
- Tägliche Überprüfung der Updates ist jetzt abschaltbar `vzd-cli config set updates.enabled true|false`
- Diverse Bugfixes
  • Loading branch information
spilikin authored Jun 20, 2024
1 parent 4b41455 commit 76e943f
Show file tree
Hide file tree
Showing 97 changed files with 2,359 additions and 382 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
= Changelog `vzd-cli`

== Version 3.0.0

- Unterstützung FHIR VZD FdV und Search Schnittstellen, s. `vzd-cli fhir`
- Neues Befehl für übergreifenden login: `vzd-cli login`
- Tägliche Überprüfung der Updates ist jetzt abschaltbar `vzd-cli config set updates.enabled true|false`
- Diverse Bugfixes

== Version 2.6.2
- TelematikID ist jetzt optional in `vzd-cli admin <ru|tu|pu> log`

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ktlint:

dist: ktlint
./gradlew clean build -x test
java -jar ./vzd-cli/build/libs/vzd-cli-all.jar admin tu login
java -jar ./vzd-cli/build/libs/vzd-cli-all.jar login tu
./run_tests.sh

release:
Expand Down
75 changes: 73 additions & 2 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ Options:
Commands:
config Manage configuration
update Updates this software
login Logins into all configured APIs
admin CLI for DirectoryAdministration API
fhir CLI for FHIR Directory
apo CLI for ApoVZD API
gui Starts HTTP Server with GUI
pers Process gematik SMC-B/HBA Exports
Expand Down Expand Up @@ -68,6 +70,8 @@ vzd-cli config set httpProxy.enabled true

* Directory Admin API
* Apo API (ApoVZD)
* FHIR FDV Search API
* FHIR Search API (nur mit einem speziellen Token, s. https://directory-beta.ccs.gematik.solutions)

Für die Nutzung dieser APIs sind jeweils entsprechende Geheimnisse bzw. Credentials erforderlich.
Diese werden von der gematik *den bereichtigten Organistionen* bereitgestellt.
Expand Down Expand Up @@ -113,6 +117,22 @@ vzd-cli apo config set apiKeys.test <API_KEY_TEST>
vzd-cli apo config set apiKeys.prod <API_KEY_PROD>
----


=== (Neu) FHIR FDV Search API Credentials

Zugriff auf FHIR FDV Search API wird mittels OAuth2 Client Credentials Flow geschützt. Die berechtige Dienstanbieter erhalten die Client ID und Client Secret von der gematik.

[source,bash]
----
# Secret für die Referenzumgebung speichern
# es folgt eine Vault Passwortabfrage für persönliche Vault
vzd-cli fhir fdv-vault store -e ru -c <CLIEND_ID> -s <CLIENT_SECRET>
# Secret für die Produktivumgebung speichern
# es folgt eine Vault Passwortabfrage für persönliche Vault
vzd-cli fhir fdv-vault store -e pu -c <CLIEND_ID> -s <CLIENT_SECRET>
----

== Erste Schritte

Befor die Directory Admin API genutzt werden kann, muss eine Anmeldung erfolgen.
Expand All @@ -122,10 +142,10 @@ Die Anmeldung muss alle 6 Stunden wiederholt werden.
----
# Anmelden in die Referenzumgebung (ru)
# es folgt eine Vault-Passwortabfrage
vzd-cli admin ru login
vzd-cli login ru
# Anmelden in die Referenzumgebung (pu)
# es folgt eine Vault-Passwortabfrage
vzd-cli admin pu login
vzd-cli login pu
----

Für vollautomatisierte Nutzung des `vzd-cli`, auch bei der Anmeldung, wird das setzten der Umgebungsvariable `VAULT_PASSWORD` empfohlen.
Expand Down Expand Up @@ -156,6 +176,16 @@ vzd-cli admin ru search Müller Berlin
vzd-cli admin ru show 1-SMC-B-Testkarte-883110000117729
----

.*(Neu) Beispiel:* Suche nach einträgen in der FHIR FDV Search API in der Produktivumgebung (`pu`)
[source,bash]
----
vzd-cli fhir pu fdv search healthcare-service -t <TelematikID>
vzd-cli fhir pu fdv search practitioner-role -t <TelematikID>
# oder in Kurzform
vzd-cli fhir pu fdv search hs -t <TelematikID>
vzd-cli fhir pu fdv search pr -t <TelematikID>
----

== Übergreifende Befehle

=== `vzd-cli config`
Expand Down Expand Up @@ -575,3 +605,44 @@ vzd-cli apo prod show 3-1234567890
== `vzd-cli gui`: Directory GUI

Durch den Befehl `vzd-cli gui` wird ein HTTP Server gestartet und ein neuer Browser-Tab mit GUI geöffnet.

== (Neu) `vzd-cli fhir`: FHIR Directory APIs

=== `vzd-cli fhir <tu|ru|pu> fdv search`

Suche nach Einträgen in der FHIR FDV Search API.

.*Beispiel:* Suche nach Einträgen mit TelematikID
[source,bash]
----
vzd-cli fhir pu fdv search hs -t 1-1234567890
----

=== `vzd-cli fhir fdv-vault`

Befehle zur Verwaltung von OAuth2 Geheimnissen für die FHIR FDV Search API. Aufbau ist analog zu `vzd-cli admin vault`.

=== `vzd-cli fhir <tu|ru|pu> token`

Lesen und Setzen eines `ACCESS_TOKEN` für die FHIR Search API.

[source,bash]
----
# Setzen des ACCESS_TOKEN für Referenzumgebung (ru)
vzd-cli fhir ru token -s <ACCESS_TOKEN>
# Lesen des ACCESS_TOKEN für Referenzumgebung (ru)
vzd-cli fhir ru token
----

=== `vzd-cli fhir <tu|ru|pu> search`

Suche nach Einträgen in der FHIR Search API.

.*Beispiel:* Suche nach Einträgen mit TelematikID
[source,bash]
----
# Suche nach HealthcareService Einträgen
vzd-cli fhir ru search hs -t 1-2234567890
# Suche nach PractitionerRole Einträgen
vzd-cli fhir ru search pr -t 1-1234567890
----
6 changes: 3 additions & 3 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
plugins {
// Support convention plugins written in Kotlin. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build.
base
kotlin("jvm") version "1.8.10" apply false
kotlin("jvm") version "1.9.22" apply false
`kotlin-dsl`
kotlin("plugin.serialization") version "1.8.10" apply false
kotlin("plugin.serialization") version "1.9.22" apply false
id("org.jlleitschuh.gradle.ktlint") version "11.6.0" apply false
}

Expand All @@ -13,5 +13,5 @@ repositories {
}

dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
}

plugins {
id("org.jetbrains.kotlin.jvm")
}
Expand All @@ -20,7 +13,6 @@ dependencies {
testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
Expand Down
6 changes: 5 additions & 1 deletion directory-lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@

plugins {
id("de.gematik.directory.library-conventions")
kotlin("plugin.serialization") version "1.8.20"
kotlin("plugin.serialization") version "1.9.22"
}

kotlin {
jvmToolchain(11)
}

java {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.gematik.ti.directory.admin
package de.gematik.ti.directory

import de.gematik.ti.directory.admin.AdminResponseException
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package de.gematik.ti.directory.admin
package de.gematik.ti.directory

@Suppress("ktlint:standard:enum-entry-name-case")
enum class AdminEnvironment {
enum class DirectoryEnvironment {
ru,
tu,
pu,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package de.gematik.ti.directory.admin

import de.gematik.ti.directory.DirectoryAuthPlugin
import de.gematik.ti.directory.DirectoryAuthPluginConfig
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.plugins.observer.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.gematik.ti.directory.admin

import de.gematik.ti.directory.DirectoryEnvironment
import de.gematik.ti.directory.DirectoryException
import kotlinx.serialization.Serializable

Expand Down Expand Up @@ -31,7 +32,7 @@ class ConfigException(message: String, cause: Throwable? = null) : DirectoryExce
data class Config(
val environments: Map<String, EnvironmentConfig>,
) {
fun environment(env: AdminEnvironment) = environments[env.name] ?: throw ConfigException("Unknown environment: ${env.name}")
fun environment(env: DirectoryEnvironment) = environments[env.name] ?: throw ConfigException("Unknown environment: ${env.name}")
}

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ data class FAD1(
@SerialName("KOM-LE_Version")
var komleVersion: String? = null,
var komLeData: List<KomLeData>? = null,
var kimData: List<KIMData>? = null,
)

@Serializable
Expand Down Expand Up @@ -213,3 +214,32 @@ data class InnerError(
val attributeName: String,
val attributeError: String,
)

/**
* kimData:
* type: array
* items:
* type: object
* properties:
* mail:
* type: string
* description: 'E-Mail-Adresse'
* version:
* type: string
* example: 1.5+
* description: 'Die höchste Version der KIM Clientmodule für diese KIM-Mail-Adresse'
* appTags:
* type: array
* items:
* type: string
* example:
* - eEB;V1.0
* - DALE-UV;Einsendung;V1.0
* description: 'Anwendungskennzeichen, welche diese KIM-Mail-Adresse verarbeiten kann'
*/
@Serializable
data class KIMData(
val mail: String,
val version: String,
val appTags: List<String>? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ fun elaborateSpecialization(specialization: String): Coding {
}
} else if (OrganisationSpecializationRegex.matches(specialization)) {
OrganisationSpecializationRegex.matchEntire(specialization)?.let {
HealthcareServiceSpecialtyVS.resolveCode("urn:oid:${it.groupValues[1]}", it.groupValues[2])
HealthcareServiceTypeVS.resolveCode("urn:oid:${it.groupValues[1]}", it.groupValues[2])
}
} else {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ data class ElaborateKIMAddress(
val mail: String,
val version: String,
val provider: ElaborateKIMProvider?,
val appTags: List<String>? = null,
)

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ fun DirectoryEntry.infereKIMAddresses(): List<ElaborateKIMAddress>? {
fad1.dn.ou?.first()?.let { fad ->
ElaborateKIMProvider(fad, fad)
}
val kimAddress =
fad1.kimData?.firstOrNull { kimData ->
kimData.mail == it.mail
}
ElaborateKIMAddress(
it.mail,
it.version,
provider,
kimAddress?.appTags,
)
} ?: emptyList()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package de.gematik.ti.directory.fhir

import org.hl7.fhir.r4.model.*

fun Bundle.filterByType(resourceType: ResourceType): List<Resource> {
return entry.filter { it.resource.resourceType == resourceType }.map { it.resource }
}

fun Bundle.filterPractitionerRoles(): List<FHIRDirectoryEntry> {
return filterByType(ResourceType.PractitionerRole).map { resource ->
val practitionerRole = resource as PractitionerRole
val practitioner = findResource(practitionerRole.practitioner)?.resource?.let { it as Practitioner }

FHIRDirectoryEntry(
telematikID = practitioner?.identifier?.firstOrNull { it.system == "https://gematik.de/fhir/sid/telematik-id" }?.value,
displayName = practitioner?.name?.firstOrNull()?.text,
resourceType = ResourceType.PractitionerRole,
practitionerRole = practitionerRole,
practitioner = practitioner,
location =
practitionerRole.location?.mapNotNull {
findResource(it)?.resource
}?.map {
it as Location
}?.ifEmpty { null },
endpoint =
practitionerRole.endpoint?.mapNotNull {
findResource(it)?.resource
}?.map {
it as Endpoint
}?.ifEmpty { null },
)
}
}

fun Bundle.filterHealthcareServices(): List<FHIRDirectoryEntry> {
return filterByType(ResourceType.HealthcareService).map { resource ->
val healthcareService = resource as HealthcareService
val organization = findResource(healthcareService.providedBy)?.resource?.let { it as Organization }
FHIRDirectoryEntry(
telematikID = organization?.identifier?.firstOrNull { it.system == "https://gematik.de/fhir/sid/telematik-id" }?.value,
displayName = organization?.name,
resourceType = ResourceType.HealthcareService,
healthcareService = healthcareService,
organization = organization,
location =
healthcareService.location?.mapNotNull {
findResource(it)?.resource
}?.map {
it as Location
}?.ifEmpty { null },
endpoint =
healthcareService.endpoint?.mapNotNull {
findResource(it)?.resource
}?.map {
it as Endpoint
}?.ifEmpty { null },
)
}
}

fun Bundle.toDirectoryEntries(): List<FHIRDirectoryEntry> {
return filterPractitionerRoles() + filterHealthcareServices()
}

fun Bundle.findResource(reference: Reference): Bundle.BundleEntryComponent? {
val id = IdType(reference.reference)
return this.entry.find {
it.resource?.fhirType() == id.resourceType && it.resource?.idElement?.idPart == id.idPart
}
}
Loading

0 comments on commit 76e943f

Please sign in to comment.