Add specific alteration error management.

This commit is contained in:
Thomas Andres Gomez 2025-04-01 22:18:55 +02:00
parent 6213d5ac15
commit f94a530621
48 changed files with 606 additions and 511 deletions

View file

@ -242,6 +242,7 @@
<string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string> <string name="game_master__character_action__add_to_npc">Ajouter aux Npcs</string>
<string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string> <string name="game_master__character_action__remove_from_npc">Retirer des Npcs</string>
<string name="game_master__create_character_sheet">Créer un personnage</string> <string name="game_master__create_character_sheet">Créer un personnage</string>
<string name="game_master__alteration__title">Édition d'Altération</string>
<string name="game_master__alteration__filter">Filtrer par nom :</string> <string name="game_master__alteration__filter">Filtrer par nom :</string>
<string name="game_master__alteration__create">Créer une altération</string> <string name="game_master__alteration__create">Créer une altération</string>
<string name="game_master__alteration__delete">Supprimer l'altération</string> <string name="game_master__alteration__delete">Supprimer l'altération</string>

View file

@ -4,10 +4,12 @@ import com.pixelized.shared.lwa.protocol.rest.APIResponse
class LwaNetworkException( class LwaNetworkException(
val status: Int, val status: Int,
val code: APIResponse.ErrorCode?,
message: String, message: String,
) : Exception(message) { ) : Exception(message) {
constructor(error: APIResponse<*>) : this( constructor(error: APIResponse<*>) : this(
status = error.status, status = error.status,
code = error.code,
message = error.message ?: "An unknown error occurred" message = error.message ?: "An unknown error occurred"
) )
} }

View file

