Skip to content

Commit

Permalink
Admin V2
Browse files Browse the repository at this point in the history
AuthOption better toString
Fix to KnownDeviceProofEndpoints's options endpoint to remove auth requirements
Protobuf support
UUID bytes support
Better Partial support with toCondition and toModification, new partialOf syntax
Fixed some VirtualInstance issues with serialization on default fields
Better catch QueryParamWebSocketHandler loop
Ktor Websocket path parsing changes
  • Loading branch information
UnknownJoe796 committed Nov 7, 2024
1 parent 0c2f1d2 commit d5162e6
Show file tree
Hide file tree
Showing 31 changed files with 1,212 additions and 44 deletions.
2 changes: 1 addition & 1 deletion server-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dependencies {
api(serverlibs.mongoBson)
api(serverlibs.kBson)
api(serverlibs.kaml)
// api(serverlibs.serializationProtobuf)
api(serverlibs.serializationProtobuf)
api(serverlibs.kotlinReflect)
implementation(serverlibs.bouncyCastleBcprov)
implementation(serverlibs.bouncyCastleBcpkix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ interface FieldCollection<Model : Any> {
limit = limit,
maxQueryMs = maxQueryMs
).map {
Partial(it, fields)
partialOf(it, fields)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class AuthOption(
val limitationDescription: String? = null,
val additionalRequirement: suspend (RequestAuth<*>) -> Boolean = { true }
) {
override fun toString(): String = "$type $scopes $maxAge"
}

data class AuthOptions<out USER : HasId<*>?>(val options: Set<AuthOption?>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class KnownDeviceProofEndpoints(
inputType = Unit.serializer(),
outputType = KnownDeviceOptions.serializer(),
description = "Gives information about how valuable working from a known device is and for how long it works.",
authOptions = anyAuthRoot,
authOptions = noAuth,
errorCases = listOf(),
examples = listOf(),
implementation = { value: Unit ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ open class ModelRestEndpoints<USER : HasId<*>?, T : HasId<ID>, ID : Comparable<I
condition = cond,
orderBy = sampleSorts.randomOrNull() ?: listOf()
),
List(10) { Partial(exampleItem()!!, paths) }
List(10) { partialOf(exampleItem()!!, paths) }
)
}
} catch (e: Exception) {
Expand Down Expand Up @@ -462,6 +462,34 @@ open class ModelRestEndpoints<USER : HasId<*>?, T : HasId<ID>, ID : Comparable<I
}
)

val modifySimple = detailPath.path("simplified").patch.api(
// belongsToInterface = interfaceName,
belongsToInterface = null,
authOptions = info.authOptions,
inputType = Partial.serializer(info.serialization.serializer),
outputType = info.serialization.serializer,
summary = "Simplified Modify",
description = "Modifies a ${collectionName} by ID, returning the new value.",
errorCases = listOf(
LSError(
http = HttpStatus.NotFound.code,
detail = "",
message = "There was no known object by that ID.",
data = ""
)
),
implementation = { input: Partial<T> ->
try {
info.collection(this)
.updateOneById(path1, input.toModification(info.serialization.serializer))
.also { if (it.old == null && it.new == null) throw NotFoundException() }
.new!!
} catch (e: UniqueViolationException) {
throw BadRequestException(detail = "unique", message = e.key?.titleCase()?.let { "$it already exists" } ?: "Already exists", cause = e)
}
}
)

val bulkDelete = post("bulk-delete").api(
belongsToInterface = interfaceName,
authOptions = info.authOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,31 @@ class MetaEndpoints(
})
}

private suspend fun openAdmin2(): HttpResponse {
val inject = buildJsonObject {
put("url", generalSettings().publicUrl)
}
val page = client.get("https://lsadmin.cs.lightningkite.com").bodyAsText()
.let { original ->
(original.substringBeforeLast("</body>") + """
<script type="application/json" id="injectedBackendInformation">${inject}</script>
</body>
""".trimIndent() + original.substringAfterLast("</body>"))
}
.let { original ->
(original.substringBeforeLast("</head>") + """
<base href="${path("admin2/").fullUrl()}">
</head>
""".trimIndent() + original.substringAfterLast("</head>"))
}
return HttpResponse.html(content = page, headers = {
set(
"Content-Security-Policy",
"script-src 'unsafe-eval' ${generalSettings().publicUrl}/ https://lsadmin.cs.lightningkite.com/"
)
})
}

val bulk = path("bulk").bulkRequestEndpoint()

val admin = path("admin/").get.handler {
Expand All @@ -84,6 +109,15 @@ class MetaEndpoints(
else
openAdmin(it)
}
val admin2 = path("admin2/").get.handler {
openAdmin2()
}
val admin2Resources = path("admin2/{...}").get.handler {
if (it.wildcard?.contains(".") == true)
HttpResponse.pathMovedOld("https://lsadmin.cs.lightningkite.com/${it.wildcard}")
else
openAdmin2()
}
val schema = path("schema").get.handler {
HttpResponse(
body = HttpContent.Text(
Expand Down Expand Up @@ -265,6 +299,7 @@ class MetaEndpoints(
health.route.endpoint,
isOnline,
admin,
admin2,
openApi,
openApiJson,
schema,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.lightningkite.lightningserver.serialization

import com.lightningkite.UUID
import com.lightningkite.lightningserver.typed.uncontextualize
import com.lightningkite.serialization.DurationMsSerializer
import com.lightningkite.serialization.listElement
import com.lightningkite.serialization.mapKeyElement
import com.lightningkite.serialization.mapValueElement
import kotlinx.datetime.Instant
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.protobuf.ProtoIntegerType
import kotlinx.serialization.protobuf.ProtoNumber
import kotlinx.serialization.protobuf.ProtoType
import kotlin.time.Duration

val ProtoBufOverrides = SerializersModule {
contextual(Duration::class, DurationMsSerializer)
contextual(UUID::class, UUIDByteArraySerializer)
contextual(Instant::class, InstantLongSerializer)
}

object UUIDByteArraySerializer : KSerializer<UUID> {
val defer = ByteArraySerializer()
override val descriptor: SerialDescriptor = SerialDescriptor("com.lightningkite.UUID", defer.descriptor)

override fun deserialize(decoder: Decoder): UUID {
return UUID.parse(decoder.decodeSerializableValue(defer))
}

override fun serialize(encoder: Encoder, value: UUID) {
encoder.encodeSerializableValue(defer, value.toBytes())
}
}

//class ProtobufSchema(val module: SerializersModule, val packageName: String) {
// val aliases = HashMap<String, PAlias>()
// val messages = HashMap<String, PMessage>()
// val enums = HashMap<String, PEnum>()
//
// data class PMessage(
// val fields: List<PField>
// )
//
// data class PAlias(
// val target: String,
// )
//
// data class PField(
// val name: String,
// val type: PType,
// val optional: Boolean,
// val number: Int,
// )
//
// data class PType(
// val name: String,
// val repeated: Boolean = false,
// val nullable: Boolean = false,
// )
//
// data class PEnum(
// val entries: List<Pair<String, Int>>
// )
//
// val typesHandled = HashSet<String>()
//
// fun KSerializer<*>.protobufType(annotations: List<Annotation> = listOf()): PType {
// val serializer = this
// return when(serializer.descriptor.kind) {
// SerialKind.CONTEXTUAL -> (Serialization.json.serializersModule.getContextual(
// serializer.descriptor.capturedKClass ?: throw IllegalStateException("No captured KClass found for ${serializer.descriptor}")
// ) ?: throw IllegalStateException("No contextual serializer found for ${serializer.descriptor.capturedKClass!!.qualifiedName}")).protobufType(annotations)
// PrimitiveKind.BOOLEAN -> PType("bool", nullable = serializer.descriptor.isNullable)
// PrimitiveKind.BYTE,
// PrimitiveKind.CHAR,
// PrimitiveKind.SHORT,
// PrimitiveKind.INT -> {
// val integerType = annotations.filterIsInstance<ProtoType>().firstOrNull()?.type ?: ProtoIntegerType.DEFAULT
// when (integerType) {
// ProtoIntegerType.DEFAULT -> PType("int32", nullable = serializer.descriptor.isNullable)
// ProtoIntegerType.SIGNED -> PType("sint32", nullable = serializer.descriptor.isNullable)
// ProtoIntegerType.FIXED -> PType("fixed32", nullable = serializer.descriptor.isNullable)
// }
// }
// PrimitiveKind.LONG -> {
// val integerType = annotations.filterIsInstance<ProtoType>().firstOrNull()?.type ?: ProtoIntegerType.DEFAULT
// when (integerType) {
// ProtoIntegerType.DEFAULT -> PType("int64", nullable = serializer.descriptor.isNullable)
// ProtoIntegerType.SIGNED -> PType("sint64", nullable = serializer.descriptor.isNullable)
// ProtoIntegerType.FIXED -> PType("fixed64", nullable = serializer.descriptor.isNullable)
// }
// }
// PrimitiveKind.FLOAT -> PType("float", nullable = serializer.descriptor.isNullable)
// PrimitiveKind.DOUBLE -> PType("double", nullable = serializer.descriptor.isNullable)
// PrimitiveKind.STRING -> PType("string", nullable = serializer.descriptor.isNullable)
// StructureKind.LIST -> {
// val element = serializer.listElement()!!
// is(element.descriptor.kind == PrimitiveKind.BYTE) {
// PType("bytes", nullable = serializer.descriptor.isNullable)
// } else {
// element.protobufType(annotations).copy(repeated = true)
// }
// serializer.listElement()?.let { this += it }
// }
// StructureKind.MAP -> {
// serializer.mapKeyElement()?.let { this += it }
// serializer.mapValueElement()?.let { this += it }
// }
// StructureKind.OBJECT -> {}
// StructureKind.CLASS -> {
// val name = descriptor.serialName.corrected()
// if(!typesHandled.add(name)) return name
// messages[name] = PEnum((0..<serializer.descriptor.elementsCount).map { index ->
// val name = serializer.descriptor.getElementName(index).corrected()
// name to (serializer.descriptor.getElementAnnotations(index).filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index)
// })
// name
// }
// SerialKind.ENUM -> {
// val name = descriptor.serialName.corrected()
// if(!typesHandled.add(name)) return name
// enums[name] = PEnum((0..<serializer.descriptor.elementsCount).map { index ->
// val name = serializer.descriptor.getElementName(index).corrected()
// name to (serializer.descriptor.getElementAnnotations(index).filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index)
// })
// name
// }
// PolymorphicKind.SEALED -> throw NotImplementedError()
// PolymorphicKind.OPEN -> throw NotImplementedError()
// }
// }
//
// fun String.corrected(): String {
// val validChars = filter { it.isLetterOrDigit() || it == '_' }
// if (validChars.isEmpty()) throw IllegalArgumentException("Can not correct name '$this' for ProtoBuf schema")
// if (validChars[0] == '_') {
// if (validChars.length == 1)
// throw IllegalArgumentException("Can not correct name '$this' for ProtoBuf schema")
// return validChars.drop(1)
// }
// }
//}
Loading

0 comments on commit d5162e6

Please sign in to comment.