Server : Character error management

This commit is contained in:
Thomas Andres Gomez 2025-03-30 21:17:17 +02:00
parent 0e7bc87cde
commit d806048510
29 changed files with 388 additions and 175 deletions

View file

@ -1,9 +1,6 @@
package com.pixelized.server.lwa package com.pixelized.server.lwa
import com.pixelized.server.lwa.server.LocalServer import com.pixelized.server.lwa.server.LocalServer
import com.pixelized.shared.lwa.sharedModuleDependencies
import org.koin.core.context.startKoin
import org.koin.java.KoinJavaComponent.inject
fun main() { fun main() {
LocalServer().create().start() LocalServer().create().start()

View file

@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import java.text.Collator
class AlterationStore( class AlterationStore(
private val pathProvider: PathProvider, private val pathProvider: PathProvider,
@ -75,6 +76,7 @@ class AlterationStore(
throw JsonConversionException(root = exception) throw JsonConversionException(root = exception)
} }
} }
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
?: emptyList() ?: emptyList()
} }
@ -117,15 +119,18 @@ class AlterationStore(
} }
// Update the dataflow. // Update the dataflow.
alterationFlow.update { alterations -> alterationFlow.update { alterations ->
val index = alterations.indexOfFirst { val index = alterations.indexOfFirst { it.id == json.id }
it.id == json.id alterations.toMutableList()
} .also {
alterations.toMutableList().also { if (index >= 0) {
when { it[index] = alteration
index >= 0 -> it[index] = alteration } else {
else -> it.add(alteration) it.add(alteration)
}
} }
} .sortedWith(compareBy(Collator.getInstance()) {
it.metadata.name
})
} }
} }
@ -144,9 +149,13 @@ class AlterationStore(
} }
// Update the data model with the deleted alteration. // Update the data model with the deleted alteration.
alterationFlow.update { alterations -> alterationFlow.update { alterations ->
alterations.toMutableList().also { alteration -> alterations.toMutableList()
alteration.removeIf { it.id == id } .also { alteration ->
} alteration.removeIf { it.id == id }
}
.sortedWith(compareBy(Collator.getInstance()) {
it.metadata.name
})
} }
} }

View file

@ -1,6 +1,5 @@
package com.pixelized.server.lwa.model.campaign package com.pixelized.server.lwa.model.campaign
import com.pixelized.server.lwa.model.alteration.AlterationStore.AlterationStoreException
import com.pixelized.shared.lwa.model.campaign.Campaign import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJson import com.pixelized.shared.lwa.model.campaign.CampaignJson
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory

View file

@ -19,7 +19,9 @@ class CharacterSheetService(
private val factory: CharacterSheetJsonFactory, private val factory: CharacterSheetJsonFactory,
) { ) {
private val scope = CoroutineScope(Dispatchers.IO + Job()) private val scope = CoroutineScope(Dispatchers.IO + Job())
private val sheets get() = sheetsFlow.value private val sheets get() = sheetsFlow.value
private val sheetsFlow = characterStore.characterSheetsFlow() private val sheetsFlow = characterStore.characterSheetsFlow()
.map { entry -> entry.associateBy { character -> character.id } } .map { entry -> entry.associateBy { character -> character.id } }
.stateIn( .stateIn(
@ -40,7 +42,7 @@ class CharacterSheetService(
return sheets[characterSheetId] return sheets[characterSheetId]
} }
fun tags() : Collection<TagJson> { fun tags(): Collection<TagJson> {
return alterationTags.value return alterationTags.value
} }
@ -52,14 +54,24 @@ class CharacterSheetService(
return sheets[characterSheetId]?.let(factory::convertToJson) return sheets[characterSheetId]?.let(factory::convertToJson)
} }
suspend fun updateCharacterSheet(character: CharacterSheetJson) { @Throws
suspend fun save(
character: CharacterSheetJson,
create: Boolean,
) {
return characterStore.save( return characterStore.save(
sheet = factory.convertFromJson(character) sheet = factory.convertFromJson(character),
create = create,
) )
} }
fun deleteCharacterSheet(characterSheetId: String): Boolean { @Throws
return characterStore.delete(id = characterSheetId) fun deleteCharacterSheet(
characterSheetId: String,
) {
characterStore.delete(
characterSheetId = characterSheetId
)
} }
// Data manipulation through WebSocket. // Data manipulation through WebSocket.
@ -78,7 +90,8 @@ class CharacterSheetService(
characterStore.save( characterStore.save(
sheet = character.copy( sheet = character.copy(
alterations = alterations, alterations = alterations,
) ),
create = false,
) )
} }
if (active.not() && contain) { if (active.not() && contain) {
@ -88,7 +101,8 @@ class CharacterSheetService(
characterStore.save( characterStore.save(
sheet = character.copy( sheet = character.copy(
alterations = alterations, alterations = alterations,
) ),
create = false,
) )
} }
} }
@ -100,7 +114,10 @@ class CharacterSheetService(
) { ) {
sheets[characterSheetId]?.let { character -> sheets[characterSheetId]?.let { character ->
val update = character.copy(damage = damage) val update = character.copy(damage = damage)
characterStore.save(sheet = update) characterStore.save(
sheet = update,
create = false,
)
} }
} }
@ -110,7 +127,10 @@ class CharacterSheetService(
) { ) {
sheets[characterSheetId]?.let { character -> sheets[characterSheetId]?.let { character ->
val update = character.copy(diminished = diminished) val update = character.copy(diminished = diminished)
characterStore.save(sheet = update) characterStore.save(
sheet = update,
create = false,
)
} }
} }
@ -120,7 +140,10 @@ class CharacterSheetService(
) { ) {
sheets[characterSheetId]?.let { character -> sheets[characterSheetId]?.let { character ->
val update = character.copy(fatigue = fatigue) val update = character.copy(fatigue = fatigue)
characterStore.save(sheet = update) characterStore.save(
sheet = update,
create = false,
)
} }
} }
@ -141,7 +164,10 @@ class CharacterSheetService(
skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill
}, },
) )
characterStore.save(sheet = update) characterStore.save(
sheet = update,
create = false,
)
} }
} }
} }

View file