@ -7,6 +7,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.pixelized.desktop.lwa.LocalErrorSnackHost import com.pixelized.desktop.lwa.LocalErrorSnackHost
import kotlinx.coroutines.flow.SharedFlow 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 @Stable
class ErrorSnackUio( class ErrorSnackUio(
@ -15,10 +19,10 @@ class ErrorSnackUio(
val duration: SnackbarDuration, val duration: SnackbarDuration,
) { ) {
companion object { companion object {
fun from(exception: Exception) = ErrorSnackUio( suspend fun from(exception: Exception) = ErrorSnackUio(
message = exception.localizedMessage, message = exception.localizedMessage,
action = "Ok", action = getString(Res.string.error__default__action),
duration = SnackbarDuration.Indefinite duration = SnackbarDuration.Long,
) )
} }
} }
@ -29,7 +33,7 @@ fun ErrorSnackHandler(
error: SharedFlow<ErrorSnackUio>, error: SharedFlow<ErrorSnackUio>,
) { ) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
error.collect { error.collectLatest {
snack.showSnackbar( snack.showSnackbar(
message = it.message, message = it.message,
actionLabel = it.action, actionLabel = it.action,

View file

@ -1,6 +1,7 @@
package com.pixelized.desktop.lwa.ui.composable.textfield package com.pixelized.desktop.lwa.ui.composable.textfield
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextField import androidx.compose.material.TextField
import androidx.compose.material.TextFieldColors import androidx.compose.material.TextFieldColors
@ -9,20 +10,21 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.style.TextOverflow 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.LwaTextFieldColors import com.pixelized.desktop.lwa.ui.theme.color.component.LwaTextFieldColors
import com.pixelized.desktop.lwa.utils.rememberKeyboardActions import com.pixelized.desktop.lwa.utils.rememberKeyboardActions
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.MutableStateFlow
@Stable @Stable
data class LwaTextFieldUio( data class LwaTextFieldUio(
val enable: Boolean = true, val enable: Boolean = true,
val isError: StateFlow<Boolean>, val isError: MutableStateFlow<Boolean>,
val labelFlow: StateFlow<String?>?, val labelFlow: MutableStateFlow<String?>?,
val valueFlow: StateFlow<String>, val valueFlow: MutableStateFlow<String>,
val placeHolderFlow: StateFlow<String?>?, val placeHolderFlow: MutableStateFlow<String?>?,
val onValueChange: (String) -> Unit, val onValueChange: (String) -> Unit,
) )
@ -30,6 +32,7 @@ data class LwaTextFieldUio(
fun LwaTextField( fun LwaTextField(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
colors: TextFieldColors = LwaTextFieldColors(), colors: TextFieldColors = LwaTextFieldColors(),
shape: Shape = MaterialTheme.shapes.small,
leadingIcon: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null,
singleLine: Boolean = true, singleLine: Boolean = true,
@ -51,6 +54,7 @@ fun LwaTextField(
TextField( TextField(
modifier = localModifier.then(other = modifier), modifier = localModifier.then(other = modifier),
colors = colors, colors = colors,
shape = shape,
keyboardActions = rememberKeyboardActions { keyboardActions = rememberKeyboardActions {
focus.moveFocus(FocusDirection.Next) focus.moveFocus(FocusDirection.Next)
}, },

View file

@ -7,7 +7,7 @@ import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navArgument 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 import com.pixelized.desktop.lwa.utils.extention.ARG
@Stable @Stable
@ -44,7 +44,7 @@ fun NavGraphBuilder.composableGameMasterAlterationEditPage() {
route = GMAlterationEditDestination.baseRoute(), route = GMAlterationEditDestination.baseRoute(),
arguments = GMAlterationEditDestination.arguments(), arguments = GMAlterationEditDestination.arguments(),
) { ) {
GMAlterationEditPage() GMAlterationEditScreen()
} }
} }

View file

@ -1,34 +1,39 @@
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit 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.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.alteration.Alteration
import com.pixelized.shared.lwa.model.tag.Tag
import com.pixelized.shared.lwa.parser.expression.ExpressionParser import com.pixelized.shared.lwa.parser.expression.ExpressionParser
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import lwacharactersheet.composeapp.generated.resources.Res 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_expression
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_field_id 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_id
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__edit_label 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 org.jetbrains.compose.resources.getString
import java.util.UUID import java.util.UUID
class GMAlterationEditFactory( class GMAlterationEditFactory(
private val expressionParser: ExpressionParser, private val expressionParser: ExpressionParser,
private val tagFactory: GMTagFactory,
) { ) {
suspend fun createForm( suspend fun createForm(
originId: String?,
alteration: Alteration?, alteration: Alteration?,
tags: Collection<Tag>,
): GMAlterationEditPageUio { ): GMAlterationEditPageUio {
val id = MutableStateFlow(alteration?.id ?: "") val id = MutableStateFlow(alteration?.id ?: "")
val label = MutableStateFlow(alteration?.metadata?.name ?: "") val label = MutableStateFlow(alteration?.metadata?.name ?: "")
val description = MutableStateFlow(alteration?.metadata?.description ?: "") val description = MutableStateFlow(alteration?.metadata?.description ?: "")
val tags = MutableStateFlow(alteration?.tags?.joinToString(", ") { it } ?: "") val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(
val fields = MutableStateFlow(alteration?.fields?.map { createField(it) } ?: listOf(createField(null))) createField(null)
))
return GMAlterationEditPageUio( return GMAlterationEditPageUio(
id = LwaTextFieldUio( id = LwaTextFieldUio(
enable = true, enable = originId == null,
isError = MutableStateFlow(false), isError = MutableStateFlow(false),
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_id)), labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_id)),
valueFlow = id, valueFlow = id,
@ -51,13 +56,9 @@ class GMAlterationEditFactory(
placeHolderFlow = null, placeHolderFlow = null,
onValueChange = { description.value = it }, onValueChange = { description.value = it },
), ),
tags = LwaTextFieldUio( tags = tagFactory.convertToGMTagItemUio(
enable = true, tags = tags,
isError = MutableStateFlow(false), selectedTagIds = alteration?.tags ?: emptyList(),
labelFlow = MutableStateFlow(getString(Res.string.game_master__alteration__edit_tags)),
valueFlow = tags,
placeHolderFlow = null,
onValueChange = { tags.value = it },
), ),
fields = fields, fields = fields,
) )
@ -101,7 +102,7 @@ class GMAlterationEditFactory(
name = form.label.valueFlow.value, name = form.label.valueFlow.value,
description = form.description.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 -> fields = form.fields.value.mapNotNull { field ->
expressionParser.parse(input = field.expression.valueFlow.value)?.let { expressionParser.parse(input = field.expression.valueFlow.value)?.let {
Alteration.Field( Alteration.Field(

View file

@ -1,9 +1,16 @@
package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.Row
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn 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.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text 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.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment 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.KeyEventType
import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type 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.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.error.ErrorSnackHandler
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler 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.LwaTextField
import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio import com.pixelized.desktop.lwa.ui.composable.textfield.LwaTextFieldUio
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController 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.color.component.LwaButtonColors
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res 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_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__edit_field_save
import lwacharactersheet.composeapp.generated.resources.game_master__alteration__title
import lwacharactersheet.composeapp.generated.resources.ic_save_24dp import lwacharactersheet.composeapp.generated.resources.ic_save_24dp
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@ -59,7 +82,7 @@ data class GMAlterationEditPageUio(
val id: LwaTextFieldUio, val id: LwaTextFieldUio,
val label: LwaTextFieldUio, val label: LwaTextFieldUio,
val description: LwaTextFieldUio, val description: LwaTextFieldUio,
val tags: LwaTextFieldUio, val tags: List<GMTagUio>,
val fields: MutableStateFlow<List<SkillUio>>, val fields: MutableStateFlow<List<SkillUio>>,
) { ) {
@Stable @Stable
@ -76,23 +99,20 @@ object GMAlterationEditPageDefault {
} }
@Composable @Composable
fun GMAlterationEditPage( fun GMAlterationEditScreen(
viewModel: GMAlterationEditViewModel = koinViewModel(), viewModel: GMAlterationEditViewModel = koinViewModel(),
) { ) {
val screen = LocalScreenController.current val screen = LocalScreenController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val form = viewModel.form.collectAsState() val form = viewModel.form.collectAsState()
AnimatedContent( GMAlterationEditContent(
targetState = form.value,
transitionSpec = { fadeIn() togetherWith fadeOut() }
) {
when (it) {
null -> Box(modifier = Modifier.fillMaxSize())
else -> GMAlterationEditContent(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
form = it, form = form,
paddings = GMAlterationEditPageDefault.paddings, paddings = GMAlterationEditPageDefault.paddings,
onBack = {
screen.navigateBack()
},
addField = { addField = {
scope.launch { scope.launch {
viewModel.addField() viewModel.addField()
@ -101,15 +121,15 @@ fun GMAlterationEditPage(
removeField = viewModel::removeField, removeField = viewModel::removeField,
onSave = { onSave = {
scope.launch { scope.launch {
viewModel.save() if (viewModel.save()) {
screen.navigateBack()
}
} }
}, },
onCancel = { onTag = { tag ->
screen.popBackStack() viewModel.addTag(tag = tag)
}, },
) )
}
}
ErrorSnackHandler( ErrorSnackHandler(
error = viewModel.error, error = viewModel.error,
@ -117,7 +137,7 @@ fun GMAlterationEditPage(
AlterationEditKeyHandler( AlterationEditKeyHandler(
onDismissRequest = { onDismissRequest = {
screen.popBackStack() screen.navigateBack()
} }
) )
} }
@ -125,17 +145,75 @@ fun GMAlterationEditPage(
@Composable @Composable
private fun GMAlterationEditContent( private fun GMAlterationEditContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
form: GMAlterationEditPageUio, scope: CoroutineScope = rememberCoroutineScope(),
tagsState: LazyListState = rememberLazyListState(),
form: State<GMAlterationEditPageUio?>,
paddings: PaddingValues, paddings: PaddingValues,
onBack: () -> Unit,
addField: () -> Unit, addField: () -> Unit,
removeField: (index: Int) -> Unit, removeField: (index: Int) -> Unit,
onSave: () -> Unit, onSave: () -> Unit,
onCancel: () -> Unit, onTag: (GMTagUio) -> Unit,
) { ) {
val fields = form.fields.collectAsState() Scaffold(
modifier = modifier,
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( LazyColumn(
modifier = modifier, modifier = Modifier.fillMaxSize(),
contentPadding = paddings, contentPadding = paddings,
verticalArrangement = Arrangement.spacedBy(space = 8.dp), verticalArrangement = Arrangement.spacedBy(space = 8.dp),
) { ) {
@ -146,7 +224,7 @@ private fun GMAlterationEditContent(
modifier = Modifier modifier = Modifier
.animateItem() .animateItem()
.fillMaxWidth(), .fillMaxWidth(),
field = form.id, field = it.id,
singleLine = true, singleLine = true,
) )
} }
@ -157,7 +235,7 @@ private fun GMAlterationEditContent(
modifier = Modifier modifier = Modifier
.animateItem() .animateItem()
.fillMaxWidth(), .fillMaxWidth(),
field = form.label, field = it.label,
singleLine = true, singleLine = true,
) )
} }
@ -168,21 +246,36 @@ private fun GMAlterationEditContent(
modifier = Modifier modifier = Modifier
.animateItem() .animateItem()
.fillMaxWidth(), .fillMaxWidth(),
field = form.description, field = it.description,
singleLine = false, singleLine = false,
) )
} }
item( item(
key = "Tags", key = "Tags",
) { ) {
LwaTextField( LazyRow(
modifier = Modifier modifier = Modifier.draggable(
.animateItem() orientation = Orientation.Horizontal,
.fillMaxWidth(), state = rememberDraggableState { delta ->
field = form.tags, scope.launch {
singleLine = true, 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( itemsIndexed(
items = fields.value, items = fields.value,
key = { _, item -> item.key }, key = { _, item -> item.key },
@ -203,12 +296,18 @@ private fun GMAlterationEditContent(
singleLine = true, singleLine = true,
) )
IconButton( IconButton(
modifier = Modifier
.size(size = 56.dp)
.background(
color = MaterialTheme.lwa.colorScheme.elevated.base1dp,
shape = MaterialTheme.shapes.small,
),
onClick = { removeField(index) }, onClick = { removeField(index) },
) { ) {
Icon( Icon(
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
contentDescription = null,
tint = MaterialTheme.lwa.colorScheme.base.primary, tint = MaterialTheme.lwa.colorScheme.base.primary,
contentDescription = null,
) )
} }
} }
@ -250,23 +349,14 @@ private fun GMAlterationEditContent(
contentDescription = null, 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 @Composable
@ -284,3 +374,8 @@ private fun AlterationEditKeyHandler(
} }
} }
} }
private fun NavHostController.navigateBack() = popBackStack(
route = GMAlterationEditDestination.baseRoute(),
inclusive = true,
)

View file

@ -3,9 +3,13 @@ package com.pixelized.desktop.lwa.ui.screen.gamemaster.alteration.edit
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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.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.composable.error.ErrorSnackUio
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.gamemaster.GMAlterationEditDestination 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.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
@ -15,6 +19,7 @@ import kotlinx.coroutines.launch
class GMAlterationEditViewModel( class GMAlterationEditViewModel(
private val alterationRepository: AlterationRepository, private val alterationRepository: AlterationRepository,
private val tagRepository: TagRepository,
private val factory: GMAlterationEditFactory, private val factory: GMAlterationEditFactory,
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
) : ViewModel() { ) : ViewModel() {
@ -29,28 +34,33 @@ class GMAlterationEditViewModel(
init { init {
viewModelScope.launch { viewModelScope.launch {
_form.value = factory.createForm( _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() { suspend fun save(): Boolean {
val edited = factory.createAlteration(form = form.value) val edited = factory.createAlteration(form = form.value) ?: return false
val actual = alterationRepository.alterationFlow.value[edited?.id]
if (edited == null) return
try { try {
if (argument.id == null && actual?.id != null) {
error("Id already taken by an another alteration")
}
alterationRepository.updateAlteration( alterationRepository.updateAlteration(
alteration = edited, alteration = edited,
create = argument.id == null, 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) { } catch (exception: Exception) {
val message = ErrorSnackUio.from(exception = exception) val message = ErrorSnackUio.from(exception = exception)
_error.emit(message) _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())
}
)
}
}
} }

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface import androidx.compose.material.Surface
import androidx.compose.material.Text 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.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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 import com.pixelized.desktop.lwa.ui.theme.lwa
@ -62,3 +64,35 @@ fun GMTag(
) )
} }
} }
@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,
)
}
}

View file

@ -5,6 +5,23 @@ import java.text.Collator
class GMTagFactory { class GMTagFactory {
fun convertToGMTagItemUio(
tags: Collection<Tag>,
selectedTagIds: List<String>,
): List<GMTagUio> {
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( fun convertToGMTagItemUio(
tags: Collection<Tag>, tags: Collection<Tag>,
selectedTagId: String?, selectedTagId: String?,

View file

@ -56,7 +56,7 @@ fun LwaTheme(
MaterialTheme( MaterialTheme(
colors = lwaColors.base, colors = lwaColors.base,
typography = MaterialTheme.typography, typography = MaterialTheme.typography,
shapes = MaterialTheme.shapes, shapes = lwaShapes.base,
content = content, content = content,
) )
} }

View file

@ -6,12 +6,14 @@ import androidx.compose.material.ContentAlpha
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
@Composable @Composable
@Stable @Stable
fun LwaButtonColors(): ButtonColors = ButtonDefaults.buttonColors( fun LwaButtonColors(
contentColor: Color = MaterialTheme.colors.primary,
): ButtonColors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.surface, backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.primary, contentColor = contentColor,
disabledContentColor = MaterialTheme.colors.surface.copy(alpha = ContentAlpha.disabled), disabledContentColor = MaterialTheme.colors.surface.copy(alpha = ContentAlpha.disabled),
) )

View file

@ -1,6 +1,7 @@
package com.pixelized.desktop.lwa.ui.theme.shapes package com.pixelized.desktop.lwa.ui.theme.shapes
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -9,6 +10,7 @@ import androidx.compose.ui.unit.dp
@Stable @Stable
data class LwaShapes( data class LwaShapes(
val base: Shapes,
val portrait: Shape, val portrait: Shape,
val panel: Shape, val panel: Shape,
val settings: Shape, val settings: Shape,
@ -18,12 +20,16 @@ data class LwaShapes(
@Stable @Stable
@Composable @Composable
fun lwaShapes( fun lwaShapes(
base: Shapes = Shapes(
small = RoundedCornerShape(4.dp),
),
portrait: Shape = RoundedCornerShape(8.dp), portrait: Shape = RoundedCornerShape(8.dp),
panel: Shape = RoundedCornerShape(8.dp), panel: Shape = RoundedCornerShape(8.dp),
settings: Shape = RoundedCornerShape(8.dp), settings: Shape = RoundedCornerShape(8.dp),
gameMaster: Shape = RoundedCornerShape(8.dp), gameMaster: Shape = RoundedCornerShape(8.dp),
): LwaShapes = remember { ): LwaShapes = remember {
LwaShapes( LwaShapes(
base = base,
portrait = portrait, portrait = portrait,
panel = panel, panel = panel,
settings = settings, settings = settings,

View file

@ -1,8 +1,14 @@
package com.pixelized.server.lwa.model.alteration 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.Alteration
import com.pixelized.shared.lwa.model.alteration.AlterationJson import com.pixelized.shared.lwa.model.alteration.AlterationJson
import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory import com.pixelized.shared.lwa.model.alteration.AlterationJsonFactory
import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.utils.PathProvider import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -93,8 +99,9 @@ class AlterationStore(
val file = alterationFile(id = json.id) val file = alterationFile(id = json.id)
// Guard case on update alteration // Guard case on update alteration
if (create && file.exists()) { if (create && file.exists()) {
val root = Exception("Alteration already exist, creation is impossible.") throw BusinessException(
throw BusinessException(root = root) message = "Alteration already exist, creation is impossible.",
)
} }
// Transform the json into the model. // Transform the json into the model.
val alteration = try { val alteration = try {
@ -102,6 +109,18 @@ class AlterationStore(
} catch (exception: Exception) { } catch (exception: Exception) {
throw JsonConversionException(root = 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. // Encode the json into a string.
val data = try { val data = try {
this.json.encodeToString(json) this.json.encodeToString(json)
@ -139,13 +158,15 @@ class AlterationStore(
val file = alterationFile(id = id) val file = alterationFile(id = id)
// Guard case on the file existence. // Guard case on the file existence.
if (file.exists().not()) { if (file.exists().not()) {
val root = Exception("Alteration doesn't not exist, deletion is impossible.") throw BusinessException(
throw BusinessException(root = root) message = "Alteration doesn't not exist, deletion is impossible.",
)
} }
// Guard case on the file deletion // Guard case on the file deletion
if (file.delete().not()) { if (file.delete().not()) {
val root = Exception("Alteration file have not been deleted for unknown reason.") throw BusinessException(
throw BusinessException(root = root) message = "Alteration file have not been deleted for unknown reason.",
)
} }
// Update the data model with the deleted alteration. // Update the data model with the deleted alteration.
alterationFlow.update { alterations -> alterationFlow.update { alterations ->
@ -162,11 +183,4 @@ class AlterationStore(
private fun alterationFile(id: String): File { private fun alterationFile(id: String): File {
return File("${pathProvider.alterationsPath()}${id}.json") 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)
} }

View file

@ -1,5 +1,6 @@
package com.pixelized.server.lwa.model.campaign 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.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJson import com.pixelized.shared.lwa.model.campaign.CampaignJson
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
@ -43,8 +44,7 @@ class CampaignService(
) { ) {
// Check if the character is already in the campaign. // Check if the character is already in the campaign.
if (campaign.instances.contains(characterSheetId)) { if (campaign.instances.contains(characterSheetId)) {
val root = Exception("Character with id:$characterSheetId is already in the campaign.") throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.")
throw CampaignStore.BusinessException(root = root)
} }
// Update the corresponding instance // Update the corresponding instance
val characters = campaign.characters.toMutableSet().also { val characters = campaign.characters.toMutableSet().also {
@ -62,8 +62,7 @@ class CampaignService(
) { ) {
// Check if the character is already in the campaign. // Check if the character is already in the campaign.
if (campaign.instances.contains(characterSheetId)) { if (campaign.instances.contains(characterSheetId)) {
val root = Exception("Character with id:$characterSheetId is already in the campaign.") throw BusinessException(message = "Character with id:$characterSheetId is already in the campaign.")
throw CampaignStore.BusinessException(root = root)
} }
// Update the corresponding instance // Update the corresponding instance
val characters = campaign.npcs.toMutableSet().also { val characters = campaign.npcs.toMutableSet().also {
@ -81,8 +80,7 @@ class CampaignService(
) { ) {
// Check if the character is in the campaign. // Check if the character is in the campaign.
if (campaign.characters.contains(characterSheetId).not()) { if (campaign.characters.contains(characterSheetId).not()) {
val root = Exception("Character with id:$characterSheetId is not in the party.") throw BusinessException(message = "Character with id:$characterSheetId is not in the party.")
throw CampaignStore.BusinessException(root = root)
} }
// Update the corresponding instance // Update the corresponding instance
val characters = campaign.characters.toMutableSet().also { val characters = campaign.characters.toMutableSet().also {
@ -100,8 +98,7 @@ class CampaignService(
) { ) {
// Check if the character is in the campaign. // Check if the character is in the campaign.
if (campaign.npcs.contains(characterSheetId).not()) { if (campaign.npcs.contains(characterSheetId).not()) {
val root = Exception("Character with id:$characterSheetId is not in the npcs.") throw BusinessException(message = "Character with id:$characterSheetId is not in the npcs.")
throw CampaignStore.BusinessException(root = root)
} }
// Update the corresponding instance // Update the corresponding instance
val characters = campaign.npcs.toMutableSet().also { val characters = campaign.npcs.toMutableSet().also {

View file

@ -1,5 +1,9 @@
package com.pixelized.server.lwa.model.campaign 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.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJson import com.pixelized.shared.lwa.model.campaign.CampaignJson
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
@ -108,11 +112,4 @@ class CampaignStore(
private fun campaignFile(): File { private fun campaignFile(): File {
return File("${pathProvider.campaignPath()}campaign.json") 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)
} }

View file

@ -1,8 +1,14 @@
package com.pixelized.server.lwa.model.character 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.CharacterSheet
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
import com.pixelized.shared.lwa.model.characterSheet.factory.CharacterSheetJsonFactory 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 com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -93,8 +99,7 @@ class CharacterSheetStore(
val file = characterSheetFile(id = sheet.id) val file = characterSheetFile(id = sheet.id)
// Guard case on update alteration // Guard case on update alteration
if (create && file.exists()) { if (create && file.exists()) {
val root = Exception("Character already exist, creation is impossible.") throw BusinessException(message = "Character already exist, creation is impossible.")
throw BusinessException(root = root)
} }
// Transform the json into the model. // Transform the json into the model.
val json = try { val json = try {
@ -139,13 +144,16 @@ class CharacterSheetStore(
val file = characterSheetFile(id = characterSheetId) val file = characterSheetFile(id = characterSheetId)
// Guard case on the file existence. // Guard case on the file existence.
if (file.exists().not()) { if (file.exists().not()) {
val root = Exception("Character file with id:$characterSheetId doesn't not exist.") throw BusinessException(
throw BusinessException(root = root) message = "Character file with id:$characterSheetId doesn't not exist.",
code = APIResponse.ErrorCode.CharacterSheetId
)
} }
// Guard case on the file deletion // Guard case on the file deletion
if (file.delete().not()) { if (file.delete().not()) {
val root = Exception("Character file have not been deleted for unknown reason.") throw BusinessException(
throw BusinessException(root = root) message = "Character file have not been deleted for unknown reason.",
)
} }
// Update the data model with the deleted character. // Update the data model with the deleted character.
characterSheetsFlow.update { characters -> characterSheetsFlow.update { characters ->
@ -158,11 +166,4 @@ class CharacterSheetStore(
private fun characterSheetFile(id: String): File { private fun characterSheetFile(id: String): File {
return File("${pathProvider.characterStorePath()}${id}.json") 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)
} }

View file

@ -1,5 +1,8 @@
package com.pixelized.server.lwa.model.tag 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.model.tag.TagJson
import com.pixelized.shared.lwa.utils.PathProvider import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -100,9 +103,4 @@ class TagStore(
private fun characterFile() = File("${pathProvider.tagsPath()}$CHARACTER.json") private fun characterFile() = File("${pathProvider.tagsPath()}$CHARACTER.json")
private fun alterationFile() = File("${pathProvider.tagsPath()}$ALTERATION.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)
} }

View file

@ -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)
)

View file

@ -0,0 +1,3 @@
package com.pixelized.server.lwa.server.exception
class FileReadException(root: Exception) : ServerException(root)

View file

@ -0,0 +1,3 @@
package com.pixelized.server.lwa.server.exception
class FileWriteException(root: Exception) : ServerException(root)

View file

@ -0,0 +1,3 @@
package com.pixelized.server.lwa.server.exception
class JsonCodingException(root: Exception) : ServerException(root)

View file

@ -0,0 +1,3 @@
package com.pixelized.server.lwa.server.exception
class JsonConversionException(root: Exception) : ServerException(root)

View file

@ -0,0 +1,4 @@
package com.pixelized.server.lwa.server.exception
class MissingParameterException(name: String) :
ServerException(root = Exception("Missing '$name' parameter."))

View file

@ -0,0 +1,3 @@
package com.pixelized.server.lwa.server.exception
sealed class ServerException(root: Exception) : Exception(root)

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.alteration package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.alterationId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -27,19 +27,9 @@ fun Engine.deleteAlteration(): suspend RoutingContext.() -> Unit {
alterationId = alterationId, alterationId = alterationId,
), ),
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception,
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -2,6 +2,7 @@ package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.alterationId 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.rest.APIResponse
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.RoutingContext
@ -22,11 +23,8 @@ fun Engine.getAlteration(): suspend RoutingContext.() -> Unit {
) )
) )
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception,
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,6 +1,7 @@
package com.pixelized.server.lwa.server.rest.alteration package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.RoutingContext
@ -14,11 +15,8 @@ fun Engine.getAlterationTags(): suspend RoutingContext.() -> Unit {
), ),
) )
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception,
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,6 +1,7 @@
package com.pixelized.server.lwa.server.rest.alteration package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.RoutingContext
@ -14,11 +15,8 @@ fun Engine.getAlterations(): suspend RoutingContext.() -> Unit {
) )
) )
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception,
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.alteration package com.pixelized.server.lwa.server.rest.alteration
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.create 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.model.alteration.AlterationJson
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
@ -29,19 +29,9 @@ fun Engine.putAlteration(): suspend RoutingContext.() -> Unit {
alterationId = form.id, alterationId = form.id,
), ),
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception,
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.campaign package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -27,19 +27,9 @@ fun Engine.removeCampaignCharacter(): suspend RoutingContext.() -> Unit {
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.campaign package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -27,19 +27,9 @@ fun Engine.removeCampaignNpc(): suspend RoutingContext.() -> Unit {
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,6 +1,7 @@
package com.pixelized.server.lwa.server.rest.campaign package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.RoutingContext
@ -14,11 +15,8 @@ fun Engine.getCampaign(): suspend RoutingContext.() -> Unit {
), ),
) )
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.campaign package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -27,19 +27,9 @@ fun Engine.putCampaignCharacter(): suspend RoutingContext.() -> Unit {
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.campaign package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -27,19 +27,9 @@ fun Engine.putCampaignNpc(): suspend RoutingContext.() -> Unit {
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,7 +1,7 @@
package com.pixelized.server.lwa.server.rest.campaign package com.pixelized.server.lwa.server.rest.campaign
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.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.model.campaign.CampaignJsonV2
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
@ -30,19 +30,9 @@ fun Engine.putCampaignScene(): suspend RoutingContext.() -> Unit {
name = scene.name, name = scene.name,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -27,19 +27,9 @@ fun Engine.deleteCharacter(): suspend RoutingContext.() -> Unit {
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
), ),
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.RoutingContext
@ -22,19 +22,9 @@ fun Engine.getCharacter(): suspend RoutingContext.() -> Unit {
data = characterSheet, data = characterSheet,
), ),
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,6 +1,7 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.RoutingContext
@ -14,11 +15,8 @@ fun Engine.getCharacterTags(): suspend RoutingContext.() -> Unit {
), ),
) )
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,6 +1,7 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import io.ktor.server.response.respond import io.ktor.server.response.respond
import io.ktor.server.routing.RoutingContext import io.ktor.server.routing.RoutingContext
@ -14,11 +15,8 @@ fun Engine.getCharacters(): suspend RoutingContext.() -> Unit {
), ),
) )
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,8 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.create import com.pixelized.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.model.characterSheet.CharacterSheetJson
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
@ -28,19 +28,9 @@ fun Engine.putCharacter(): suspend RoutingContext.() -> Unit {
characterSheetId = form.id, characterSheetId = form.id,
), ),
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,10 +1,10 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.active import com.pixelized.server.lwa.utils.extentions.active
import com.pixelized.server.lwa.utils.extentions.alterationId import com.pixelized.server.lwa.utils.extentions.alterationId
import com.pixelized.server.lwa.utils.extentions.characterSheetId import com.pixelized.server.lwa.utils.extentions.characterSheetId
import com.pixelized.server.lwa.utils.extentions.exception
import com.pixelized.shared.lwa.protocol.rest.APIResponse import com.pixelized.shared.lwa.protocol.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -35,19 +35,9 @@ fun Engine.putCharacterAlteration(): suspend RoutingContext.() -> Unit {
active = active, active = active,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,9 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException import com.pixelized.server.lwa.server.exception.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -14,10 +15,7 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
// get the query parameter // get the query parameter
val characterSheetId = call.queryParameters.characterSheetId val characterSheetId = call.queryParameters.characterSheetId
val damage = call.queryParameters["damage"]?.toIntOrNull() val damage = call.queryParameters["damage"]?.toIntOrNull()
?: throw MissingParameterException( ?: throw MissingParameterException(name = "damage")
name = "damage",
errorCode = APIResponse.MISSING_DAMAGE
)
// fetch the character sheet // fetch the character sheet
val characterSheet = characterService.character(characterSheetId) val characterSheet = characterService.character(characterSheetId)
?: error("CharacterSheet with id:$characterSheetId not found.") ?: error("CharacterSheet with id:$characterSheetId not found.")
@ -38,19 +36,9 @@ fun Engine.putCharacterDamage(): suspend RoutingContext.() -> Unit {
damage = damage, damage = damage,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,9 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException import com.pixelized.server.lwa.server.exception.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -14,10 +15,7 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit {
// get the query parameter // get the query parameter
val characterSheetId = call.queryParameters.characterSheetId val characterSheetId = call.queryParameters.characterSheetId
val diminished = call.queryParameters["diminished"]?.toIntOrNull() val diminished = call.queryParameters["diminished"]?.toIntOrNull()
?: throw MissingParameterException( ?: throw MissingParameterException(name = "diminished")
name = "diminished",
errorCode = APIResponse.MISSING_DIMINISHED
)
// Update the character damage // Update the character damage
characterService.updateDiminished( characterService.updateDiminished(
characterSheetId = characterSheetId, characterSheetId = characterSheetId,
@ -34,19 +32,9 @@ fun Engine.putCharacterDiminished(): suspend RoutingContext.() -> Unit {
diminished = diminished, diminished = diminished,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,8 +1,9 @@
package com.pixelized.server.lwa.server.rest.character package com.pixelized.server.lwa.server.rest.character
import com.pixelized.server.lwa.server.Engine import com.pixelized.server.lwa.server.Engine
import com.pixelized.server.lwa.utils.extentions.MissingParameterException import com.pixelized.server.lwa.server.exception.MissingParameterException
import com.pixelized.server.lwa.utils.extentions.characterSheetId 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.rest.APIResponse
import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent import com.pixelized.shared.lwa.protocol.websocket.CharacterSheetEvent
import io.ktor.server.response.respond import io.ktor.server.response.respond
@ -14,10 +15,7 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit {
// get the query parameter // get the query parameter
val characterSheetId = call.queryParameters.characterSheetId val characterSheetId = call.queryParameters.characterSheetId
val fatigue = call.queryParameters["fatigue"]?.toIntOrNull() val fatigue = call.queryParameters["fatigue"]?.toIntOrNull()
?: throw MissingParameterException( ?: throw MissingParameterException(name = "fatigue")
name = "fatigue",
errorCode = APIResponse.MISSING_FATIGUE
)
// fetch the character sheet // fetch the character sheet
val characterSheet = characterService.character(characterSheetId) val characterSheet = characterService.character(characterSheetId)
?: error("CharacterSheet with id:$characterSheetId not found.") ?: error("CharacterSheet with id:$characterSheetId not found.")
@ -38,19 +36,9 @@ fun Engine.putCharacterFatigue(): suspend RoutingContext.() -> Unit {
fatigue = fatigue, fatigue = fatigue,
) )
) )
} catch (exception: MissingParameterException) {
call.respond(
message = APIResponse.error(
status = exception.errorCode,
message = exception.message ?: "?",
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
call.respond( call.exception(
message = APIResponse.error( exception = exception
status = APIResponse.GENERIC,
message = exception.message ?: "?",
)
) )
} }
} }

View file

@ -1,41 +1,24 @@
package com.pixelized.server.lwa.utils.extentions 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 import io.ktor.http.Parameters
val Parameters.characterSheetId val Parameters.characterSheetId
get() = "characterSheetId".let { param -> get() = "characterSheetId".let { param ->
this[param] ?: throw MissingParameterException( this[param] ?: throw MissingParameterException(name = param)
name = param,
errorCode = APIResponse.MISSING_CHARACTER_SHEET_ID,
)
} }
val Parameters.alterationId val Parameters.alterationId
get() = "alterationId".let { param -> get() = "alterationId".let { param ->
this[param] ?: throw MissingParameterException( this[param] ?: throw MissingParameterException(name = param)
name = param,
errorCode = APIResponse.MISSING_ALTERATION_ID,
)
} }
val Parameters.create val Parameters.create
get() = "create".let { param -> get() = "create".let { param ->
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException( this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
name = param,
errorCode = APIResponse.MISSING_CREATE
)
} }
val Parameters.active val Parameters.active
get() = "active".let { param -> get() = "active".let { param ->
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException( this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
name = param,
errorCode = APIResponse.MISSING_ACTIVE
)
} }
class MissingParameterException(
name: String,
val errorCode: Int,
) : Exception("Missing $name parameter.")

View file

@ -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 <reified T : Exception> 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 ?: "?",
)
)
}
}

View file

@ -7,46 +7,52 @@ data class APIResponse<T>(
val success: Boolean, val success: Boolean,
val status: Int, val status: Int,
val message: String?, val message: String?,
val code: ErrorCode?,
val data: T?, val data: T?,
) { ) {
@Serializable
enum class ErrorCode {
AlterationId,
AlterationName,
CharacterSheetId,
}
companion object { 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( fun error(
status: Int, status: Int,
code: ErrorCode? = null,
message: String?, message: String?,
) = APIResponse( ) = APIResponse(
success = false, success = false,
status = status, status = status,
code = code,
message = message, message = message,
data = null, data = null,
) )
fun success() = APIResponse( fun success(
status: Int = OK,
) = APIResponse(
success = true, success = true,
status = SUCCESS, status = status,
code = null,
message = null, message = null,
data = null, data = null,
) )
inline fun <reified T> success( inline fun <reified T> success(
status: Int = OK,
data: T? = null, data: T? = null,
) = APIResponse( ) = APIResponse(
success = true, success = true,
status = SUCCESS, status = status,
code = null,
message = null, message = null,
data = data, data = data,
) )
const val OK = 200
const val BAD_REQUEST = 400
const val INTERNAL_ERROR = 500
} }
} }