Server : Alteration error management
This commit is contained in:
		
							parent
							
								
									81c6450dbe
								
							
						
					
					
						commit
						acb445c480
					
				
					 10 changed files with 220 additions and 80 deletions
				
			
		| 
						 | 
				
			
			@ -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,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,15 +28,15 @@ class AlterationStore(
 | 
			
		|||
        val scope = CoroutineScope(Dispatchers.IO + Job())
 | 
			
		||||
        // load the initial data
 | 
			
		||||
        scope.launch {
 | 
			
		||||
            updateAlterations()
 | 
			
		||||
            updateAlterationFlow()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun alterationsFlow(): StateFlow<List<Alteration>> = 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<Alteration> {
 | 
			
		||||
    private fun load(
 | 
			
		||||
        directory: File = this.directory,
 | 
			
		||||
    ): List<Alteration> {
 | 
			
		||||
        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<AlterationJson>(json)
 | 
			
		||||
                } catch (exception: Exception) {
 | 
			
		||||
                    throw JsonCodingException(root = exception)
 | 
			
		||||
                }
 | 
			
		||||
                // parse the json string.
 | 
			
		||||
                try {
 | 
			
		||||
                    val data = this.json.decodeFromString<AlterationJson>(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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 ?: "?",
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 ?: "?",
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 ?: "?",
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 ?: "?",
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<AlterationJson>()
 | 
			
		||||
            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 ?: "?",
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.")
 | 
			
		||||
    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.")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue