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
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() {
LocalServer().create().start()

View file

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

View file

@ -19,7 +19,9 @@ class CharacterSheetService(
private val factory: CharacterSheetJsonFactory,
) {
private val scope = CoroutineScope(Dispatchers.IO + Job())
private val sheets get() = sheetsFlow.value
private val sheetsFlow = characterStore.characterSheetsFlow()
.map { entry -> entry.associateBy { character -> character.id } }
.stateIn(
@ -40,7 +42,7 @@ class CharacterSheetService(
return sheets[characterSheetId]
}
fun tags() : Collection<TagJson> {
fun tags(): Collection<TagJson> {
return alterationTags.value
}
@ -52,14 +54,24 @@ class CharacterSheetService(
return sheets[characterSheetId]?.let(factory::convertToJson)
}
suspend fun updateCharacterSheet(character: CharacterSheetJson) {
@Throws
suspend fun save(
character: CharacterSheetJson,
create: Boolean,
) {
return characterStore.save(
sheet = factory.convertFromJson(character)
sheet = factory.convertFromJson(character),
create = create,
)
}
fun deleteCharacterSheet(characterSheetId: String): Boolean {
return characterStore.delete(id = characterSheetId)
@Throws
fun deleteCharacterSheet(
characterSheetId: String,
) {
characterStore.delete(
characterSheetId = characterSheetId
)
}
// Data manipulation through WebSocket.
@ -78,7 +90,8 @@ class CharacterSheetService(
characterStore.save(
sheet = character.copy(
alterations = alterations,
)
),
create = false,
)
}
if (active.not() && contain) {
@ -88,7 +101,8 @@ class CharacterSheetService(
characterStore.save(
sheet = character.copy(
alterations = alterations,
)
),
create = false,
)
}
}
@ -100,7 +114,10 @@ class CharacterSheetService(
) {
sheets[characterSheetId]?.let { character ->
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 ->
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 ->
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
},
)
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.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import java.io.File
@ -27,15 +28,15 @@ class CharacterSheetStore(
val scope = CoroutineScope(Dispatchers.IO + Job())
// load the initial data
scope.launch {
updateCharacterSheets()
updateCharacterFlow()
}
}
fun characterSheetsFlow(): StateFlow<List<CharacterSheet>> = characterSheetsFlow
private suspend fun updateCharacterSheets() {
private suspend fun updateCharacterFlow() {
characterSheetsFlow.value = try {
loadCharacterSheets()
load()
} catch (exception: Exception) {
println(exception) // TODO proper exception handling
emptyList()
@ -44,9 +45,12 @@ class CharacterSheetStore(
@Throws(
FileReadException::class,
JsonCodingException::class,
JsonConversionException::class,
)
suspend fun loadCharacterSheets(): List<CharacterSheet> {
suspend fun load(
directory: File = this.directory,
): List<CharacterSheet> {
return directory
.listFiles()
?.mapNotNull { file ->
@ -59,9 +63,15 @@ class CharacterSheetStore(
if (json.isBlank()) {
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 {
val sheet = this.json.decodeFromString<CharacterSheetJson>(json)
factory.convertFromJson(sheet)
factory.convertFromJson(data)
} catch (exception: Exception) {
throw JsonConversionException(root = exception)
}
@ -71,19 +81,35 @@ class CharacterSheetStore(
}
@Throws(
FileWriteException::class,
BusinessException::class,
JsonConversionException::class,
JsonCodingException::class,
FileWriteException::class,
)
fun save(sheet: CharacterSheet) {
// convert the character sheet into json format.
val data = try {
factory.convertToJson(sheet = sheet).let(json::encodeToString)
fun save(
sheet: CharacterSheet,
create: Boolean,
) {
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) {
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.
try {
val file = characterSheetFile(id = sheet.id)
file.writeText(
text = data,
charset = Charsets.UTF_8,
@ -92,8 +118,8 @@ class CharacterSheetStore(
throw FileWriteException(root = exception)
}
// Update the dataflow.
characterSheetsFlow.value = characterSheetsFlow.value
.toMutableList()
characterSheetsFlow.update { characters ->
characters.toMutableList()
.also { data ->
val index = data.indexOfFirst { it.id == sheet.id }
if (index >= 0) {
@ -102,22 +128,31 @@ class CharacterSheetStore(
data.add(sheet)
}
}
.sortedWith(compareBy(Collator.getInstance()) { it.name })
.sortedWith(compareBy(Collator.getInstance()) {
it.name
})
}
}
fun delete(id: String): Boolean {
val file = characterSheetFile(id = id)
val deleted = file.delete()
if (deleted) {
characterSheetsFlow.value = characterSheetsFlow.value.toMutableList()
.also { data ->
data.removeIf { it.id == id }
@Throws(BusinessException::class)
fun delete(characterSheetId: String) {
val file = characterSheetFile(id = characterSheetId)
// Guard case on the file existence.
if (file.exists().not()) {
val root = Exception("Character file with id:$characterSheetId doesn't not exist.")
throw BusinessException(root = root)
}
.sortedBy {
it.name
// Guard case on the file deletion
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 {
@ -126,6 +161,8 @@ class CharacterSheetStore(
sealed class CharacterSheetStoreException(root: Exception) : Exception(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 FileReadException(root: Exception) : CharacterSheetStoreException(root)
}

View file

@ -21,6 +21,14 @@ class Engine(
val webSocket = MutableSharedFlow<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) {
is RollEvent -> Unit // Nothing to do here.
@ -61,10 +69,22 @@ class Engine(
}
is CampaignEvent -> when (message) {
is CampaignEvent.CharacterAdded -> Unit // TODO
is CampaignEvent.CharacterRemoved -> Unit // TODO
is CampaignEvent.NpcAdded -> Unit // TODO
is CampaignEvent.NpcRemoved -> Unit // TODO
is CampaignEvent.CharacterAdded -> campaignService.addCharacter(
characterSheetId = message.characterSheetId,
)
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
}

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.getAlterations
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.putCampaignCharacter
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.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.getCharacter
import com.pixelized.server.lwa.server.rest.character.getCharacterTags

View file

@ -1,14 +1,14 @@
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.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.alterationId
import com.pixelized.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
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 {
try {
// 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.shared.lwa.protocol.rest.ResultJson
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 {
try {
// 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.shared.lwa.protocol.rest.ResultJson
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 {
try {
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.shared.lwa.protocol.rest.ResultJson
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 {
try {
call.respond(

View file

@ -1,6 +1,5 @@
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.utils.extentions.MissingParameterException
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 io.ktor.server.request.receive
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 {
try {
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.websocket.CampaignEvent
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 {
try {
// 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.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
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.removeCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
fun Engine.removeCampaignNpc(): suspend RoutingContext.() -> Unit {
return {
try {
// 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.shared.lwa.protocol.rest.ResultJson
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 {
try {
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.websocket.CampaignEvent
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 {
try {
// 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.websocket.CampaignEvent
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 {
try {
// 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.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.http.HttpStatusCode
import io.ktor.server.request.receive
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 {
try {
// Get the scene json from the body of the request

View file

@ -1,23 +1,25 @@
package com.pixelized.server.lwa.server.rest.character
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.shared.lwa.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.http.HttpStatusCode
import io.ktor.server.response.respondText
import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext
fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
fun Engine.deleteCharacter(): suspend RoutingContext.() -> Unit {
return {
try {
// get the query parameter
val characterSheetId = call.parameters.characterSheetId
val deleted = characterService.deleteCharacterSheet(
// delete the character sheet.
characterService.deleteCharacterSheet(
characterSheetId = characterSheetId
)
if (deleted) {
call.respondText(
text = "${HttpStatusCode.OK}",
status = HttpStatusCode.OK,
// API & WebSocket responses.
call.respond(
message = ResultJson.Success()
)
webSocket.emit(
value = ApiSynchronisation.CharacterSheetDelete(
@ -25,10 +27,19 @@ fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() -
characterSheetId = characterSheetId,
),
)
} else {
call.respondText(
text = "${HttpStatusCode.UnprocessableEntity}",
status = HttpStatusCode.UnprocessableEntity,
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
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
import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
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.respondText
import io.ktor.server.routing.RoutingContext
fun Engine.getCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
fun Engine.getCharacter(): suspend RoutingContext.() -> Unit {
return {
try {
// get the query parameter
@ -19,10 +20,19 @@ fun Engine.getCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> U
call.respond(
message = characterSheet,
)
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) {
call.respondText(
text = "${HttpStatusCode.UnprocessableEntity}",
status = HttpStatusCode.UnprocessableEntity
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
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.routing.RoutingContext
fun Engine.getCharacterTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
fun Engine.getCharacterTags(): suspend RoutingContext.() -> Unit {
return {
try {
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
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.routing.RoutingContext
fun Engine.getCharacters(): suspend io.ktor.server.routing.RoutingContext.() -> Unit {
fun Engine.getCharacters(): suspend RoutingContext.() -> Unit {
return {
try {
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
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.protocol.rest.ResultJson
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.http.HttpStatusCode
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 {
try {
val form = call.receive<CharacterSheetJson>()
characterService.updateCharacterSheet(
val create = call.queryParameters.create
characterService.save(
character = form,
create = create,
)
call.respondText(
text = "${HttpStatusCode.OK}",
status = HttpStatusCode.OK,
call.respond(
message = ResultJson.Success()
)
webSocket.emit(
value = ApiSynchronisation.CharacterSheetUpdate(
@ -24,10 +28,19 @@ fun Engine.putCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> U
characterSheetId = form.id,
),
)
} catch (exception: MissingParameterException) {
call.respond(
message = ResultJson.Error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) {
call.respondText(
text = "${HttpStatusCode.UnprocessableEntity}",
status = HttpStatusCode.UnprocessableEntity,
call.respond(
message = ResultJson.Error(
status = ResultJson.Error.GENERIC,
message = exception.message ?: "?",
)
)
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -19,6 +19,10 @@ sealed interface ResultJson {
const val MISSING_CHARACTER_SHEET_ID = MISSING_PARAMETER + 1
const val MISSING_ALTERATION_ID = MISSING_PARAMETER + 2
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
}
}