mirror of
https://github.com/InsanusMokrassar/PlaguPoster.git
synced 2024-12-22 14:07:14 +00:00
add selector
This commit is contained in:
parent
cf5a4c0f61
commit
f3f7761bf9
22
ratings/selector/build.gradle
Normal file
22
ratings/selector/build.gradle
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
id "com.android.library"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":plaguposter.common")
|
||||||
|
api project(":plaguposter.ratings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
ratings/selector/src/commonMain/kotlin/DefaultSelector.kt
Normal file
21
ratings/selector/src/commonMain/kotlin/DefaultSelector.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector
|
||||||
|
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
|
import dev.inmo.plaguposter.ratings.selector.models.SelectorConfig
|
||||||
|
|
||||||
|
class DefaultSelector (
|
||||||
|
private val config: SelectorConfig,
|
||||||
|
private val repo: RatingsRepo
|
||||||
|
) : Selector {
|
||||||
|
override suspend fun take(n: Int): List<PostId> {
|
||||||
|
val result = mutableListOf<PostId>()
|
||||||
|
|
||||||
|
do {
|
||||||
|
val selected = config.active ?.ratings ?.select(repo, result) ?: break
|
||||||
|
result.add(selected)
|
||||||
|
} while (result.size < n)
|
||||||
|
|
||||||
|
return result.toList()
|
||||||
|
}
|
||||||
|
}
|
1
ratings/selector/src/commonMain/kotlin/PackageInfo.kt
Normal file
1
ratings/selector/src/commonMain/kotlin/PackageInfo.kt
Normal file
@ -0,0 +1 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector
|
7
ratings/selector/src/commonMain/kotlin/Selector.kt
Normal file
7
ratings/selector/src/commonMain/kotlin/Selector.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector
|
||||||
|
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
|
||||||
|
interface Selector {
|
||||||
|
suspend fun take(n: Int = 1): List<PostId>
|
||||||
|
}
|
104
ratings/selector/src/commonMain/kotlin/models/RatingConfig.kt
Normal file
104
ratings/selector/src/commonMain/kotlin/models/RatingConfig.kt
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector.models
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
import dev.inmo.plaguposter.ratings.models.Rating
|
||||||
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class RatingConfig(
|
||||||
|
val min: Rating?,
|
||||||
|
val max: Rating?,
|
||||||
|
val prefer: Prefer
|
||||||
|
) {
|
||||||
|
suspend fun select(repo: RatingsRepo, exclude: List<PostId>): PostId? {
|
||||||
|
var reversed: Boolean = false
|
||||||
|
var count: Int? = null
|
||||||
|
|
||||||
|
when (prefer) {
|
||||||
|
Prefer.Max -> {
|
||||||
|
reversed = true
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
Prefer.Min -> {
|
||||||
|
reversed = false
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
Prefer.Random -> {
|
||||||
|
reversed = false
|
||||||
|
count = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val posts = when(min) {
|
||||||
|
null -> {
|
||||||
|
when (max) {
|
||||||
|
null -> {
|
||||||
|
repo.keys(
|
||||||
|
count ?.let { Pagination(0, it) } ?: FirstPagePagination(repo.count().toInt()),
|
||||||
|
reversed
|
||||||
|
).results.filterNot {
|
||||||
|
it in exclude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
repo.getPostsWithRatingLessEq(max, exclude = exclude).keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
when (max) {
|
||||||
|
null -> {
|
||||||
|
repo.getPostsWithRatingGreaterEq(min, exclude = exclude).keys
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
repo.getPosts(min .. max, reversed, count, exclude = exclude).keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (prefer) {
|
||||||
|
Prefer.Max,
|
||||||
|
Prefer.Min -> posts.firstOrNull()
|
||||||
|
Prefer.Random -> posts.randomOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable(Prefer.Serializer::class)
|
||||||
|
sealed interface Prefer {
|
||||||
|
val type: String
|
||||||
|
@Serializable(Serializer::class)
|
||||||
|
object Max : Prefer { override val type: String = "max" }
|
||||||
|
@Serializable(Serializer::class)
|
||||||
|
object Min : Prefer { override val type: String = "min" }
|
||||||
|
@Serializable(Serializer::class)
|
||||||
|
object Random : Prefer { override val type: String = "random" }
|
||||||
|
|
||||||
|
object Serializer : KSerializer<Prefer> {
|
||||||
|
override val descriptor: SerialDescriptor = String.serializer().descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Prefer {
|
||||||
|
val identifier = decoder.decodeString().lowercase()
|
||||||
|
return values.first { it.type.lowercase() == identifier }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Prefer) {
|
||||||
|
encoder.encodeString(value.type.lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val values = arrayOf(Max, Min, Random)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SelectorConfig(
|
||||||
|
val items: List<SelectorConfigItem>
|
||||||
|
) {
|
||||||
|
val active: SelectorConfigItem?
|
||||||
|
get() = items.firstOrNull { it.time.isActive }
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SelectorConfigItem(
|
||||||
|
val time: TimeConfig,
|
||||||
|
val ratings: RatingConfig
|
||||||
|
)
|
36
ratings/selector/src/commonMain/kotlin/models/TimeConfig.kt
Normal file
36
ratings/selector/src/commonMain/kotlin/models/TimeConfig.kt
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector.models
|
||||||
|
|
||||||
|
import com.soywiz.klock.*
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class TimeConfig(
|
||||||
|
@Serializable(TimeSerializer::class)
|
||||||
|
val from: Time,
|
||||||
|
@Serializable(TimeSerializer::class)
|
||||||
|
val to: Time
|
||||||
|
) {
|
||||||
|
@Transient
|
||||||
|
val range = from .. to
|
||||||
|
|
||||||
|
val isActive: Boolean
|
||||||
|
get() = DateTime.now().time in range
|
||||||
|
|
||||||
|
|
||||||
|
object TimeSerializer : KSerializer<Time> {
|
||||||
|
val format = TimeFormat("HH:mm")
|
||||||
|
override val descriptor: SerialDescriptor = String.serializer().descriptor
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): Time {
|
||||||
|
return format.parseTime(decoder.decodeString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: Time) {
|
||||||
|
encoder.encodeString(format.format(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
ratings/selector/src/jvmMain/kotlin/Plugin.kt
Normal file
14
ratings/selector/src/jvmMain/kotlin/Plugin.kt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.selector
|
||||||
|
|
||||||
|
import dev.inmo.plagubot.Plugin
|
||||||
|
import dev.inmo.plaguposter.ratings.selector.models.SelectorConfig
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
|
object Plugin : Plugin {
|
||||||
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||||
|
single { get<Json>().decodeFromJsonElement(SelectorConfig.serializer(), params["selector"] ?: return@single null) }
|
||||||
|
single<Selector> { DefaultSelector(get(), get()) }
|
||||||
|
}
|
||||||
|
}
|
1
ratings/selector/src/main/AndroidManifest.xml
Normal file
1
ratings/selector/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.plaguposter.ratings.selector"/>
|
@ -7,4 +7,6 @@ import kotlin.jvm.JvmInline
|
|||||||
@JvmInline
|
@JvmInline
|
||||||
value class Rating(
|
value class Rating(
|
||||||
val double: Double
|
val double: Double
|
||||||
)
|
) : Comparable<Rating> {
|
||||||
|
override fun compareTo(other: Rating): Int = double.compareTo(other.double)
|
||||||
|
}
|
||||||
|
@ -4,4 +4,24 @@ import dev.inmo.micro_utils.repos.ReadKeyValueRepo
|
|||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.plaguposter.ratings.models.Rating
|
import dev.inmo.plaguposter.ratings.models.Rating
|
||||||
|
|
||||||
interface ReadRatingsRepo : ReadKeyValueRepo<PostId, Rating>
|
interface ReadRatingsRepo : ReadKeyValueRepo<PostId, Rating> {
|
||||||
|
suspend fun getPosts(
|
||||||
|
range: ClosedRange<Rating>,
|
||||||
|
reversed: Boolean = false,
|
||||||
|
count: Int? = null,
|
||||||
|
exclude: List<PostId> = emptyList()
|
||||||
|
): Map<PostId, Rating>
|
||||||
|
|
||||||
|
suspend fun getPostsWithRatingGreaterEq(
|
||||||
|
then: Rating,
|
||||||
|
reversed: Boolean = false,
|
||||||
|
count: Int? = null,
|
||||||
|
exclude: List<PostId> = emptyList()
|
||||||
|
): Map<PostId, Rating>
|
||||||
|
suspend fun getPostsWithRatingLessEq(
|
||||||
|
then: Rating,
|
||||||
|
reversed: Boolean = false,
|
||||||
|
count: Int? = null,
|
||||||
|
exclude: List<PostId> = emptyList()
|
||||||
|
): Map<PostId, Rating>
|
||||||
|
}
|
||||||
|
@ -1,24 +1,81 @@
|
|||||||
package dev.inmo.plaguposter.ratings.exposed
|
package dev.inmo.plaguposter.ratings.exposed
|
||||||
|
|
||||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.mappers.withMapper
|
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.plaguposter.ratings.models.Rating
|
import dev.inmo.plaguposter.ratings.models.Rating
|
||||||
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
import org.jetbrains.exposed.sql.Column
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
|
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
class ExposedRatingsRepo(
|
class ExposedRatingsRepo (
|
||||||
database: Database
|
database: Database
|
||||||
) : RatingsRepo, KeyValueRepo<PostId, Rating> by ExposedKeyValueRepo(
|
) : RatingsRepo, AbstractExposedKeyValueRepo<PostId, Rating>(
|
||||||
database,
|
database,
|
||||||
{ text("post_id") },
|
|
||||||
{ double("rating") },
|
|
||||||
"ratings"
|
"ratings"
|
||||||
).withMapper(
|
) {
|
||||||
{ string },
|
override val keyColumn = text("post_id")
|
||||||
{ double },
|
val ratingsColumn = double("rating")
|
||||||
{ PostId(this) },
|
override val selectById: SqlExpressionBuilder.(PostId) -> Op<Boolean> = { keyColumn.eq(it.string) }
|
||||||
{ Rating(this) }
|
override val selectByValue: SqlExpressionBuilder.(Rating) -> Op<Boolean> = { ratingsColumn.eq(it.double) }
|
||||||
)
|
override val ResultRow.asKey: PostId
|
||||||
|
get() = get(keyColumn).let(::PostId)
|
||||||
|
override val ResultRow.asObject: Rating
|
||||||
|
get() = get(ratingsColumn).let(::Rating)
|
||||||
|
|
||||||
|
override fun update(k: PostId, v: Rating, it: UpdateStatement) {
|
||||||
|
it[ratingsColumn] = v.double
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun insert(k: PostId, v: Rating, it: InsertStatement<Number>) {
|
||||||
|
it[keyColumn] = k.string
|
||||||
|
it[ratingsColumn] = v.double
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Query.optionallyLimit(limit: Int?) = if (limit == null) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
limit(limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPosts(
|
||||||
|
range: ClosedRange<Rating>,
|
||||||
|
reversed: Boolean,
|
||||||
|
count: Int?,
|
||||||
|
exclude: List<PostId>
|
||||||
|
): Map<PostId, Rating> = transaction(database) {
|
||||||
|
select {
|
||||||
|
ratingsColumn.greaterEq(range.start.double).and(
|
||||||
|
ratingsColumn.lessEq(range.endInclusive.double)
|
||||||
|
).and(
|
||||||
|
keyColumn.notInList(exclude.map { it.string })
|
||||||
|
)
|
||||||
|
}.optionallyLimit(count).optionallyReverse(reversed).map {
|
||||||
|
it.asKey to it.asObject
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
override suspend fun getPostsWithRatingGreaterEq(
|
||||||
|
then: Rating,
|
||||||
|
reversed: Boolean,
|
||||||
|
count: Int?,
|
||||||
|
exclude: List<PostId>
|
||||||
|
) = transaction(database) {
|
||||||
|
select { ratingsColumn.greaterEq(then.double).and(keyColumn.notInList(exclude.map { it.string })) }.optionallyLimit(count).optionallyReverse(reversed).map {
|
||||||
|
it.asKey to it.asObject
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
|
||||||
|
override suspend fun getPostsWithRatingLessEq(
|
||||||
|
then: Rating,
|
||||||
|
reversed: Boolean,
|
||||||
|
count: Int?,
|
||||||
|
exclude: List<PostId>
|
||||||
|
): Map<PostId, Rating> = transaction(database) {
|
||||||
|
select { ratingsColumn.lessEq(then.double).and(keyColumn.notInList(exclude.map { it.string })) }.optionallyLimit(count).optionallyReverse(reversed).map {
|
||||||
|
it.asKey to it.asObject
|
||||||
|
}
|
||||||
|
}.toMap()
|
||||||
|
}
|
||||||
|
@ -15,6 +15,7 @@ dependencies {
|
|||||||
api project(":plaguposter.triggers.command")
|
api project(":plaguposter.triggers.command")
|
||||||
api project(":plaguposter.ratings")
|
api project(":plaguposter.ratings")
|
||||||
api project(":plaguposter.ratings.source")
|
api project(":plaguposter.ratings.source")
|
||||||
|
api project(":plaguposter.ratings.selector")
|
||||||
|
|
||||||
api libs.psql
|
api libs.psql
|
||||||
}
|
}
|
||||||
|
@ -24,5 +24,42 @@
|
|||||||
},
|
},
|
||||||
"autoAttach": true,
|
"autoAttach": true,
|
||||||
"ratingOfferText": "What do you think about it?"
|
"ratingOfferText": "What do you think about it?"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"time": {
|
||||||
|
"from": "00:00",
|
||||||
|
"to": "00:00"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0,
|
||||||
|
"prefer": "random"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time": {
|
||||||
|
"from": "00:00",
|
||||||
|
"to": "00:00"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0,
|
||||||
|
"prefer": "min"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"time": {
|
||||||
|
"from": "00:00",
|
||||||
|
"to": "00:00"
|
||||||
|
},
|
||||||
|
"rating": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0,
|
||||||
|
"prefer": "max"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ String[] includes = [
|
|||||||
":posts_registrar",
|
":posts_registrar",
|
||||||
":ratings",
|
":ratings",
|
||||||
":ratings:source",
|
":ratings:source",
|
||||||
|
":ratings:selector",
|
||||||
":triggers:command",
|
":triggers:command",
|
||||||
// ":settings",
|
// ":settings",
|
||||||
":runner"
|
":runner"
|
||||||
|
Loading…
Reference in New Issue
Block a user