From: Lanius Trolling Date: Sat, 27 Apr 2024 14:01:33 +0000 (-0400) Subject: Various refactorings X-Git-Url: https://gitweb.starshipfights.net/?a=commitdiff_plain;h=a978a84cfb5abfc603cdacb8c3dda8637a84ef00;p=factbooks Various refactorings --- diff --git a/build.gradle.kts b/build.gradle.kts index e56ea3a..4a5b7b3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -175,7 +175,6 @@ kotlin { implementation("io.ktor:ktor-client-core:2.3.10") implementation("io.ktor:ktor-client-java:2.3.10") - implementation("io.ktor:ktor-client-auth:2.3.10") implementation("io.ktor:ktor-client-content-negotiation:2.3.10") implementation("io.ktor:ktor-client-logging:2.3.10") diff --git a/src/jvmMain/kotlin/info/mechyrdia/auth/WebDav.kt b/src/jvmMain/kotlin/info/mechyrdia/auth/WebDav.kt index 4fde137..b1701c9 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/auth/WebDav.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/auth/WebDav.kt @@ -27,7 +27,7 @@ data class WebDavToken( override val Table = DocumentTable() override suspend fun initialize() { - Table.index(WebDavToken::holder) + Table.index(WebDavToken::holder.ascending) Table.expire(WebDavToken::validUntil) } } diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt b/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt index 4cd6cb1..f2d4320 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/Comments.kt @@ -25,8 +25,8 @@ data class Comment( override val Table = DocumentTable() override suspend fun initialize() { - Table.index(Comment::submittedBy, Comment::submittedAt) - Table.index(Comment::submittedIn, Comment::submittedAt) + Table.index(Comment::submittedBy.ascending, Comment::submittedAt.descending) + Table.index(Comment::submittedIn.ascending, Comment::submittedAt.descending) } suspend fun getCommentsIn(page: List): Flow { @@ -53,9 +53,9 @@ data class CommentReplyLink( override val Table = DocumentTable() override suspend fun initialize() { - Table.index(CommentReplyLink::originalPost) - Table.index(CommentReplyLink::replyingPost) - Table.unique(CommentReplyLink::replyingPost, CommentReplyLink::originalPost) + Table.index(CommentReplyLink::originalPost.ascending) + Table.index(CommentReplyLink::replyingPost.ascending) + Table.unique(CommentReplyLink::replyingPost.ascending, CommentReplyLink::originalPost.ascending) } suspend fun updateComment(updatedReply: Id, repliesTo: Set>, now: Instant) { diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/Data.kt b/src/jvmMain/kotlin/info/mechyrdia/data/Data.kt index 66afe39..bb6b356 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/Data.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/Data.kt @@ -108,27 +108,60 @@ interface DataDocument> { const val MONGODB_ID_KEY = "_id" +enum class IndexSort { + ASCENDING, + DESCENDING; +} + +typealias IndexSortProperty = Pair, IndexSort> + +val KProperty1.ascending: IndexSortProperty + get() = this to IndexSort.ASCENDING + +val KProperty1.descending: IndexSortProperty + get() = this to IndexSort.DESCENDING + class DocumentTable>(private val kClass: KClass) { private suspend fun collection() = ConnectionHolder.getDatabase().getCollection(kClass.simpleName!!, kClass.java) - suspend fun index(vararg properties: KProperty1) { - collection().createIndex(Indexes.ascending(*properties.map { it.serialName }.toTypedArray())) + suspend fun index(vararg properties: IndexSortProperty) { + collection().createIndex(Indexes.compoundIndex(properties.map { (prop, sort) -> + when (sort) { + IndexSort.ASCENDING -> Indexes.ascending(prop.serialName) + IndexSort.DESCENDING -> Indexes.descending(prop.serialName) + } + })) } - suspend fun unique(vararg properties: KProperty1) { - collection().createIndex(Indexes.ascending(*properties.map { it.serialName }.toTypedArray()), IndexOptions().unique(true)) + suspend fun unique(vararg properties: IndexSortProperty) { + collection().createIndex(Indexes.compoundIndex(properties.map { (prop, sort) -> + when (sort) { + IndexSort.ASCENDING -> Indexes.ascending(prop.serialName) + IndexSort.DESCENDING -> Indexes.descending(prop.serialName) + } + }), IndexOptions().unique(true)) } suspend fun expire(property: KProperty1) { collection().createIndex(Indexes.ascending(property.serialName), IndexOptions().expireAfter(0L)) } - suspend fun indexIf(condition: Bson, vararg properties: KProperty1) { - collection().createIndex(Indexes.ascending(*properties.map { it.serialName }.toTypedArray()), IndexOptions().partialFilterExpression(condition)) + suspend fun indexIf(condition: Bson, vararg properties: IndexSortProperty) { + collection().createIndex(Indexes.compoundIndex(properties.map { (prop, sort) -> + when (sort) { + IndexSort.ASCENDING -> Indexes.ascending(prop.serialName) + IndexSort.DESCENDING -> Indexes.descending(prop.serialName) + } + }), IndexOptions().partialFilterExpression(condition)) } - suspend fun uniqueIf(condition: Bson, vararg properties: KProperty1) { - collection().createIndex(Indexes.ascending(*properties.map { it.serialName }.toTypedArray()), IndexOptions().unique(true).partialFilterExpression(condition)) + suspend fun uniqueIf(condition: Bson, vararg properties: IndexSortProperty) { + collection().createIndex(Indexes.compoundIndex(properties.map { (prop, sort) -> + when (sort) { + IndexSort.ASCENDING -> Indexes.ascending(prop.serialName) + IndexSort.DESCENDING -> Indexes.descending(prop.serialName) + } + }), IndexOptions().unique(true).partialFilterExpression(condition)) } suspend fun put(doc: T) { @@ -201,13 +234,15 @@ class DocumentTable>(private val kClass: KClass) { return collection().deleteMany(where).deletedCount } - suspend fun aggregate(pipeline: List, resultClass: KClass): Flow { + suspend fun aggregate(pipeline: List, resultClass: KClass): Flow { return collection().aggregate(pipeline, resultClass.java) } + + suspend inline fun aggregate(pipeline: List): Flow { + return aggregate(pipeline, R::class) + } } -suspend inline fun , reified R : Any> DocumentTable.aggregate(pipeline: List) = aggregate(pipeline, R::class) - suspend inline fun > DocumentTable.getOrPut(id: Id, defaultValue: () -> T): T { val value = get(id) return if (value == null) { diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt b/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt index c6a9e80..77f9563 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/DataFiles.kt @@ -351,7 +351,7 @@ private class GridFsStorage(val table: DocumentTable, val bucket: G } override suspend fun prepare() { - table.unique(GridFsEntry::path) + table.unique(GridFsEntry::path.ascending) emptyFileId = getOrCreateEmptyFile() } diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/Nations.kt b/src/jvmMain/kotlin/info/mechyrdia/data/Nations.kt index ff42a35..50e8ba9 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/Nations.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/Nations.kt @@ -29,7 +29,7 @@ data class NationData( override val Table = DocumentTable() override suspend fun initialize() { - Table.index(NationData::name) + Table.index(NationData::name.ascending) } fun unknown(id: Id): NationData { diff --git a/src/jvmMain/kotlin/info/mechyrdia/data/Visits.kt b/src/jvmMain/kotlin/info/mechyrdia/data/Visits.kt index 2f1fa10..5b185f5 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/data/Visits.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/data/Visits.kt @@ -41,9 +41,9 @@ data class PageVisitData( override val Table = DocumentTable() override suspend fun initialize() { - Table.index(PageVisitData::path) - Table.unique(PageVisitData::path, PageVisitData::visitor) - Table.index(PageVisitData::lastVisit) + Table.index(PageVisitData::path.ascending) + Table.unique(PageVisitData::path.ascending, PageVisitData::visitor.ascending) + Table.index(PageVisitData::lastVisit.ascending) } suspend fun visit(path: String, visitor: String) { @@ -61,7 +61,7 @@ data class PageVisitData( } suspend fun totalVisits(path: String): PageVisitTotals { - return Table.aggregate<_, PageVisitTotals>( + return Table.aggregate( listOf( Aggregates.match(Filters.eq(PageVisitData::path.serialName, path)), Aggregates.group( diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt index 04ed077..1b7a652 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ArticleListing.kt @@ -3,7 +3,6 @@ package info.mechyrdia.lore import info.mechyrdia.Configuration import info.mechyrdia.data.FileStorage import info.mechyrdia.data.StoragePath -import info.mechyrdia.data.StoredFileType import info.mechyrdia.route.Root import info.mechyrdia.route.href import io.ktor.server.application.* @@ -14,10 +13,8 @@ import kotlinx.html.UL import kotlinx.html.a import kotlinx.html.li import kotlinx.html.ul -import java.text.CollationKey import java.text.Collator import java.util.* -import kotlin.Comparator data class ArticleNode(val name: String, val title: String, val subNodes: List?) @@ -40,12 +37,8 @@ private val collator: Collator = Collator.getInstance(Locale.US).apply { decomposition = Collator.FULL_DECOMPOSITION } -private val collationSorter = Comparator> { (_, a), (_, b) -> - a.compareTo(b) -} - -fun List.sortedLexically(selector: (T) -> String) = map { it to collator.getCollationKey(selector(it)) } - .sortedWith(collationSorter) +fun List.sortedLexically(selector: (T) -> String?) = map { it to collator.getCollationKey(selector(it)) } + .sortedBy { it.second } .map { (it, _) -> it } private fun List.sortedAsArticles() = sortedLexically { it.title }.sortedBy { it.subNodes == null } @@ -61,7 +54,7 @@ val StoragePath.isViewable: Boolean context(ApplicationCall) fun List.renderInto(list: UL, base: List = emptyList(), format: LoreArticleFormat = LoreArticleFormat.HTML) { - for (node in this) { + for (node in this) if (node.isViewable) list.li { val nodePath = base + node.name @@ -72,7 +65,6 @@ fun List.renderInto(list: UL, base: List = emptyList(), for } } } - } } suspend fun StoragePath.toFriendlyPageTitle() = ArticleTitleCache.get(this) @@ -86,7 +78,7 @@ suspend fun StoragePath.toFriendlyPathTitle(): String { val lorePath = elements.drop(1) if (lorePath.isEmpty()) return TOC_TITLE - return lorePath.indices.drop(1).map { index -> - StoragePath(lorePath.take(index)).toFriendlyPageTitle() + return lorePath.indices.mapSuspend { index -> + StoragePath(lorePath.take(index + 1)).toFriendlyPageTitle() }.joinToString(separator = " - ") } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt index a91c8f0..8824707 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocess.kt @@ -14,8 +14,6 @@ class PreProcessorContext private constructor( val variables: MutableMap, val parent: PreProcessorContext? = null, ) { - constructor(parent: PreProcessorContext? = null, vararg variables: Pair) : this(mutableMapOf(*variables), parent) - operator fun get(name: String): ParserTree = variables[name] ?: parent?.get(name) ?: formatErrorToParserTree("Unable to resolve variable $name") operator fun set(name: String, value: ParserTree) { @@ -40,13 +38,11 @@ class PreProcessorContext private constructor( operator fun plus(other: Map) = PreProcessorContext(other.toMutableMap(), this) - fun toMap(): Map = parent?.toMap().orEmpty() + variables - companion object { operator fun invoke(variables: Map, parent: PreProcessorContext? = null) = PreProcessorContext(variables.toMutableMap(), parent) - const val PAGE_PATH_KEY = "PAGE_PATH" - const val INSTANT_NOW_KEY = "INSTANT_NOW" + private const val PAGE_PATH_KEY = "PAGE_PATH" + private const val INSTANT_NOW_KEY = "INSTANT_NOW" context(ApplicationCall) fun defaults() = defaults(StoragePath(request.path())) @@ -283,14 +279,14 @@ enum class PreProcessorTags(val type: PreProcessorLexerTag) { } }), DEFAULT(PreProcessorLexerTag { env, param, subNodes -> - param.requireParam("var") { varName -> + param.requireParam("default") { varName -> if (varName in env.context) env.context[varName] else env.processTree(subNodes) } }), SET_PARAM(PreProcessorLexerTag { env, param, subNodes -> - param.requireParam("var") { varName -> + param.requireParam("set_param") { varName -> val paramValue = env.context[varName].treeToText() val withParams = subNodes.map { node -> if (node is ParserTreeNode.Tag && node.param == null) @@ -397,7 +393,8 @@ enum class PreProcessorTags(val type: PreProcessorLexerTag) { } }), MATH(PreProcessorMathOperators), - LOGIC(PreProcessorLogicOperator), + LOGIC(PreProcessorLogicVariadicOperator), + FORMAT(PreProcessorFormatter), TEST(PreProcessorInputTest), JSON_PARSE(PreProcessorLexerTag { _, param, subNodes -> param.forbidParam("json_parse") { @@ -418,7 +415,7 @@ enum class PreProcessorTags(val type: PreProcessorLexerTag) { }), FILTER(PreProcessorLexerTag { env, param, subNodes -> param.requireParam("filter") { scriptName -> - PreProcessorScriptLoader.runScriptSafe(scriptName, env.processTree(subNodes), env) { + PreProcessorScriptLoader.runScriptSafe(scriptName, env.processTree(subNodes).unparse(), env) { it.renderInBBCode() } } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessInclude.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessInclude.kt index 055acfd..20904f6 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessInclude.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessInclude.kt @@ -102,8 +102,8 @@ object PreProcessorScriptLoader { return runScriptWithBindings(scriptName, mapOf("args" to groovyArgs), env, errorHandler) } - suspend fun runScriptSafe(scriptName: String, input: ParserTree, env: AsyncLexerTagEnvironment, errorHandler: (Exception) -> ParserTree): ParserTree { - return runScriptWithBindings(scriptName, mapOf("text" to input.unparse()), env, errorHandler) + suspend fun runScriptSafe(scriptName: String, input: String, env: AsyncLexerTagEnvironment, errorHandler: (Exception) -> ParserTree): ParserTree { + return runScriptWithBindings(scriptName, mapOf("text" to input), env, errorHandler) } } diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessMath.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessMath.kt index afc0f3c..5a7de20 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessMath.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/ParserPreprocessMath.kt @@ -18,6 +18,7 @@ fun Boolean.booleanToTree(): ParserTree = listOf(ParserTreeNode.Tag("val", null, object PreProcessorMathOperators : PreProcessorFunctionLibrary("math") { override val functions: Map = mapOf( "neg" to PreProcessorMathUnaryOperator(Double::unaryMinus), + "inv" to PreProcessorMathUnaryOperator { 1.0 / it }, "sin" to PreProcessorMathUnaryOperator(::sin), "cos" to PreProcessorMathUnaryOperator(::cos), "tan" to PreProcessorMathUnaryOperator(::tan), @@ -65,7 +66,7 @@ fun interface PreProcessorMathUnaryOperator : PreProcessorFunction { return input.treeToNumberOrNull(String::toDoubleOrNull) ?.let { calculate(it) } ?.numberToTree() - .formatError("Math operations require numerical inputs, got ${input.unparse()}") + .formatError("Math operations require numerical inputs, got in = ${input.unparse()}") } fun calculate(input: Double): Double @@ -80,7 +81,7 @@ fun interface PreProcessorMathBinaryOperator : PreProcessorFunction { val right = rightValue.treeToNumberOrNull(String::toDoubleOrNull) if (left == null || right == null) - return formatErrorToParserTree("Math operations require numerical inputs, got ${leftValue.unparse()} and ${rightValue.unparse()}") + return formatErrorToParserTree("Math operations require numerical inputs, got left = ${leftValue.unparse()} and right = ${rightValue.unparse()}") return calculate(left, right).numberToTree() } @@ -94,7 +95,7 @@ fun interface PreProcessorMathVariadicOperator : PreProcessorFunction { val args = argsList.asPreProcessorList().mapNotNull { it.treeToNumberOrNull(String::toDoubleOrNull) } if (args.isEmpty() && argsList.isNotEmpty()) - return formatErrorToParserTree("Math operations require numerical inputs, got ${argsList.unparse()}") + return formatErrorToParserTree("Math operations require numerical inputs, got in = ${argsList.unparse()}") return calculate(args).numberToTree() } @@ -111,7 +112,7 @@ fun interface PreProcessorMathPredicate : PreProcessorFunction { val right = rightValue.treeToNumberOrNull(String::toDoubleOrNull) if (left == null || right == null) - return formatErrorToParserTree("Math operations require numerical inputs, got ${leftValue.unparse()} and ${rightValue.unparse()}") + return formatErrorToParserTree("Math operations require numerical inputs, got left = ${leftValue.unparse()} and right = ${rightValue.unparse()}") return calculate(left, right).booleanToTree() } @@ -128,7 +129,7 @@ fun interface PreProcessorLogicBinaryOperator : PreProcessorFunction { val right = rightValue.treeToBooleanOrNull() if (left == null || right == null) - return formatErrorToParserTree("Logical operations require boolean inputs, got ${leftValue.unparse()} and ${rightValue.unparse()}") + return formatErrorToParserTree("Logical operations require boolean inputs, got left = ${leftValue.unparse()} and right = ${rightValue.unparse()}") return calculate(left, right).booleanToTree() } @@ -136,13 +137,13 @@ fun interface PreProcessorLogicBinaryOperator : PreProcessorFunction { fun calculate(left: Boolean, right: Boolean): Boolean } -fun interface PreProcessorLogicOperator : PreProcessorFunction { +fun interface PreProcessorLogicVariadicOperator : PreProcessorFunction { override suspend fun execute(env: AsyncLexerTagEnvironment): ParserTree { val argsList = env.processTree(env.context["in"]) val args = argsList.asPreProcessorList().mapNotNull { it.treeToBooleanOrNull() } if (args.isEmpty() && argsList.isNotEmpty()) - return formatErrorToParserTree("Logical operations require boolean inputs, got ${argsList.unparse()}") + return formatErrorToParserTree("Logical operations require boolean inputs, got in = ${argsList.unparse()}") return calculate(args).booleanToTree() } @@ -166,18 +167,18 @@ fun interface PreProcessorLogicOperator : PreProcessorFunction { "nand" to PreProcessorLogicBinaryOperator { left, right -> !(left && right) }, "nor" to PreProcessorLogicBinaryOperator { left, right -> !(left || right) }, "xnor" to PreProcessorLogicBinaryOperator { left, right -> !(left xor right) }, - "implies" to PreProcessorLogicBinaryOperator { left, right -> left || !right }, + "implies" to PreProcessorLogicBinaryOperator { left, right -> !left || right }, - "all" to PreProcessorLogicOperator { inputs -> inputs.all { it } }, - "any" to PreProcessorLogicOperator { inputs -> inputs.any { it } }, - "not_all" to PreProcessorLogicOperator { inputs -> inputs.any { !it } }, - "none" to PreProcessorLogicOperator { inputs -> inputs.none { it } }, + "all" to PreProcessorLogicVariadicOperator { inputs -> inputs.all { it } }, + "any" to PreProcessorLogicVariadicOperator { inputs -> inputs.any { it } }, + "not_all" to PreProcessorLogicVariadicOperator { inputs -> inputs.any { !it } }, + "none" to PreProcessorLogicVariadicOperator { inputs -> inputs.none { it } }, "count" to PreProcessorFunction { env -> val argsList = env.processTree(env.context["in"]) val args = argsList.asPreProcessorList().mapNotNull { it.treeToBooleanOrNull() } if (args.isEmpty() && argsList.isNotEmpty()) - formatErrorToParserTree("Logical operations require boolean inputs, got ${argsList.unparse()}") + formatErrorToParserTree("Logical operations require boolean inputs, got in = ${argsList.unparse()}") else args.count { it }.numberToTree() }, @@ -203,7 +204,7 @@ fun interface PreProcessorFormatter : PreProcessorFilter { "local_instant" to PreProcessorFormatter { it.toLongOrNull() ?.let { long -> - listOf(ParserTreeNode.Tag("moment", null, listOf(ParserTreeNode.Text(long.toString())))) + listOf(ParserTreeNode.Tag("moment", null, long.toString().textToTree())) }.formatError("ISO Instant values must be formatted as base-10 long values, got $it") }, ) diff --git a/src/jvmMain/kotlin/info/mechyrdia/robot/RobotUserLimiter.kt b/src/jvmMain/kotlin/info/mechyrdia/robot/RobotUserLimiter.kt index a03f41b..75e3e83 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/robot/RobotUserLimiter.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/robot/RobotUserLimiter.kt @@ -23,7 +23,7 @@ data class RobotUser( override val Table: DocumentTable = DocumentTable() override suspend fun initialize() { - Table.unique() + Table.unique(RobotUser::usedByUser.ascending, RobotUser::usedInMonth.ascending) } private fun currentMonth(): Int {