basic types and identifier implementation
This commit is contained in:
@@ -6,6 +6,7 @@ kotlinx-html = "0.12.0"
|
|||||||
kotlinx-browser = "0.5.0"
|
kotlinx-browser = "0.5.0"
|
||||||
kotlinx-serialization = "1.8.0"
|
kotlinx-serialization = "1.8.0"
|
||||||
kotlin-css-jvm = "2025.6.4"
|
kotlin-css-jvm = "2025.6.4"
|
||||||
|
kotlinx-datetime = "0.7.1"
|
||||||
ktor = "3.4.1"
|
ktor = "3.4.1"
|
||||||
ktor-server-rate-limiting = "2.2.1"
|
ktor-server-rate-limiting = "2.2.1"
|
||||||
logback = "1.5.13"
|
logback = "1.5.13"
|
||||||
@@ -25,6 +26,7 @@ kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotl
|
|||||||
kotlinx-html = { module = "org.jetbrains.kotlinx:kotlinx-html", version.ref = "kotlinx-html" }
|
kotlinx-html = { module = "org.jetbrains.kotlinx:kotlinx-html", version.ref = "kotlinx-html" }
|
||||||
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinx-browser" }
|
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinx-browser" }
|
||||||
kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
|
||||||
|
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
|
||||||
kotlin-css = { module = "org.jetbrains.kotlin-wrappers:kotlin-css-jvm", version.ref = "kotlin-css-jvm" }
|
kotlin-css = { module = "org.jetbrains.kotlin-wrappers:kotlin-css-jvm", version.ref = "kotlin-css-jvm" }
|
||||||
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
|
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
|
||||||
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||||
@@ -35,7 +37,7 @@ kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version
|
|||||||
[bundles]
|
[bundles]
|
||||||
jvmMain = ["ktor-server-core", "ktor-server-netty", "ktor-server-auth", "ktor-server-content-negotiation", "ktor-serialization-kotlinx-json", "ktor-server-sessions", "ktor-server-host-common", "ktor-server-html-builder", "ktor-server-rate-limiting", "kotlinx-html", "kotlin-css", "exposed-core", "exposed-jdbc", "h2"]
|
jvmMain = ["ktor-server-core", "ktor-server-netty", "ktor-server-auth", "ktor-server-content-negotiation", "ktor-serialization-kotlinx-json", "ktor-server-sessions", "ktor-server-host-common", "ktor-server-html-builder", "ktor-server-rate-limiting", "kotlinx-html", "kotlin-css", "exposed-core", "exposed-jdbc", "h2"]
|
||||||
jvmTest = ["ktor-test", "kotlin-test-junit"]
|
jvmTest = ["ktor-test", "kotlin-test-junit"]
|
||||||
commonMain = ["kotlinx-serialization"]
|
commonMain = ["kotlinx-serialization", "kotlinx-datetime"]
|
||||||
commonTest = ["kotlin-test"]
|
commonTest = ["kotlin-test"]
|
||||||
jsMain = ["kotlinx-browser"]
|
jsMain = ["kotlinx-browser"]
|
||||||
jsTest = []
|
jsTest = []
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
@file:OptIn(ExperimentalUuidApi::class)
|
|
||||||
package online.maestoso.cofront
|
|
||||||
|
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
data class Identifier(val id: Uuid, val domain: String) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "$id@$domain"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun String.toIdentifier(): Identifier {
|
|
||||||
val split = split('@', limit = 2)
|
|
||||||
return Identifier(Uuid.parse(split[0]), split[1])
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package online.maestoso.cofront.payloads
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import online.maestoso.cofront.types.Member
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetMemberPayload(val member: Member)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package online.maestoso.cofront.payloads
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import online.maestoso.cofront.types.Identifier
|
||||||
|
import online.maestoso.cofront.types.JID
|
||||||
|
import online.maestoso.cofront.types.Member
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SystemLookupPayload(
|
||||||
|
/**
|
||||||
|
* The list of current fronters.
|
||||||
|
*/
|
||||||
|
val fronters: Set<Identifier>,
|
||||||
|
/**
|
||||||
|
* The list of all members.
|
||||||
|
*/
|
||||||
|
val members: Set<Identifier>,
|
||||||
|
/**
|
||||||
|
* The list of all JIDs associated with this system.
|
||||||
|
*/
|
||||||
|
val jids: Set<JID>,
|
||||||
|
/**
|
||||||
|
* URL to a dashboard of more information to this system.
|
||||||
|
*/
|
||||||
|
val url: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package online.maestoso.cofront.types
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import online.maestoso.cofront.util.toByteArray
|
||||||
|
import online.maestoso.cofront.util.toUInt
|
||||||
|
import online.maestoso.cofront.util.toULong
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
|
||||||
|
@Serializable(with = IdentifierSerializer::class)
|
||||||
|
data class Identifier(val system: SystemId, val member: MemberId?, val domain: String) {
|
||||||
|
data class MemberId(val serial: ULong, val time: ULong, val id: ULong)
|
||||||
|
data class SystemId(val site: UInt, val host: UInt, val time: ULong, val id: ULong)
|
||||||
|
}
|
||||||
|
object IdentifierSerializer : KSerializer<Identifier> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("memberid", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(
|
||||||
|
encoder: Encoder,
|
||||||
|
value: Identifier
|
||||||
|
) {
|
||||||
|
val buf = ByteArray(48) {0}
|
||||||
|
value.system.site.toByteArray().copyInto(buf, 0)
|
||||||
|
value.system.host.toByteArray().copyInto(buf, 4)
|
||||||
|
value.system.time.toByteArray().copyInto(buf, 8)
|
||||||
|
value.system.id.toByteArray().copyInto(buf, 16)
|
||||||
|
if (value.member != null) {
|
||||||
|
value.member.serial.toByteArray().copyInto(buf, 24)
|
||||||
|
value.member.time.toByteArray().copyInto(buf, 32)
|
||||||
|
value.member.id.toByteArray().copyInto(buf, 40)
|
||||||
|
} else {
|
||||||
|
buf.dropLast(24)
|
||||||
|
}
|
||||||
|
encoder.encodeString(Base64.UrlSafe.encode(buf) + ":${value.domain}")
|
||||||
|
}
|
||||||
|
@Throws(IllegalArgumentException::class)
|
||||||
|
override fun deserialize(decoder: Decoder): Identifier {
|
||||||
|
val str = decoder.decodeString()
|
||||||
|
val i = str.indexOfFirst {it == ':'}
|
||||||
|
if(i != 24 || i != 48 || i == -1) throw IllegalArgumentException("colon not correctly placed")
|
||||||
|
val id = str.substring(0..<i)
|
||||||
|
val domain = str.substring(i..<str.length)
|
||||||
|
val buf = Base64.UrlSafe.decode(id)
|
||||||
|
val member: Identifier.MemberId? = when (buf.size) {
|
||||||
|
24 -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
48 -> {
|
||||||
|
Identifier.MemberId(
|
||||||
|
buf.copyOfRange(24, 31).toULong(),
|
||||||
|
buf.copyOfRange(32, 39).toULong(),
|
||||||
|
buf.copyOfRange(40, 47).toULong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> throw IllegalArgumentException("identifier was the wrong size: expected 24 or 48 bytes, got ${buf.size}")
|
||||||
|
}
|
||||||
|
return Identifier(
|
||||||
|
Identifier.SystemId(
|
||||||
|
buf.copyOfRange(0, 3).toUInt(),
|
||||||
|
buf.copyOfRange(4, 7).toUInt(),
|
||||||
|
buf.copyOfRange(8, 15).toULong(),
|
||||||
|
buf.copyOfRange(16, 23).toULong(),
|
||||||
|
),
|
||||||
|
member,
|
||||||
|
domain
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/commonMain/kotlin/online/maestoso/cofront/types/JID.kt
Normal file
29
src/commonMain/kotlin/online/maestoso/cofront/types/JID.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package online.maestoso.cofront.types
|
||||||
|
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Serializer
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
|
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
@Serializable(with = JIDSerializer::class)
|
||||||
|
data class JID(val node: String, val domain: String, val resource: String) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "$node@$domain/$resource"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class JIDSerializer : KSerializer<JID> {
|
||||||
|
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("jid", PrimitiveKind.STRING)
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: JID) {
|
||||||
|
encoder.encodeString(value.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): JID {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package online.maestoso.cofront.types
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Member(
|
||||||
|
/**
|
||||||
|
Unique identifier for this member.
|
||||||
|
*/
|
||||||
|
val id: Identifier,
|
||||||
|
/**
|
||||||
|
Short but human-readable name for this member.
|
||||||
|
*/
|
||||||
|
val shortname: String,
|
||||||
|
/**
|
||||||
|
* Creation date and time for this member.
|
||||||
|
*/
|
||||||
|
val created: Instant,
|
||||||
|
/**
|
||||||
|
* The profile associated with this member.
|
||||||
|
*/
|
||||||
|
val profile: Profile,
|
||||||
|
/**
|
||||||
|
* A set of descriptions of how this member is related to other members in the system.
|
||||||
|
* This can include a variety of implementation-specific information, such as groups,
|
||||||
|
* a "root member" for a system profile, or represent interpersonal relationships.
|
||||||
|
*/
|
||||||
|
val relations: Set<MemberRelation>,
|
||||||
|
/**
|
||||||
|
* Endpoint to look up fronter/full system information.
|
||||||
|
*/
|
||||||
|
val system: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package online.maestoso.cofront.types
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MemberRelation(val from: Identifier, val to: Identifier, val type: String)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package online.maestoso.cofront.types
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Profile(
|
||||||
|
/**
|
||||||
|
* Long display name for this profile.
|
||||||
|
*/
|
||||||
|
val name: String?,
|
||||||
|
/**
|
||||||
|
* Profile's pronouns.
|
||||||
|
*/
|
||||||
|
val pronouns: String?,
|
||||||
|
/**
|
||||||
|
* Profile's preferred accent color.
|
||||||
|
*/
|
||||||
|
val color: String?,
|
||||||
|
/**
|
||||||
|
* URL to access profile picture.
|
||||||
|
*/
|
||||||
|
val avatar: String?,
|
||||||
|
/**
|
||||||
|
* This member's preferred birthday.
|
||||||
|
*/
|
||||||
|
val birthday: LocalDateTime?,
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package online.maestoso.cofront.util
|
||||||
|
|
||||||
|
fun UInt.toByteArray(): ByteArray {
|
||||||
|
val b = ByteArray(4) {0}
|
||||||
|
b[0] = this.toByte()
|
||||||
|
b[1] = (this shr 8).toByte()
|
||||||
|
b[2] = (this shr 16).toByte()
|
||||||
|
b[3] = (this shr 24).toByte()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toUInt(): UInt {
|
||||||
|
var i = 0u
|
||||||
|
i = i or this[0].toUInt()
|
||||||
|
i = i or (this[1].toUInt() shl 8)
|
||||||
|
i = i or (this[2].toUInt() shl 16)
|
||||||
|
i = i or (this[3].toUInt() shl 24)
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ULong.toByteArray(): ByteArray {
|
||||||
|
val b = ByteArray(8) {0}
|
||||||
|
b[0] = this.toByte()
|
||||||
|
b[1] = (this shr 8).toByte()
|
||||||
|
b[2] = (this shr 16).toByte()
|
||||||
|
b[3] = (this shr 24).toByte()
|
||||||
|
b[4] = (this shr 32).toByte()
|
||||||
|
b[5] = (this shr 40).toByte()
|
||||||
|
b[6] = (this shr 48).toByte()
|
||||||
|
b[7] = (this shr 56).toByte()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ByteArray.toULong(): ULong {
|
||||||
|
var i = 0UL
|
||||||
|
i = i or this[0].toULong()
|
||||||
|
i = i or (this[1].toULong() shl 8)
|
||||||
|
i = i or (this[2].toULong() shl 16)
|
||||||
|
i = i or (this[3].toULong() shl 24)
|
||||||
|
i = i or (this[4].toULong() shl 32)
|
||||||
|
i = i or (this[5].toULong() shl 40)
|
||||||
|
i = i or (this[6].toULong() shl 48)
|
||||||
|
i = i or (this[7].toULong() shl 56)
|
||||||
|
return i
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package online.maestoso.cofront.util
|
||||||
|
|
||||||
|
fun String.isValidDomain(): Boolean {
|
||||||
|
// regex stolen from https://stackoverflow.com/a/3824105. if it's broken its not my fault
|
||||||
|
val regex = """^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$"""
|
||||||
|
.toRegex()
|
||||||
|
return regex.matches(this)
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
@file:OptIn(ExperimentalUuidApi::class)
|
@file:OptIn(ExperimentalUuidApi::class)
|
||||||
|
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import online.maestoso.cofront.Identifier
|
import online.maestoso.cofront.types.Identifier
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
import kotlin.uuid.ExperimentalUuidApi
|
||||||
import kotlin.uuid.Uuid
|
import kotlin.uuid.Uuid
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
window.alert("kotlin js test")
|
window.alert("kotlin js test")
|
||||||
val i = Identifier(Uuid.generateV7(), "localhost")
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1 @@
|
|||||||
@file:OptIn(ExperimentalUuidApi::class)
|
|
||||||
|
|
||||||
package online.maestoso.cofront.api
|
package online.maestoso.cofront.api
|
||||||
|
|
||||||
import online.maestoso.cofront.Identifier
|
|
||||||
import kotlin.uuid.ExperimentalUuidApi
|
|
||||||
import kotlin.uuid.Uuid
|
|
||||||
|
|
||||||
fun test() {
|
|
||||||
val i = Identifier(Uuid.generateV7(), "localhost")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user