@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
@ -27,15 +28,15 @@ class CharacterSheetStore(
val scope = CoroutineScope(Dispatchers.IO + Job()) val scope = CoroutineScope(Dispatchers.IO + Job())
// load the initial data // load the initial data
scope.launch { scope.launch {
updateCharacterSheets() updateCharacterFlow()
} }
} }
fun characterSheetsFlow(): StateFlow<List<CharacterSheet>> = characterSheetsFlow fun characterSheetsFlow(): StateFlow<List<CharacterSheet>> = characterSheetsFlow
private suspend fun updateCharacterSheets() { private suspend fun updateCharacterFlow() {
characterSheetsFlow.value = try { characterSheetsFlow.value = try {
loadCharacterSheets() load()
} catch (exception: Exception) { } catch (exception: Exception) {
println(exception) // TODO proper exception handling println(exception) // TODO proper exception handling
emptyList() emptyList()
@ -44,9 +45,12 @@ class CharacterSheetStore(
@Throws( @Throws(
FileReadException::class, FileReadException::class,
JsonCodingException::class,
JsonConversionException::class, JsonConversionException::class,
) )
suspend fun loadCharacterSheets(): List<CharacterSheet> { suspend fun load(
directory: File = this.directory,
): List<CharacterSheet> {
return directory return directory
.listFiles() .listFiles()
?.mapNotNull { file -> ?.mapNotNull { file ->
@ -59,9 +63,15 @@ class CharacterSheetStore(
if (json.isBlank()) { if (json.isBlank()) {
return@mapNotNull null return@mapNotNull null
} }
// decode the file
val data = try {
this.json.decodeFromString<CharacterSheetJson>(json)
} catch (exception: Exception) {
throw JsonCodingException(root = exception)
}
// parse the json string.
try { try {
val sheet = this.json.decodeFromString<CharacterSheetJson>(json) factory.convertFromJson(data)
factory.convertFromJson(sheet)
} catch (exception: Exception) { } catch (exception: Exception) {
throw JsonConversionException(root = exception) throw JsonConversionException(root = exception)
} }
@ -71,19 +81,35 @@ class CharacterSheetStore(
} }
@Throws( @Throws(
FileWriteException::class, BusinessException::class,
JsonConversionException::class, JsonConversionException::class,
JsonCodingException::class,
FileWriteException::class,
) )
fun save(sheet: CharacterSheet) { fun save(
// convert the character sheet into json format. sheet: CharacterSheet,
val data = try { create: Boolean,
factory.convertToJson(sheet = sheet).let(json::encodeToString) ) {
val file = characterSheetFile(id = sheet.id)
// Guard case on update alteration
if (create && file.exists()) {
val root = Exception("Character already exist, creation is impossible.")
throw BusinessException(root = root)
}
// Transform the json into the model.
val json = try {
factory.convertToJson(sheet = sheet)
} catch (exception: Exception) { } catch (exception: Exception) {
throw JsonConversionException(root = exception) throw JsonConversionException(root = exception)
} }
// Encode the json into a string.
val data = try {
this.json.encodeToString(json)
} catch (exception: Exception) {
throw JsonCodingException(root = exception)
}
// write the character file. // write the character file.
try { try {
val file = characterSheetFile(id = sheet.id)
file.writeText( file.writeText(
text = data, text = data,
charset = Charsets.UTF_8, charset = Charsets.UTF_8,
@ -92,32 +118,41 @@ class CharacterSheetStore(
throw FileWriteException(root = exception) throw FileWriteException(root = exception)
} }
// Update the dataflow. // Update the dataflow.
characterSheetsFlow.value = characterSheetsFlow.value characterSheetsFlow.update { characters ->
.toMutableList() characters.toMutableList()
.also { data -> .also { data ->
val index = data.indexOfFirst { it.id == sheet.id } val index = data.indexOfFirst { it.id == sheet.id }
if (index >= 0) { if (index >= 0) {
data[index] = sheet data[index] = sheet
} else { } else {
data.add(sheet) data.add(sheet)
}
} }
} .sortedWith(compareBy(Collator.getInstance()) {
.sortedWith(compareBy(Collator.getInstance()) { it.name }) it.name
})
}
} }
fun delete(id: String): Boolean { @Throws(BusinessException::class)
val file = characterSheetFile(id = id) fun delete(characterSheetId: String) {
val deleted = file.delete() val file = characterSheetFile(id = characterSheetId)
if (deleted) { // Guard case on the file existence.
characterSheetsFlow.value = characterSheetsFlow.value.toMutableList() if (file.exists().not()) {
.also { data -> val root = Exception("Character file with id:$characterSheetId doesn't not exist.")
data.removeIf { it.id == id } throw BusinessException(root = root)
} }
.sortedBy { // Guard case on the file deletion
it.name if (file.delete().not()) {
} val root = Exception("Character file have not been deleted for unknown reason.")
throw BusinessException(root = root)
}
// Update the data model with the deleted character.
characterSheetsFlow.update { characters ->
characters.toMutableList()
.also { data -> data.removeIf { it.id == characterSheetId } }
.sortedWith(compareBy(Collator.getInstance()) { it.name })
} }
return deleted
} }
private fun characterSheetFile(id: String): File { private fun characterSheetFile(id: String): File {
@ -126,6 +161,8 @@ class CharacterSheetStore(
sealed class CharacterSheetStoreException(root: Exception) : Exception(root) sealed class CharacterSheetStoreException(root: Exception) : Exception(root)
class JsonConversionException(root: Exception) : CharacterSheetStoreException(root) class JsonConversionException(root: Exception) : CharacterSheetStoreException(root)
class JsonCodingException(root: Exception) : CharacterSheetStoreException(root)
class BusinessException(root: Exception) : CharacterSheetStoreException(root)
class FileWriteException(root: Exception) : CharacterSheetStoreException(root) class FileWriteException(root: Exception) : CharacterSheetStoreException(root)
class FileReadException(root: Exception) : CharacterSheetStoreException(root) class FileReadException(root: Exception) : CharacterSheetStoreException(root)
} }

View file

@ -21,6 +21,14 @@ class Engine(
val webSocket = MutableSharedFlow<SocketMessage>() val webSocket = MutableSharedFlow<SocketMessage>()
suspend fun handle(message: SocketMessage) { suspend fun handle(message: SocketMessage) {
try {
unSafeHandle(message)
} catch (exception: Exception) {
println(exception) // TODO proper exception handling
}
}
private suspend fun unSafeHandle(message: SocketMessage) {
when (message) { when (message) {
is RollEvent -> Unit // Nothing to do here. is RollEvent -> Unit // Nothing to do here.
@ -61,10 +69,22 @@ class Engine(
} }
is CampaignEvent -> when (message) { is CampaignEvent -> when (message) {
is CampaignEvent.CharacterAdded -> Unit // TODO is CampaignEvent.CharacterAdded -> campaignService.addCharacter(
is CampaignEvent.CharacterRemoved -> Unit // TODO characterSheetId = message.characterSheetId,
is CampaignEvent.NpcAdded -> Unit // TODO )
is CampaignEvent.NpcRemoved -> Unit // TODO
is CampaignEvent.CharacterRemoved -> campaignService.removeCharacter(
characterSheetId = message.characterSheetId,
)
is CampaignEvent.NpcAdded -> campaignService.addNpc(
characterSheetId = message.characterSheetId,
)
is CampaignEvent.NpcRemoved -> campaignService.removeNpc(
characterSheetId = message.characterSheetId,
)
is CampaignEvent.UpdateScene -> Unit // TODO is CampaignEvent.UpdateScene -> Unit // TODO
} }

View file

@ -6,12 +6,12 @@ import com.pixelized.server.lwa.server.rest.alteration.getAlteration
import com.pixelized.server.lwa.server.rest.alteration.getAlterationTags import com.pixelized.server.lwa.server.rest.alteration.getAlterationTags
import com.pixelized.server.lwa.server.rest.alteration.getAlterations import com.pixelized.server.lwa.server.rest.alteration.getAlterations
import com.pixelized.server.lwa.server.rest.alteration.putAlteration import com.pixelized.server.lwa.server.rest.alteration.putAlteration
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignNpc
import com.pixelized.server.lwa.server.rest.campaign.getCampaign import com.pixelized.server.lwa.server.rest.campaign.getCampaign
import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter import com.pixelized.server.lwa.server.rest.campaign.putCampaignCharacter
import com.pixelized.server.lwa.server.rest.campaign.putCampaignNpc import com.pixelized.server.lwa.server.rest.campaign.putCampaignNpc
import com.pixelized.server.lwa.server.rest.campaign.putCampaignScene import com.pixelized.server.lwa.server.rest.campaign.putCampaignScene
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignCharacter import com.pixelized.server.lwa.server.rest.campaign.removeCampaignCharacter
import com.pixelized.server.lwa.server.rest.campaign.removeCampaignNpc
import com.pixelized.server.lwa.server.rest.character.deleteCharacter import com.pixelized.server.lwa.server.rest.character.deleteCharacter
import com.pixelized.server.lwa.server.rest.character.getCharacter import com.pixelized.server.lwa.server.rest.character.getCharacter
import com.pixelized.server.lwa.server.rest.character.getCharacterTags import com.pixelized.server.lwa.server.rest.character.getCharacterTags

View file

@ -1,14 +1,14 @@
package com.pixelized.server.lwa.server.rest.alteration package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.model.alteration.AlterationStore
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.alterationId import com.pixelized.server.lwa.utils.extentions.alterationId
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.deleteAlteration(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter

View file

@ -4,8 +4,9 @@ import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.alterationId import com.pixelized.server.lwa.utils.extentions.alterationId
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.getAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.getAlteration(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter

View file

@ -3,8 +3,9 @@ package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.getAlterationTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.getAlterationTags(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
call.respond( call.respond(

View file

@ -3,8 +3,9 @@ package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.getAlterations(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.getAlterations(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
call.respond( call.respond(

View file

@ -1,6 +1,5 @@
package com.pixelized.server.lwa.server.rest.alteration package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.model.alteration.AlterationStore
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.create import com.pixelized.server.lwa.utils.extentions.create
@ -9,8 +8,9 @@ import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.server.request.receive import io.ktor.server.request.receive
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.putAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putAlteration(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
val form = call.receive<AlterationJson>() val form = call.receive<AlterationJson>()

View file

@ -6,8 +6,9 @@ import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.removeCampaignCharacter(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter

View file

@ -5,11 +5,10 @@ import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.removeCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.removeCampaignNpc(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter

View file

@ -3,8 +3,9 @@ package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.getCampaign(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.getCampaign(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
call.respond( call.respond(

View file

@ -6,8 +6,9 @@ import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCampaignCharacter(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter

View file

@ -6,8 +6,9 @@ import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCampaignNpc(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter

View file

@ -5,12 +5,11 @@ import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.shared.lwa.model.campaign.CampaignJsonV2 import com.pixelized.shared.lwa.model.campaign.CampaignJsonV2
import com.pixelized.shared.lwa.protocol.rest.ResultJson import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive import io.ktor.server.request.receive
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCampaignScene(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// Get the scene json from the body of the request // Get the scene json from the body of the request

View file

@ -1,23 +1,25 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.http.HttpStatusCode import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.deleteCharacter(): suspend RoutingContext.() -> Unit {
return { return {
val characterSheetId = call.parameters.characterSheetId try {
// get the query parameter
val deleted = characterService.deleteCharacterSheet( val characterSheetId = call.parameters.characterSheetId
characterSheetId = characterSheetId // delete the character sheet.
) characterService.deleteCharacterSheet(
characterSheetId = characterSheetId
if (deleted) { )
call.respondText( // API & WebSocket responses.
text = "${HttpStatusCode.OK}", call.respond(
status = HttpStatusCode.OK, message = ResultJson.Success()
) )
webSocket.emit( webSocket.emit(
value = ApiSynchronisation.CharacterSheetDelete( value = ApiSynchronisation.CharacterSheetDelete(
@ -25,10 +27,19 @@ fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
), ),
) )
} else { } catch (exception: MissingParameterException) {
call.respondText( call.respond(
text = "${HttpStatusCode.UnprocessableEntity}", message = ResultJson.Error(
status = HttpStatusCode.UnprocessableEntity, status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) {
call.respond(
message = ResultJson.Error(
status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,12 +1,13 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import io.ktor.http.HttpStatusCode import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.getCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.getCharacter(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter
@ -19,10 +20,19 @@ fun Engine.getCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> U
call.respond( call.respond(
message = characterSheet, message = characterSheet,
) )
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respondText( call.respond(
text = "${HttpStatusCode.UnprocessableEntity}", message = ResultJson.Error(
status = HttpStatusCode.UnprocessableEntity status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,12 +1,23 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.getCharacterTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.getCharacterTags(): suspend RoutingContext.() -> Unit {
return { return {
call.respond( try {
message = characterService.tags(), call.respond(
) message = characterService.tags(),
)
} catch (exception: Exception) {
call.respond(
message = ResultJson.Error(
status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
)
}
} }
} }

View file

@ -1,12 +1,23 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.getCharacters(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.getCharacters(): suspend RoutingContext.() -> Unit {
return { return {
call.respond( try {
message = characterService.charactersJson(), call.respond(
) message = characterService.charactersJson(),
)
} catch (exception: Exception) {
call.respond(
message = ResultJson.Error(
status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
)
}
} }
} }

View file

@ -1,22 +1,26 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.create
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive import io.ktor.server.request.receive
import io.ktor.server.response.respondText import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.putCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCharacter(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
val form = call.receive<CharacterSheetJson>() val form = call.receive<CharacterSheetJson>()
characterService.updateCharacterSheet( val create = call.queryParameters.create
characterService.save(
character = form, character = form,
create = create,
) )
call.respondText( call.respond(
text = "${HttpStatusCode.OK}", message = ResultJson.Success()
status = HttpStatusCode.OK,
) )
webSocket.emit( webSocket.emit(
value = ApiSynchronisation.CharacterSheetUpdate( value = ApiSynchronisation.CharacterSheetUpdate(
@ -24,10 +28,19 @@ fun Engine.putCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> U
characterSheetId = form.id, characterSheetId = form.id,
), ),
) )
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respondText( call.respond(
text = "${HttpStatusCode.UnprocessableEntity}", message = ResultJson.Error(
status = HttpStatusCode.UnprocessableEntity, status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,20 +1,22 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.active
import com.pixelized.server.lwa.utils.extentions.alterationId import com.pixelized.server.lwa.utils.extentions.alterationId
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.http.HttpStatusCode import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.putCharacterAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCharacterAlteration(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter
val characterSheetId = call.queryParameters.characterSheetId val characterSheetId = call.queryParameters.characterSheetId
val alterationId = call.queryParameters.alterationId val alterationId = call.queryParameters.alterationId
val active = call.queryParameters["active"]?.toBooleanStrictOrNull() val active = call.queryParameters.active
?: error("Missing active parameter.")
// Update the character damage // Update the character damage
characterService.updateAlteration( characterService.updateAlteration(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
@ -22,9 +24,8 @@ fun Engine.putCharacterAlteration(): suspend io.ktor.server.routing.RoutingConte
active = active active = active
) )
// API & WebSocket responses. // API & WebSocket responses.
call.respondText( call.respond(
text = "${HttpStatusCode.OK}", message = ResultJson.Success()
status = HttpStatusCode.OK,
) )
webSocket.emit( webSocket.emit(
value = CharacterSheetEvent.UpdateAlteration( value = CharacterSheetEvent.UpdateAlteration(
@ -34,10 +35,19 @@ fun Engine.putCharacterAlteration(): suspend io.ktor.server.routing.RoutingConte
active = active, active = active,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respondText( call.respond(
text = "${HttpStatusCode.UnprocessableEntity}", message = ResultJson.Error(
status = HttpStatusCode.UnprocessableEntity, status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,30 +1,34 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.http.HttpStatusCode import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.putCharacterDamage(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter
val characterSheetId = call.queryParameters.characterSheetId val characterSheetId = call.queryParameters.characterSheetId
val damage = call.queryParameters["damage"]?.toIntOrNull() val damage = call.queryParameters["damage"]?.toIntOrNull()
?: error("Missing damage parameter.") ?: throw MissingParameterException(
name = "damage",
errorCode = ResultJson.Error.MISSING_DAMAGE
)
// fetch the character sheet // fetch the character sheet
val characterSheet = characterService.character(characterSheetId) val characterSheet = characterService.character(characterSheetId)
?: error("Character sheet not found for characterSheetId: $characterSheetId") ?: error("CharacterSheet with id:$characterSheetId not found.")
// Update the character damage // Update the character damage
characterService.updateDamage( characterService.updateDamage(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
damage = damage damage = damage
) )
// API & WebSocket responses. // API & WebSocket responses.
call.respondText( call.respond(
text = "${HttpStatusCode.OK}", message = ResultJson.Success()
status = HttpStatusCode.OK,
) )
webSocket.emit( webSocket.emit(
value = CharacterSheetEvent.UpdateDamage( value = CharacterSheetEvent.UpdateDamage(
@ -34,10 +38,19 @@ fun Engine.putCharacterDamage(): suspend io.ktor.server.routing.RoutingContext.(
damage = damage, damage = damage,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respondText( call.respond(
text = "${HttpStatusCode.UnprocessableEntity}", message = ResultJson.Error(
status = HttpStatusCode.UnprocessableEntity, status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,27 +1,31 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.http.HttpStatusCode import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.putCharacterDiminished(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter
val characterSheetId = call.queryParameters.characterSheetId val characterSheetId = call.queryParameters.characterSheetId
val diminished = call.queryParameters["diminished"]?.toIntOrNull() val diminished = call.queryParameters["diminished"]?.toIntOrNull()
?: error("Missing diminished parameter.") ?: throw MissingParameterException(
name = "diminished",
errorCode = ResultJson.Error.MISSING_DIMINISHED
)
// Update the character damage // Update the character damage
characterService.updateDiminished( characterService.updateDiminished(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
diminished = diminished diminished = diminished
) )
// API & WebSocket responses. // API & WebSocket responses.
call.respondText( call.respond(
text = "${HttpStatusCode.OK}", message = ResultJson.Success()
status = HttpStatusCode.OK,
) )
webSocket.emit( webSocket.emit(
value = CharacterSheetEvent.UpdateDiminished( value = CharacterSheetEvent.UpdateDiminished(
@ -30,10 +34,19 @@ fun Engine.putCharacterDiminished(): suspend io.ktor.server.routing.RoutingConte
diminished = diminished, diminished = diminished,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respondText( call.respond(
text = "${HttpStatusCode.UnprocessableEntity}", message = ResultJson.Error(
status = HttpStatusCode.UnprocessableEntity, status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,30 +1,34 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.http.HttpStatusCode import io.ktor.server.response.respond
import io.ktor.server.response.respondText import io.ktor.server.routing.RoutingContext
fun Engine.putCharacterFatigue(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit {
return { return {
try { try {
// get the query parameter // get the query parameter
val characterSheetId = call.queryParameters.characterSheetId val characterSheetId = call.queryParameters.characterSheetId
val fatigue = call.queryParameters["fatigue"]?.toIntOrNull() val fatigue = call.queryParameters["fatigue"]?.toIntOrNull()
?: error("Missing fatigue parameter.") ?: throw MissingParameterException(
name = "fatigue",
errorCode = ResultJson.Error.MISSING_FATIGUE
)
// fetch the character sheet // fetch the character sheet
val characterSheet = characterService.character(characterSheetId) val characterSheet = characterService.character(characterSheetId)
?: error("Character sheet not found for characterSheetId: $characterSheetId") ?: error("CharacterSheet with id:$characterSheetId not found.")
// Update the character damage // Update the character damage
characterService.updateFatigue( characterService.updateFatigue(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
fatigue = fatigue fatigue = fatigue
) )
// API & WebSocket responses. // API & WebSocket responses.
call.respondText( call.respond(
text = "${HttpStatusCode.OK}", message = ResultJson.Success()
status = HttpStatusCode.OK,
) )
webSocket.emit( webSocket.emit(
value = CharacterSheetEvent.UpdateFatigue( value = CharacterSheetEvent.UpdateFatigue(
@ -34,10 +38,19 @@ fun Engine.putCharacterFatigue(): suspend io.ktor.server.routing.RoutingContext.
fatigue = fatigue, fatigue = fatigue,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respondText( call.respond(
text = "${HttpStatusCode.UnprocessableEntity}", message = ResultJson.Error(
status = HttpStatusCode.UnprocessableEntity, status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -4,25 +4,36 @@ import com.pixelized.shared.lwa.protocol.rest.ResultJson
import io.ktor.http.Parameters import io.ktor.http.Parameters
val Parameters.characterSheetId val Parameters.characterSheetId
get() = this["characterSheetId"] get() = "characterSheetId".let { param ->
?: throw MissingParameterException( this[param] ?: throw MissingParameterException(
name = "characterSheetId", name = param,
errorCode = ResultJson.Error.MISSING_CHARACTER_SHEET_ID, errorCode = ResultJson.Error.MISSING_CHARACTER_SHEET_ID,
) )
}
val Parameters.alterationId val Parameters.alterationId
get() = this["alterationId"] get() = "alterationId".let { param ->
?: throw MissingParameterException( this[param] ?: throw MissingParameterException(
name = "alterationId", name = param,
errorCode = ResultJson.Error.MISSING_ALTERATION_ID, errorCode = ResultJson.Error.MISSING_ALTERATION_ID,
) )
}
val Parameters.create val Parameters.create
get() = this["create"]?.toBooleanStrictOrNull() get() = "create".let { param ->
?: throw MissingParameterException( this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(
name = "create", name = param,
errorCode = ResultJson.Error.MISSING_CREATE errorCode = ResultJson.Error.MISSING_CREATE
) )
}
val Parameters.active
get() = "active".let { param ->
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(
name = param,
errorCode = ResultJson.Error.MISSING_ACTIVE
)
}
class MissingParameterException( class MissingParameterException(
name: String, name: String,

View file

@ -19,6 +19,10 @@ sealed interface ResultJson {
const val MISSING_CHARACTER_SHEET_ID = MISSING_PARAMETER + 1 const val MISSING_CHARACTER_SHEET_ID = MISSING_PARAMETER + 1
const val MISSING_ALTERATION_ID = MISSING_PARAMETER + 2 const val MISSING_ALTERATION_ID = MISSING_PARAMETER + 2
const val MISSING_CREATE = MISSING_PARAMETER + 3 const val MISSING_CREATE = MISSING_PARAMETER + 3
const val MISSING_ACTIVE = MISSING_PARAMETER + 4
const val MISSING_DAMAGE = MISSING_PARAMETER + 5
const val MISSING_FATIGUE = MISSING_PARAMETER + 6
const val MISSING_DIMINISHED = MISSING_PARAMETER + 7
} }
} }