Skip to content

Commit

Permalink
Add advanced search
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziedelth committed Aug 20, 2024
1 parent 41c5ee0 commit 6d3d2ac
Show file tree
Hide file tree
Showing 23 changed files with 373 additions and 113 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package fr.shikkanime.caches

import fr.shikkanime.entities.enums.CountryCode
import fr.shikkanime.entities.enums.LangType

data class CountryCodeNamePaginationKeyCache(
override val countryCode: CountryCode?,
val name: String,
override val page: Int,
override val limit: Int,
val searchTypes: List<LangType>?
) : CountryCodePaginationKeyCache(countryCode, page, limit)
19 changes: 12 additions & 7 deletions src/main/kotlin/fr/shikkanime/controllers/api/AnimeController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package fr.shikkanime.controllers.api
import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.*
import fr.shikkanime.dtos.AnimeDto
import fr.shikkanime.dtos.enums.Status
import fr.shikkanime.entities.enums.CountryCode
import fr.shikkanime.entities.enums.LangType
import fr.shikkanime.services.AnimeService
import fr.shikkanime.services.caches.AnimeCacheService
import fr.shikkanime.services.caches.MemberFollowAnimeCacheService
Expand Down Expand Up @@ -80,6 +80,8 @@ class AnimeController : HasPageableRoute() {
descParam: String?,
@QueryParam("status", description = "Status to filter by", type = Status::class)
statusParam: Status?,
@QueryParam("searchTypes", description = "Search types to filter by", type = LangType::class)
searchTypes: Array<LangType>?,
): Response {
if (simulcastParam != null && name != null) {
return Response.conflict(
Expand All @@ -103,7 +105,7 @@ class AnimeController : HasPageableRoute() {

return Response.ok(
if (!name.isNullOrBlank()) {
animeCacheService.findAllByName(name, countryParam, page, limit)
animeCacheService.findAllByName(countryParam, name, page, limit, searchTypes?.toList())
} else {
animeCacheService.findAllBy(countryParam, simulcastParam, sortParameters, page, limit, statusParam)
}
Expand Down Expand Up @@ -154,7 +156,7 @@ class AnimeController : HasPageableRoute() {
@JWTUser
uuid: UUID?,
@QueryParam("country", description = "Country code to filter by", example = "FR", type = CountryCode::class)
countryParam: String?,
countryParam: CountryCode?,
@QueryParam(
"date",
description = "Date to filter by. Format: yyyy-MM-dd",
Expand All @@ -168,10 +170,13 @@ class AnimeController : HasPageableRoute() {
return Response.badRequest(MessageDto(MessageDto.Type.ERROR, "Invalid week format"))
}

val startOfWeekDay = parsedDate.with(DayOfWeek.MONDAY)
val countryCode = CountryCode.fromNullable(countryParam) ?: CountryCode.FR

return Response.ok(animeCacheService.getWeeklyAnimes(uuid, startOfWeekDay, countryCode))
return Response.ok(
animeCacheService.getWeeklyAnimes(
countryParam ?: CountryCode.FR,
uuid,
parsedDate.with(DayOfWeek.MONDAY),
)
)
}

@Path("/missed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class EpisodeMappingController : HasPageableRoute() {
)
private fun getAll(
@QueryParam("country", description = "Country code to filter by", example = "FR", type = CountryCode::class)
countryParam: String?,
countryParam: CountryCode?,
@QueryParam("anime", description = "UUID of the anime to filter by")
animeParam: UUID?,
@QueryParam("season", description = "Season number to filter by")
Expand Down Expand Up @@ -86,7 +86,7 @@ class EpisodeMappingController : HasPageableRoute() {

return Response.ok(
episodeMappingCacheService.findAllBy(
CountryCode.fromNullable(countryParam) ?: CountryCode.FR,
countryParam ?: CountryCode.FR,
animeParam,
seasonParam,
sortParameters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,11 +223,13 @@ class SiteController {
@Get
private fun search(
@QueryParam("q") query: String?,
@QueryParam("page") pageParam: Int?,
): Response {
return Response.template(
Link.SEARCH,
mutableMapOf(
"query" to query
"query" to query,
"page" to pageParam,
)
)
}
Expand All @@ -246,7 +248,7 @@ class SiteController {
return Response.template(
Link.CALENDAR,
mutableMapOf(
"weeklyAnimes" to animeCacheService.getWeeklyAnimes(null, startOfWeekDay, CountryCode.FR),
"weeklyAnimes" to animeCacheService.getWeeklyAnimes(CountryCode.FR, null, startOfWeekDay),
"previousWeek" to startOfWeekDay.minusDays(7),
"nextWeek" to startOfWeekDay.plusDays(7).takeIf { it <= ZonedDateTime.now().toLocalDate() }
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package fr.shikkanime.converters.anime

import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.SeasonDto
import fr.shikkanime.dtos.AnimeDto
import fr.shikkanime.dtos.SeasonDto
import fr.shikkanime.dtos.SimulcastDto
import fr.shikkanime.entities.Anime
import fr.shikkanime.entities.enums.LangType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package fr.shikkanime.converters.episode_mapping

import com.google.inject.Inject
import fr.shikkanime.converters.AbstractConverter
import fr.shikkanime.dtos.PlatformDto
import fr.shikkanime.dtos.AnimeDto
import fr.shikkanime.dtos.PlatformDto
import fr.shikkanime.dtos.mappings.EpisodeMappingDto
import fr.shikkanime.dtos.variants.EpisodeVariantWithoutMappingDto
import fr.shikkanime.entities.EpisodeMapping
Expand Down
11 changes: 11 additions & 0 deletions src/main/kotlin/fr/shikkanime/dtos/enums/Status.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,15 @@ package fr.shikkanime.dtos.enums
enum class Status {
VALID,
INVALID,
;

companion object {
fun fromNullable(string: String?): Status? {
return if (string == null) null else try {
Status.valueOf(string.uppercase())
} catch (e: IllegalArgumentException) {
null
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fr.shikkanime.dtos.mappings

import fr.shikkanime.dtos.PlatformDto
import fr.shikkanime.dtos.AnimeDto
import fr.shikkanime.dtos.PlatformDto
import fr.shikkanime.dtos.enums.Status
import fr.shikkanime.dtos.variants.EpisodeVariantWithoutMappingDto
import fr.shikkanime.entities.enums.EpisodeType
Expand Down
1 change: 0 additions & 1 deletion src/main/kotlin/fr/shikkanime/entities/Anime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ class Anime(
uuid: UUID? = null,
@Column(nullable = false, name = "country_code")
@Enumerated(EnumType.STRING)
@FullTextField
val countryCode: CountryCode? = null,
@Column(nullable = false)
@FullTextField(analyzer = "shikkanime_analyzer")
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/fr/shikkanime/entities/EpisodeMapping.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class EpisodeMapping(
@Column(nullable = true, name = "status")
@Enumerated(EnumType.STRING)
var status: Status = Status.VALID,
@OneToMany(mappedBy = "mapping", fetch = FetchType.LAZY)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
var variants: MutableSet<EpisodeVariant> = mutableSetOf(),
@OneToMany(mappedBy = "episode", fetch = FetchType.LAZY)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
var memberFollowEpisodes: MutableSet<MemberFollowEpisode> = mutableSetOf(),
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/fr/shikkanime/entities/Pageable.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package fr.shikkanime.entities

data class Pageable<T>(
val data: List<T>,
var data: List<T>,
val page: Int,
val limit: Int,
val total: Long,
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/fr/shikkanime/entities/enums/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,13 @@ enum class Platform(
fun findByName(name: String): Platform? {
return entries.find { it.platformName == name }
}

fun fromNullable(string: String?): Platform? {
return if (string == null) null else try {
Platform.valueOf(string.uppercase())
} catch (e: IllegalArgumentException) {
null
}
}
}
}
93 changes: 43 additions & 50 deletions src/main/kotlin/fr/shikkanime/modules/Routing.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package fr.shikkanime.modules

import fr.shikkanime.dtos.*
import fr.shikkanime.dtos.AnimeDto
import fr.shikkanime.dtos.enums.Status
import fr.shikkanime.dtos.mappings.EpisodeMappingDto
import fr.shikkanime.entities.enums.ConfigPropertyKey
import fr.shikkanime.entities.enums.CountryCode
import fr.shikkanime.entities.enums.LangType
import fr.shikkanime.entities.enums.Platform
import fr.shikkanime.services.caches.ConfigCacheService
import fr.shikkanime.utils.Constant
Expand All @@ -32,17 +31,20 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.sessions.*
import io.ktor.server.util.*
import io.ktor.util.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
import java.time.ZonedDateTime
import java.util.*
import java.util.logging.Level
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KParameter
import kotlin.reflect.KType
import kotlin.reflect.full.*
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.jvmErasure

private val logger = LoggerFactory.getLogger("Routing")
private val callStartTime = AttributeKey<ZonedDateTime>("CallStartTime")
Expand Down Expand Up @@ -176,7 +178,7 @@ private suspend fun handleRequest(
val replacedPath = replacePathWithParameters("$prefix$path", parameters)

try {
val response = callMethodWithParameters(method, controller, call, parameters)
val response = callMethodWithParameters(method, controller, call)

if (method.hasAnnotation<Cached>()) {
val cached = method.findAnnotation<Cached>()!!.maxAgeSeconds
Expand Down Expand Up @@ -219,20 +221,15 @@ private fun replacePathWithParameters(path: String, parameters: Map<String, List
acc.replace("{$param}", values.joinToString(", "))
}

private suspend fun callMethodWithParameters(
method: KFunction<*>,
controller: Any,
call: ApplicationCall,
parameters: Map<String, List<String>>
): Response {
private suspend fun callMethodWithParameters(method: KFunction<*>, controller: Any, call: ApplicationCall): Response {
val methodParams = method.parameters.associateWith { kParameter ->
when {
kParameter.name.isNullOrBlank() -> controller
kParameter.hasAnnotation<JWTUser>() -> call.principal<JWTPrincipal>()?.payload?.getClaim("uuid")?.asString()?.let { UUID.fromString(it) }
kParameter.hasAnnotation<AdminSessionUser>() -> call.principal<TokenDto>()
kParameter.hasAnnotation<BodyParam>() -> handleBodyParam(kParameter, call)
kParameter.hasAnnotation<PathParam>() -> handlePathParam(kParameter, call)
kParameter.hasAnnotation<QueryParam>() -> handleQueryParam(kParameter, call)
kParameter.hasAnnotation<PathParam>() -> handlePathParam(kParameter, parameters)
kParameter.hasAnnotation<BodyParam>() -> handleBodyParam(kParameter, call)
else -> throw Exception("Unknown parameter ${kParameter.name}")
}
}
Expand All @@ -247,49 +244,45 @@ private suspend fun callMethodWithParameters(
}
}

private suspend fun handleBodyParam(kParameter: KParameter, call: ApplicationCall): Any {
return when (kParameter.type.javaType) {
Array<UUID>::class.java -> call.receive<Array<UUID>>()
Parameters::class.java -> call.receiveParameters()
ConfigDto::class.java -> call.receive<ConfigDto>()
AnimeDto::class.java -> call.receive<AnimeDto>()
EpisodeMappingDto::class.java -> call.receive<EpisodeMappingDto>()
GenericDto::class.java -> call.receive<GenericDto>()
MultiPartData::class.java -> call.receiveMultipart()
else -> call.receive<String>()
private fun fromString(value: String?, type: KType): Any? {
if (type.jvmErasure == Array<LangType>::class) {
return value?.takeIf { it.isNotBlank() }
?.split(",")
?.map(LangType::valueOf)
?.toTypedArray()
}

val converters: Map<KClass<*>, (String?) -> Any?> = mapOf(
UUID::class to { it?.let(UUID::fromString) },
CountryCode::class to { CountryCode.fromNullable(it) },
Platform::class to { Platform.fromNullable(it) },
Status::class to { Status.fromNullable(it) },
String::class to { it },
Int::class to { it?.toIntOrNull() },
)

return converters.entries.firstOrNull { (kClass, _) ->
kClass.starProjectedType.withNullability(true) == type || kClass.starProjectedType == type
}?.value?.invoke(value)
}

private fun handlePathParam(kParameter: KParameter, call: ApplicationCall): Any? {
val name = kParameter.findAnnotation<PathParam>()?.name ?: kParameter.name
val value = name?.let { call.parameters[name] }
return fromString(value, kParameter.type)
}

private fun handleQueryParam(kParameter: KParameter, call: ApplicationCall): Any? {
val name = kParameter.findAnnotation<QueryParam>()?.name ?: kParameter.name
val queryParamValue = name?.let { call.request.queryParameters[it] }

return when (kParameter.type) {
Int::class.starProjectedType.withNullability(true) -> queryParamValue?.toIntOrNull()
String::class.starProjectedType.withNullability(true) -> queryParamValue
CountryCode::class.starProjectedType.withNullability(true) -> CountryCode.fromNullable(queryParamValue)
UUID::class.starProjectedType.withNullability(true) -> queryParamValue?.let { UUID.fromString(it) }
Status::class.starProjectedType.withNullability(true) -> queryParamValue?.let {
try {
Status.valueOf(it)
} catch (e: Exception) {
null
}
}

else -> throw Exception("Unknown type ${kParameter.type}")
}
val value = name?.let { call.request.queryParameters[it] }
return fromString(value, kParameter.type)
}

private fun handlePathParam(kParameter: KParameter, parameters: Map<String, List<String>>): Any? {
val name = kParameter.findAnnotation<PathParam>()?.name ?: kParameter.name
val pathParamValue = parameters[name]?.firstOrNull()

return when (kParameter.type.javaType) {
UUID::class.java -> pathParamValue?.let { UUID.fromString(it) }
Platform::class.java -> pathParamValue?.let { Platform.valueOf(it) }
String::class.java -> pathParamValue
Int::class.java -> pathParamValue?.toIntOrNull()
else -> throw Exception("Unknown type ${kParameter.type}")
}
private suspend fun handleBodyParam(kParameter: KParameter, call: ApplicationCall): Any {
val type = kParameter.type

return if (type.isSubtypeOf(MultiPartData::class.starProjectedType))
call.receiveMultipart()
else
call.receive(type.jvmErasure)
}
Loading

0 comments on commit 6d3d2ac

Please sign in to comment.