Server : Add item service.
This commit is contained in:
parent
b09a6d5184
commit
0aaa56a4aa
24 changed files with 607 additions and 28 deletions
|
|
@ -4,6 +4,8 @@ import com.pixelized.server.lwa.model.campaign.CampaignService
|
|||
import com.pixelized.server.lwa.model.campaign.CampaignStore
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetStore
|
||||
import com.pixelized.server.lwa.model.item.ItemService
|
||||
import com.pixelized.server.lwa.model.item.ItemStore
|
||||
import com.pixelized.server.lwa.model.tag.TagStore
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
|
|
@ -37,6 +39,7 @@ val storeDependencies
|
|||
singleOf(::CampaignStore)
|
||||
singleOf(::AlterationStore)
|
||||
singleOf(::TagStore)
|
||||
singleOf(::ItemStore)
|
||||
}
|
||||
|
||||
val serviceDependencies
|
||||
|
|
@ -44,4 +47,5 @@ val serviceDependencies
|
|||
singleOf(::CharacterSheetService)
|
||||
singleOf(::CampaignService)
|
||||
singleOf(::AlterationService)
|
||||
singleOf(::ItemService)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.stateIn
|
|||
|
||||
class AlterationService(
|
||||
private val alterationStore: AlterationStore,
|
||||
tagStore: TagStore,
|
||||
private val tagStore: TagStore,
|
||||
factory: AlterationJsonFactory,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
|
@ -26,26 +26,18 @@ class AlterationService(
|
|||
initialValue = emptyMap()
|
||||
)
|
||||
|
||||
private val alterationTags = tagStore.alterationTags()
|
||||
.map { it.values.toList() }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
fun alterations(): List<AlterationJson> {
|
||||
return alterationHashFlow.value.values.toList()
|
||||
}
|
||||
|
||||
fun tags(): List<TagJson> {
|
||||
return alterationTags.value
|
||||
}
|
||||
|
||||
fun alteration(alterationId: String): AlterationJson? {
|
||||
return alterationHashFlow.value[alterationId]
|
||||
}
|
||||
|
||||
fun tags(): List<TagJson> {
|
||||
return tagStore.alterationTags().value.values.toList()
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun save(
|
||||
json: AlterationJson,
|
||||
|
|
@ -59,8 +51,6 @@ class AlterationService(
|
|||
|
||||
@Throws
|
||||
fun delete(alterationId: String) {
|
||||
return alterationStore.delete(
|
||||
id = alterationId,
|
||||
)
|
||||
alterationStore.delete(id = alterationId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
package com.pixelized.server.lwa.model.item
|
||||
|
||||
import com.pixelized.server.lwa.model.tag.TagStore
|
||||
import com.pixelized.shared.lwa.model.item.ItemJson
|
||||
import com.pixelized.shared.lwa.model.item.factory.ItemJsonFactory
|
||||
import com.pixelized.shared.lwa.model.tag.TagJson
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class ItemService(
|
||||
private val itemStore: ItemStore,
|
||||
private val tagStore: TagStore,
|
||||
factory: ItemJsonFactory,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
private val itemHashFlow = itemStore.itemsFlow()
|
||||
.map { items -> items.associate { it.id to factory.convertToJson(it) } }
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyMap()
|
||||
)
|
||||
|
||||
fun items(): List<ItemJson> {
|
||||
return itemHashFlow.value.values.toList()
|
||||
}
|
||||
|
||||
fun item(itemId: String): ItemJson? {
|
||||
return itemHashFlow.value[itemId]
|
||||
}
|
||||
|
||||
fun tags(): List<TagJson> {
|
||||
return tagStore.itemTags().value.values.toList()
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun save(
|
||||
json: ItemJson,
|
||||
create: Boolean,
|
||||
) {
|
||||
itemStore.save(
|
||||
json = json,
|
||||
create = create,
|
||||
)
|
||||
}
|
||||
|
||||
@Throws
|
||||
fun delete(itemId: String) {
|
||||
itemStore.delete(id = itemId)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
package com.pixelized.server.lwa.model.item
|
||||
|
||||
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.item.Item
|
||||
import com.pixelized.shared.lwa.model.item.ItemJson
|
||||
import com.pixelized.shared.lwa.model.item.factory.ItemJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.utils.PathProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.text.Collator
|
||||
|
||||
class ItemStore(
|
||||
private val pathProvider: PathProvider,
|
||||
private val factory: ItemJsonFactory,
|
||||
private val json: Json,
|
||||
) {
|
||||
private val directory = File(pathProvider.itemsPath()).also { it.mkdirs() }
|
||||
|
||||
private val itemFlow = MutableStateFlow<List<Item>>(emptyList())
|
||||
|
||||
init {
|
||||
val scope = CoroutineScope(Dispatchers.IO + Job())
|
||||
|
||||
scope.launch {
|
||||
updateItemsFlow()
|
||||
}
|
||||
}
|
||||
|
||||
fun itemsFlow(): StateFlow<List<Item>> = itemFlow
|
||||
|
||||
private fun updateItemsFlow() {
|
||||
itemFlow.value = try {
|
||||
load()
|
||||
} catch (exception: Exception) {
|
||||
println(exception.message) // TODO proper exception handling
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(
|
||||
FileReadException::class,
|
||||
JsonConversionException::class,
|
||||
)
|
||||
private fun load(
|
||||
directory: File = this.directory,
|
||||
): List<Item> {
|
||||
return directory
|
||||
.listFiles()
|
||||
?.mapNotNull { file ->
|
||||
val json = try {
|
||||
file.readText(charset = Charsets.UTF_8)
|
||||
} catch (exception: Exception) {
|
||||
throw FileReadException(root = exception)
|
||||
}
|
||||
// Guard, if the json is blank no item have been save, ignore this file.
|
||||
if (json.isBlank()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
// decode the file
|
||||
val data = try {
|
||||
this.json.decodeFromString<ItemJson>(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// parse the json string.
|
||||
try {
|
||||
factory.convertFromJson(data)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
}
|
||||
?.sortedWith(compareBy(Collator.getInstance()) { it.metadata.name })
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
@Throws(
|
||||
BusinessException::class,
|
||||
JsonConversionException::class,
|
||||
JsonCodingException::class,
|
||||
FileWriteException::class,
|
||||
)
|
||||
fun save(
|
||||
json: ItemJson,
|
||||
create: Boolean,
|
||||
) {
|
||||
val file = itemFile(id = json.id)
|
||||
// Guard case on update alteration
|
||||
if (create && file.exists()) {
|
||||
throw BusinessException(
|
||||
message = "Item already exist, creation is impossible.",
|
||||
)
|
||||
}
|
||||
// Transform the json into the model.
|
||||
val item = try {
|
||||
factory.convertFromJson(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonConversionException(root = exception)
|
||||
}
|
||||
if (item.id.isEmpty()) {
|
||||
throw BusinessException(
|
||||
message = "Item 'id' is a mandatory field.",
|
||||
code = APIResponse.ErrorCode.ItemId,
|
||||
)
|
||||
}
|
||||
if (item.metadata.name.isEmpty()) {
|
||||
throw BusinessException(
|
||||
message = "Item 'name' is a mandatory field.",
|
||||
code = APIResponse.ErrorCode.ItemName,
|
||||
)
|
||||
}
|
||||
// Encode the json into a string.
|
||||
val data = try {
|
||||
this.json.encodeToString(json)
|
||||
} catch (exception: Exception) {
|
||||
throw JsonCodingException(root = exception)
|
||||
}
|
||||
// Write the alteration into a file.
|
||||
try {
|
||||
file.writeText(
|
||||
text = data,
|
||||
charset = Charsets.UTF_8,
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
throw FileWriteException(root = exception)
|
||||
}
|
||||
// Update the dataflow.
|
||||
itemFlow.update { items ->
|
||||
val index = items.indexOfFirst { it.id == json.id }
|
||||
items.toMutableList()
|
||||
.also {
|
||||
if (index >= 0) {
|
||||
it[index] = item
|
||||
} else {
|
||||
it.add(item)
|
||||
}
|
||||
}
|
||||
.sortedWith(compareBy(Collator.getInstance()) {
|
||||
it.metadata.name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(BusinessException::class)
|
||||
fun delete(id: String) {
|
||||
val file = itemFile(id = id)
|
||||
// Guard case on the file existence.
|
||||
if (file.exists().not()) {
|
||||
throw BusinessException(
|
||||
message = "Item doesn't not exist, deletion is impossible.",
|
||||
)
|
||||
}
|
||||
// Guard case on the file deletion
|
||||
if (file.delete().not()) {
|
||||
throw BusinessException(
|
||||
message = "Item file have not been deleted for unknown reason.",
|
||||
)
|
||||
}
|
||||
// Update the data model with the deleted alteration.
|
||||
itemFlow.update { items ->
|
||||
items.toMutableList()
|
||||
.also { item ->
|
||||
item.removeIf { it.id == id }
|
||||
}
|
||||
.sortedWith(compareBy(Collator.getInstance()) {
|
||||
it.metadata.name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun itemFile(id: String): File {
|
||||
return File("${pathProvider.itemsPath()}${id}.json")
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import java.io.File
|
|||
|
||||
private const val CHARACTER = "character"
|
||||
private const val ALTERATION = "alteration"
|
||||
private const val ITEM = "item"
|
||||
|
||||
class TagStore(
|
||||
private val pathProvider: PathProvider,
|
||||
|
|
@ -23,6 +24,7 @@ class TagStore(
|
|||
) {
|
||||
private val alterationTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
||||
private val characterTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
||||
private val itemTagsFlow = MutableStateFlow<Map<String, TagJson>>(emptyMap())
|
||||
|
||||
init {
|
||||
// make the file path.
|
||||
|
|
@ -39,11 +41,16 @@ class TagStore(
|
|||
flow = characterTagsFlow,
|
||||
file = characterFile(),
|
||||
)
|
||||
update(
|
||||
flow = itemTagsFlow,
|
||||
file = itemFile(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun alterationTags(): StateFlow<Map<String, TagJson>> = alterationTagsFlow
|
||||
fun characterTags(): StateFlow<Map<String, TagJson>> = characterTagsFlow
|
||||
fun itemTags(): StateFlow<Map<String, TagJson>> = itemTagsFlow
|
||||
|
||||
private fun update(
|
||||
flow: MutableStateFlow<Map<String, TagJson>>,
|
||||
|
|
@ -101,6 +108,6 @@ class TagStore(
|
|||
}
|
||||
|
||||
private fun characterFile() = File("${pathProvider.tagsPath()}$CHARACTER.json")
|
||||
|
||||
private fun alterationFile() = File("${pathProvider.tagsPath()}$ALTERATION.json")
|
||||
private fun itemFile() = File("${pathProvider.tagsPath()}$ITEM.json")
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.pixelized.server.lwa.server
|
|||
import com.pixelized.server.lwa.model.alteration.AlterationService
|
||||
import com.pixelized.server.lwa.model.campaign.CampaignService
|
||||
import com.pixelized.server.lwa.model.character.CharacterSheetService
|
||||
import com.pixelized.server.lwa.model.item.ItemService
|
||||
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import com.pixelized.shared.lwa.protocol.websocket.CampaignEvent
|
||||
|
|
@ -16,6 +17,7 @@ class Engine(
|
|||
val characterService: CharacterSheetService,
|
||||
val campaignService: CampaignService,
|
||||
val alterationService: AlterationService,
|
||||
val itemService: ItemService,
|
||||
val campaignJsonFactory: CampaignJsonFactory,
|
||||
) {
|
||||
val webSocket = MutableSharedFlow<SocketMessage>()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,11 @@ import com.pixelized.server.lwa.server.rest.character.putCharacterAlteration
|
|||
import com.pixelized.server.lwa.server.rest.character.putCharacterDamage
|
||||
import com.pixelized.server.lwa.server.rest.character.putCharacterDiminished
|
||||
import com.pixelized.server.lwa.server.rest.character.putCharacterFatigue
|
||||
import com.pixelized.server.lwa.server.rest.item.deleteItem
|
||||
import com.pixelized.server.lwa.server.rest.item.getItem
|
||||
import com.pixelized.server.lwa.server.rest.item.getItemTags
|
||||
import com.pixelized.server.lwa.server.rest.item.getItems
|
||||
import com.pixelized.server.lwa.server.rest.item.putItem
|
||||
import com.pixelized.shared.lwa.SERVER_PORT
|
||||
import com.pixelized.shared.lwa.protocol.websocket.SocketMessage
|
||||
import com.pixelized.shared.lwa.sharedModuleDependencies
|
||||
|
|
@ -113,12 +118,13 @@ class LocalServer {
|
|||
}
|
||||
}.onFailure { exception ->
|
||||
// TODO
|
||||
println("WebSocket exception: ${exception.localizedMessage}")
|
||||
println("WebSocket exception: ${exception.message}")
|
||||
}.also {
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
)
|
||||
// TODO Tags.
|
||||
route(
|
||||
path = "/alteration",
|
||||
) {
|
||||
|
|
@ -217,6 +223,28 @@ class LocalServer {
|
|||
body = engine.putCampaignScene(),
|
||||
)
|
||||
}
|
||||
route(path = "item") {
|
||||
get(
|
||||
path = "/all",
|
||||
body = engine.getItems(),
|
||||
)
|
||||
get(
|
||||
path = "/detail",
|
||||
body = engine.getItem(),
|
||||
)
|
||||
get(
|
||||
path = "/tags",
|
||||
body = engine.getItemTags(),
|
||||
)
|
||||
put(
|
||||
path = "/update",
|
||||
body = engine.putItem(),
|
||||
)
|
||||
delete(
|
||||
path = "/delete",
|
||||
body = engine.deleteItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ fun Engine.getAlteration(): suspend RoutingContext.() -> Unit {
|
|||
// get the query parameter
|
||||
val alterationId = call.queryParameters.alterationId
|
||||
// get the alteration of the given id.
|
||||
val alteration = alterationService
|
||||
.alteration(alterationId = alterationId)
|
||||
val alteration = alterationService.alteration(alterationId = alterationId)
|
||||
?: error("Alteration with id:$alterationId not found.")
|
||||
// send it back to the user.
|
||||
call.respond(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
package com.pixelized.server.lwa.server.rest.item
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.server.lwa.utils.extentions.itemId
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.deleteItem(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val itemId = call.parameters.itemId
|
||||
// delete the alteration.
|
||||
itemService.delete(
|
||||
itemId = itemId
|
||||
)
|
||||
// API & WebSocket responses.
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.ItemDelete(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
itemId = itemId,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.pixelized.server.lwa.server.rest.item
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.server.lwa.utils.extentions.itemId
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getItem(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
// get the query parameter
|
||||
val itemId = call.queryParameters.itemId
|
||||
// get the alteration of the given id.
|
||||
val item = itemService.item(itemId = itemId)
|
||||
?: error("Item with id:$itemId not found.")
|
||||
// send it back to the user.
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = item
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.pixelized.server.lwa.server.rest.item
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getItemTags(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = itemService.tags(),
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.pixelized.server.lwa.server.rest.item
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
import com.pixelized.server.lwa.utils.extentions.exception
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.getItems(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
call.respond(
|
||||
message = APIResponse.success(
|
||||
data = itemService.items(),
|
||||
)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.pixelized.server.lwa.server.rest.item
|
||||
|
||||
import com.pixelized.server.lwa.server.Engine
|
||||
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.item.ItemJson
|
||||
import com.pixelized.shared.lwa.protocol.rest.APIResponse
|
||||
import com.pixelized.shared.lwa.protocol.websocket.ApiSynchronisation
|
||||
import io.ktor.server.request.receive
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.RoutingContext
|
||||
|
||||
fun Engine.putItem(): suspend RoutingContext.() -> Unit {
|
||||
return {
|
||||
try {
|
||||
val form = call.receive<ItemJson>()
|
||||
val create = call.queryParameters.create
|
||||
|
||||
itemService.save(
|
||||
json = form,
|
||||
create = create,
|
||||
)
|
||||
call.respond(
|
||||
message = APIResponse.success(),
|
||||
)
|
||||
webSocket.emit(
|
||||
value = ApiSynchronisation.ItemUpdate(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
itemId = form.id,
|
||||
),
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
call.exception(
|
||||
exception = exception,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,11 @@ val Parameters.alterationId
|
|||
this[param] ?: throw MissingParameterException(name = param)
|
||||
}
|
||||
|
||||
val Parameters.itemId
|
||||
get() = "itemId".let { param ->
|
||||
this[param] ?: throw MissingParameterException(name = param)
|
||||
}
|
||||
|
||||
val Parameters.create
|
||||
get() = "create".let { param ->
|
||||
this[param]?.toBooleanStrictOrNull() ?: throw MissingParameterException(name = param)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue