From acb445c480adf3f3e42301bb21a6752cda01d488 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sun, 30 Mar 2025 13:30:22 +0200 Subject: [PATCH 1/6] Server : Alteration error management --- .../lwa/model/alteration/AlterationService.kt | 18 +++- .../lwa/model/alteration/AlterationStore.kt | 99 +++++++++++-------- .../rest/alteration/DELETE_Alteration.kt | 38 ++++--- .../server/rest/alteration/GET_Alteration.kt | 11 ++- .../rest/alteration/GET_AlterationTags.kt | 16 ++- .../server/rest/alteration/GET_Alterations.kt | 16 ++- .../server/rest/alteration/PUT_Alateration.kt | 43 +++++--- .../lwa/utils/extentions/ParametersExt.kt | 25 ++++- .../kotlin/com/pixelized/shared/lwa/Module.kt | 1 + .../shared/lwa/protocol/rest/PutResultJson.kt | 33 +++++++ 10 files changed, 220 insertions(+), 80 deletions(-) create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/PutResultJson.kt diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt index 9d5b098..7f2b49f 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationService.kt @@ -46,11 +46,21 @@ class AlterationService( return alterationHashFlow.value[alterationId] } - fun update(json: AlterationJson) { - alterationStore.save(alteration = json) + @Throws + fun save( + json: AlterationJson, + create: Boolean, + ) { + alterationStore.save( + json = json, + create = create, + ) } - fun delete(alterationId: String): Boolean { - return alterationStore.delete(id = alterationId) + @Throws + fun delete(alterationId: String) { + return alterationStore.delete( + id = alterationId, + ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt index 6505b33..1eba998 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt @@ -28,15 +28,15 @@ class AlterationStore( val scope = CoroutineScope(Dispatchers.IO + Job()) // load the initial data scope.launch { - updateAlterations() + updateAlterationFlow() } } fun alterationsFlow(): StateFlow> = alterationFlow - private fun updateAlterations() { + private fun updateAlterationFlow() { alterationFlow.value = try { - loadAlterations() + load() } catch (exception: Exception) { println(exception) // TODO proper exception handling emptyList() @@ -47,7 +47,9 @@ class AlterationStore( FileReadException::class, JsonConversionException::class, ) - private fun loadAlterations(): List { + private fun load( + directory: File = this.directory, + ): List { return directory .listFiles() ?.mapNotNull { file -> @@ -60,8 +62,14 @@ class AlterationStore( if (json.isBlank()) { return@mapNotNull null } + // decode the file + val data = try { + this.json.decodeFromString(json) + } catch (exception: Exception) { + throw JsonCodingException(root = exception) + } + // parse the json string. try { - val data = this.json.decodeFromString(json) factory.convertFromJson(data) } catch (exception: Exception) { throw JsonConversionException(root = exception) @@ -70,67 +78,76 @@ class AlterationStore( ?: emptyList() } - @Throws(JsonConversionException::class, FileWriteException::class) + @Throws( + BusinessException::class, + JsonConversionException::class, + JsonCodingException::class, + FileWriteException::class, + ) fun save( - alteration: Alteration, + json: AlterationJson, + create: Boolean, ) { - val json = try { - factory.convertToJson(data = alteration) + val file = alterationFile(id = json.id) + // Guard case on update alteration + if (create && file.exists()) { + val root = Exception("Alteration already exist, creation is impossible.") + throw BusinessException(root = root) + } + // Transform the json into the model. + val alteration = try { + factory.convertFromJson(json) } catch (exception: Exception) { throw JsonConversionException(root = exception) } - - save(alteration = json) - } - - @Throws(FileWriteException::class) - fun save( - alteration: AlterationJson, - ) { - // encode the json into a string + // Encode the json into a string. val data = try { - json.encodeToString(alteration) + this.json.encodeToString(json) } catch (exception: Exception) { - throw JsonConversionException(root = exception) + throw JsonCodingException(root = exception) } - // write the alteration into a file. + // Write the alteration into a file. try { - val file = alterationFile(id = alteration.id) file.writeText( text = data, charset = Charsets.UTF_8, ) } catch (exception: Exception) { - throw FileWriteException( - root = exception - ) + throw FileWriteException(root = exception) } // Update the dataflow. alterationFlow.update { alterations -> - val index = alterations.indexOfFirst { it.id == alteration.id } - val alt = factory.convertFromJson(alteration) - + val index = alterations.indexOfFirst { + it.id == json.id + } alterations.toMutableList().also { - if (index >= 0) { - it[index] = alt - } else { - it.add(alt) + when { + index >= 0 -> it[index] = alteration + else -> it.add(alteration) } } } } - fun delete(id: String): Boolean { + @Throws(BusinessException::class) + fun delete(id: String) { val file = alterationFile(id = id) - val deleted = file.delete() - if (deleted) { - alterationFlow.update { alterations -> - alterations.toMutableList().also { alteration -> - alteration.removeIf { it.id == id } - } + // Guard case on the file existence. + if (file.exists().not()) { + val root = Exception("Alteration doesn't not exist, deletion is impossible.") + throw BusinessException(root = root) + } + // Guard case on the file deletion + if (file.delete().not()) { + val root = Exception("Alteration file have not been deleted for unknown reason.") + throw BusinessException(root = root) + } + // Update the data model with the deleted alteration. + alterationFlow.update { alterations -> + alterations.toMutableList().also { alteration -> + alteration.removeIf { it.id == id } } } - return deleted } private fun alterationFile(id: String): File { @@ -139,6 +156,8 @@ class AlterationStore( sealed class AlterationStoreException(root: Exception) : Exception(root) class JsonConversionException(root: Exception) : AlterationStoreException(root) + class JsonCodingException(root: Exception) : AlterationStoreException(root) + class BusinessException(root: Exception) : AlterationStoreException(root) class FileWriteException(root: Exception) : AlterationStoreException(root) class FileReadException(root: Exception) : AlterationStoreException(root) } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt index c347afc..99fa222 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt @@ -1,25 +1,23 @@ 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.http.HttpStatusCode -import io.ktor.server.response.respondText +import io.ktor.server.response.respond fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { try { val alterationId = call.parameters.alterationId - val deleted = alterationService.delete( + alterationService.delete( alterationId = alterationId ) - - if (deleted.not()) error("Unexpected error occurred") - - call.respondText( - text = "${HttpStatusCode.OK}", - status = HttpStatusCode.OK, + call.respond( + message = ResultJson.Success(), ) webSocket.emit( value = ApiSynchronisation.AlterationDelete( @@ -27,10 +25,26 @@ fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.() alterationId = alterationId, ), ) + } catch (exception: MissingParameterException) { + call.respond( + message = ResultJson.Error( + status = exception.errorCode, + message = exception.message ?: "?", + ) + ) + } catch (exception: AlterationStore.BusinessException) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.FILE_DOES_NOT_EXIST, + message = "Alteration doesn't exist." + ) + ) } catch (exception: Exception) { - call.respondText( - text = "${HttpStatusCode.UnprocessableEntity}", - status = HttpStatusCode.UnprocessableEntity, + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt index 0414d46..a5e4df7 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt @@ -2,9 +2,8 @@ package com.pixelized.server.lwa.server.rest.alteration import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.utils.extentions.alterationId -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 fun Engine.getAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { @@ -20,9 +19,11 @@ fun Engine.getAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> message = alteration, ) } catch (exception: Exception) { - call.respondText( - text = exception.localizedMessage, - status = HttpStatusCode.UnprocessableEntity, + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt index 23baeeb..76ac1d9 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt @@ -1,12 +1,22 @@ 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 fun Engine.getAlterationTags(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { - call.respond( - message = alterationService.tags(), - ) + try { + call.respond( + message = alterationService.tags(), + ) + } catch (exception: Exception) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) + ) + } } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt index 43316d2..e0c029b 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt @@ -1,12 +1,22 @@ 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 fun Engine.getAlterations(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { - call.respond( - message = alterationService.alterations(), - ) + try { + call.respond( + message = alterationService.alterations(), + ) + } catch (exception: Exception) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) + ) + } } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt index 6885a81..141c60c 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt @@ -1,22 +1,27 @@ 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 import com.pixelized.shared.lwa.model.alteration.AlterationJson +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 fun Engine.putAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { try { val form = call.receive() + val create = call.queryParameters.create - alterationService.update(json = form) - - call.respondText( - text = "${HttpStatusCode.OK}", - status = HttpStatusCode.OK, + alterationService.save( + json = form, + create = create, + ) + call.respond( + message = ResultJson.Success(), ) webSocket.emit( value = ApiSynchronisation.AlterationUpdate( @@ -24,10 +29,26 @@ fun Engine.putAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> alterationId = form.id, ), ) - } catch (exception : Exception) { - call.respondText( - text = "${HttpStatusCode.UnprocessableEntity}", - status = HttpStatusCode.UnprocessableEntity, + } catch (exception: MissingParameterException) { + call.respond( + message = ResultJson.Error( + status = exception.errorCode, + message = exception.message ?: "?", + ) + ) + } catch (exception: AlterationStore.BusinessException) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.FILE_ALREADY_EXIST, + message = "Alteration file already exist." + ) + ) + } catch (exception: Exception) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt index ef7f6da..4901cdb 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt @@ -1,9 +1,30 @@ package com.pixelized.server.lwa.utils.extentions +import com.pixelized.shared.lwa.protocol.rest.ResultJson import io.ktor.http.Parameters val Parameters.characterSheetId - get() = this["characterSheetId"] ?: error("Missing characterSheetId parameter.") + get() = this["characterSheetId"] + ?: throw MissingParameterException( + name = "characterSheetId", + errorCode = ResultJson.Error.MISSING_CHARACTER_SHEET_ID, + ) val Parameters.alterationId - get() = this["alterationId"] ?: error("Missing alterationId parameter.") \ No newline at end of file + get() = this["alterationId"] + ?: throw MissingParameterException( + name = "alterationId", + errorCode = ResultJson.Error.MISSING_ALTERATION_ID, + ) + +val Parameters.create + get() = this["create"]?.toBooleanStrictOrNull() + ?: throw MissingParameterException( + name = "create", + errorCode = ResultJson.Error.MISSING_CREATE + ) + +class MissingParameterException( + name: String, + val errorCode: Int, +) : Exception("Missing $name parameter.") diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt index a7a9956..e5047ac 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/Module.kt @@ -31,6 +31,7 @@ val toolsDependencies get() = module { factory { Json { + encodeDefaults = true explicitNulls = false prettyPrint = true } diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/PutResultJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/PutResultJson.kt new file mode 100644 index 0000000..c646c0f --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/PutResultJson.kt @@ -0,0 +1,33 @@ +package com.pixelized.shared.lwa.protocol.rest + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface ResultJson { + val success: Boolean + + @Serializable + data class Error( + override val success: Boolean = false, + val status: Int, + val message: String, + ) : ResultJson { + companion object { + const val GENERIC = 500 + + const val FILE_ALREADY_EXIST = GENERIC + 1 + const val FILE_DOES_NOT_EXIST = GENERIC + 2 + + const val MISSING_PARAMETER = 1000 + const val MISSING_CHARACTER_SHEET_ID = MISSING_PARAMETER + 1 + const val MISSING_ALTERATION_ID = MISSING_PARAMETER + 2 + const val MISSING_CREATE = MISSING_PARAMETER + 3 + } + } + + @Serializable + data class Success( + override val success: Boolean = true, + val status: Int = 100, + ) : ResultJson +} \ No newline at end of file From 0e7bc87cde421640fcd651d716591ae7e93759c0 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sun, 30 Mar 2025 18:25:27 +0200 Subject: [PATCH 2/6] Server : Campaign error management --- .../lwa/model/campaign/CampaignService.kt | 268 +++++------------- .../lwa/model/campaign/CampaignStore.kt | 60 ++-- .../rest/alteration/DELETE_Alteration.kt | 11 +- .../server/rest/alteration/PUT_Alateration.kt | 7 - .../campaign/DELETE_Campaign_Character.kt | 31 +- .../rest/campaign/DELETE_Campaign_Npc.kt | 31 +- .../lwa/server/rest/campaign/GET_Campaign.kt | 16 +- .../rest/campaign/PUT_Campaign_Character.kt | 33 ++- .../server/rest/campaign/PUT_Campaign_Npc.kt | 33 ++- .../rest/campaign/PUT_Campaign_Scene_Name.kt | 29 +- .../server/rest/character/DELETE_Character.kt | 5 - .../rest/{PutResultJson.kt => ResultJson.kt} | 7 +- 12 files changed, 222 insertions(+), 309 deletions(-) rename shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/{PutResultJson.kt => ResultJson.kt} (78%) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt index d57577d..105df98 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt @@ -37,223 +37,95 @@ class CampaignService( return campaignJsonFlow.value } + @Throws fun addCharacter( characterSheetId: String, - ): Boolean { - // check if the character is already in the campaign. - if (campaign.characters.contains(characterSheetId)) return false - // update the corresponding instance - val characters = campaign.characters.toMutableSet().also { it.add(characterSheetId) } - // save the campaign to the disk (update the flow). - return try { - store.save( - campaign = campaign.copy(characters = characters) - ) - true - } catch (exception: Exception) { - false + ) { + // Check if the character is already in the campaign. + if (campaign.instances.contains(characterSheetId)) { + val root = Exception("Character with id:$characterSheetId is already in the campaign.") + throw CampaignStore.BusinessException(root = root) } - } - - suspend fun addNpc( - characterSheetId: String, - ): Boolean { - // check if the character is already in the campaign. - if (campaign.npcs.contains(characterSheetId)) return false - // update the corresponding instance - val characters = campaign.npcs.toMutableSet().also { it.add(characterSheetId) } - // save the campaign to the disk (update the flow). - return try { - store.save( - campaign = campaign.copy(npcs = characters) - ) - true - } catch (exception: Exception) { - false + // Update the corresponding instance + val characters = campaign.characters.toMutableSet().also { + it.add(characterSheetId) } + // Save the campaign to the disk (update the flow). + store.save( + campaign = campaign.copy(characters = characters) + ) } - suspend fun removeCharacter( + @Throws + fun addNpc( characterSheetId: String, - ): Boolean { - // check if the character is in the campaign. - if (campaign.characters.contains(characterSheetId).not()) return false - // update the corresponding instance - val characters = campaign.characters.toMutableSet().also { it.remove(characterSheetId) } - // save the campaign to the disk + update the flow. - return try { - store.save( - campaign = campaign.copy(characters = characters) - ) - true - } catch (exception: Exception) { - false + ) { + // Check if the character is already in the campaign. + if (campaign.instances.contains(characterSheetId)) { + val root = Exception("Character with id:$characterSheetId is already in the campaign.") + throw CampaignStore.BusinessException(root = root) } - } - - suspend fun removeNpc( - characterSheetId: String, - ): Boolean { - // check if the character is in the campaign. - if (campaign.npcs.contains(characterSheetId).not()) return false - // update the corresponding instance - val characters = campaign.npcs.toMutableSet().also { it.remove(characterSheetId) } - // save the campaign to the disk + update the flow. - return try { - store.save( - campaign = campaign.copy(npcs = characters) - ) - true - } catch (exception: Exception) { - false + // Update the corresponding instance + val characters = campaign.npcs.toMutableSet().also { + it.add(characterSheetId) } + // Save the campaign to the disk (update the flow). + store.save( + campaign = campaign.copy(npcs = characters) + ) } - suspend fun removeInstance( + @Throws + fun removeCharacter( characterSheetId: String, - ): Boolean { - return removeCharacter(characterSheetId) || removeNpc(characterSheetId) + ) { + // Check if the character is in the campaign. + if (campaign.characters.contains(characterSheetId).not()) { + val root = Exception("Character with id:$characterSheetId is not in the party.") + throw CampaignStore.BusinessException(root = root) + } + // Update the corresponding instance + val characters = campaign.characters.toMutableSet().also { + it.remove(characterSheetId) + } + // Save the campaign to the disk + update the flow. + store.save( + campaign = campaign.copy(characters = characters) + ) } - suspend fun setScene( + @Throws + fun removeNpc( + characterSheetId: String, + ) { + // Check if the character is in the campaign. + if (campaign.npcs.contains(characterSheetId).not()) { + val root = Exception("Character with id:$characterSheetId is not in the npcs.") + throw CampaignStore.BusinessException(root = root) + } + // Update the corresponding instance + val characters = campaign.npcs.toMutableSet().also { + it.remove(characterSheetId) + } + // Save the campaign to the disk + update the flow. + store.save( + campaign = campaign.copy(npcs = characters) + ) + } + + @Throws + fun setScene( scene: Campaign.Scene, - ): Boolean { + ) { // save the campaign to the disk + update the flow. - return try { - store.save( - campaign.copy(scene = scene) - ) - true - } catch (exception: Exception) { - false - } + store.save( + campaign = campaign.copy(scene = scene) + ) } // Data manipulation through WebSocket. -// suspend fun updateCharacteristic( -// characterInstanceId: Campaign.CharacterInstance.Id, -// characteristic: Campaign.CharacterInstance.Characteristic, -// value: Int, -// ) { -// when (characterInstanceId.prefix) { -// Campaign.CharacterInstance.Id.PLAYER -> { -// // fetch all the current campaign character -// val characters = campaign.characters.toMutableMap() -// // update the corresponding character using the use case. -// characters[characterInstanceId] = useCase.updateCharacteristic( -// instance = campaign.character(id = characterInstanceId), -// characteristic = characteristic, -// value = value, -// ) -// // save the campaign to the disk + update the flow. -// store.save( -// campaign = campaign.copy(characters = characters) -// ) -// } -// -// Campaign.CharacterInstance.Id.NPC -> { -// // fetch all the current campaign character -// val npcs = campaign.npcs.toMutableMap() -// // update the corresponding character using the use case. -// npcs[characterInstanceId] = useCase.updateCharacteristic( -// instance = campaign.npc(id = characterInstanceId), -// characteristic = characteristic, -// value = value, -// ) -// // save the campaign to the disk + update the flow. -// store.save( -// campaign = campaign.copy(npcs = npcs) -// ) -// } -// } -// } -// -// suspend fun updateDiminished( -// characterInstanceId: Campaign.CharacterInstance.Id, -// diminished: Int, -// ) { -// when (characterInstanceId.prefix) { -// Campaign.CharacterInstance.Id.PLAYER -> { -// // fetch all the current campaign character -// val characters = campaign.characters.toMutableMap() -// // update the corresponding character using the use case. -// characters[characterInstanceId] = useCase.updateDiminished( -// instance = campaign.character(id = characterInstanceId), -// diminished = diminished, -// ) -// // save the campaign to the disk + update the flow. -// store.save( -// campaign = campaign.copy(characters = characters) -// ) -// } -// -// Campaign.CharacterInstance.Id.NPC -> { -// // fetch all the current campaign character -// val npcs = campaign.npcs.toMutableMap() -// // update the corresponding character using the use case. -// npcs[characterInstanceId] = useCase.updateDiminished( -// instance = campaign.npc(id = characterInstanceId), -// diminished = diminished, -// ) -// // save the campaign to the disk + update the flow. -// store.save( -// campaign = campaign.copy(npcs = npcs) -// ) -// } -// } -// } -// -// suspend fun toggleAlteration( -// characterInstanceId: Campaign.CharacterInstance.Id, -// alterationId: String, -// ) { -// when (characterInstanceId.prefix) { -// Campaign.CharacterInstance.Id.PLAYER -> { -// // fetch all the current campaign character -// val characters = campaign.characters.toMutableMap() -// // update the corresponding character alterations -// characters[characterInstanceId]?.let { character -> -// characters[characterInstanceId] = character.copy( -// alterations = character.alterations.toMutableList().also { alterations -> -// if (alterations.contains(alterationId)) { -// alterations.remove(alterationId) -// } else { -// alterations.add(alterationId) -// } -// }, -// ) -// } -// // save the campaign to the disk + update the flow. -// store.save( -// campaign = campaign.copy(characters = characters) -// ) -// } -// -// Campaign.CharacterInstance.Id.NPC -> { -// // fetch all the current campaign character -// val characters = campaign.npcs.toMutableMap() -// // update the corresponding character alterations -// characters[characterInstanceId]?.let { character -> -// characters[characterInstanceId] = character.copy( -// alterations = character.alterations.toMutableList().also { alterations -> -// if (alterations.contains(alterationId)) { -// alterations.remove(alterationId) -// } else { -// alterations.add(alterationId) -// } -// }, -// ) -// } -// // save the campaign to the disk + update the flow. -// store.save( -// campaign = campaign.copy(npcs = characters) -// ) -// } -// } -// } - - suspend fun updateToggleParty() { + fun updateToggleParty() { store.save( campaign = campaign.copy( options = campaign.options.copy( @@ -263,7 +135,7 @@ class CampaignService( ) } - suspend fun updateToggleNpc() { + fun updateToggleNpc() { store.save( campaign = campaign.copy( options = campaign.options.copy( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt index e81062f..16fd568 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -1,5 +1,6 @@ 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 @@ -9,6 +10,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 +29,15 @@ class CampaignStore( val scope = CoroutineScope(Dispatchers.IO + Job()) // load the initial data scope.launch { - updateCampaignFromDisk() + updateCampaignFlow() } } fun campaignFlow(): StateFlow = campaignFlow - private fun updateCampaignFromDisk() { + private fun updateCampaignFlow() { campaignFlow.value = try { - loadCampaign() + load() } catch (exception: Exception) { println(exception) // TODO proper exception handling Campaign.empty() @@ -44,60 +46,74 @@ class CampaignStore( @Throws( FileReadException::class, + JsonCodingException::class, JsonConversionException::class, ) - fun loadCampaign(): Campaign { - val file = file() - val json = try { + fun load(): Campaign { + val file = campaignFile() + // Read the campaign file. + val data = try { file.readText(charset = Charsets.UTF_8) } catch (exception: Exception) { throw FileReadException(root = exception) } - // Guard, if the file is empty we load a default campaign. - if (json.isBlank()) return Campaign.empty() - + if (data.isBlank()) return Campaign.empty() + // Decode the json into a string. + val json = try { + this.json.decodeFromString(data) + } catch (exception: Exception) { + throw JsonCodingException(root = exception) + } + // Convert from the Json format val campaign = try { - val data = this.json.decodeFromString(json) - factory.convertFromJson(data) + factory.convertFromJson(json) } catch (exception: Exception) { throw JsonConversionException(root = exception) } - return campaign } @Throws( JsonConversionException::class, + JsonCodingException::class, FileWriteException::class, ) fun save(campaign: Campaign) { - // convert the data to json format + // Transform the json into the model. val json = try { - factory.convertToJson(campaign = campaign).let(json::encodeToString) + factory.convertToJson(campaign = campaign) } catch (exception: Exception) { throw JsonConversionException(root = exception) } - // write the file + // Encode the json into a string. + val data = try { + this.json.encodeToString(json) + } catch (exception: Exception) { + throw JsonCodingException(root = exception) + } + // Write the file try { - val file = file() + val file = campaignFile() file.writeText( - text = json, + text = data, charset = Charsets.UTF_8, ) } catch (exception: Exception) { throw FileWriteException(root = exception) } // Update the dataflow. - campaignFlow.value = campaign + campaignFlow.update { campaign } + } + + private fun campaignFile(): File { + return File("${pathProvider.campaignPath()}campaign.json") } sealed class CampaignStoreException(root: Exception) : Exception(root) class JsonConversionException(root: Exception) : CampaignStoreException(root) + class JsonCodingException(root: Exception) : CampaignStoreException(root) + class BusinessException(root: Exception) : CampaignStoreException(root) class FileWriteException(root: Exception) : CampaignStoreException(root) class FileReadException(root: Exception) : CampaignStoreException(root) - - private fun file(): File { - return File("${pathProvider.campaignPath()}campaign.json") - } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt index 99fa222..0652f60 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt @@ -11,11 +11,13 @@ import io.ktor.server.response.respond fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { try { + // get the query parameter val alterationId = call.parameters.alterationId - + // delete the alteration. alterationService.delete( alterationId = alterationId ) + // API & WebSocket responses. call.respond( message = ResultJson.Success(), ) @@ -32,13 +34,6 @@ fun Engine.deleteAlteration(): suspend io.ktor.server.routing.RoutingContext.() message = exception.message ?: "?", ) ) - } catch (exception: AlterationStore.BusinessException) { - call.respond( - message = ResultJson.Error( - status = ResultJson.Error.FILE_DOES_NOT_EXIST, - message = "Alteration doesn't exist." - ) - ) } catch (exception: Exception) { call.respond( message = ResultJson.Error( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt index 141c60c..c8bed66 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt @@ -36,13 +36,6 @@ fun Engine.putAlteration(): suspend io.ktor.server.routing.RoutingContext.() -> message = exception.message ?: "?", ) ) - } catch (exception: AlterationStore.BusinessException) { - call.respond( - message = ResultJson.Error( - status = ResultJson.Error.FILE_ALREADY_EXIST, - message = "Alteration file already exist." - ) - ) } catch (exception: Exception) { call.respond( message = ResultJson.Error( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt index 42bf910..e73e688 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt @@ -1,10 +1,11 @@ package com.pixelized.server.lwa.server.rest.campaign 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.CampaignEvent -import io.ktor.http.HttpStatusCode -import io.ktor.server.response.respondText +import io.ktor.server.response.respond fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { @@ -12,17 +13,12 @@ fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingCont // get the query parameter val characterSheetId = call.queryParameters.characterSheetId // remove the character form the party - val updated = campaignService.removeCharacter( + campaignService.removeCharacter( characterSheetId = characterSheetId, ) - // error case - if (updated.not()) { - error("Unexpected error when removing character (characterSheetId:$characterSheetId) from party.") - } // API & WebSocket responses - call.respondText( - text = "${HttpStatusCode.OK}", - status = HttpStatusCode.OK, + call.respond( + message = ResultJson.Success(), ) webSocket.emit( value = CampaignEvent.CharacterRemoved( @@ -30,10 +26,19 @@ fun Engine.removeCampaignCharacter(): suspend io.ktor.server.routing.RoutingCont characterSheetId = characterSheetId, ) ) + } catch (exception: MissingParameterException) { + call.respond( + message = ResultJson.Error( + status = exception.errorCode, + message = exception.message ?: "?", + ) + ) } catch (exception: Exception) { - call.respondText( - text = exception.localizedMessage, - status = HttpStatusCode.UnprocessableEntity, + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt index fadfbd0..8a85c97 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt @@ -1,9 +1,12 @@ package com.pixelized.server.lwa.server.rest.campaign 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.CampaignEvent import io.ktor.http.HttpStatusCode +import io.ktor.server.response.respond import io.ktor.server.response.respondText fun Engine.removeCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { @@ -12,15 +15,12 @@ fun Engine.removeCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() // get the query parameter val characterSheetId = call.queryParameters.characterSheetId // remove the character form the party - val updated = campaignService.removeNpc(characterSheetId = characterSheetId) - // error case - if (updated.not()) { - error("Unexpected error when removing character (characterSheetId:$characterSheetId) from npcs.") - } + campaignService.removeNpc( + characterSheetId = characterSheetId, + ) // API & WebSocket responses - call.respondText( - text = "${HttpStatusCode.OK}", - status = HttpStatusCode.OK, + call.respond( + message = ResultJson.Success(), ) webSocket.emit( value = CampaignEvent.NpcRemoved( @@ -28,10 +28,19 @@ fun Engine.removeCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() characterSheetId = characterSheetId, ) ) + } catch (exception: MissingParameterException) { + call.respond( + message = ResultJson.Error( + status = exception.errorCode, + message = exception.message ?: "?", + ) + ) } catch (exception: Exception) { - call.respondText( - text = exception.localizedMessage, - status = HttpStatusCode.UnprocessableEntity, + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt index 13bf259..135c559 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt @@ -1,12 +1,22 @@ 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 fun Engine.getCampaign(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { - call.respond( - message = campaignService.campaignJson(), - ) + try { + call.respond( + message = campaignService.campaignJson(), + ) + } catch (exception: Exception) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) + ) + } } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt index 1c5a381..fea4532 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt @@ -1,10 +1,11 @@ package com.pixelized.server.lwa.server.rest.campaign 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.CampaignEvent -import io.ktor.http.HttpStatusCode -import io.ktor.server.response.respondText +import io.ktor.server.response.respond fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { @@ -12,15 +13,12 @@ fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext // get the query parameter val characterSheetId = call.queryParameters.characterSheetId // add the character to the party. - val update = campaignService.addCharacter(characterSheetId = characterSheetId) - // error case - if (update.not()) { - error("Unexpected error occurred when the character instance was added to the party") - } + campaignService.addCharacter( + characterSheetId = characterSheetId, + ) // API & WebSocket responses. - call.respondText( - text = "Character $characterSheetId successfully added to the party", - status = HttpStatusCode.OK, + call.respond( + message = ResultJson.Success(), ) webSocket.emit( value = CampaignEvent.CharacterAdded( @@ -28,10 +26,19 @@ fun Engine.putCampaignCharacter(): suspend io.ktor.server.routing.RoutingContext characterSheetId = characterSheetId, ) ) + } catch (exception: MissingParameterException) { + call.respond( + message = ResultJson.Error( + status = exception.errorCode, + message = exception.message ?: "?", + ) + ) } catch (exception: Exception) { - call.respondText( - text = "${exception.message}", - status = HttpStatusCode.UnprocessableEntity, + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt index d9d31aa..f461b19 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt @@ -1,10 +1,11 @@ package com.pixelized.server.lwa.server.rest.campaign 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.CampaignEvent -import io.ktor.http.HttpStatusCode -import io.ktor.server.response.respondText +import io.ktor.server.response.respond fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { return { @@ -12,15 +13,12 @@ fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> // get the query parameter val characterSheetId = call.queryParameters.characterSheetId // add the character to the npcs. - val update = campaignService.addNpc(characterSheetId = characterSheetId) - // error case - if (update.not()) { - error("Unexpected error occurred when the character instance was added to the npcs") - } + campaignService.addNpc( + characterSheetId = characterSheetId, + ) // API & WebSocket responses. - call.respondText( - text = "Character $characterSheetId successfully added to the npcs", - status = HttpStatusCode.OK, + call.respond( + message = ResultJson.Success(), ) webSocket.emit( value = CampaignEvent.NpcAdded( @@ -28,10 +26,19 @@ fun Engine.putCampaignNpc(): suspend io.ktor.server.routing.RoutingContext.() -> characterSheetId = characterSheetId, ) ) + } catch (exception: MissingParameterException) { + call.respond( + message = ResultJson.Error( + status = exception.errorCode, + message = exception.message ?: "?", + ) + ) } catch (exception: Exception) { - call.respondText( - text = "${exception.message}", - status = HttpStatusCode.UnprocessableEntity, + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt index 0eeb799..e5b2d1e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt @@ -1,10 +1,13 @@ package com.pixelized.server.lwa.server.rest.campaign import com.pixelized.server.lwa.server.Engine +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 fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.() -> Unit { @@ -15,15 +18,10 @@ fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.() // convert the scene into the a usable data model. val scene = campaignJsonFactory.convertFromJson(json = form) // update the campaign. - val updated = campaignService.setScene(scene = scene) - // error case - if (updated.not()) { - error("Unexpected error when updating the scene.") - } + campaignService.setScene(scene = scene) // API & WebSocket responses - call.respondText( - text = "${HttpStatusCode.OK}", - status = HttpStatusCode.OK, + call.respond( + message = ResultJson.Success(), ) webSocket.emit( value = CampaignEvent.UpdateScene( @@ -31,10 +29,19 @@ fun Engine.putCampaignScene(): suspend io.ktor.server.routing.RoutingContext.() name = scene.name, ) ) + } catch (exception: MissingParameterException) { + call.respond( + message = ResultJson.Error( + status = exception.errorCode, + message = exception.message ?: "?", + ) + ) } catch (exception: Exception) { - call.respondText( - text = exception.localizedMessage, - status = HttpStatusCode.UnprocessableEntity, + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt index 28dea04..ca99cda 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt @@ -13,11 +13,6 @@ fun Engine.deleteCharacter(): suspend io.ktor.server.routing.RoutingContext.() - val deleted = characterService.deleteCharacterSheet( characterSheetId = characterSheetId ) - // Remove the character fom the campaign if needed. - // TODO probably useless because all data will not be cleaned up (all campaign / screnes) - campaignService.removeInstance( - characterSheetId = characterSheetId, - ) if (deleted) { call.respondText( diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/PutResultJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt similarity index 78% rename from shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/PutResultJson.kt rename to shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt index c646c0f..c23172c 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/PutResultJson.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt @@ -13,12 +13,9 @@ sealed interface ResultJson { val message: String, ) : ResultJson { companion object { - const val GENERIC = 500 + const val GENERIC = 600 - const val FILE_ALREADY_EXIST = GENERIC + 1 - const val FILE_DOES_NOT_EXIST = GENERIC + 2 - - const val MISSING_PARAMETER = 1000 + const val MISSING_PARAMETER = 700 const val MISSING_CHARACTER_SHEET_ID = MISSING_PARAMETER + 1 const val MISSING_ALTERATION_ID = MISSING_PARAMETER + 2 const val MISSING_CREATE = MISSING_PARAMETER + 3 From d806048510de1b03651101db32c501963c5120e1 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sun, 30 Mar 2025 21:17:17 +0200 Subject: [PATCH 3/6] Server : Character error management --- .../com/pixelized/server/lwa/Application.kt | 3 - .../lwa/model/alteration/AlterationStore.kt | 31 ++++-- .../lwa/model/campaign/CampaignStore.kt | 1 - .../model/character/CharacterSheetService.kt | 48 ++++++-- .../model/character/CharacterSheetStore.kt | 105 ++++++++++++------ .../com/pixelized/server/lwa/server/Engine.kt | 28 ++++- .../com/pixelized/server/lwa/server/Server.kt | 2 +- .../rest/alteration/DELETE_Alteration.kt | 4 +- .../server/rest/alteration/GET_Alteration.kt | 3 +- .../rest/alteration/GET_AlterationTags.kt | 3 +- .../server/rest/alteration/GET_Alterations.kt | 3 +- .../server/rest/alteration/PUT_Alateration.kt | 4 +- .../campaign/DELETE_Campaign_Character.kt | 3 +- .../rest/campaign/DELETE_Campaign_Npc.kt | 5 +- .../lwa/server/rest/campaign/GET_Campaign.kt | 3 +- .../rest/campaign/PUT_Campaign_Character.kt | 3 +- .../server/rest/campaign/PUT_Campaign_Npc.kt | 3 +- .../rest/campaign/PUT_Campaign_Scene_Name.kt | 5 +- .../server/rest/character/DELETE_Character.kt | 45 +++++--- .../server/rest/character/GET_Character.kt | 22 +++- .../rest/character/GET_CharacterTags.kt | 19 +++- .../server/rest/character/GET_Characters.kt | 19 +++- .../server/rest/character/PUT_Character.kt | 33 ++++-- .../character/PUT_Character_Alteration.kt | 32 ++++-- .../rest/character/PUT_Character_Damage.kt | 35 ++++-- .../character/PUT_Character_Diminished.kt | 33 ++++-- .../rest/character/PUT_Character_Fatigue.kt | 35 ++++-- .../lwa/utils/extentions/ParametersExt.kt | 29 +++-- .../shared/lwa/protocol/rest/ResultJson.kt | 4 + 29 files changed, 388 insertions(+), 175 deletions(-) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt b/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt index cff6e7c..70e6e5e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/Application.kt @@ -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() diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt index 1eba998..106f4be 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt @@ -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 -> - alteration.removeIf { it.id == id } - } + alterations.toMutableList() + .also { alteration -> + alteration.removeIf { it.id == id } + } + .sortedWith(compareBy(Collator.getInstance()) { + it.metadata.name + }) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt index 16fd568..856b7ee 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt index 5222b7d..57d7b92 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetService.kt @@ -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 { + fun tags(): Collection { 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, + ) } } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt index 2a7e54b..a3ee360 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt @@ -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> = 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 { + suspend fun load( + directory: File = this.directory, + ): List { 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(json) + } catch (exception: Exception) { + throw JsonCodingException(root = exception) + } + // parse the json string. try { - val sheet = this.json.decodeFromString(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,32 +118,41 @@ class CharacterSheetStore( throw FileWriteException(root = exception) } // Update the dataflow. - characterSheetsFlow.value = characterSheetsFlow.value - .toMutableList() - .also { data -> - val index = data.indexOfFirst { it.id == sheet.id } - if (index >= 0) { - data[index] = sheet - } else { - data.add(sheet) + characterSheetsFlow.update { characters -> + characters.toMutableList() + .also { data -> + val index = data.indexOfFirst { it.id == sheet.id } + if (index >= 0) { + data[index] = sheet + } else { + 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 } - } - .sortedBy { - it.name - } + @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) + } + // 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) } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt index 58be8dd..f59604f 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt @@ -21,6 +21,14 @@ class Engine( val webSocket = MutableSharedFlow() 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 } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt index 49cfc50..29f003c 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Server.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt index 0652f60..4fd10d8 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt index a5e4df7..443b39e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt index 76ac1d9..5639499 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt @@ -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( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt index e0c029b..10cf56f 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt @@ -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( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt index c8bed66..1438132 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt @@ -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() diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt index e73e688..9159681 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt index 8a85c97..9c6837c 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt index 135c559..63efe2e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt @@ -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( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt index fea4532..c76a380 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt index f461b19..7fe30c6 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt index e5b2d1e..0efc8b7 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt @@ -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 diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt index ca99cda..01be69c 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt @@ -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 { - val characterSheetId = call.parameters.characterSheetId - - val deleted = characterService.deleteCharacterSheet( - characterSheetId = characterSheetId - ) - - if (deleted) { - call.respondText( - text = "${HttpStatusCode.OK}", - status = HttpStatusCode.OK, + try { + // get the query parameter + val characterSheetId = call.parameters.characterSheetId + // delete the character sheet. + characterService.deleteCharacterSheet( + characterSheetId = characterSheetId + ) + // 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 ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt index ea8c381..dd8a726 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt @@ -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 ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt index 9da14ff..81607df 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt @@ -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 { - call.respond( - message = characterService.tags(), - ) + try { + call.respond( + message = characterService.tags(), + ) + } catch (exception: Exception) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) + ) + } } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt index 727b986..8e8fab6 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt @@ -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 { - call.respond( - message = characterService.charactersJson(), - ) + try { + call.respond( + message = characterService.charactersJson(), + ) + } catch (exception: Exception) { + call.respond( + message = ResultJson.Error( + status = ResultJson.Error.GENERIC, + message = exception.message ?: "?", + ) + ) + } } } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt index e521ca5..5f8d4eb 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt @@ -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() - 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 ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt index b4fd0fa..6e0f399 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt @@ -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 ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt index 0dae255..831b2d8 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt @@ -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 ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt index fc44ae9..7f8426e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt @@ -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 ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt index 5b31494..62431ca 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt @@ -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 ?: "?", + ) ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt index 4901cdb..ac119ad 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt @@ -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, diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt index c23172c..2b4ebc5 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt @@ -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 } } From 1ebb7c14007f36f47c7c2de0820dcbaf15fe03ba Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Tue, 1 Apr 2025 16:01:43 +0200 Subject: [PATCH 4/6] Client : Error management --- .../kotlin/com/pixelized/desktop/lwa/App.kt | 7 +- .../desktop/lwa/DataSyncViewModel.kt | 9 +- .../com/pixelized/desktop/lwa/Module.kt | 6 +- .../desktop/lwa/network/LwaClient.kt | 70 +++---- .../desktop/lwa/network/LwaClientImpl.kt | 87 +++++---- .../lwa/network/LwaNetworkException.kt | 13 ++ .../alteration/AlterationRepository.kt | 18 +- .../repository/alteration/AlterationStore.kt | 83 ++++----- .../repository/campaign/CampaignRepository.kt | 40 ++-- .../lwa/repository/campaign/CampaignStore.kt | 108 +++++------ .../CharacterSheetRepository.kt | 33 +--- .../characterSheet/CharacterSheetStore.kt | 171 ++++++++---------- .../lwa/repository/tag/TagRepository.kt | 16 ++ .../desktop/lwa/repository/tag/TagStore.kt | 79 ++++++++ .../lwa/ui/composable/error/ErrorSnackUio.kt | 2 +- .../screen/campaign/network/NetworkDialog.kt | 4 +- .../alteration/edit/GMAlterationEditPage.kt | 5 + .../edit/GMAlterationEditViewModel.kt | 24 ++- .../alteration/list/GMAlterationViewModel.kt | 6 +- .../character/list/GMCharacterViewModel.kt | 4 +- .../lwa/model/alteration/AlterationStore.kt | 2 +- .../lwa/model/campaign/CampaignStore.kt | 2 +- .../model/character/CharacterSheetStore.kt | 2 +- .../server/lwa/model/tag/TagStore.kt | 2 +- .../com/pixelized/server/lwa/server/Engine.kt | 2 +- .../rest/alteration/DELETE_Alteration.kt | 10 +- .../server/rest/alteration/GET_Alteration.kt | 10 +- .../rest/alteration/GET_AlterationTags.kt | 10 +- .../server/rest/alteration/GET_Alterations.kt | 10 +- .../server/rest/alteration/PUT_Alateration.kt | 10 +- .../campaign/DELETE_Campaign_Character.kt | 10 +- .../rest/campaign/DELETE_Campaign_Npc.kt | 10 +- .../lwa/server/rest/campaign/GET_Campaign.kt | 10 +- .../rest/campaign/PUT_Campaign_Character.kt | 10 +- .../server/rest/campaign/PUT_Campaign_Npc.kt | 10 +- .../rest/campaign/PUT_Campaign_Scene_Name.kt | 14 +- .../server/rest/character/DELETE_Character.kt | 10 +- .../server/rest/character/GET_Character.kt | 12 +- .../rest/character/GET_CharacterTags.kt | 10 +- .../server/rest/character/GET_Characters.kt | 10 +- .../server/rest/character/PUT_Character.kt | 10 +- .../character/PUT_Character_Alteration.kt | 10 +- .../rest/character/PUT_Character_Damage.kt | 12 +- .../character/PUT_Character_Diminished.kt | 12 +- .../rest/character/PUT_Character_Fatigue.kt | 12 +- .../lwa/utils/extentions/ParametersExt.kt | 10 +- .../shared/lwa/protocol/rest/APIResponse.kt | 52 ++++++ .../shared/lwa/protocol/rest/ResultJson.kt | 34 ---- 48 files changed, 630 insertions(+), 473 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagRepository.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagStore.kt create mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt delete mode 100644 shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt index b2e5a63..93dd0e0 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -1,5 +1,8 @@ package com.pixelized.desktop.lwa +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.ButtonDefaults import androidx.compose.material.MaterialTheme @@ -10,11 +13,13 @@ import androidx.compose.material.SnackbarDefaults import androidx.compose.material.SnackbarDuration import androidx.compose.material.SnackbarHost import androidx.compose.material.SnackbarHostState +import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember @@ -146,7 +151,7 @@ private fun MainWindowScreen( } LwaScaffold( - modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize() ) { MainNavHost() diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt index 349d046..4cc3f87 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/DataSyncViewModel.kt @@ -6,6 +6,7 @@ import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.repository.settings.SettingsRepository +import com.pixelized.desktop.lwa.repository.tag.TagRepository import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.distinctUntilChanged @@ -19,6 +20,7 @@ class DataSyncViewModel( private val characterRepository: CharacterSheetRepository, private val alterationRepository: AlterationRepository, private val campaignRepository: CampaignRepository, + private val tagRepository: TagRepository, private val settingsRepository: SettingsRepository, private val networkRepository: NetworkRepository, ) : ViewModel() { @@ -40,10 +42,10 @@ class DataSyncViewModel( networkRepository.status .filter { status -> status == NetworkRepository.Status.CONNECTED } .onEach { + tagRepository.updateAlterationTags() alterationRepository.updateAlterations() - alterationRepository.updateTags() + tagRepository.updateCharacterTags() characterRepository.updateCharacterPreviews() - characterRepository.updateTags() campaignRepository.updateCampaign() } .launchIn(this) @@ -54,9 +56,8 @@ class DataSyncViewModel( .distinctUntilChanged() .onEach { instances -> instances.forEach { characterSheetId -> - characterRepository.characterDetail( + characterRepository.updateCharacterSheet( characterSheetId = characterSheetId, - forceUpdate = true, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt index 9d9574f..4c03877 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -13,6 +13,8 @@ import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository import com.pixelized.desktop.lwa.repository.settings.SettingsFactory import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.repository.settings.SettingsStore +import com.pixelized.desktop.lwa.repository.tag.TagRepository +import com.pixelized.desktop.lwa.repository.tag.TagStore import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogFactory import com.pixelized.desktop.lwa.ui.composable.character.diminished.CharacterSheetDiminishedDialogFactory @@ -37,9 +39,9 @@ import com.pixelized.desktop.lwa.ui.screen.characterSheet.edit.common.SkillField import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.action.GMActionViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditFactory +import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationFactory import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list.GMAlterationViewModel -import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterFactory import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterViewModel import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory @@ -100,6 +102,7 @@ val storeDependencies singleOf(::SettingsStore) singleOf(::AlterationStore) singleOf(::CampaignStore) + singleOf(::TagStore) } val repositoryDependencies @@ -110,6 +113,7 @@ val repositoryDependencies singleOf(::SettingsRepository) singleOf(::AlterationRepository) singleOf(::CampaignRepository) + singleOf(::TagRepository) } val factoryDependencies diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt index 24e8365..e3c66b8 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClient.kt @@ -5,83 +5,89 @@ import com.pixelized.shared.lwa.model.campaign.CampaignJson import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson import com.pixelized.shared.lwa.model.tag.TagJson import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse interface LwaClient { // Alteration - suspend fun alterations(): List + suspend fun getAlterations(): APIResponse> - suspend fun alterations( + suspend fun getAlteration( alterationId: String, - ): AlterationJson? + ): APIResponse - suspend fun updateAlteration( - alterationJson: AlterationJson - ) + suspend fun putAlteration( + alterationJson: AlterationJson, + create: Boolean, + ): APIResponse suspend fun deleteAlteration( alterationId: String, - ) + ): APIResponse - suspend fun alterationTags(): List + suspend fun getAlterationTags(): APIResponse> // Campaign - suspend fun campaign(): CampaignJson + suspend fun getCampaign(): APIResponse - suspend fun campaignAddCharacter( + suspend fun putCampaignCharacter( characterSheetId: String, - ) + ): APIResponse - suspend fun campaignRemoveCharacter( + suspend fun removeCampaignCharacter( characterSheetId: String, - ) + ): APIResponse - suspend fun campaignAddNpc( + suspend fun putCampaignNpc( characterSheetId: String, - ) + ): APIResponse - suspend fun campaignRemoveNpc( + suspend fun removeCampaignNpc( characterSheetId: String, - ) + ): APIResponse // Character - suspend fun characters(): List + suspend fun getCharacters(): APIResponse> - suspend fun characterTags(): List + suspend fun getCharacterTags(): APIResponse> - suspend fun character( + suspend fun getCharacter( characterSheetId: String, - ): CharacterSheetJson + ): APIResponse - suspend fun updateCharacter( + suspend fun putCharacter( sheet: CharacterSheetJson, - ) + ): APIResponse - suspend fun updateCharacterDamage( + suspend fun putCharacterDamage( characterSheetId: String, damage: Int, - ) + ): APIResponse - suspend fun updateCharacterFatigue( + suspend fun putCharacterFatigue( characterSheetId: String, fatigue: Int, - ) + ): APIResponse - suspend fun updateCharacterDiminished( + suspend fun putCharacterDiminished( characterSheetId: String, diminished: Int, - ) + ): APIResponse - suspend fun updateCharacterAlteration( + suspend fun putCharacterAlteration( characterSheetId: String, alterationId: Int, active: Boolean, - ) + ): APIResponse suspend fun deleteCharacterSheet( characterSheetId: String, - ) + ): APIResponse + + companion object { + fun error(error: APIResponse<*>): Nothing = throw LwaNetworkException(error) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt index 62d2449..cf4bf3a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaClientImpl.kt @@ -5,6 +5,7 @@ import com.pixelized.shared.lwa.model.alteration.AlterationJson import com.pixelized.shared.lwa.model.campaign.CampaignJson import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson import com.pixelized.shared.lwa.model.tag.TagJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.CharacterPreviewJson import io.ktor.client.HttpClient import io.ktor.client.call.body @@ -21,114 +22,134 @@ class LwaClientImpl( ) : LwaClient { private val root = setting.settings().network.root - override suspend fun alterations(): List = client + @Throws + override suspend fun getAlterations(): APIResponse> = client .get("$root/alteration/all") .body() - override suspend fun alterations( + @Throws + override suspend fun getAlteration( alterationId: String, - ): AlterationJson? = client + ): APIResponse = client .get("$root/alteration/detail?alterationId=$alterationId") .body() - override suspend fun updateAlteration( - alterationJson: AlterationJson + @Throws + override suspend fun putAlteration( + alterationJson: AlterationJson, + create: Boolean, ) = client - .put("$root/alteration/update") { + .put("$root/alteration/update?create=$create") { contentType(ContentType.Application.Json) setBody(alterationJson) } - .body() + .body>() + @Throws override suspend fun deleteAlteration(alterationId: String) = client .delete("$root/alteration/delete?alterationId=$alterationId") - .body() + .body>() - override suspend fun alterationTags(): List = client + @Throws + override suspend fun getAlterationTags(): APIResponse> = client .get("$root/alteration/tags") .body() - override suspend fun campaign(): CampaignJson = client + @Throws + override suspend fun getCampaign(): APIResponse = client .get("$root/campaign") .body() - override suspend fun campaignAddCharacter( + @Throws + override suspend fun putCampaignCharacter( characterSheetId: String, ) = client .put("$root/campaign/character/add?characterSheetId=$characterSheetId") - .body() + .body>() - override suspend fun campaignRemoveCharacter( + @Throws + override suspend fun removeCampaignCharacter( characterSheetId: String, ) = client .delete("$root/campaign/character/delete?characterSheetId=$characterSheetId") - .body() + .body>() - override suspend fun campaignAddNpc( + @Throws + override suspend fun putCampaignNpc( characterSheetId: String, ) = client .put("$root/campaign/npc/add?characterSheetId=$characterSheetId") - .body() + .body>() - override suspend fun campaignRemoveNpc( + @Throws + override suspend fun removeCampaignNpc( characterSheetId: String, ) = client .delete("$root/campaign/npc/delete?characterSheetId=$characterSheetId") - .body() + .body>() - override suspend fun characters(): List = client + @Throws + override suspend fun getCharacters(): APIResponse> = client .get("$root/character/all") .body() - override suspend fun characterTags(): List = client + @Throws + override suspend fun getCharacterTags(): APIResponse> = client .get("$root/character/tags") .body() - override suspend fun character( + @Throws + override suspend fun getCharacter( characterSheetId: String, - ): CharacterSheetJson = client + ): APIResponse = client .get("$root/character/detail?characterSheetId=$characterSheetId") .body() - override suspend fun updateCharacter( + @Throws + override suspend fun putCharacter( sheet: CharacterSheetJson, ) = client .put("$root/character/update/sheet") { contentType(ContentType.Application.Json) setBody(sheet) } - .body() + .body>() - override suspend fun updateCharacterDamage( + @Throws + override suspend fun putCharacterDamage( characterSheetId: String, damage: Int, ) = client .put("$root/character/update/damage?characterSheetId=$characterSheetId&damage=$damage") - .body() + .body>() - override suspend fun updateCharacterFatigue( + @Throws + override suspend fun putCharacterFatigue( characterSheetId: String, fatigue: Int, ) = client .put("$root/character/update/fatigue?characterSheetId=$characterSheetId&fatigue=$fatigue") - .body() + .body>() - override suspend fun updateCharacterDiminished( + @Throws + override suspend fun putCharacterDiminished( characterSheetId: String, diminished: Int, ) = client .put("$root/character/update/diminished?characterSheetId=$characterSheetId&diminished=$diminished") - .body() + .body>() - override suspend fun updateCharacterAlteration( + @Throws + override suspend fun putCharacterAlteration( characterSheetId: String, alterationId: Int, active: Boolean, ) = client .put("$root/character/update/alteration?characterSheetId=$characterSheetId&alterationId=$alterationId&active=$active") - .body() + .body>() + @Throws override suspend fun deleteCharacterSheet(characterSheetId: String) = client .delete("$root/character/delete?characterSheetId=$characterSheetId") - .body() + .body>() } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt new file mode 100644 index 0000000..3f048e8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt @@ -0,0 +1,13 @@ +package com.pixelized.desktop.lwa.network + +import com.pixelized.shared.lwa.protocol.rest.APIResponse + +class LwaNetworkException( + val status: Int, + message: String, +) : Exception(message) { + constructor(error: APIResponse<*>) : this( + status = error.status, + message = error.message ?: "An unknown error occurred" + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationRepository.kt index 7687eaa..18be883 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationRepository.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.flow.stateIn class AlterationRepository( private val alterationStore: AlterationStore, + campaignRepository: CampaignRepository, characterRepository: CharacterSheetRepository, ) { @@ -26,8 +27,6 @@ class AlterationRepository( val alterationFlow get() = alterationStore.alterationsFlow - val tagsFlow get() = alterationStore.tagsFlow - /** * This flow transform the campaign instance (player + npc) into a * Map>. @@ -66,11 +65,7 @@ class AlterationRepository( ) suspend fun updateAlterations() { - alterationStore.updateAlterations() - } - - suspend fun updateTags() { - alterationStore.updateTags() + alterationStore.updateAlterationsFlow() } fun alteration( @@ -91,10 +86,15 @@ class AlterationRepository( return activeAlterationMapFlow.map { it[characterSheetId] ?: emptyMap() } } + @Throws suspend fun updateAlteration( - alteration: Alteration + alteration: Alteration, + create: Boolean, ) { - alterationStore.putAlteration(alteration) + alterationStore.putAlteration( + alteration = alteration, + create = create, + ) } suspend fun deleteAlteration( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt index 1924f74..74ed229 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt @@ -4,8 +4,6 @@ import com.pixelized.desktop.lwa.network.LwaClient import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.shared.lwa.model.alteration.Alteration import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory -import com.pixelized.shared.lwa.model.tag.Tag -import com.pixelized.shared.lwa.model.tag.TagJsonFactory import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.SocketMessage import kotlinx.coroutines.CoroutineScope @@ -18,22 +16,14 @@ import kotlinx.coroutines.launch class AlterationStore( private val networkRepository: NetworkRepository, - private val alterationFactory: AlterationJsonFactory, - private val tagFactory: TagJsonFactory, + private val factory: AlterationJsonFactory, private val client: LwaClient, ) { private val _alterationsFlow = MutableStateFlow>(emptyMap()) val alterationsFlow: StateFlow> = _alterationsFlow - private val _tagsFlow = MutableStateFlow>(emptyMap()) - val tagsFlow: StateFlow> = _tagsFlow - init { val scope = CoroutineScope(Dispatchers.IO + Job()) - scope.launch { - updateAlterations() - updateTags() - } // data update through WebSocket. scope.launch { networkRepository.data.collect(::handleMessage) @@ -44,19 +34,11 @@ class AlterationStore( return alterationsFlow.value.values } - fun tags(): Collection { - return tagsFlow.value.values - } - fun alteration(alterationId: String): Alteration? { return alterationsFlow.value[alterationId] } - fun tag(tagId: String): Tag? { - return tagsFlow.value[tagId] - } - - suspend fun updateAlterations() { + suspend fun updateAlterationsFlow() { _alterationsFlow.value = try { getAlteration() } catch (exception: Exception) { @@ -65,7 +47,7 @@ class AlterationStore( } } - suspend fun updateAlteration( + private suspend fun updateAlterationFlow( alterationId: String, ) { val alteration = try { @@ -74,9 +56,9 @@ class AlterationStore( println(exception.message) // TODO proper exception handling null } - + // guard case if getAlteration failed if (alteration == null) return - + // update the flow with the alteration. _alterationsFlow.update { alterations -> alterations.toMutableMap().also { it[alterationId] = alteration @@ -84,60 +66,59 @@ class AlterationStore( } } - suspend fun updateTags() { - _tagsFlow.value = try { - getAlterationTag() - } catch (exception: Exception) { - println(exception.message) // TODO proper exception handling - emptyMap() - } - } - @Throws private suspend fun getAlteration(): Map { - val request = client.alterations() - val data = request.map { alterationFactory.convertFromJson(json = it) } - return data.associateBy { it.id } + val request = client.getAlterations() + return when (request.success) { + true -> request.data + ?.map { factory.convertFromJson(json = it) } + ?.associateBy { it.id } + ?: emptyMap() + + else -> LwaClient.error(error = request) + } } @Throws private suspend fun getAlteration( alterationId: String, ): Alteration? { - val request = client.alterations(alterationId = alterationId) - return request?.let { alterationFactory.convertFromJson(json = it) } - } - - @Throws - private suspend fun getAlterationTag(): Map { - val request = client.alterationTags() - val data = request.map { tagFactory.convertFromJson(json = it) } - return data.associateBy { it.id } + val request = client.getAlteration(alterationId = alterationId) + return when (request.success) { + true -> request.data?.let { factory.convertFromJson(json = it) } + else -> LwaClient.error(error = request) + } } @Throws suspend fun putAlteration( alteration: Alteration, + create: Boolean, ) { - client.updateAlteration( - alterationJson = alterationFactory.convertToJson(data = alteration) + val request = client.putAlteration( + alterationJson = factory.convertToJson(data = alteration), + create = create, ) + if (request.success.not()) { + LwaClient.error(error = request) + } } @Throws suspend fun deleteAlteration( - alterationId: String + alterationId: String, ) { - client.deleteAlteration( - alterationId = alterationId - ) + val request = client.deleteAlteration(alterationId = alterationId) + if (request.success.not()) { + LwaClient.error(error = request) + } } // region: WebSocket & data update. private suspend fun handleMessage(message: SocketMessage) { when (message) { - is ApiSynchronisation.AlterationUpdate -> updateAlteration( + is ApiSynchronisation.AlterationUpdate -> updateAlterationFlow( alterationId = message.alterationId, ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt index 7041c69..da1b34b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignRepository.kt @@ -3,33 +3,45 @@ package com.pixelized.desktop.lwa.repository.campaign class CampaignRepository( private val store: CampaignStore, ) { - val campaignFlow get() = store.campaignFlow + val campaignFlow = store.campaignFlow() suspend fun updateCampaign() { - store.campaign(update = true) + store.updateCampaignFlow() } + @Throws suspend fun addCharacter( characterSheetId: String, - ) = store.addCharacter( - characterSheetId = characterSheetId, - ) + ) { + store.addCharacter( + characterSheetId = characterSheetId, + ) + } + @Throws suspend fun removeCharacter( characterSheetId: String, - ) = store.removeCharacter( - characterSheetId = characterSheetId, - ) + ) { + store.removeCharacter( + characterSheetId = characterSheetId, + ) + } + @Throws suspend fun addNpc( characterSheetId: String, - ) = store.addNpc( - characterSheetId = characterSheetId, - ) + ) { + store.addNpc( + characterSheetId = characterSheetId, + ) + } + @Throws suspend fun removeNpc( characterSheetId: String, - ) = store.removeNpc( - characterSheetId = characterSheetId, - ) + ) { + store.removeNpc( + characterSheetId = characterSheetId, + ) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt index 7e0d480..74adc1c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt @@ -19,81 +19,76 @@ class CampaignStore( private val client: LwaClient, private val network: NetworkRepository, private val factory: CampaignJsonFactory, - - ) { - private val _campaignFlow = MutableStateFlow(value = Campaign.empty()) - val campaignFlow: StateFlow get() = _campaignFlow +) { + private val campaignFlow = MutableStateFlow(value = Campaign.empty()) init { val scope = CoroutineScope(Dispatchers.IO + Job()) - scope.launch { - campaign(update = true) - } scope.launch { network.data.collect(::handleMessage) } } - suspend fun campaign(update: Boolean): Campaign { - val campaign = _campaignFlow.value - if (update || campaign == Campaign.empty()) { - val data = try { - val request = client.campaign() - factory.convertFromJson(json = request) - } catch (exception: Exception) { - println(exception) // TODO proper exception handling - Campaign.empty() - } - return _campaignFlow.update(data) + fun campaignFlow(): StateFlow = campaignFlow + + suspend fun updateCampaignFlow() { + campaignFlow.value = try { + getCampaign() + } catch (exception: Exception) { + println(exception.message) // TODO proper exception handling + Campaign.empty() } - return campaign } + @Throws suspend fun addCharacter( characterSheetId: String, ) { - try { - client.campaignAddCharacter( - characterSheetId = characterSheetId - ) - } catch (exception: Exception) { - println(exception) // TODO proper exception handling + val request = client.putCampaignCharacter(characterSheetId = characterSheetId) + if (request.success.not()) { + LwaClient.error(error = request) } } + @Throws suspend fun removeCharacter( characterSheetId: String, ) { - try { - client.campaignRemoveCharacter( - characterSheetId = characterSheetId, - ) - } catch (exception: Exception) { - println(exception) // TODO proper exception handling + val request = client.removeCampaignCharacter(characterSheetId = characterSheetId) + if (request.success.not()) { + LwaClient.error(error = request) } } + @Throws suspend fun addNpc( characterSheetId: String, ) { - try { - client.campaignAddNpc( - characterSheetId = characterSheetId - ) - } catch (exception: Exception) { - println(exception) // TODO proper exception handling + val request = client.putCampaignNpc(characterSheetId = characterSheetId) + if (request.success.not()) { + LwaClient.error(error = request) } } + @Throws suspend fun removeNpc( characterSheetId: String, ) { - try { - client.campaignRemoveNpc( - characterSheetId = characterSheetId, - ) - } catch (exception: Exception) { - println(exception) // TODO proper exception handling + val request = client.removeCampaignNpc(characterSheetId = characterSheetId) + if (request.success.not()) { + LwaClient.error(error = request) + } + } + + @Throws + private suspend fun getCampaign(): Campaign { + val request = client.getCampaign() + return when (request.success) { + true -> request.data + ?.let { factory.convertFromJson(json = it) } + ?: Campaign.empty() + + else -> LwaClient.error(error = request) } } @@ -103,7 +98,7 @@ class CampaignStore( when (message) { is CampaignEvent -> { when (message) { - is CampaignEvent.CharacterAdded -> _campaignFlow.update { campaign -> + is CampaignEvent.CharacterAdded -> campaignFlow.update { campaign -> campaign.copy( characters = campaign.characters.toMutableSet().also { it.add(message.characterSheetId) @@ -111,7 +106,7 @@ class CampaignStore( ) } - is CampaignEvent.CharacterRemoved -> _campaignFlow.update { campaign -> + is CampaignEvent.CharacterRemoved -> campaignFlow.update { campaign -> campaign.copy( characters = campaign.characters.toMutableSet().also { it.remove(message.characterSheetId) @@ -119,7 +114,7 @@ class CampaignStore( ) } - is CampaignEvent.NpcAdded -> _campaignFlow.update { campaign -> + is CampaignEvent.NpcAdded -> campaignFlow.update { campaign -> campaign.copy( npcs = campaign.npcs.toMutableSet().also { it.add(message.characterSheetId) @@ -127,7 +122,7 @@ class CampaignStore( ) } - is CampaignEvent.NpcRemoved -> _campaignFlow.update { campaign -> + is CampaignEvent.NpcRemoved -> campaignFlow.update { campaign -> campaign.copy( npcs = campaign.npcs.toMutableSet().also { it.remove(message.characterSheetId) @@ -135,7 +130,7 @@ class CampaignStore( ) } - is CampaignEvent.UpdateScene -> _campaignFlow.update { campaign -> + is CampaignEvent.UpdateScene -> campaignFlow.update { campaign -> campaign.copy( scene = Campaign.Scene(name = message.name) ) @@ -145,17 +140,17 @@ class CampaignStore( is GameMasterEvent -> when (message) { is GameMasterEvent.ToggleNpc -> { - _campaignFlow.value = _campaignFlow.value.copy( - options = _campaignFlow.value.options.copy( - showNpcs = _campaignFlow.value.options.showNpcs.not() + campaignFlow.value = campaignFlow.value.copy( + options = campaignFlow.value.options.copy( + showNpcs = campaignFlow.value.options.showNpcs.not() ) ) } is GameMasterEvent.TogglePlayer -> { - _campaignFlow.value = _campaignFlow.value.copy( - options = _campaignFlow.value.options.copy( - showParty = _campaignFlow.value.options.showParty.not() + campaignFlow.value = campaignFlow.value.copy( + options = campaignFlow.value.options.copy( + showParty = campaignFlow.value.options.showParty.not() ) ) } @@ -168,9 +163,4 @@ class CampaignStore( } // endregion - - private fun MutableStateFlow.update(campaign: Campaign): Campaign { - value = campaign - return campaign - } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt index 34f1f3d..beba518 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetRepository.kt @@ -18,46 +18,27 @@ class CharacterSheetRepository( val characterSheetPreviewFlow get() = store.previewFlow - val characterDetailFlow get() = store.detailFlow - val tagsFlow get() = store.tagsFlow - suspend fun updateCharacterPreviews() { - store.updateCharactersPreview() + store.updateCharactersPreviewFlow() } - suspend fun updateTags() { - store.updateTags() + suspend fun updateCharacterSheet(characterSheetId: String) { + store.updateCharacterSheetDetailFlow(characterSheetId = characterSheetId) } fun characterPreview(characterId: String?): CharacterSheetPreview? { return characterSheetPreviewFlow.value.firstOrNull { it.characterSheetId == characterId } } - suspend fun characterDetail( + fun characterDetail( characterSheetId: String?, - forceUpdate: Boolean = false, ): CharacterSheet? { - return try { - characterSheetId?.let { - store.getCharacterSheet( - characterSheetId = it, - forceUpdate = forceUpdate - ) - } - } catch (exception: Exception) { - null - } + return store.detailFlow.value[characterSheetId] } fun characterDetailFlow( characterSheetId: String?, ): StateFlow { - val initial = store.detailFlow.value[characterSheetId] - - if (initial == null) { - scope.launch { characterDetail(characterSheetId = characterSheetId) } - } - return store.detailFlow .map { sheets -> sheets[characterSheetId] @@ -65,16 +46,18 @@ class CharacterSheetRepository( .stateIn( scope = scope, started = SharingStarted.Eagerly, - initialValue = initial, + initialValue = store.detailFlow.value[characterSheetId], ) } + @Throws suspend fun updateCharacter( sheet: CharacterSheet, ) { store.updateCharacterSheet(sheet = sheet) } + @Throws suspend fun deleteCharacter( characterSheetId: String, ) { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt index def69ff..29c4635 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt @@ -6,12 +6,9 @@ import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetPreview import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory -import com.pixelized.shared.lwa.model.tag.Tag -import com.pixelized.shared.lwa.model.tag.TagJsonFactory import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.SocketMessage -import com.pixelized.shared.lwa.usecase.CharacterSheetUseCase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -25,7 +22,6 @@ class CharacterSheetStore( private val client: LwaClient, private val network: NetworkRepository, private val factory: CharacterSheetJsonFactory, - private val tagFactory: TagJsonFactory, ) { private val _previewFlow = MutableStateFlow>(value = emptyList()) val previewFlow: StateFlow> get() = _previewFlow @@ -33,16 +29,8 @@ class CharacterSheetStore( private val _detailFlow = MutableStateFlow>(value = emptyMap()) val detailFlow: StateFlow> get() = _detailFlow - private val _tagsFlow = MutableStateFlow>(emptyMap()) - val tagsFlow: StateFlow> = _tagsFlow - init { val scope = CoroutineScope(Dispatchers.IO + Job()) - // initial data loading. - scope.launch { - updateCharactersPreview() - updateTags() - } // data update through WebSocket. scope.launch { network.data.collect(::handleMessage) @@ -51,71 +39,75 @@ class CharacterSheetStore( // region Rest - suspend fun updateCharactersPreview(): List { - val request = try { - client.characters() + suspend fun updateCharactersPreviewFlow() { + _previewFlow.value = try { + getCharacters() } catch (exception: Exception) { - println(exception) // TODO proper exception handling + println(exception.message) // TODO proper exception handling emptyList() } - val characters = request.map { - factory.convertFromJson(it) - } - return _previewFlow.update(characters) } - suspend fun updateTags(): Map { - val request = try { - client.characterTags() - } catch (exception: Exception) { - println(exception) // TODO proper exception handling - emptyList() - } - val tags = request.map { - tagFactory.convertFromJson(json = it) - }.associateBy { it.id } - - _tagsFlow.value = tags - return tags - } - - suspend fun getCharacterSheet( + suspend fun updateCharacterSheetDetailFlow( characterSheetId: String, - forceUpdate: Boolean = false, - ): CharacterSheet? { - val character = _detailFlow.value[characterSheetId] - if (forceUpdate || character == null) { - try { - val request = client.character(characterSheetId = characterSheetId) - val data = factory.convertFromJson(json = request) - return _detailFlow.update(data) - } catch (exception: Exception) { - println(exception) // TODO proper exception handling - return null + ) { + val characterSheet = try { + getCharacterSheet(characterSheetId = characterSheetId) + } catch (exception: Exception) { + println(exception.message) // TODO proper exception handling + null + } + if (characterSheet == null) return + _detailFlow.update { data -> + data.toMutableMap().also { + it[characterSheetId] = characterSheet } } - return character } + @Throws + suspend fun getCharacters(): List { + val request = client.getCharacters() + return when (request.success) { + true -> request.data + ?.map { factory.convertFromJson(it) } + ?: emptyList() + + else -> LwaClient.error(error = request) + } + } + + @Throws + suspend fun getCharacterSheet( + characterSheetId: String, + ): CharacterSheet { + val request = client.getCharacter(characterSheetId = characterSheetId) + return when (request.success) { + true -> request.data!!.let { factory.convertFromJson(json = it) } + else -> LwaClient.error(error = request) + } + } + + // TODO check crash + @Throws suspend fun updateCharacterSheet( sheet: CharacterSheet, ) { val json = factory.convertToJson(sheet = sheet) - try { - client.updateCharacter(sheet = json) - } catch (exception: Exception) { - println(exception) // TODO + val request = client.putCharacter(sheet = json) + if (request.success) { + LwaClient.error(error = request) } - _detailFlow.update(sheet = sheet) } + // TODO check crash + @Throws suspend fun deleteCharacterSheet( characterSheetId: String, ) { - try { - client.deleteCharacterSheet(characterSheetId = characterSheetId) - } catch (exception: Exception) { - println(exception) // TODO + val request = client.deleteCharacterSheet(characterSheetId = characterSheetId) + if (request.success) { + LwaClient.error(error = request) } } @@ -124,32 +116,35 @@ class CharacterSheetStore( private suspend fun handleMessage(message: SocketMessage) { when (message) { - is ApiSynchronisation -> when (message) { - is ApiSynchronisation.CharacterSheetUpdate -> { - getCharacterSheet( - characterSheetId = message.characterSheetId, - forceUpdate = true, - ) - if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) { - updateCharactersPreview() - } - } - - is ApiSynchronisation.CharacterSheetDelete -> { - _previewFlow.update { previews -> - previews.toMutableList().also { sheet -> - sheet.removeIf { - it.characterSheetId == message.characterSheetId - } + is ApiSynchronisation -> try { + when (message) { + is ApiSynchronisation.CharacterSheetUpdate -> { + _detailFlow.update( + sheet = getCharacterSheet(characterSheetId = message.characterSheetId) + ) + if (_previewFlow.value.firstOrNull { it.characterSheetId == message.characterSheetId } == null) { + _previewFlow.value = getCharacters() } } - _detailFlow.update { sheets -> - sheets.toMutableMap().also { it.remove(message.characterSheetId) } - } - } - is ApiSynchronisation.AlterationUpdate -> Unit - is ApiSynchronisation.AlterationDelete -> Unit + is ApiSynchronisation.CharacterSheetDelete -> { + _previewFlow.update { previews -> + previews.toMutableList().also { sheet -> + sheet.removeIf { + it.characterSheetId == message.characterSheetId + } + } + } + _detailFlow.update { sheets -> + sheets.toMutableMap().also { it.remove(message.characterSheetId) } + } + } + + is ApiSynchronisation.AlterationUpdate -> Unit + is ApiSynchronisation.AlterationDelete -> Unit + } + } catch (exception: Exception) { + println(exception.message) // TODO proper exception handling } is CharacterSheetEvent -> when (message) { @@ -190,7 +185,7 @@ class CharacterSheetStore( skillId: String, used: Boolean, ) { - val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return + val sheet = getCharacterSheet(characterSheetId = characterSheetId) val character = sheet.copy( commonSkills = sheet.commonSkills.map { skill -> skill.takeIf { skill.id == skillId }?.copy(used = used) ?: skill @@ -211,7 +206,7 @@ class CharacterSheetStore( active: Boolean, ) { if (alterationStore.alteration(alterationId = alterationId) == null) return - val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return + val sheet = getCharacterSheet(characterSheetId = characterSheetId) val containAlteration = sheet.alterations.contains(alterationId) @@ -233,7 +228,7 @@ class CharacterSheetStore( characterSheetId: String, damage: Int, ) { - val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return + val sheet = getCharacterSheet(characterSheetId = characterSheetId) _detailFlow.update(sheet.copy(damage = damage)) } @@ -241,7 +236,7 @@ class CharacterSheetStore( characterSheetId: String, diminished: Int, ) { - val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return + val sheet = getCharacterSheet(characterSheetId = characterSheetId) _detailFlow.update(sheet.copy(diminished = diminished)) } @@ -249,18 +244,12 @@ class CharacterSheetStore( characterSheetId: String, fatigue: Int, ) { - val sheet = getCharacterSheet(characterSheetId = characterSheetId) ?: return + val sheet = getCharacterSheet(characterSheetId = characterSheetId) _detailFlow.update(sheet.copy(fatigue = fatigue)) } // endregion - private fun MutableStateFlow>.update( - previews: List, - ): List { - value = previews - return previews - } private fun MutableStateFlow>.update( sheet: CharacterSheet, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagRepository.kt new file mode 100644 index 0000000..b44049c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagRepository.kt @@ -0,0 +1,16 @@ +package com.pixelized.desktop.lwa.repository.tag + +import com.pixelized.shared.lwa.model.tag.Tag +import kotlinx.coroutines.flow.StateFlow + +class TagRepository( + private val store: TagStore, +) { + suspend fun updateCharacterTags() = store.updateCharacterTagsFlow() + + suspend fun updateAlterationTags() = store.updateAlterationTagsFlow() + + fun charactersTagFlow(): StateFlow> = store.charactersTagFlow() + + fun alterationsTagFlow(): StateFlow> = store.alterationsTagFlow() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagStore.kt new file mode 100644 index 0000000..3263260 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/tag/TagStore.kt @@ -0,0 +1,79 @@ +package com.pixelized.desktop.lwa.repository.tag + +import com.pixelized.desktop.lwa.network.LwaClient +import com.pixelized.shared.lwa.model.tag.Tag +import com.pixelized.shared.lwa.model.tag.TagJsonFactory +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class TagStore( + private val factory: TagJsonFactory, + private val client: LwaClient, +) { + private val characterTagsFlow = MutableStateFlow>(emptyMap()) + private val alterationTagsFlow = MutableStateFlow>(emptyMap()) + + fun charactersTagFlow(): StateFlow> = characterTagsFlow + + fun characters(): Collection { + return characterTagsFlow.value.values + } + + fun character(tagId: String): Tag? { + return characterTagsFlow.value[tagId] + } + + fun alterationsTagFlow(): StateFlow> = alterationTagsFlow + + fun alterations(): Collection { + return alterationTagsFlow.value.values + } + + fun alteration(tagId: String): Tag? { + return alterationTagsFlow.value[tagId] + } + + suspend fun updateCharacterTagsFlow() { + characterTagsFlow.value = try { + getCharacterTag() + } catch (exception: Exception) { + println(exception.message) // TODO proper exception handling + emptyMap() + } + } + + suspend fun updateAlterationTagsFlow() { + alterationTagsFlow.value = try { + getAlterationTag() + } catch (exception: Exception) { + println(exception.message) // TODO proper exception handling + emptyMap() + } + } + + @Throws + private suspend fun getCharacterTag(): Map { + val request = client.getCharacterTags() + return when (request.success) { + true -> request.data + ?.map { factory.convertFromJson(json = it) } + ?.associateBy { it.id } + ?: emptyMap() + + else -> LwaClient.error(error = request) + } + } + + @Throws + private suspend fun getAlterationTag(): Map { + val request = client.getAlterationTags() + return when (request.success) { + true -> request.data + ?.map { factory.convertFromJson(json = it) } + ?.associateBy { it.id } + ?: emptyMap() + + else -> LwaClient.error(error = request) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt index b45f07f..3b39cda 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt @@ -24,7 +24,7 @@ class ErrorSnackUio( } @Composable -fun ErrorSnack( +fun ErrorSnackHandler( snack: SnackbarHostState = LocalErrorSnackHost.current, error: SharedFlow, ) { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/network/NetworkDialog.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/network/NetworkDialog.kt index 37d295d..a2a34ea 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/network/NetworkDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/network/NetworkDialog.kt @@ -43,7 +43,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.pixelized.desktop.lwa.LocalSnackHost import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent -import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnack +import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler import com.pixelized.desktop.lwa.ui.theme.lwa import kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res @@ -148,7 +148,7 @@ fun NetworkDialog( } } - ErrorSnack( + ErrorSnackHandler( error = viewModel.networkError, ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt index 84716ee..d0f2099 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio @@ -109,6 +110,10 @@ fun GMAlterationEditPage( } } + ErrorSnackHandler( + error = viewModel.error, + ) + AlterationEditKeyHandler( onDismissRequest = { screen.popBackStack() diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt index 6b706a1..c3a1d1f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt @@ -4,8 +4,11 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository +import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -20,6 +23,9 @@ class GMAlterationEditViewModel( private val _form = MutableStateFlow(null) val form: StateFlow get() = _form + private val _error = MutableSharedFlow() + val error: SharedFlow get() = _error + init { viewModelScope.launch { _form.value = factory.createForm( @@ -30,14 +36,22 @@ class GMAlterationEditViewModel( suspend fun save() { val edited = factory.createAlteration(form = form.value) - val actual = alterationRepository.alterationFlow.value[edited?.id] - // TODO if argument.id == null et actual?.id != null on créer et on écrase existant !!! + if (edited == null) return - if (edited != null) - - alterationRepository.updateAlteration(edited) + try { + if (argument.id == null && actual?.id != null) { + error("Id already taken by an another alteration") + } + alterationRepository.updateAlteration( + alteration = edited, + create = argument.id == null, + ) + } catch (exception: Exception) { + val message = ErrorSnackUio.from(exception = exception) + _error.emit(message) + } } suspend fun addField() { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/list/GMAlterationViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/list/GMAlterationViewModel.kt index b12767d..d30b99c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/list/GMAlterationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/list/GMAlterationViewModel.kt @@ -3,6 +3,7 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.list import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository +import com.pixelized.desktop.lwa.repository.tag.TagRepository import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio @@ -22,6 +23,7 @@ import org.jetbrains.compose.resources.getString class GMAlterationViewModel( private val alterationRepository: AlterationRepository, alterationFactory: GMAlterationFactory, + tagRepository: TagRepository, tagFactory: GMTagFactory, ) : ViewModel() { @@ -38,7 +40,7 @@ class GMAlterationViewModel( ) val tags: StateFlow> = combine( - alterationRepository.tagsFlow, + tagRepository.alterationsTagFlow(), selectedTagId, ) { tags, selectedTagId -> tagFactory.convertToGMTagItemUio( @@ -53,7 +55,7 @@ class GMAlterationViewModel( val alterations: StateFlow> = combine( alterationRepository.alterationFlow, - alterationRepository.tagsFlow, + tagRepository.alterationsTagFlow(), filter.valueFlow.map { it.unAccent() }, selectedTagId, ) { alterations, tags, unAccentFilter, selectedTagId -> diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterViewModel.kt index 212848f..162334b 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/character/list/GMCharacterViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.repository.tag.TagRepository import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.screen.gamemaster.character.list.GMCharacterItemUio.Action import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory @@ -27,6 +28,7 @@ class GMCharacterViewModel( private val networkRepository: NetworkRepository, private val campaignRepository: CampaignRepository, characterSheetRepository: CharacterSheetRepository, + tagRepository: TagRepository, private val factory: GMCharacterFactory, private val tagFactory: GMTagFactory, ) : ViewModel() { @@ -44,7 +46,7 @@ class GMCharacterViewModel( ) val tags = combine( - characterSheetRepository.tagsFlow, + tagRepository.charactersTagFlow(), selectedTagId, ) { tags, selectedTagId -> tagFactory.convertToGMTagItemUio( diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt index 106f4be..0a155cd 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt @@ -39,7 +39,7 @@ class AlterationStore( alterationFlow.value = try { load() } catch (exception: Exception) { - println(exception) // TODO proper exception handling + println(exception.message) // TODO proper exception handling emptyList() } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt index 856b7ee..1baa868 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -38,7 +38,7 @@ class CampaignStore( campaignFlow.value = try { load() } catch (exception: Exception) { - println(exception) // TODO proper exception handling + println(exception.message) // TODO proper exception handling Campaign.empty() } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt index a3ee360..d804870 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt @@ -38,7 +38,7 @@ class CharacterSheetStore( characterSheetsFlow.value = try { load() } catch (exception: Exception) { - println(exception) // TODO proper exception handling + println(exception.message) // TODO proper exception handling emptyList() } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt index 137e4d0..5bbff0b 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt @@ -49,7 +49,7 @@ class TagStore( flow.value = try { file.readTags().associateBy { it.id } } catch (exception: Exception) { - println(exception) // TODO proper exception handling + println(exception.message) // TODO proper exception handling emptyMap() } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt index f59604f..fa178ec 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/Engine.kt @@ -24,7 +24,7 @@ class Engine( try { unSafeHandle(message) } catch (exception: Exception) { - println(exception) // TODO proper exception handling + println(exception.message) // TODO proper exception handling } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt index 4fd10d8..557ce43 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt @@ -3,7 +3,7 @@ package com.pixelized.server.lwa.server.rest.alteration 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -19,7 +19,7 @@ fun Engine.deleteAlteration(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success(), + message = APIResponse.success(), ) webSocket.emit( value = ApiSynchronisation.AlterationDelete( @@ -29,15 +29,15 @@ fun Engine.deleteAlteration(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt index 443b39e..6a70c41 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt @@ -2,7 +2,7 @@ package com.pixelized.server.lwa.server.rest.alteration import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.utils.extentions.alterationId -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -17,12 +17,14 @@ fun Engine.getAlteration(): suspend RoutingContext.() -> Unit { ?: error("Alteration with id:$alterationId not found.") // send it back to the user. call.respond( - message = alteration, + message = APIResponse.success( + data = alteration + ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt index 5639499..07edd33 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt @@ -1,7 +1,7 @@ package com.pixelized.server.lwa.server.rest.alteration import com.pixelized.server.lwa.server.Engine -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -9,12 +9,14 @@ fun Engine.getAlterationTags(): suspend RoutingContext.() -> Unit { return { try { call.respond( - message = alterationService.tags(), + message = APIResponse.success( + data = alterationService.tags(), + ), ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt index 10cf56f..6022814 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt @@ -1,7 +1,7 @@ package com.pixelized.server.lwa.server.rest.alteration import com.pixelized.server.lwa.server.Engine -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -9,12 +9,14 @@ fun Engine.getAlterations(): suspend RoutingContext.() -> Unit { return { try { call.respond( - message = alterationService.alterations(), + message = APIResponse.success( + data = alterationService.alterations(), + ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt index 1438132..da2e233 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt @@ -4,7 +4,7 @@ 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.alteration.AlterationJson -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import io.ktor.server.request.receive import io.ktor.server.response.respond @@ -21,7 +21,7 @@ fun Engine.putAlteration(): suspend RoutingContext.() -> Unit { create = create, ) call.respond( - message = ResultJson.Success(), + message = APIResponse.success(), ) webSocket.emit( value = ApiSynchronisation.AlterationUpdate( @@ -31,15 +31,15 @@ fun Engine.putAlteration(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt index 9159681..a2e1a2d 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt @@ -3,7 +3,7 @@ package com.pixelized.server.lwa.server.rest.campaign 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -19,7 +19,7 @@ fun Engine.removeCampaignCharacter(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses call.respond( - message = ResultJson.Success(), + message = APIResponse.success(), ) webSocket.emit( value = CampaignEvent.CharacterRemoved( @@ -29,15 +29,15 @@ fun Engine.removeCampaignCharacter(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt index 9c6837c..336e562 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt @@ -3,7 +3,7 @@ package com.pixelized.server.lwa.server.rest.campaign 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -19,7 +19,7 @@ fun Engine.removeCampaignNpc(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses call.respond( - message = ResultJson.Success(), + message = APIResponse.success(), ) webSocket.emit( value = CampaignEvent.NpcRemoved( @@ -29,15 +29,15 @@ fun Engine.removeCampaignNpc(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt index 63efe2e..f345e13 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt @@ -1,7 +1,7 @@ package com.pixelized.server.lwa.server.rest.campaign import com.pixelized.server.lwa.server.Engine -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -9,12 +9,14 @@ fun Engine.getCampaign(): suspend RoutingContext.() -> Unit { return { try { call.respond( - message = campaignService.campaignJson(), + message = APIResponse.success( + data = campaignService.campaignJson(), + ), ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt index c76a380..80f012a 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt @@ -3,7 +3,7 @@ package com.pixelized.server.lwa.server.rest.campaign 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -19,7 +19,7 @@ fun Engine.putCampaignCharacter(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success(), + message = APIResponse.success(), ) webSocket.emit( value = CampaignEvent.CharacterAdded( @@ -29,15 +29,15 @@ fun Engine.putCampaignCharacter(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt index 7fe30c6..07dfd5d 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt @@ -3,7 +3,7 @@ package com.pixelized.server.lwa.server.rest.campaign 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -19,7 +19,7 @@ fun Engine.putCampaignNpc(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success(), + message = APIResponse.success(), ) webSocket.emit( value = CampaignEvent.NpcAdded( @@ -29,15 +29,15 @@ fun Engine.putCampaignNpc(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt index 0efc8b7..e2e37bb 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt @@ -3,7 +3,7 @@ package com.pixelized.server.lwa.server.rest.campaign import com.pixelized.server.lwa.server.Engine 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.request.receive import io.ktor.server.response.respond @@ -17,10 +17,12 @@ fun Engine.putCampaignScene(): suspend RoutingContext.() -> Unit { // convert the scene into the a usable data model. val scene = campaignJsonFactory.convertFromJson(json = form) // update the campaign. - campaignService.setScene(scene = scene) + campaignService.setScene( + scene = scene, + ) // API & WebSocket responses call.respond( - message = ResultJson.Success(), + message = APIResponse.success(), ) webSocket.emit( value = CampaignEvent.UpdateScene( @@ -30,15 +32,15 @@ fun Engine.putCampaignScene(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt index 01be69c..42629b4 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt @@ -3,7 +3,7 @@ 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -19,7 +19,7 @@ fun Engine.deleteCharacter(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success() + message = APIResponse.success() ) webSocket.emit( value = ApiSynchronisation.CharacterSheetDelete( @@ -29,15 +29,15 @@ fun Engine.deleteCharacter(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt index dd8a726..200bba4 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt @@ -3,7 +3,7 @@ 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.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -18,19 +18,21 @@ fun Engine.getCharacter(): suspend RoutingContext.() -> Unit { ?: error("CharacterSheet with id:$characterSheetId not found.") // send it back to the user. call.respond( - message = characterSheet, + message = APIResponse.success( + data = characterSheet, + ), ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt index 81607df..52b5c3f 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt @@ -1,7 +1,7 @@ package com.pixelized.server.lwa.server.rest.character import com.pixelized.server.lwa.server.Engine -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -9,12 +9,14 @@ fun Engine.getCharacterTags(): suspend RoutingContext.() -> Unit { return { try { call.respond( - message = characterService.tags(), + message = APIResponse.success( + data = characterService.tags(), + ), ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt index 8e8fab6..354b4ee 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt @@ -1,7 +1,7 @@ package com.pixelized.server.lwa.server.rest.character import com.pixelized.server.lwa.server.Engine -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -9,12 +9,14 @@ fun Engine.getCharacters(): suspend RoutingContext.() -> Unit { return { try { call.respond( - message = characterService.charactersJson(), + message = APIResponse.success( + data = characterService.charactersJson(), + ), ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt index 5f8d4eb..dbc0f21 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt @@ -4,7 +4,7 @@ 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import io.ktor.server.request.receive import io.ktor.server.response.respond @@ -20,7 +20,7 @@ fun Engine.putCharacter(): suspend RoutingContext.() -> Unit { create = create, ) call.respond( - message = ResultJson.Success() + message = APIResponse.success() ) webSocket.emit( value = ApiSynchronisation.CharacterSheetUpdate( @@ -30,15 +30,15 @@ fun Engine.putCharacter(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt index 6e0f399..a6fd05b 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt @@ -5,7 +5,7 @@ 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -25,7 +25,7 @@ fun Engine.putCharacterAlteration(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success() + message = APIResponse.success() ) webSocket.emit( value = CharacterSheetEvent.UpdateAlteration( @@ -37,15 +37,15 @@ fun Engine.putCharacterAlteration(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt index 831b2d8..427c4be 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt @@ -3,7 +3,7 @@ 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -16,7 +16,7 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit { val damage = call.queryParameters["damage"]?.toIntOrNull() ?: throw MissingParameterException( name = "damage", - errorCode = ResultJson.Error.MISSING_DAMAGE + errorCode = APIResponse.MISSING_DAMAGE ) // fetch the character sheet val characterSheet = characterService.character(characterSheetId) @@ -28,7 +28,7 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success() + message = APIResponse.success() ) webSocket.emit( value = CharacterSheetEvent.UpdateDamage( @@ -40,15 +40,15 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt index 7f8426e..b695e26 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt @@ -3,7 +3,7 @@ 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -16,7 +16,7 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit { val diminished = call.queryParameters["diminished"]?.toIntOrNull() ?: throw MissingParameterException( name = "diminished", - errorCode = ResultJson.Error.MISSING_DIMINISHED + errorCode = APIResponse.MISSING_DIMINISHED ) // Update the character damage characterService.updateDiminished( @@ -25,7 +25,7 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success() + message = APIResponse.success() ) webSocket.emit( value = CharacterSheetEvent.UpdateDiminished( @@ -36,15 +36,15 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt index 62431ca..6f565cc 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt @@ -3,7 +3,7 @@ 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.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -16,7 +16,7 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit { val fatigue = call.queryParameters["fatigue"]?.toIntOrNull() ?: throw MissingParameterException( name = "fatigue", - errorCode = ResultJson.Error.MISSING_FATIGUE + errorCode = APIResponse.MISSING_FATIGUE ) // fetch the character sheet val characterSheet = characterService.character(characterSheetId) @@ -28,7 +28,7 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit { ) // API & WebSocket responses. call.respond( - message = ResultJson.Success() + message = APIResponse.success() ) webSocket.emit( value = CharacterSheetEvent.UpdateFatigue( @@ -40,15 +40,15 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit { ) } catch (exception: MissingParameterException) { call.respond( - message = ResultJson.Error( + message = APIResponse.error( status = exception.errorCode, message = exception.message ?: "?", ) ) } catch (exception: Exception) { call.respond( - message = ResultJson.Error( - status = ResultJson.Error.GENERIC, + message = APIResponse.error( + status = APIResponse.GENERIC, message = exception.message ?: "?", ) ) diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt index ac119ad..b719213 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt @@ -1,13 +1,13 @@ package com.pixelized.server.lwa.utils.extentions -import com.pixelized.shared.lwa.protocol.rest.ResultJson +import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.http.Parameters val Parameters.characterSheetId get() = "characterSheetId".let { param -> this[param] ?: throw MissingParameterException( name = param, - errorCode = ResultJson.Error.MISSING_CHARACTER_SHEET_ID, + errorCode = APIResponse.MISSING_CHARACTER_SHEET_ID, ) } @@ -15,7 +15,7 @@ val Parameters.alterationId get() = "alterationId".let { param -> this[param] ?: throw MissingParameterException( name = param, - errorCode = ResultJson.Error.MISSING_ALTERATION_ID, + errorCode = APIResponse.MISSING_ALTERATION_ID, ) } @@ -23,7 +23,7 @@ val Parameters.create get() = "create".let { param -> this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException( name = param, - errorCode = ResultJson.Error.MISSING_CREATE + errorCode = APIResponse.MISSING_CREATE ) } @@ -31,7 +31,7 @@ val Parameters.active get() = "active".let { param -> this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException( name = param, - errorCode = ResultJson.Error.MISSING_ACTIVE + errorCode = APIResponse.MISSING_ACTIVE ) } diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt new file mode 100644 index 0000000..f5893b4 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt @@ -0,0 +1,52 @@ +package com.pixelized.shared.lwa.protocol.rest + +import kotlinx.serialization.Serializable + +@Serializable +data class APIResponse( + val success: Boolean, + val status: Int, + val message: String?, + val data: T?, +) { + companion object { + const val SUCCESS = 100 + + const val GENERIC = 600 + + const val MISSING_PARAMETER = 700 + 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 + + fun error( + status: Int, + message: String?, + ) = APIResponse( + success = false, + status = status, + message = message, + data = null, + ) + + fun success() = APIResponse( + success = true, + status = SUCCESS, + message = null, + data = null, + ) + + inline fun success( + data: T? = null, + ) = APIResponse( + success = true, + status = SUCCESS, + message = null, + data = data, + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt deleted file mode 100644 index 2b4ebc5..0000000 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/ResultJson.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.pixelized.shared.lwa.protocol.rest - -import kotlinx.serialization.Serializable - -@Serializable -sealed interface ResultJson { - val success: Boolean - - @Serializable - data class Error( - override val success: Boolean = false, - val status: Int, - val message: String, - ) : ResultJson { - companion object { - const val GENERIC = 600 - - const val MISSING_PARAMETER = 700 - 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 - } - } - - @Serializable - data class Success( - override val success: Boolean = true, - val status: Int = 100, - ) : ResultJson -} \ No newline at end of file From 6213d5ac15c696a1e005313ee10ac03a840160a3 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Tue, 1 Apr 2025 17:29:39 +0200 Subject: [PATCH 5/6] Add a navigation layer into the GameMaster screens. --- .../kotlin/com/pixelized/desktop/lwa/App.kt | 3 +- .../repository/alteration/AlterationStore.kt | 4 +- .../lwa/repository/campaign/CampaignStore.kt | 2 +- .../characterSheet/CharacterSheetStore.kt | 4 +- .../gamemaster/GameMasterDestination.kt | 30 +++++++++++++ .../ui/screen/gamemaster/GameMasterNavHost.kt | 42 +++++++++++++++++++ .../ui/screen/gamemaster/GameMasterScreen.kt | 41 +++++++----------- .../alteration/edit/GMAlterationEditPage.kt | 1 + 8 files changed, 93 insertions(+), 34 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GameMasterDestination.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterNavHost.kt diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt index 93dd0e0..acd03fc 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -52,6 +52,7 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWin import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight import com.pixelized.desktop.lwa.ui.overlay.roll.RollHostState import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost +import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterNavHost import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage import com.pixelized.desktop.lwa.ui.theme.LwaTheme @@ -184,7 +185,7 @@ private fun WindowsHandler( is RollHistoryWindow -> RollHistoryPage() is GameMasterWindow -> LwaScaffold { - GameMasterScreen() + GameMasterNavHost() } } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt index 74ed229..708f579 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/alteration/AlterationStore.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class AlterationStore( - private val networkRepository: NetworkRepository, + private val network: NetworkRepository, private val factory: AlterationJsonFactory, private val client: LwaClient, ) { @@ -26,7 +26,7 @@ class AlterationStore( val scope = CoroutineScope(Dispatchers.IO + Job()) // data update through WebSocket. scope.launch { - networkRepository.data.collect(::handleMessage) + network.data.collect(::handleMessage) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt index 74adc1c..402211f 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/campaign/CampaignStore.kt @@ -16,9 +16,9 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class CampaignStore( - private val client: LwaClient, private val network: NetworkRepository, private val factory: CampaignJsonFactory, + private val client: LwaClient, ) { private val campaignFlow = MutableStateFlow(value = Campaign.empty()) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt index 29c4635..163c44d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/characterSheet/CharacterSheetStore.kt @@ -19,9 +19,9 @@ import kotlinx.coroutines.launch class CharacterSheetStore( private val alterationStore: AlterationStore, - private val client: LwaClient, private val network: NetworkRepository, private val factory: CharacterSheetJsonFactory, + private val client: LwaClient, ) { private val _previewFlow = MutableStateFlow>(value = emptyList()) val previewFlow: StateFlow> get() = _previewFlow @@ -88,7 +88,6 @@ class CharacterSheetStore( } } - // TODO check crash @Throws suspend fun updateCharacterSheet( sheet: CharacterSheet, @@ -100,7 +99,6 @@ class CharacterSheetStore( } } - // TODO check crash @Throws suspend fun deleteCharacterSheet( characterSheetId: String, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GameMasterDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GameMasterDestination.kt new file mode 100644 index 0000000..43968cd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GameMasterDestination.kt @@ -0,0 +1,30 @@ +package com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster + +import androidx.compose.runtime.Stable +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import com.pixelized.desktop.lwa.ui.screen.gamemaster.GameMasterScreen + +@Stable +object GameMasterDestination { + private const val ROUTE = "GameMasterMain" + + fun baseRoute() = ROUTE + + @Stable + fun navigationRoute() = ROUTE +} + +fun NavGraphBuilder.composableGameMasterMainPage() { + composable( + route = GameMasterDestination.baseRoute(), + ) { + GameMasterScreen() + } +} + +fun NavHostController.navigateToGameMasterMainPage() { + val route = GameMasterDestination.navigationRoute() + navigate(route = route) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterNavHost.kt new file mode 100644 index 0000000..8b8b056 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterNavHost.kt @@ -0,0 +1,42 @@ +package com.pixelized.desktop.lwa.ui.screen.gamemaster + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.ui.Modifier +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GameMasterDestination +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationEditPage +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterMainPage + +val LocalGMScreenController = compositionLocalOf { + error("GameMaster NavHost controller is not yet ready") +} + +@Composable +fun GameMasterNavHost() { + val controller = rememberNavController() + + CompositionLocalProvider( + LocalScreenController provides controller, + LocalGMScreenController provides rememberNavController(), + ) { + Surface( + modifier = Modifier.fillMaxSize() + ) { + NavHost( + modifier = Modifier.fillMaxSize(), + navController = controller, + startDestination = GameMasterDestination.navigationRoute(), + ) { + composableGameMasterMainPage() + composableGameMasterAlterationEditPage() + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt index 8b7b6ea..abd0c70 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/GameMasterScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.material.Switch import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment @@ -28,7 +27,6 @@ import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController -import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMActionDestination import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterActionPage import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.composableGameMasterAlterationEditPage @@ -52,35 +50,24 @@ import org.koin.compose.viewmodel.koinViewModel @Composable fun GameMasterScreen( gameMasterViewModel: GameMasterViewModel = koinViewModel(), - + controller: NavHostController = LocalGMScreenController.current, ) { - val screen = rememberNavController() val gameMaster = gameMasterViewModel.isGameMaster.collectAsState() - CompositionLocalProvider( - LocalScreenController provides screen, - ) { - Surface( - modifier = Modifier.fillMaxSize() - ) { - Box { - GameMasterContent( - modifier = Modifier.fillMaxSize(), - controller = screen, - gameMaster = gameMaster, - onGameMaster = gameMasterViewModel::onGameMaster, - onTab = { - when (it) { - GMTabUio.Actions -> screen.navigateToGameMasterActionPage() - GMTabUio.Characters -> screen.navigateToGameMasterCharacterPage() - GMTabUio.Alterations -> screen.navigateToGameMasterAlterationPage() - GMTabUio.Objects -> screen.navigateToGameMasterObjectPage() - } - }, - ) + GameMasterContent( + modifier = Modifier.fillMaxSize(), + controller = controller, + gameMaster = gameMaster, + onGameMaster = gameMasterViewModel::onGameMaster, + onTab = { + when (it) { + GMTabUio.Actions -> controller.navigateToGameMasterActionPage() + GMTabUio.Characters -> controller.navigateToGameMasterCharacterPage() + GMTabUio.Alterations -> controller.navigateToGameMasterAlterationPage() + GMTabUio.Objects -> controller.navigateToGameMasterObjectPage() } - } - } + }, + ) } @Composable diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt index d0f2099..8d6c051 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt @@ -40,6 +40,7 @@ import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController +import com.pixelized.desktop.lwa.ui.screen.gamemaster.LocalGMScreenController import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors import com.pixelized.desktop.lwa.ui.theme.lwa import kotlinx.coroutines.flow.MutableStateFlow From f94a530621a45a13718e54dceddd6275ee98653a Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Tue, 1 Apr 2025 22:18:55 +0200 Subject: [PATCH 6/6] Add specific alteration error management. --- .../composeResources/values/strings.xml | 1 + .../lwa/network/LwaNetworkException.kt | 2 + .../lwa/ui/composable/error/ErrorSnackUio.kt | 12 +- .../ui/composable/textfield/LwaTextField.kt | 14 +- .../gamemaster/GMAlterationEditDestination.kt | 4 +- .../edit/GMAlterationEditFactory.kt | 27 +- .../alteration/edit/GMAlterationEditPage.kt | 425 +++++++++++------- .../edit/GMAlterationEditViewModel.kt | 40 +- .../ui/screen/gamemaster/common/tag/GMTag.kt | 34 ++ .../gamemaster/common/tag/GMTagFactory.kt | 17 + .../desktop/lwa/ui/theme/LwaTheme.kt | 2 +- .../theme/color/component/LwaButtonColors.kt | 10 +- .../desktop/lwa/ui/theme/shapes/LwaShapes.kt | 6 + .../lwa/model/alteration/AlterationStore.kt | 40 +- .../lwa/model/campaign/CampaignService.kt | 13 +- .../lwa/model/campaign/CampaignStore.kt | 11 +- .../model/character/CharacterSheetStore.kt | 27 +- .../server/lwa/model/tag/TagStore.kt | 8 +- .../lwa/server/exception/BusinessException.kt | 10 + .../lwa/server/exception/FileReadException.kt | 3 + .../server/exception/FileWriteException.kt | 3 + .../server/exception/JsonCodingException.kt | 3 + .../exception/JsonConversionException.kt | 3 + .../exception/MissingParameterException.kt | 4 + .../lwa/server/exception/ServerException.kt | 3 + .../rest/alteration/DELETE_Alteration.kt | 16 +- .../server/rest/alteration/GET_Alteration.kt | 8 +- .../rest/alteration/GET_AlterationTags.kt | 8 +- .../server/rest/alteration/GET_Alterations.kt | 8 +- .../{PUT_Alateration.kt => PUT_Alteration.kt} | 16 +- .../campaign/DELETE_Campaign_Character.kt | 16 +- .../rest/campaign/DELETE_Campaign_Npc.kt | 16 +- .../lwa/server/rest/campaign/GET_Campaign.kt | 8 +- .../rest/campaign/PUT_Campaign_Character.kt | 16 +- .../server/rest/campaign/PUT_Campaign_Npc.kt | 16 +- .../rest/campaign/PUT_Campaign_Scene_Name.kt | 16 +- .../server/rest/character/DELETE_Character.kt | 16 +- .../server/rest/character/GET_Character.kt | 16 +- .../rest/character/GET_CharacterTags.kt | 8 +- .../server/rest/character/GET_Characters.kt | 8 +- .../server/rest/character/PUT_Character.kt | 16 +- .../character/PUT_Character_Alteration.kt | 16 +- .../rest/character/PUT_Character_Damage.kt | 22 +- .../character/PUT_Character_Diminished.kt | 22 +- .../rest/character/PUT_Character_Fatigue.kt | 22 +- .../lwa/utils/extentions/ParametersExt.kt | 27 +- .../extentions/RoutingCallExt+exception.kt | 40 ++ .../shared/lwa/protocol/rest/APIResponse.kt | 38 +- 48 files changed, 606 insertions(+), 511 deletions(-) create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/exception/BusinessException.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileReadException.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileWriteException.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonCodingException.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonConversionException.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/exception/MissingParameterException.kt create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/server/exception/ServerException.kt rename server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/{PUT_Alateration.kt => PUT_Alteration.kt} (68%) create mode 100644 server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/RoutingCallExt+exception.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 3501227..0df87e7 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -242,6 +242,7 @@ Ajouter aux Npcs Retirer des Npcs Créer un personnage + Édition d'Altération Filtrer par nom : Créer une altération Supprimer l'altération diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt index 3f048e8..753751a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/network/LwaNetworkException.kt @@ -4,10 +4,12 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse class LwaNetworkException( val status: Int, + val code: APIResponse.ErrorCode?, message: String, ) : Exception(message) { constructor(error: APIResponse<*>) : this( status = error.status, + code = error.code, message = error.message ?: "An unknown error occurred" ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt index 3b39cda..d3f60bf 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/error/ErrorSnackUio.kt @@ -7,6 +7,10 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import com.pixelized.desktop.lwa.LocalErrorSnackHost import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.collectLatest +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.error__default__action +import org.jetbrains.compose.resources.getString @Stable class ErrorSnackUio( @@ -15,10 +19,10 @@ class ErrorSnackUio( val duration: SnackbarDuration, ) { companion object { - fun from(exception: Exception) = ErrorSnackUio( + suspend fun from(exception: Exception) = ErrorSnackUio( message = exception.localizedMessage, - action = "Ok", - duration = SnackbarDuration.Indefinite + action = getString(Res.string.error__default__action), + duration = SnackbarDuration.Long, ) } } @@ -29,7 +33,7 @@ fun ErrorSnackHandler( error: SharedFlow, ) { LaunchedEffect(Unit) { - error.collect { + error.collectLatest { snack.showSnackbar( message = it.message, actionLabel = it.action, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextField.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextField.kt index ada79c6..22fdf75 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextField.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/composable/textfield/LwaTextField.kt @@ -1,6 +1,7 @@ package com.pixelized.desktop.lwa.ui.composable.textfield import androidx.compose.foundation.layout.height +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.material.TextFieldColors @@ -9,20 +10,21 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection +import androidx.compose.ui.graphics.Shape import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors import com.pixelized.desktop.lwa.utils.rememberKeyboardActions -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.MutableStateFlow @Stable data class LwaTextFieldUio( val enable: Boolean = true, - val isError: StateFlow, - val labelFlow: StateFlow?, - val valueFlow: StateFlow, - val placeHolderFlow: StateFlow?, + val isError: MutableStateFlow, + val labelFlow: MutableStateFlow?, + val valueFlow: MutableStateFlow, + val placeHolderFlow: MutableStateFlow?, val onValueChange: (String) -> Unit, ) @@ -30,6 +32,7 @@ data class LwaTextFieldUio( fun LwaTextField( modifier: Modifier = Modifier, colors: TextFieldColors = LwaTextFieldColors(), + shape: Shape = MaterialTheme.shapes.small, leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, singleLine: Boolean = true, @@ -51,6 +54,7 @@ fun LwaTextField( TextField( modifier = localModifier.then(other = modifier), colors = colors, + shape = shape, keyboardActions = rememberKeyboardActions { focus.moveFocus(FocusDirection.Next) }, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GMAlterationEditDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GMAlterationEditDestination.kt index 8bdd8f9..b24461a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GMAlterationEditDestination.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/gamemaster/GMAlterationEditDestination.kt @@ -7,7 +7,7 @@ import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.composable import androidx.navigation.navArgument -import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditPage +import com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit.GMAlterationEditScreen import com.pixelized.desktop.lwa.utils.extention.ARG @Stable @@ -44,7 +44,7 @@ fun NavGraphBuilder.composableGameMasterAlterationEditPage() { route = GMAlterationEditDestination.baseRoute(), arguments = GMAlterationEditDestination.arguments(), ) { - GMAlterationEditPage() + GMAlterationEditScreen() } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditFactory.kt index 14e5d51..c06cae7 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditFactory.kt @@ -1,34 +1,39 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio +import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagFactory import com.pixelized.shared.lwa.model.alteration.Alteration +import com.pixelized.shared.lwa.model.tag.Tag import com.pixelized.shared.lwa.parser.expression.ExpressionParser import kotlinx.coroutines.flow.MutableStateFlow import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_description import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_expression import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_id import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_id import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_label -import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_description -import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_tags import org.jetbrains.compose.resources.getString import java.util.UUID class GMAlterationEditFactory( private val expressionParser: ExpressionParser, + private val tagFactory: GMTagFactory, ) { suspend fun createForm( + originId: String?, alteration: Alteration?, + tags: Collection, ): GMAlterationEditPageUio { val id = MutableStateFlow(alteration?.id ?: "") val label = MutableStateFlow(alteration?.metadata?.name ?: "") val description = MutableStateFlow(alteration?.metadata?.description ?: "") - val tags = MutableStateFlow(alteration?.tags?.joinToString(", ") { it } ?: "") - val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(createField(null))) + val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf( + createField(null) + )) return GMAlterationEditPageUio( id = LwaTextFieldUio( - enable = true, + enable = originId == null, isError = MutableStateFlow(false), labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_id)), valueFlow = id, @@ -51,13 +56,9 @@ class GMAlterationEditFactory( placeHolderFlow = null, onValueChange = { description.value = it }, ), - tags = LwaTextFieldUio( - enable = true, - isError = MutableStateFlow(false), - labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_tags)), - valueFlow = tags, - placeHolderFlow = null, - onValueChange = { tags.value = it }, + tags = tagFactory.convertToGMTagItemUio( + tags = tags, + selectedTagIds = alteration?.tags ?: emptyList(), ), fields = fields, ) @@ -101,7 +102,7 @@ class GMAlterationEditFactory( name = form.label.valueFlow.value, description = form.description.valueFlow.value, ), - tags = form.tags.valueFlow.value.split(","), + tags = form.tags.filter { it.highlight }.map { it.id }, fields = form.fields.value.mapNotNull { field -> expressionParser.parse(input = field.expression.valueFlow.value)?.let { Alteration.Field( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt index 8d6c051..e07e731 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditPage.kt @@ -1,9 +1,16 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.draggable +import androidx.compose.foundation.gestures.rememberDraggableState +import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,21 +18,31 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Button import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -34,21 +51,27 @@ import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.type +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackHandler import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextField import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController -import com.pixelized.desktop.lwa.ui.screen.gamemaster.LocalGMScreenController +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination +import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagButton +import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors import com.pixelized.desktop.lwa.ui.theme.lwa +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_add_field -import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_cancel import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_save +import lwacharactersheet.composeapp.generated.resources.game_master__alteration__title import lwacharactersheet.composeapp.generated.resources.ic_save_24dp import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.stringResource @@ -59,7 +82,7 @@ data class GMAlterationEditPageUio( val id: LwaTextFieldUio, val label: LwaTextFieldUio, val description: LwaTextFieldUio, - val tags: LwaTextFieldUio, + val tags: List, val fields: MutableStateFlow>, ) { @Stable @@ -76,40 +99,37 @@ object GMAlterationEditPageDefault { } @Composable -fun GMAlterationEditPage( +fun GMAlterationEditScreen( viewModel: GMAlterationEditViewModel = koinViewModel(), ) { val screen = LocalScreenController.current val scope = rememberCoroutineScope() val form = viewModel.form.collectAsState() - AnimatedContent( - targetState = form.value, - transitionSpec = { fadeIn() togetherWith fadeOut() } - ) { - when (it) { - null -> Box(modifier = Modifier.fillMaxSize()) - else -> GMAlterationEditContent( - modifier = Modifier.fillMaxSize(), - form = it, - paddings = GMAlterationEditPageDefault.paddings, - addField = { - scope.launch { - viewModel.addField() - } - }, - removeField = viewModel::removeField, - onSave = { - scope.launch { - viewModel.save() - } - }, - onCancel = { - screen.popBackStack() - }, - ) - } - } + GMAlterationEditContent( + modifier = Modifier.fillMaxSize(), + form = form, + paddings = GMAlterationEditPageDefault.paddings, + onBack = { + screen.navigateBack() + }, + addField = { + scope.launch { + viewModel.addField() + } + }, + removeField = viewModel::removeField, + onSave = { + scope.launch { + if (viewModel.save()) { + screen.navigateBack() + } + } + }, + onTag = { tag -> + viewModel.addTag(tag = tag) + }, + ) ErrorSnackHandler( error = viewModel.error, @@ -117,7 +137,7 @@ fun GMAlterationEditPage( AlterationEditKeyHandler( onDismissRequest = { - screen.popBackStack() + screen.navigateBack() } ) } @@ -125,148 +145,218 @@ fun GMAlterationEditPage( @Composable private fun GMAlterationEditContent( modifier: Modifier = Modifier, - form: GMAlterationEditPageUio, + scope: CoroutineScope = rememberCoroutineScope(), + tagsState: LazyListState = rememberLazyListState(), + form: State, paddings: PaddingValues, + onBack: () -> Unit, addField: () -> Unit, removeField: (index: Int) -> Unit, onSave: () -> Unit, - onCancel: () -> Unit, + onTag: (GMTagUio) -> Unit, ) { - val fields = form.fields.collectAsState() - - LazyColumn( + Scaffold( modifier = modifier, - contentPadding = paddings, - verticalArrangement = Arrangement.spacedBy(space = 8.dp), - ) { - item( - key = "Id", - ) { - LwaTextField( - modifier = Modifier - .animateItem() - .fillMaxWidth(), - field = form.id, - singleLine = true, - ) - } - item( - key = "Name", - ) { - LwaTextField( - modifier = Modifier - .animateItem() - .fillMaxWidth(), - field = form.label, - singleLine = true, - ) - } - item( - key = "Description", - ) { - LwaTextField( - modifier = Modifier - .animateItem() - .fillMaxWidth(), - field = form.description, - singleLine = false, - ) - } - item( - key = "Tags", - ) { - LwaTextField( - modifier = Modifier - .animateItem() - .fillMaxWidth(), - field = form.tags, - singleLine = true, - ) - } - itemsIndexed( - items = fields.value, - key = { _, item -> item.key }, - ) { index, item -> - Row( - modifier = Modifier.animateItem(), - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - LwaTextField( - modifier = Modifier.weight(1f), - field = item.id, - singleLine = true, - ) - LwaTextField( - modifier = Modifier.weight(1f), - field = item.expression, - singleLine = true, - ) - IconButton( - onClick = { removeField(index) }, - ) { - Icon( - imageVector = Icons.Default.Close, - contentDescription = null, - tint = MaterialTheme.lwa.colorScheme.base.primary, + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(Res.string.game_master__alteration__title), ) + }, + navigationIcon = { + IconButton( + onClick = onBack, + ) { + Icon( + imageVector = Icons.AutoMirrored.Default.ArrowBack, + contentDescription = null, + ) + } + }, + actions = { + TextButton( + onClick = onSave, + ) { + Text( + modifier = Modifier.padding(end = 4.dp), + color = MaterialTheme.lwa.colorScheme.base.primary, + fontWeight = FontWeight.SemiBold, + text = stringResource(Res.string.game_master__alteration__edit_field_save), + ) + Icon( + painter = painterResource(Res.drawable.ic_save_24dp), + tint = MaterialTheme.lwa.colorScheme.base.primary, + contentDescription = null, + ) + } + } + ) + }, + content = { + AnimatedContent( + targetState = form.value, + transitionSpec = { + if (initialState?.id == targetState?.id) { + EnterTransition.None togetherWith ExitTransition.None + } else { + fadeIn() togetherWith fadeOut() + } + } + ) { + when (it) { + null -> Box( + modifier = Modifier.fillMaxSize(), + ) + + else -> { + val fields = it.fields.collectAsState() + + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = paddings, + verticalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + item( + key = "Id", + ) { + LwaTextField( + modifier = Modifier + .animateItem() + .fillMaxWidth(), + field = it.id, + singleLine = true, + ) + } + item( + key = "Name", + ) { + LwaTextField( + modifier = Modifier + .animateItem() + .fillMaxWidth(), + field = it.label, + singleLine = true, + ) + } + item( + key = "Description", + ) { + LwaTextField( + modifier = Modifier + .animateItem() + .fillMaxWidth(), + field = it.description, + singleLine = false, + ) + } + item( + key = "Tags", + ) { + LazyRow( + modifier = Modifier.draggable( + orientation = Orientation.Horizontal, + state = rememberDraggableState { delta -> + scope.launch { + tagsState.scrollBy(-delta) + } + }, + ), + state = tagsState, + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + items( + items = it.tags, + ) { tag -> + GMTagButton( + modifier = Modifier.height(48.dp), + tag = tag, + onTag = { onTag(tag) } + ) + } + } + } + itemsIndexed( + items = fields.value, + key = { _, item -> item.key }, + ) { index, item -> + Row( + modifier = Modifier.animateItem(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + LwaTextField( + modifier = Modifier.weight(1f), + field = item.id, + singleLine = true, + ) + LwaTextField( + modifier = Modifier.weight(1f), + field = item.expression, + singleLine = true, + ) + IconButton( + modifier = Modifier + .size(size = 56.dp) + .background( + color = MaterialTheme.lwa.colorScheme.elevated.base1dp, + shape = MaterialTheme.shapes.small, + ), + onClick = { removeField(index) }, + ) { + Icon( + imageVector = Icons.Default.Close, + tint = MaterialTheme.lwa.colorScheme.base.primary, + contentDescription = null, + ) + } + } + } + item( + key = "Actions", + ) { + Column( + modifier = Modifier + .animateItem() + .fillMaxWidth(), + horizontalAlignment = Alignment.End + ) { + Button( + colors = LwaButtonColors(), + shape = CircleShape, + onClick = addField, + ) { + Text( + modifier = Modifier.padding(end = 4.dp), + text = stringResource(Res.string.game_master__alteration__edit_add_field), + ) + Icon( + imageVector = Icons.Default.Add, + contentDescription = null, + ) + } + Button( + colors = LwaButtonColors(), + shape = CircleShape, + onClick = onSave, + ) { + Text( + modifier = Modifier.padding(end = 4.dp), + text = stringResource(Res.string.game_master__alteration__edit_field_save), + ) + Icon( + painter = painterResource(Res.drawable.ic_save_24dp), + contentDescription = null, + ) + } + } + } + } + } } } } - item( - key = "Actions", - ) { - Column( - modifier = Modifier - .animateItem() - .fillMaxWidth(), - horizontalAlignment = Alignment.End - ) { - Button( - colors = LwaButtonColors(), - shape = CircleShape, - onClick = addField, - ) { - Text( - modifier = Modifier.padding(end = 4.dp), - text = stringResource(Res.string.game_master__alteration__edit_add_field), - ) - Icon( - imageVector = Icons.Default.Add, - contentDescription = null, - ) - } - Button( - colors = LwaButtonColors(), - shape = CircleShape, - onClick = onSave, - ) { - Text( - modifier = Modifier.padding(end = 4.dp), - text = stringResource(Res.string.game_master__alteration__edit_field_save), - ) - Icon( - painter = painterResource(Res.drawable.ic_save_24dp), - contentDescription = null, - ) - } - Button( - colors = LwaButtonColors(), - shape = CircleShape, - onClick = onCancel, - ) { - Text( - modifier = Modifier.padding(end = 4.dp), - text = stringResource(Res.string.game_master__alteration__edit_field_cancel), - ) - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null, - ) - } - } - } - } + ) } @Composable @@ -283,4 +373,9 @@ private fun AlterationEditKeyHandler( else -> false } } -} \ No newline at end of file +} + +private fun NavHostController.navigateBack() = popBackStack( + route = GMAlterationEditDestination.baseRoute(), + inclusive = true, +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt index c3a1d1f..aa27701 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/alteration/edit/GMAlterationEditViewModel.kt @@ -3,9 +3,13 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.pixelized.desktop.lwa.network.LwaNetworkException import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository +import com.pixelized.desktop.lwa.repository.tag.TagRepository import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination +import com.pixelized.desktop.lwa.ui.screen.gamemaster.common.tag.GMTagUio +import com.pixelized.shared.lwa.protocol.rest.APIResponse.ErrorCode import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow @@ -15,6 +19,7 @@ import kotlinx.coroutines.launch class GMAlterationEditViewModel( private val alterationRepository: AlterationRepository, + private val tagRepository: TagRepository, private val factory: GMAlterationEditFactory, savedStateHandle: SavedStateHandle, ) : ViewModel() { @@ -29,28 +34,33 @@ class GMAlterationEditViewModel( init { viewModelScope.launch { _form.value = factory.createForm( - alteration = alterationRepository.alteration(alterationId = argument.id) + originId = argument.id, + alteration = alterationRepository.alteration(alterationId = argument.id), + tags = tagRepository.alterationsTagFlow().value.values, ) } } - suspend fun save() { - val edited = factory.createAlteration(form = form.value) - val actual = alterationRepository.alterationFlow.value[edited?.id] - - if (edited == null) return + suspend fun save(): Boolean { + val edited = factory.createAlteration(form = form.value) ?: return false try { - if (argument.id == null && actual?.id != null) { - error("Id already taken by an another alteration") - } alterationRepository.updateAlteration( alteration = edited, create = argument.id == null, ) + return true + } catch (exception: LwaNetworkException) { + _form.value?.id?.isError?.value = exception.code == ErrorCode.AlterationId + _form.value?.label?.isError?.value = exception.code == ErrorCode.AlterationName + + val message = ErrorSnackUio.from(exception = exception) + _error.emit(message) + return false } catch (exception: Exception) { val message = ErrorSnackUio.from(exception = exception) _error.emit(message) + return false } } @@ -69,4 +79,16 @@ class GMAlterationEditViewModel( } } } + + fun addTag(tag: GMTagUio) { + _form.update { + it?.copy( + tags = it.tags.toMutableList().also { tags -> + val index = tags.indexOf(tag) + if (index > -1) + tags[index] = tag.copy(highlight = tag.highlight.not()) + } + ) + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt index 1437e22..f1d7b05 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTag.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.material.Text @@ -15,6 +16,7 @@ import androidx.compose.ui.graphics.Shape import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.ui.theme.color.component.LwaButtonColors import com.pixelized.desktop.lwa.ui.theme.lwa @@ -61,4 +63,36 @@ fun GMTag( text = tag.label, ) } +} + + +@Composable +fun GMTagButton( + modifier: Modifier = Modifier, + padding: PaddingValues = GmTagDefault.padding, + shape: Shape = MaterialTheme.shapes.small, + tag: GMTagUio, + onTag: (() -> Unit)? = null, +) { + val animatedColor = animateColorAsState( + when (tag.highlight) { + true -> MaterialTheme.lwa.colorScheme.base.secondary + else -> MaterialTheme.lwa.colorScheme.base.onSurface + } + ) + Button( + modifier = modifier, + colors = LwaButtonColors(contentColor = animatedColor.value), + shape = shape, + enabled = onTag != null, + onClick = { onTag?.invoke() }, + ) { + Text( + modifier = Modifier.padding(paddingValues = padding), + color = animatedColor.value, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = tag.label, + ) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTagFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTagFactory.kt index 10f59cd..76e7d4c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTagFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/gamemaster/common/tag/GMTagFactory.kt @@ -5,6 +5,23 @@ import java.text.Collator class GMTagFactory { + fun convertToGMTagItemUio( + tags: Collection, + selectedTagIds: List, + ): List { + return tags + .map { tag -> + GMTagUio( + id = tag.id, + label = tag.label, + highlight = selectedTagIds.contains(tag.id), + ) + } + .sortedWith( + comparator = compareBy(Collator.getInstance()) { it.label } + ) + } + fun convertToGMTagItemUio( tags: Collection, selectedTagId: String?, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt index 9438901..3e06b69 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt @@ -56,7 +56,7 @@ fun LwaTheme( MaterialTheme( colors = lwaColors.base, typography = MaterialTheme.typography, - shapes = MaterialTheme.shapes, + shapes = lwaShapes.base, content = content, ) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/component/LwaButtonColors.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/component/LwaButtonColors.kt index 8a8b2b6..de546e3 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/component/LwaButtonColors.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/component/LwaButtonColors.kt @@ -6,12 +6,14 @@ import androidx.compose.material.ContentAlpha import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.ui.graphics.Color @Composable @Stable -fun LwaButtonColors(): ButtonColors = ButtonDefaults.buttonColors( +fun LwaButtonColors( + contentColor: Color = MaterialTheme.colors.primary, +): ButtonColors = ButtonDefaults.buttonColors( backgroundColor = MaterialTheme.colors.surface, - contentColor = MaterialTheme.colors.primary, + contentColor = contentColor, disabledContentColor = MaterialTheme.colors.surface.copy(alpha = ContentAlpha.disabled), -) - +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/shapes/LwaShapes.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/shapes/LwaShapes.kt index 17dfada..97b11a7 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/shapes/LwaShapes.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/shapes/LwaShapes.kt @@ -1,6 +1,7 @@ package com.pixelized.desktop.lwa.ui.theme.shapes import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember @@ -9,6 +10,7 @@ import androidx.compose.ui.unit.dp @Stable data class LwaShapes( + val base: Shapes, val portrait: Shape, val panel: Shape, val settings: Shape, @@ -18,12 +20,16 @@ data class LwaShapes( @Stable @Composable fun lwaShapes( + base: Shapes = Shapes( + small = RoundedCornerShape(4.dp), + ), portrait: Shape = RoundedCornerShape(8.dp), panel: Shape = RoundedCornerShape(8.dp), settings: Shape = RoundedCornerShape(8.dp), gameMaster: Shape = RoundedCornerShape(8.dp), ): LwaShapes = remember { LwaShapes( + base = base, portrait = portrait, panel = panel, settings = settings, diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt index 0a155cd..ce74f7e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/alteration/AlterationStore.kt @@ -1,8 +1,14 @@ package com.pixelized.server.lwa.model.alteration +import com.pixelized.server.lwa.server.exception.BusinessException +import com.pixelized.server.lwa.server.exception.FileReadException +import com.pixelized.server.lwa.server.exception.FileWriteException +import com.pixelized.server.lwa.server.exception.JsonCodingException +import com.pixelized.server.lwa.server.exception.JsonConversionException import com.pixelized.shared.lwa.model.alteration.Alteration import com.pixelized.shared.lwa.model.alteration.AlterationJson import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory +import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.utils.PathProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -93,8 +99,9 @@ class AlterationStore( val file = alterationFile(id = json.id) // Guard case on update alteration if (create && file.exists()) { - val root = Exception("Alteration already exist, creation is impossible.") - throw BusinessException(root = root) + throw BusinessException( + message = "Alteration already exist, creation is impossible.", + ) } // Transform the json into the model. val alteration = try { @@ -102,6 +109,18 @@ class AlterationStore( } catch (exception: Exception) { throw JsonConversionException(root = exception) } + if (alteration.id.isEmpty()) { + throw BusinessException( + message = "Alteration 'id' is a mandatory field.", + code = APIResponse.ErrorCode.AlterationId, + ) + } + if (alteration.metadata.name.isEmpty()) { + throw BusinessException( + message = "Alteration 'name' is a mandatory field.", + code = APIResponse.ErrorCode.AlterationName, + ) + } // Encode the json into a string. val data = try { this.json.encodeToString(json) @@ -139,13 +158,15 @@ class AlterationStore( val file = alterationFile(id = id) // Guard case on the file existence. if (file.exists().not()) { - val root = Exception("Alteration doesn't not exist, deletion is impossible.") - throw BusinessException(root = root) + throw BusinessException( + message = "Alteration doesn't not exist, deletion is impossible.", + ) } // Guard case on the file deletion if (file.delete().not()) { - val root = Exception("Alteration file have not been deleted for unknown reason.") - throw BusinessException(root = root) + throw BusinessException( + message = "Alteration file have not been deleted for unknown reason.", + ) } // Update the data model with the deleted alteration. alterationFlow.update { alterations -> @@ -162,11 +183,4 @@ class AlterationStore( private fun alterationFile(id: String): File { return File("${pathProvider.alterationsPath()}${id}.json") } - - sealed class AlterationStoreException(root: Exception) : Exception(root) - class JsonConversionException(root: Exception) : AlterationStoreException(root) - class JsonCodingException(root: Exception) : AlterationStoreException(root) - class BusinessException(root: Exception) : AlterationStoreException(root) - class FileWriteException(root: Exception) : AlterationStoreException(root) - class FileReadException(root: Exception) : AlterationStoreException(root) } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt index 105df98..e71b8fa 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignService.kt @@ -1,5 +1,6 @@ package com.pixelized.server.lwa.model.campaign +import com.pixelized.server.lwa.server.exception.BusinessException 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 @@ -43,8 +44,7 @@ class CampaignService( ) { // Check if the character is already in the campaign. if (campaign.instances.contains(characterSheetId)) { - val root = Exception("Character with id:$characterSheetId is already in the campaign.") - throw CampaignStore.BusinessException(root = root) + throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.") } // Update the corresponding instance val characters = campaign.characters.toMutableSet().also { @@ -62,8 +62,7 @@ class CampaignService( ) { // Check if the character is already in the campaign. if (campaign.instances.contains(characterSheetId)) { - val root = Exception("Character with id:$characterSheetId is already in the campaign.") - throw CampaignStore.BusinessException(root = root) + throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.") } // Update the corresponding instance val characters = campaign.npcs.toMutableSet().also { @@ -81,8 +80,7 @@ class CampaignService( ) { // Check if the character is in the campaign. if (campaign.characters.contains(characterSheetId).not()) { - val root = Exception("Character with id:$characterSheetId is not in the party.") - throw CampaignStore.BusinessException(root = root) + throw BusinessException(message = "Character with id:$characterSheetId is not in the party.") } // Update the corresponding instance val characters = campaign.characters.toMutableSet().also { @@ -100,8 +98,7 @@ class CampaignService( ) { // Check if the character is in the campaign. if (campaign.npcs.contains(characterSheetId).not()) { - val root = Exception("Character with id:$characterSheetId is not in the npcs.") - throw CampaignStore.BusinessException(root = root) + throw BusinessException(message = "Character with id:$characterSheetId is not in the npcs.") } // Update the corresponding instance val characters = campaign.npcs.toMutableSet().also { diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt index 1baa868..5a499b4 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/campaign/CampaignStore.kt @@ -1,5 +1,9 @@ package com.pixelized.server.lwa.model.campaign +import com.pixelized.server.lwa.server.exception.FileReadException +import com.pixelized.server.lwa.server.exception.FileWriteException +import com.pixelized.server.lwa.server.exception.JsonCodingException +import com.pixelized.server.lwa.server.exception.JsonConversionException 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 @@ -108,11 +112,4 @@ class CampaignStore( private fun campaignFile(): File { return File("${pathProvider.campaignPath()}campaign.json") } - - sealed class CampaignStoreException(root: Exception) : Exception(root) - class JsonConversionException(root: Exception) : CampaignStoreException(root) - class JsonCodingException(root: Exception) : CampaignStoreException(root) - class BusinessException(root: Exception) : CampaignStoreException(root) - class FileWriteException(root: Exception) : CampaignStoreException(root) - class FileReadException(root: Exception) : CampaignStoreException(root) } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt index d804870..06ff442 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/character/CharacterSheetStore.kt @@ -1,8 +1,14 @@ package com.pixelized.server.lwa.model.character +import com.pixelized.server.lwa.server.exception.BusinessException +import com.pixelized.server.lwa.server.exception.FileReadException +import com.pixelized.server.lwa.server.exception.FileWriteException +import com.pixelized.server.lwa.server.exception.JsonCodingException +import com.pixelized.server.lwa.server.exception.JsonConversionException import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory +import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.utils.PathProvider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -93,8 +99,7 @@ class CharacterSheetStore( 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) + throw BusinessException(message = "Character already exist, creation is impossible.") } // Transform the json into the model. val json = try { @@ -139,13 +144,16 @@ class CharacterSheetStore( 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) + throw BusinessException( + message = "Character file with id:$characterSheetId doesn't not exist.", + code = APIResponse.ErrorCode.CharacterSheetId + ) } // 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) + throw BusinessException( + message = "Character file have not been deleted for unknown reason.", + ) } // Update the data model with the deleted character. characterSheetsFlow.update { characters -> @@ -158,11 +166,4 @@ class CharacterSheetStore( private fun characterSheetFile(id: String): File { return File("${pathProvider.characterStorePath()}${id}.json") } - - 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) } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt b/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt index 5bbff0b..1538552 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/model/tag/TagStore.kt @@ -1,5 +1,8 @@ package com.pixelized.server.lwa.model.tag +import com.pixelized.server.lwa.server.exception.FileReadException +import com.pixelized.server.lwa.server.exception.FileWriteException +import com.pixelized.server.lwa.server.exception.JsonConversionException import com.pixelized.shared.lwa.model.tag.TagJson import com.pixelized.shared.lwa.utils.PathProvider import kotlinx.coroutines.CoroutineScope @@ -100,9 +103,4 @@ class TagStore( private fun characterFile() = File("${pathProvider.tagsPath()}$CHARACTER.json") private fun alterationFile() = File("${pathProvider.tagsPath()}$ALTERATION.json") - - sealed class TagStoreException(root: Exception) : Exception(root) - class JsonConversionException(root: Exception) : TagStoreException(root) - class FileWriteException(root: Exception) : TagStoreException(root) - class FileReadException(root: Exception) : TagStoreException(root) } \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/BusinessException.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/BusinessException.kt new file mode 100644 index 0000000..54d0a7b --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/BusinessException.kt @@ -0,0 +1,10 @@ +package com.pixelized.server.lwa.server.exception + +import com.pixelized.shared.lwa.protocol.rest.APIResponse + +class BusinessException( + message: String, + val code: APIResponse.ErrorCode? = null, +) : ServerException( + root = Exception(message) +) \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileReadException.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileReadException.kt new file mode 100644 index 0000000..f3fe591 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileReadException.kt @@ -0,0 +1,3 @@ +package com.pixelized.server.lwa.server.exception + +class FileReadException(root: Exception) : ServerException(root) \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileWriteException.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileWriteException.kt new file mode 100644 index 0000000..5b66d40 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/FileWriteException.kt @@ -0,0 +1,3 @@ +package com.pixelized.server.lwa.server.exception + +class FileWriteException(root: Exception) : ServerException(root) \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonCodingException.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonCodingException.kt new file mode 100644 index 0000000..35f271e --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonCodingException.kt @@ -0,0 +1,3 @@ +package com.pixelized.server.lwa.server.exception + +class JsonCodingException(root: Exception) : ServerException(root) \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonConversionException.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonConversionException.kt new file mode 100644 index 0000000..0c98955 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/JsonConversionException.kt @@ -0,0 +1,3 @@ +package com.pixelized.server.lwa.server.exception + +class JsonConversionException(root: Exception) : ServerException(root) \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/MissingParameterException.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/MissingParameterException.kt new file mode 100644 index 0000000..9ae5498 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/MissingParameterException.kt @@ -0,0 +1,4 @@ +package com.pixelized.server.lwa.server.exception + +class MissingParameterException(name: String) : + ServerException(root = Exception("Missing '$name' parameter.")) \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/ServerException.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/ServerException.kt new file mode 100644 index 0000000..4b3fc3a --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/exception/ServerException.kt @@ -0,0 +1,3 @@ +package com.pixelized.server.lwa.server.exception + +sealed class ServerException(root: Exception) : Exception(root) \ No newline at end of file diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt index 557ce43..837240e 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/DELETE_Alteration.kt @@ -1,8 +1,8 @@ package com.pixelized.server.lwa.server.rest.alteration 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.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import io.ktor.server.response.respond @@ -27,19 +27,9 @@ fun Engine.deleteAlteration(): suspend RoutingContext.() -> Unit { alterationId = alterationId, ), ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception, ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt index 6a70c41..30f89cd 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alteration.kt @@ -2,6 +2,7 @@ package com.pixelized.server.lwa.server.rest.alteration import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.utils.extentions.alterationId +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -22,11 +23,8 @@ fun Engine.getAlteration(): suspend RoutingContext.() -> Unit { ) ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception, ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt index 07edd33..fe86478 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_AlterationTags.kt @@ -1,6 +1,7 @@ package com.pixelized.server.lwa.server.rest.alteration import com.pixelized.server.lwa.server.Engine +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -14,11 +15,8 @@ fun Engine.getAlterationTags(): suspend RoutingContext.() -> Unit { ), ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception, ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt index 6022814..728f750 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/GET_Alterations.kt @@ -1,6 +1,7 @@ package com.pixelized.server.lwa.server.rest.alteration import com.pixelized.server.lwa.server.Engine +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -14,11 +15,8 @@ fun Engine.getAlterations(): suspend RoutingContext.() -> Unit { ) ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception, ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alteration.kt similarity index 68% rename from server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt rename to server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alteration.kt index da2e233..ee1a824 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alateration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/alteration/PUT_Alteration.kt @@ -1,8 +1,8 @@ package com.pixelized.server.lwa.server.rest.alteration 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.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.model.alteration.AlterationJson import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation @@ -29,19 +29,9 @@ fun Engine.putAlteration(): suspend RoutingContext.() -> Unit { alterationId = form.id, ), ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception, ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt index a2e1a2d..fdec0e7 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Character.kt @@ -1,8 +1,8 @@ package com.pixelized.server.lwa.server.rest.campaign 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.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond @@ -27,19 +27,9 @@ fun Engine.removeCampaignCharacter(): suspend RoutingContext.() -> Unit { characterSheetId = characterSheetId, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt index 336e562..7d4f911 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/DELETE_Campaign_Npc.kt @@ -1,8 +1,8 @@ package com.pixelized.server.lwa.server.rest.campaign 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.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond @@ -27,19 +27,9 @@ fun Engine.removeCampaignNpc(): suspend RoutingContext.() -> Unit { characterSheetId = characterSheetId, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt index f345e13..72a4ad7 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/GET_Campaign.kt @@ -1,6 +1,7 @@ package com.pixelized.server.lwa.server.rest.campaign import com.pixelized.server.lwa.server.Engine +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -14,11 +15,8 @@ fun Engine.getCampaign(): suspend RoutingContext.() -> Unit { ), ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt index 80f012a..a301e2a 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Character.kt @@ -1,8 +1,8 @@ package com.pixelized.server.lwa.server.rest.campaign 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.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond @@ -27,19 +27,9 @@ fun Engine.putCampaignCharacter(): suspend RoutingContext.() -> Unit { characterSheetId = characterSheetId, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt index 07dfd5d..c0dbcad 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Npc.kt @@ -1,8 +1,8 @@ package com.pixelized.server.lwa.server.rest.campaign 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.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import io.ktor.server.response.respond @@ -27,19 +27,9 @@ fun Engine.putCampaignNpc(): suspend RoutingContext.() -> Unit { characterSheetId = characterSheetId, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt index e2e37bb..2da6626 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/campaign/PUT_Campaign_Scene_Name.kt @@ -1,7 +1,7 @@ package com.pixelized.server.lwa.server.rest.campaign import com.pixelized.server.lwa.server.Engine -import com.pixelized.server.lwa.utils.extentions.MissingParameterException +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.model.campaign.CampaignJsonV2 import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent @@ -30,19 +30,9 @@ fun Engine.putCampaignScene(): suspend RoutingContext.() -> Unit { name = scene.name, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt index 42629b4..89dc7c7 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/DELETE_Character.kt @@ -1,8 +1,8 @@ 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.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import io.ktor.server.response.respond @@ -27,19 +27,9 @@ fun Engine.deleteCharacter(): suspend RoutingContext.() -> Unit { characterSheetId = characterSheetId, ), ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt index 200bba4..2e70f75 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Character.kt @@ -1,8 +1,8 @@ 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.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -22,19 +22,9 @@ fun Engine.getCharacter(): suspend RoutingContext.() -> Unit { data = characterSheet, ), ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt index 52b5c3f..8f1b876 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_CharacterTags.kt @@ -1,6 +1,7 @@ package com.pixelized.server.lwa.server.rest.character import com.pixelized.server.lwa.server.Engine +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -14,11 +15,8 @@ fun Engine.getCharacterTags(): suspend RoutingContext.() -> Unit { ), ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt index 354b4ee..4f62a70 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/GET_Characters.kt @@ -1,6 +1,7 @@ package com.pixelized.server.lwa.server.rest.character import com.pixelized.server.lwa.server.Engine +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import io.ktor.server.response.respond import io.ktor.server.routing.RoutingContext @@ -14,11 +15,8 @@ fun Engine.getCharacters(): suspend RoutingContext.() -> Unit { ), ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt index dbc0f21..58225a3 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character.kt @@ -1,8 +1,8 @@ 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.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation @@ -28,19 +28,9 @@ fun Engine.putCharacter(): suspend RoutingContext.() -> Unit { characterSheetId = form.id, ), ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt index a6fd05b..bb77670 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Alteration.kt @@ -1,10 +1,10 @@ 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.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond @@ -35,19 +35,9 @@ fun Engine.putCharacterAlteration(): suspend RoutingContext.() -> Unit { active = active, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt index 427c4be..3687b79 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Damage.kt @@ -1,8 +1,9 @@ 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.server.exception.MissingParameterException import com.pixelized.server.lwa.utils.extentions.characterSheetId +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond @@ -14,10 +15,7 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit { // get the query parameter val characterSheetId = call.queryParameters.characterSheetId val damage = call.queryParameters["damage"]?.toIntOrNull() - ?: throw MissingParameterException( - name = "damage", - errorCode = APIResponse.MISSING_DAMAGE - ) + ?: throw MissingParameterException(name = "damage") // fetch the character sheet val characterSheet = characterService.character(characterSheetId) ?: error("CharacterSheet with id:$characterSheetId not found.") @@ -38,19 +36,9 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit { damage = damage, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt index b695e26..666eb99 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Diminished.kt @@ -1,8 +1,9 @@ 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.server.exception.MissingParameterException import com.pixelized.server.lwa.utils.extentions.characterSheetId +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond @@ -14,10 +15,7 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit { // get the query parameter val characterSheetId = call.queryParameters.characterSheetId val diminished = call.queryParameters["diminished"]?.toIntOrNull() - ?: throw MissingParameterException( - name = "diminished", - errorCode = APIResponse.MISSING_DIMINISHED - ) + ?: throw MissingParameterException(name = "diminished") // Update the character damage characterService.updateDiminished( characterSheetId = characterSheetId, @@ -34,19 +32,9 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit { diminished = diminished, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt index 6f565cc..1709485 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/server/rest/character/PUT_Character_Fatigue.kt @@ -1,8 +1,9 @@ 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.server.exception.MissingParameterException import com.pixelized.server.lwa.utils.extentions.characterSheetId +import com.pixelized.server.lwa.utils.extentions.exception import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import io.ktor.server.response.respond @@ -14,10 +15,7 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit { // get the query parameter val characterSheetId = call.queryParameters.characterSheetId val fatigue = call.queryParameters["fatigue"]?.toIntOrNull() - ?: throw MissingParameterException( - name = "fatigue", - errorCode = APIResponse.MISSING_FATIGUE - ) + ?: throw MissingParameterException(name = "fatigue") // fetch the character sheet val characterSheet = characterService.character(characterSheetId) ?: error("CharacterSheet with id:$characterSheetId not found.") @@ -38,19 +36,9 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit { fatigue = fatigue, ) ) - } catch (exception: MissingParameterException) { - call.respond( - message = APIResponse.error( - status = exception.errorCode, - message = exception.message ?: "?", - ) - ) } catch (exception: Exception) { - call.respond( - message = APIResponse.error( - status = APIResponse.GENERIC, - message = exception.message ?: "?", - ) + call.exception( + exception = exception ) } } diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt index b719213..1559b91 100644 --- a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt +++ b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/ParametersExt.kt @@ -1,41 +1,24 @@ package com.pixelized.server.lwa.utils.extentions -import com.pixelized.shared.lwa.protocol.rest.APIResponse +import com.pixelized.server.lwa.server.exception.MissingParameterException import io.ktor.http.Parameters val Parameters.characterSheetId get() = "characterSheetId".let { param -> - this[param] ?: throw MissingParameterException( - name = param, - errorCode = APIResponse.MISSING_CHARACTER_SHEET_ID, - ) + this[param] ?: throw MissingParameterException(name = param) } val Parameters.alterationId get() = "alterationId".let { param -> - this[param] ?: throw MissingParameterException( - name = param, - errorCode = APIResponse.MISSING_ALTERATION_ID, - ) + this[param] ?: throw MissingParameterException(name = param) } val Parameters.create get() = "create".let { param -> - this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException( - name = param, - errorCode = APIResponse.MISSING_CREATE - ) + this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param) } val Parameters.active get() = "active".let { param -> - this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException( - name = param, - errorCode = APIResponse.MISSING_ACTIVE - ) + this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param) } - -class MissingParameterException( - name: String, - val errorCode: Int, -) : Exception("Missing $name parameter.") diff --git a/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/RoutingCallExt+exception.kt b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/RoutingCallExt+exception.kt new file mode 100644 index 0000000..45688b6 --- /dev/null +++ b/server/src/main/kotlin/com/pixelized/server/lwa/utils/extentions/RoutingCallExt+exception.kt @@ -0,0 +1,40 @@ +package com.pixelized.server.lwa.utils.extentions + +import com.pixelized.server.lwa.server.exception.BusinessException +import com.pixelized.server.lwa.server.exception.MissingParameterException +import com.pixelized.shared.lwa.protocol.rest.APIResponse +import io.ktor.server.response.respond +import io.ktor.server.routing.RoutingCall + + +suspend inline fun RoutingCall.exception(exception: T) { + when (exception) { + + is MissingParameterException -> { + respond( + message = APIResponse.error( + status = APIResponse.BAD_REQUEST, + message = exception.message ?: "?", + code = APIResponse.ErrorCode.AlterationName, + ) + ) + } + + is BusinessException -> { + respond( + message = APIResponse.error( + status = APIResponse.INTERNAL_ERROR, + message = exception.message ?: "?", + code = exception.code, + ) + ) + } + + else -> respond( + message = APIResponse.error( + status = APIResponse.INTERNAL_ERROR, + message = exception.message ?: "?", + ) + ) + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt index f5893b4..a6d2d9e 100644 --- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt +++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/rest/APIResponse.kt @@ -7,46 +7,52 @@ data class APIResponse( val success: Boolean, val status: Int, val message: String?, + val code: ErrorCode?, val data: T?, ) { + @Serializable + enum class ErrorCode { + AlterationId, + AlterationName, + CharacterSheetId, + } + companion object { - const val SUCCESS = 100 - - const val GENERIC = 600 - - const val MISSING_PARAMETER = 700 - 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 - fun error( status: Int, + code: ErrorCode? = null, message: String?, ) = APIResponse( success = false, status = status, + code = code, message = message, data = null, ) - fun success() = APIResponse( + fun success( + status: Int = OK, + ) = APIResponse( success = true, - status = SUCCESS, + status = status, + code = null, message = null, data = null, ) inline fun success( + status: Int = OK, data: T? = null, ) = APIResponse( success = true, - status = SUCCESS, + status = status, + code = null, message = null, data = data, ) + + const val OK = 200 + const val BAD_REQUEST = 400 + const val INTERNAL_ERROR = 500 } } \ No newline at end of file