Basic support for FHIR payloads.

Support for nembed
This commit is contained in:
Vitor Pamplona 2024-02-27 18:50:20 -05:00
parent e305f3c198
commit 328ed226c6
7 changed files with 470 additions and 3 deletions

View File

@ -63,6 +63,7 @@ import com.vitorpamplona.quartz.events.DeletionEvent
import com.vitorpamplona.quartz.events.EmojiPackEvent import com.vitorpamplona.quartz.events.EmojiPackEvent
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.FhirResourceEvent
import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileServersEvent import com.vitorpamplona.quartz.events.FileServersEvent
import com.vitorpamplona.quartz.events.FileStorageEvent import com.vitorpamplona.quartz.events.FileStorageEvent
@ -1363,6 +1364,26 @@ object LocalCache {
refreshObservers(note) refreshObservers(note)
} }
fun consume(
event: FhirResourceEvent,
relay: Relay?,
) {
val note = getOrCreateNote(event.id)
val author = getOrCreateUser(event.pubKey)
if (relay != null) {
author.addRelayBeingUsed(relay, event.createdAt)
note.addRelay(relay)
}
// Already processed this event.
if (note.event != null) return
note.loadEvent(event, author, emptyList())
refreshObservers(note)
}
fun consume( fun consume(
event: HighlightEvent, event: HighlightEvent,
relay: Relay?, relay: Relay?,
@ -1981,6 +2002,7 @@ object LocalCache {
is EmojiPackEvent -> consume(event, relay) is EmojiPackEvent -> consume(event, relay)
is EmojiPackSelectionEvent -> consume(event, relay) is EmojiPackSelectionEvent -> consume(event, relay)
is SealedGossipEvent -> consume(event, relay) is SealedGossipEvent -> consume(event, relay)
is FhirResourceEvent -> consume(event, relay)
is FileHeaderEvent -> consume(event, relay) is FileHeaderEvent -> consume(event, relay)
is FileServersEvent -> consume(event, relay) is FileServersEvent -> consume(event, relay)
is FileStorageEvent -> consume(event, relay) is FileStorageEvent -> consume(event, relay)

View File

@ -0,0 +1,123 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.note
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "resourceType",
)
@JsonSubTypes(
JsonSubTypes.Type(value = Practitioner::class, name = "Practitioner"),
JsonSubTypes.Type(value = Patient::class, name = "Patient"),
JsonSubTypes.Type(value = Bundle::class, name = "Bundle"),
JsonSubTypes.Type(value = VisionPrescription::class, name = "VisionPrescription"),
)
open class Resource(
var resourceType: String? = null,
var id: String = "",
)
class Practitioner(
resourceType: String? = null,
id: String = "",
var active: Boolean? = null,
var name: ArrayList<HumanName> = arrayListOf(),
var gender: String? = null,
) : Resource(resourceType, id)
class Patient(
resourceType: String? = null,
id: String = "",
var active: Boolean? = null,
var name: ArrayList<HumanName> = arrayListOf(),
var gender: String? = null,
) : Resource(resourceType, id)
class HumanName(
var use: String? = null,
var family: String? = null,
var given: ArrayList<String> = arrayListOf(),
) {
fun assembleName(): String {
return given.joinToString(" ") + " " + family
}
}
class Bundle(
resourceType: String? = null,
id: String = "",
var type: String? = null,
var created: String? = null,
var entry: List<Resource> = arrayListOf(),
) : Resource(resourceType, id)
class VisionPrescription(
resourceType: String? = null,
id: String = "",
var status: String? = null,
var created: String? = null,
var patient: Reference? = Reference(),
var encounter: Reference? = Reference(),
var dateWritten: String? = null,
var prescriber: Reference? = Reference(),
var lensSpecification: List<LensSpecification> = arrayListOf(),
) : Resource(resourceType, id)
class LensSpecification(
var eye: String? = null,
var sphere: Double? = null,
var cylinder: Double? = null,
var axis: Double? = null,
var add: Double? = null,
var prism: List<Prism> = arrayListOf(),
// contact lenses
var power: Double? = null,
var backCurve: Double? = null,
var diameter: Double? = null,
var color: String? = null,
var brand: String? = null,
var note: String? = null,
)
class Prism(
var amount: Double? = null,
var base: String? = null,
)
class Reference(
var reference: String? = null,
)
fun findReferenceInDb(
it: String,
db: Map<String, Resource>,
): Resource? {
val parts = it.split("/")
return if (parts.size == 2) {
db.get(parts[1].removePrefix("#"))
} else {
db.get(it.removePrefix("#"))
}
}

View File

@ -34,6 +34,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -53,11 +54,13 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.VerticalDivider
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@ -66,6 +69,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@ -81,9 +85,11 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
@ -94,6 +100,8 @@ import androidx.lifecycle.map
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter import coil.compose.AsyncImagePainter
import coil.request.SuccessResult import coil.request.SuccessResult
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fonfon.kgeohash.GeoHash import com.fonfon.kgeohash.GeoHash
import com.fonfon.kgeohash.toGeoHash import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@ -125,6 +133,7 @@ import com.vitorpamplona.amethyst.ui.components.VideoView
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.elements.AddButton import com.vitorpamplona.amethyst.ui.elements.AddButton
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingCommunityInPost
import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost import com.vitorpamplona.amethyst.ui.elements.DisplayFollowingHashtagsInPost
@ -206,6 +215,8 @@ import com.vitorpamplona.quartz.events.EmojiPackEvent
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.events.EmojiUrl import com.vitorpamplona.quartz.events.EmojiUrl
import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.EmptyTagList
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.FhirResourceEvent
import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
import com.vitorpamplona.quartz.events.GenericRepostEvent import com.vitorpamplona.quartz.events.GenericRepostEvent
@ -239,8 +250,11 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.net.URL import java.net.URL
import java.text.DecimalFormat
import java.text.NumberFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@ -1176,6 +1190,9 @@ private fun RenderNoteRow(
is BadgeAwardEvent -> { is BadgeAwardEvent -> {
RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav) RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
} }
is FhirResourceEvent -> {
RenderFhirResource(baseNote, accountViewModel, nav)
}
is PeopleListEvent -> { is PeopleListEvent -> {
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav) DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
} }
@ -4346,3 +4363,223 @@ fun CreateImageHeader(
} }
} }
} }
@Preview
@Composable
fun RenderEyeGlassesPrescriptionPreview() {
val accountViewModel = mockAccountViewModel()
val nav: (String) -> Unit = {}
val prescriptionEvent = Event.fromJson("{\"id\":\"0c15d2bc6f7dcc42fa4426d35d30d09840c9afa5b46d100415006e41d6471416\",\"pubkey\":\"bcd4715cc34f98dce7b52fddaf1d826e5ce0263479b7e110a5bd3c3789486ca8\",\"created_at\":1709074097,\"kind\":82,\"tags\":[],\"content\":\"{\\\"resourceType\\\":\\\"Bundle\\\",\\\"id\\\":\\\"bundle-vision-test\\\",\\\"type\\\":\\\"document\\\",\\\"entry\\\":[{\\\"resourceType\\\":\\\"Practitioner\\\",\\\"id\\\":\\\"2\\\",\\\"active\\\":true,\\\"name\\\":[{\\\"use\\\":\\\"official\\\",\\\"family\\\":\\\"Careful\\\",\\\"given\\\":[\\\"Adam\\\"]}],\\\"gender\\\":\\\"male\\\"},{\\\"resourceType\\\":\\\"Patient\\\",\\\"id\\\":\\\"1\\\",\\\"active\\\":true,\\\"name\\\":[{\\\"use\\\":\\\"official\\\",\\\"family\\\":\\\"Duck\\\",\\\"given\\\":[\\\"Donald\\\"]}],\\\"gender\\\":\\\"male\\\"},{\\\"resourceType\\\":\\\"VisionPrescription\\\",\\\"status\\\":\\\"active\\\",\\\"created\\\":\\\"2014-06-15\\\",\\\"patient\\\":{\\\"reference\\\":\\\"#1\\\"},\\\"dateWritten\\\":\\\"2014-06-15\\\",\\\"prescriber\\\":{\\\"reference\\\":\\\"#2\\\"},\\\"lensSpecification\\\":[{\\\"eye\\\":\\\"right\\\",\\\"sphere\\\":-2,\\\"prism\\\":[{\\\"amount\\\":0.5,\\\"base\\\":\\\"down\\\"}],\\\"add\\\":2},{\\\"eye\\\":\\\"left\\\",\\\"sphere\\\":-1,\\\"cylinder\\\":-0.5,\\\"axis\\\":180,\\\"prism\\\":[{\\\"amount\\\":0.5,\\\"base\\\":\\\"up\\\"}],\\\"add\\\":2}]}]}\",\"sig\":\"dc58f6109111ca06920c0c711aeaf8e2ee84975afa60d939828d4e01e2edea738f735fb5b1fcadf6d5496e36ac429abf7020a55fd1e4ed215738afc8d07cb950\"}") as FhirResourceEvent
RenderFhirResource(prescriptionEvent, accountViewModel, nav)
}
@Composable
fun RenderFhirResource(
baseNote: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val event = baseNote.event as? FhirResourceEvent ?: return
RenderFhirResource(event, accountViewModel, nav)
}
@Composable
fun RenderFhirResource(
event: FhirResourceEvent,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
var baseResource: Resource? by remember(event) {
mutableStateOf(null)
}
LaunchedEffect(key1 = event) {
withContext(Dispatchers.IO) {
val mapper =
jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
try {
baseResource = mapper.readValue(event.content, Resource::class.java)
} catch (e: Exception) {
Log.e("RenderEyeGlassesPrescription", "Parser error", e)
}
}
}
baseResource?.let { resource ->
when (resource) {
is Bundle -> {
val db = resource.entry.associate { it.id to it }
val vision = resource.entry.filterIsInstance(VisionPrescription::class.java)
vision.firstOrNull()?.let {
RenderEyeGlassesPrescription(it, db, accountViewModel, nav)
}
}
is VisionPrescription -> {
val db = mapOf(resource.id to resource)
RenderEyeGlassesPrescription(resource, db, accountViewModel, nav)
}
else -> {
}
}
}
}
@Composable
fun RenderEyeGlassesPrescription(
visionPrescription: VisionPrescription,
db: Map<String, Resource>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
Column(
modifier =
Modifier
.fillMaxWidth()
.padding(Size10dp),
) {
val rightEye = visionPrescription.lensSpecification.firstOrNull { it.eye == "right" }
val leftEye = visionPrescription.lensSpecification.firstOrNull { it.eye == "left" }
Text(
"Eyeglasses Prescription",
modifier = Modifier.padding(4.dp).fillMaxWidth(),
textAlign = TextAlign.Center,
)
Spacer(StdVertSpacer)
visionPrescription.patient?.reference?.let {
val patient = findReferenceInDb(it, db) as? Patient
patient?.name?.firstOrNull()?.assembleName()?.let {
Text(
text = "Patient: $it",
modifier = Modifier.padding(4.dp).fillMaxWidth(),
)
}
}
visionPrescription.status?.let {
Text(
text = "Status: ${it.capitalize()}",
modifier = Modifier.padding(4.dp).fillMaxWidth(),
)
}
Spacer(DoubleVertSpacer)
RenderEyeGlassesPrescriptionHeaderRow()
HorizontalDivider(thickness = DividerThickness)
rightEye?.let {
RenderEyeGlassesPrescriptionRow(data = it)
HorizontalDivider(thickness = DividerThickness)
}
leftEye?.let {
RenderEyeGlassesPrescriptionRow(data = it)
HorizontalDivider(thickness = DividerThickness)
}
Spacer(DoubleVertSpacer)
visionPrescription.prescriber?.reference?.let {
val practitioner = findReferenceInDb(it, db) as? Practitioner
practitioner?.name?.firstOrNull()?.assembleName()?.let {
Text(
text = "Signed by: $it",
modifier = Modifier.padding(4.dp).fillMaxWidth(),
textAlign = TextAlign.Right,
)
}
}
}
}
@Composable
fun RenderEyeGlassesPrescriptionHeaderRow() {
Row(
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = "Eye",
modifier = Modifier.padding(4.dp).weight(1f),
)
VerticalDivider(thickness = DividerThickness)
Text(
text = "Sph",
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
Text(
text = "Cyl",
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
Text(
text = "Axis",
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
VerticalDivider(thickness = DividerThickness)
Text(
text = "Add",
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
}
}
@Composable
fun RenderEyeGlassesPrescriptionRow(data: LensSpecification) {
Row(
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
val numberFormat = DecimalFormat("##.00")
val integerFormat = DecimalFormat("###")
Text(
text = data.eye?.capitalize() ?: "Unknown",
modifier = Modifier.padding(4.dp).weight(1f),
)
VerticalDivider(thickness = DividerThickness)
Text(
text = formatOrBlank(data.sphere, numberFormat),
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
Text(
text = formatOrBlank(data.cylinder, numberFormat),
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
Text(
text = formatOrBlank(data.axis, integerFormat),
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
VerticalDivider(thickness = DividerThickness)
Text(
text = formatOrBlank(data.add, numberFormat),
textAlign = TextAlign.Right,
modifier = Modifier.padding(4.dp).weight(1f),
)
}
}
fun formatOrBlank(
amount: Double?,
numberFormat: NumberFormat,
): String {
if (amount == null) return ""
if (Math.abs(amount) < 0.01) return ""
return numberFormat.format(amount)
}

View File

@ -319,8 +319,8 @@ class RichTextParser() {
Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE) Pattern.compile("#([^\\s!@#\$%^&*()=+./,\\[{\\]};:'\"?><]+)(.*)", Pattern.CASE_INSENSITIVE)
val acceptedNIP19schemes = val acceptedNIP19schemes =
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1") + listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1", "nembed") +
listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").map { listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1", "nembed").map {
it.uppercase() it.uppercase()
} }

View File

@ -102,6 +102,38 @@ class NIP19EmbedTests {
assertEquals(eyeglassesPrescriptionEvent!!.toJson(), decodedNote.toJson()) assertEquals(eyeglassesPrescriptionEvent!!.toJson(), decodedNote.toJson())
} }
@Test
fun testVisionPrescriptionBundleEmbedEvent() {
val signer =
NostrSignerInternal(
KeyPair(Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")),
)
var eyeglassesPrescriptionEvent: Event? = null
val countDownLatch = CountDownLatch(1)
FhirResourceEvent.create(fhirPayload = visionPrescriptionBundle, signer = signer) {
eyeglassesPrescriptionEvent = it
countDownLatch.countDown()
}
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
assertNotNull(eyeglassesPrescriptionEvent)
val bech32 = Nip19Bech32.createNEmbed(eyeglassesPrescriptionEvent!!)
println(eyeglassesPrescriptionEvent!!.toJson())
println(bech32)
val decodedNote = (Nip19Bech32.uriToRoute(bech32)?.entity as Nip19Bech32.NEmbed).event
assertTrue(decodedNote.hasValidSignature())
assertEquals(eyeglassesPrescriptionEvent!!.toJson(), decodedNote.toJson())
}
/* /*
@Test @Test
fun testCompressionSizes() { fun testCompressionSizes() {
@ -138,5 +170,6 @@ class NIP19EmbedTests {
assertTrue(true) assertTrue(true)
}*/ }*/
val visionPrescriptionFhir = "{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"Patient/example\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"Practitioner/example\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}" val visionPrescriptionFhir = "{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"Patient/Donald Duck\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"Practitioner/Adam Careful\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}"
val visionPrescriptionBundle = "{\"resourceType\":\"Bundle\",\"id\":\"bundle-vision-test\",\"type\":\"document\",\"entry\":[{\"resourceType\":\"Practitioner\",\"id\":\"2\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Careful\",\"given\":[\"Adam\"]}],\"gender\":\"male\"},{\"resourceType\":\"Patient\",\"id\":\"1\",\"active\":true,\"name\":[{\"use\":\"official\",\"family\":\"Duck\",\"given\":[\"Donald\"]}],\"gender\":\"male\"},{\"resourceType\":\"VisionPrescription\",\"status\":\"active\",\"created\":\"2014-06-15\",\"patient\":{\"reference\":\"#1\"},\"dateWritten\":\"2014-06-15\",\"prescriber\":{\"reference\":\"#2\"},\"lensSpecification\":[{\"eye\":\"right\",\"sphere\":-2,\"prism\":[{\"amount\":0.5,\"base\":\"down\"}],\"add\":2},{\"eye\":\"left\",\"sphere\":-1,\"cylinder\":-0.5,\"axis\":180,\"prism\":[{\"amount\":0.5,\"base\":\"up\"}],\"add\":2}]}]}"
} }

View File

@ -87,6 +87,7 @@ class EventFactory {
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig) FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent.KIND ->
FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig) FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig) GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig) GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig) GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.events
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
class FhirResourceEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
companion object {
const val KIND = 82
fun create(
fhirPayload: String,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (FhirResourceEvent) -> Unit,
) {
val tags = mutableListOf<Array<String>>()
signer.sign(createdAt, KIND, tags.toTypedArray(), fhirPayload, onReady)
}
}
}