Compare commits

..

No commits in common. "a113b7e677843741290bd6f816b53217467cf582" and "d7ca2965d2e3c7456f6b830a3c9209f9994c2ee3" have entirely different histories.

17 changed files with 152 additions and 476 deletions

View File

@ -1,11 +0,0 @@
package com.jeluchu.core.enums
import kotlinx.serialization.Serializable
@Serializable
enum class AnimeStatusTypes {
FINISHED, ONGOING, UPCOMING, UNKNOWN
}
val animeStatusTypesErrorList = AnimeStatusTypes.entries.joinToString(", ") { it.name.lowercase() }
fun parseAnimeStatusType(type: String) = AnimeStatusTypes.entries.firstOrNull { it.name.equals(type, ignoreCase = true) }

View File

@ -1,8 +0,0 @@
package com.jeluchu.core.enums
enum class Season {
WINTER, SPRING, SUMMER, FALL
}
val seasonsErrorList = AnimeTypes.entries.joinToString(", ") { it.name.lowercase() }
fun parseSeasons(type: String) = Season.entries.firstOrNull { it.name.equals(type, ignoreCase = true) }

View File

@ -12,7 +12,6 @@ sealed class ErrorMessages(val message: String) {
data object InvalidMangaType : ErrorMessages("Invalid 'type' parameter. Valid values are: ${MangaTypes.entries.joinToString(", ") { it.name.lowercase() }}")
data object InvalidSizeAndPage : ErrorMessages("Invalid page and size parameters")
data object InvalidTopAnimeType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeTypesErrorList")
data object InvalidAnimeStatusType : ErrorMessages("Invalid 'status' parameter. Valid values are: $animeStatusTypesErrorList")
data object InvalidTopAnimeFilterType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeFilterTypesErrorList")
data object InvalidTopMangaType : ErrorMessages("Invalid 'type' parameter. Valid values are: $mangaTypesErrorList")
data object InvalidTopMangaFilterType : ErrorMessages("Invalid 'type' parameter. Valid values are: $mangaFilterTypesErrorList")

View File

@ -1,38 +0,0 @@
package com.jeluchu.core.models
import com.jeluchu.core.extensions.getDocumentSafe
import com.jeluchu.core.extensions.getIntSafe
import com.jeluchu.core.extensions.getStringSafe
import com.jeluchu.core.utils.SeasonCalendar
import kotlinx.serialization.Serializable
import org.bson.Document
@Serializable
data class SimpleAnimeEntity(
val malId: Int,
val type: String,
val title: String,
val image: String,
val score: String,
val season: SeasonInfo
) {
@Serializable
data class SeasonInfo(
val year: Int? = null,
val station: String? = null
)
}
fun documentToSimpleAnimeEntity(doc: Document) = SimpleAnimeEntity(
malId = doc.getIntSafe("malId"),
title = doc.getStringSafe("title"),
type = doc.getStringSafe("type"),
score = doc.getStringSafe("score"),
image = doc.getStringSafe("poster"),
season = doc.getDocumentSafe("season")?.let { documentToSeasonInfo(it) } ?: SimpleAnimeEntity.SeasonInfo(),
)
fun documentToSeasonInfo(doc: Document) = SimpleAnimeEntity.SeasonInfo(
year = doc.getIntSafe("year"),
station = doc.getStringSafe("station")
)

View File

@ -1,47 +0,0 @@
package com.jeluchu.core.utils
import com.jeluchu.core.models.PaginationResponse
import com.mongodb.client.MongoCollection
import org.bson.Document
import org.bson.conversions.Bson
fun <T> getRemoteData(
filters: Bson,
mapper: (Document) -> T,
onQuerySuccess: (List<T>) -> Unit,
newCollection: MongoCollection<Document>,
remoteCollection: MongoCollection<Document>,
) {
newCollection.deleteMany(Document())
val query = remoteCollection
.find(filters)
.toList()
.map { mapper(it) }
onQuerySuccess(query)
}
suspend fun <T> getLocalData(
page: Int,
size: Int,
skipCount: Int,
mapper: (Document) -> T,
collection: MongoCollection<Document>,
onQuerySuccess: suspend (PaginationResponse<T>) -> Unit
) {
val query = collection
.find()
.skip(skipCount)
.limit(size)
.toList()
.map { mapper(it) }
val paginate = PaginationResponse(
page = page,
data = query,
size = query.size
)
onQuerySuccess(paginate)
}

View File

@ -52,13 +52,11 @@ object Routes {
const val ES = "/es"
const val EN = "/en"
const val TOP = "/top"
const val TAGS = "/tags"
const val NEWS = "/news"
const val ANIME = "/anime"
const val MANGA = "/manga"
const val PEOPLE = "/people"
const val SEARCH = "/search"
const val TOP_TEN = "/topTen"
const val GALLERY = "/gallery"
const val SCHEDULE = "/schedule"
const val RADIO_STATIONS = "/radio"
@ -70,13 +68,9 @@ object Routes {
const val EPISODES = "/episodes"
const val ID = "/{id}"
const val TYPE = "/{type}"
const val SEASON = "/season"
const val SEASON_PARAMS = "/{year}/{season}"
const val SEASON = "/{year}/{season}"
const val DAY = "/{day}"
const val THEMES = "/themes"
const val SUGGESTIONS = "/suggestions"
const val YEAR_INDEX = "/yearIndex"
const val RANDOM = "/random"
}
object TimerKey {
@ -92,7 +86,6 @@ object TimerKey {
object Collections {
const val TIMERS = "timers"
const val TOP_TEN = "top_ten"
const val NEWS_ES = "news_es"
const val NEWS_EN = "news_en"
const val SCHEDULES = "schedule"
@ -107,6 +100,5 @@ object Collections {
const val ANIME_DIRECTORY = "anime_directory"
const val CHARACTER_RANKING = "character_ranking"
const val ANIME_PICTURES_QUERY = "anime_pictures_query"
const val ANIME_RANKING_TOP_TEN = "anime_ranking_top_ten"
const val ANIME_PICTURES_RECENT = "anime_pictures_recent"
}

View File

@ -1,9 +1,44 @@
package com.jeluchu.core.utils
import com.jeluchu.features.rankings.models.AnimeTopEntity
import com.jeluchu.features.schedule.models.DayEntity
import com.jeluchu.features.schedule.models.ScheduleData
import kotlinx.serialization.KSerializer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.bson.Document
fun parseTopDataToDocuments(data: ScheduleData): List<Document> {
val documents = mutableListOf<Document>()
fun processDay(dayList: List<DayEntity>?) {
dayList?.forEach { animeData ->
val animeJsonString = Json.encodeToString(animeData)
val document = Document.parse(animeJsonString)
documents.add(document)
}
}
processDay(data.monday)
processDay(data.tuesday)
processDay(data.wednesday)
processDay(data.thursday)
processDay(data.friday)
processDay(data.saturday)
processDay(data.sunday)
return documents
}
fun parseTopDataToDocuments(data: List<AnimeTopEntity>?): List<Document> {
val documents = mutableListOf<Document>()
data?.forEach { animeData ->
val animeJsonString = Json.encodeToString(animeData)
val document = Document.parse(animeJsonString)
documents.add(document)
}
return documents
}
fun <T> parseDataToDocuments(data: List<T>?, serializer: KSerializer<T>): List<Document> {
val documents = mutableListOf<Document>()
data?.forEach { item ->

View File

@ -1,22 +0,0 @@
package com.jeluchu.core.utils
import com.jeluchu.core.enums.Season
import java.util.Calendar
import java.util.Locale
object SeasonCalendar {
private val calendar: Calendar by lazy {
Calendar.getInstance(Locale.getDefault())
}
val currentYear = calendar.get(Calendar.YEAR)
private val month = calendar.get(Calendar.MONTH)
val currentSeason = when (month) {
0, 1, 11 -> Season.WINTER
2, 3, 4 -> Season.SPRING
5, 6, 7 -> Season.SUMMER
8, 9, 10 -> Season.FALL
else -> Season.SPRING
}
}

View File

@ -1,10 +0,0 @@
package com.jeluchu.features.anime.models.seasons
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class YearSeasons(
@SerialName("year") val year: Int,
@SerialName("seasons") val seasons: List<String>
)

View File

@ -4,34 +4,20 @@ import com.jeluchu.core.extensions.getToJson
import com.jeluchu.core.utils.Routes
import com.jeluchu.features.anime.services.AnimeService
import com.jeluchu.features.anime.services.DirectoryService
import com.jeluchu.features.anime.services.SeasonService
import com.jeluchu.features.anime.services.TagsService
import com.mongodb.client.MongoDatabase
import io.ktor.server.routing.*
fun Route.animeEndpoints(
mongoDatabase: MongoDatabase,
service: AnimeService = AnimeService(mongoDatabase),
tagsService: TagsService = TagsService(mongoDatabase),
seasonService: SeasonService = SeasonService(mongoDatabase),
directoryService: DirectoryService = DirectoryService(mongoDatabase),
) = route(Routes.ANIME) {
getToJson { service.getAnimeByType(call) }
getToJson(Routes.ID) { service.getAnimeByMalId(call) }
getToJson(Routes.RANDOM) { service.getRandomAnime(call) }
getToJson(Routes.LAST_EPISODES) { service.getLastEpisodes(call) }
route(Routes.SUGGESTIONS) {
getToJson { tagsService.getAnimeByAnyTag(call) }
}
route(Routes.SEASON) {
getToJson { seasonService.getAnimeBySeason(call) }
getToJson(Routes.YEAR_INDEX) { seasonService.getYearsAndSeasons(call) }
}
route(Routes.DIRECTORY) {
getToJson { service.getDirectory(call) }
getToJson(Routes.TYPE) { directoryService.getAnimeByType(call) }
getToJson(Routes.SEASON) { directoryService.getAnimeBySeason(call) }
}
}

View File

@ -1,17 +1,13 @@
package com.jeluchu.features.anime.services
import com.jeluchu.core.connection.RestClient
import com.jeluchu.core.enums.AnimeStatusTypes
import com.jeluchu.core.enums.AnimeTypes
import com.jeluchu.core.enums.TimeUnit
import com.jeluchu.core.enums.parseAnimeStatusType
import com.jeluchu.core.enums.parseAnimeType
import com.jeluchu.core.extensions.needsUpdate
import com.jeluchu.core.extensions.update
import com.jeluchu.core.messages.ErrorMessages
import com.jeluchu.core.models.ErrorResponse
import com.jeluchu.core.models.PaginationResponse
import com.jeluchu.core.models.documentToSimpleAnimeEntity
import com.jeluchu.features.anime.models.lastepisodes.LastEpisodeEntity
import com.jeluchu.features.anime.models.lastepisodes.LastEpisodeEntity.Companion.toLastEpisodeData
import com.jeluchu.core.models.jikan.search.AnimeSearch
@ -23,9 +19,7 @@ import com.jeluchu.features.anime.mappers.documentToAnimeLastEpisodeEntity
import com.jeluchu.features.anime.mappers.documentToAnimeTypeEntity
import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.Aggregates
import com.mongodb.client.model.Filters
import com.mongodb.client.model.Sorts
import com.mongodb.client.model.Filters.eq
import io.ktor.http.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
@ -69,7 +63,7 @@ class AnimeService(
call.respond(HttpStatusCode.OK, Json.encodeToString(response))
} else {
val animes = directoryCollection
.find(Filters.eq("type", type.uppercase()))
.find(eq("type", type.uppercase()))
.skip(skipCount)
.limit(size)
.toList()
@ -90,7 +84,7 @@ class AnimeService(
suspend fun getAnimeByMalId(call: RoutingCall) = try {
val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message)
directoryCollection.find(Filters.eq("malId", id)).firstOrNull()?.let { anime ->
directoryCollection.find(eq("malId", id)).firstOrNull()?.let { anime ->
val info = documentToMoreInfoEntity(anime)
call.respond(HttpStatusCode.OK, Json.encodeToString(info))
} ?: call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.AnimeNotFound.message))
@ -98,42 +92,6 @@ class AnimeService(
call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message))
}
suspend fun getRandomAnime(call: RoutingCall) = try {
val nsfw = call.request.queryParameters["nsfw"]?.toBoolean() ?: false
val filters = mutableListOf<org.bson.conversions.Bson>().apply {
add(Filters.`in`("type", listOf(
AnimeTypes.TV,
AnimeTypes.MOVIE,
AnimeTypes.OVA,
AnimeTypes.SPECIAL,
AnimeTypes.ONA,
AnimeTypes.TV_SPECIAL
)))
add(Filters.nin("status", listOf(
AnimeStatusTypes.UPCOMING
)))
if (!nsfw) add(Filters.eq("nsfw", false))
}
val aggregates = listOf(
Aggregates.match(Filters.and(filters)),
Aggregates.sample(1)
)
directoryCollection.aggregate(aggregates).firstOrNull()?.let { anime ->
val info = documentToMoreInfoEntity(anime)
call.response.headers.append("Cache-Control", "no-store")
call.respond(HttpStatusCode.OK, Json.encodeToString(info))
} ?: call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.AnimeNotFound.message))
} catch (ex: Exception) {
call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message))
}
suspend fun getLastEpisodes(call: RoutingCall) = try {
val dayOfWeek = LocalDate.now()
.dayOfWeek
@ -152,42 +110,38 @@ class AnimeService(
if (needsUpdate) {
collection.deleteMany(Document())
val animes = mutableListOf<LastEpisodeEntity>()
RestClient.request(
BaseUrls.JIKAN + "anime?status=airing&type=tv&page=1",
val response = RestClient.request(
BaseUrls.JIKAN + "anime?status=airing&type=tv",
AnimeSearch.serializer()
).let { firstPage ->
val totalPage = firstPage.pagination?.lastPage ?: 2
)
firstPage.data?.let { firstAnimes ->
firstAnimes.map { it.toLastEpisodeData() }.let { animes.addAll(it) }
}
val animes = mutableListOf<LastEpisodeEntity>()
val totalPage = response.pagination?.lastPage ?: 0
response.data?.map { it.toLastEpisodeData() }.orEmpty().let { animes.addAll(it) }
for (page in 2..totalPage) {
RestClient.request(
val responsePage = RestClient.request(
BaseUrls.JIKAN + "anime?status=airing&type=tv&page=$page",
AnimeSearch.serializer()
).data?.let { pagesAnimes ->
animes.addAll(pagesAnimes.map { it.toLastEpisodeData() })
).data?.map { it.toLastEpisodeData() }.orEmpty()
animes.addAll(responsePage)
delay(1000)
}
}
}
val documentsToInsert = parseDataToDocuments(animes.distinctBy { it.malId }, LastEpisodeEntity.serializer())
val documentsToInsert = parseDataToDocuments(animes, LastEpisodeEntity.serializer())
if (documentsToInsert.isNotEmpty()) collection.insertMany(documentsToInsert)
timers.update(timerKey)
val queryDb = collection
.find(Filters.eq("day", dayOfWeek))
.find(eq("day", dayOfWeek))
.toList()
val elements = queryDb.map { documentToAnimeLastEpisodeEntity(it) }
call.respond(HttpStatusCode.OK, Json.encodeToString(elements))
} else {
val elements = collection
.find(Filters.eq("day", dayOfWeek))
.find(eq("day", dayOfWeek))
.toList()
.map { documentToAnimeLastEpisodeEntity(it) }
@ -196,25 +150,4 @@ class AnimeService(
} catch (ex: Exception) {
call.respond(HttpStatusCode.Unauthorized, ErrorResponse(ErrorMessages.UnauthorizedMongo.message))
}
suspend fun getAnimeByType(call: RoutingCall) = try {
val type = call.request.queryParameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopAnimeType.message)
val status = call.request.queryParameters["status"] ?: throw IllegalArgumentException(ErrorMessages.InvalidAnimeStatusType.message)
val nsfw = call.request.queryParameters["nsfw"].toBoolean()
val animes = directoryCollection.find(
Filters.and(
Filters.eq("type", parseAnimeType(type)),
Filters.eq("status", parseAnimeStatusType(status)),
Filters.eq("nsfw", nsfw),
)
)
.sort(Sorts.descending("aired.from"))
.toList()
val elements = animes.map { documentToSimpleAnimeEntity(it) }
call.respond(HttpStatusCode.OK, Json.encodeToString(elements))
} catch (ex: Exception) {
call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message))
}
}

View File

@ -4,10 +4,9 @@ import com.jeluchu.core.enums.TimeUnit
import com.jeluchu.core.enums.parseAnimeType
import com.jeluchu.core.extensions.*
import com.jeluchu.core.messages.ErrorMessages
import com.jeluchu.core.models.PaginationResponse
import com.jeluchu.core.utils.Collections
import com.jeluchu.core.utils.TimerKey
import com.jeluchu.core.utils.getLocalData
import com.jeluchu.core.utils.getRemoteData
import com.jeluchu.features.anime.mappers.documentToAnimeDirectoryEntity
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
@ -18,6 +17,7 @@ import io.ktor.server.routing.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.bson.Document
import org.bson.conversions.Bson
class DirectoryService(
private val database: MongoDatabase,
@ -58,4 +58,83 @@ class DirectoryService(
onQuerySuccess = { data -> call.respond(HttpStatusCode.OK, Json.encodeToString(data)) }
)
}
suspend fun getAnimeBySeason(call: RoutingCall) {
val year = call.getIntSafeParam("year")
val season = call.getStringSafeParam("season")
val page = call.getIntSafeQueryParam("page", 1)
val size = call.getIntSafeQueryParam("size", 10)
val skipCount = (page - 1) * size
val timerKey = "${TimerKey.ANIME_TYPE}${year}_${season.lowercase()}"
val collection = database.getCollection(timerKey)
if (page < 1 || size < 1) call.badRequestError(ErrorMessages.InvalidSizeAndPage.message)
if (timers.needsUpdate(timerKey, 30, TimeUnit.DAY)) {
getRemoteData(
newCollection = collection,
remoteCollection = directory,
mapper = { documentToAnimeDirectoryEntity(it) },
filters = Filters.and(
Filters.eq("year", year),
Filters.eq("season", season.lowercase())
),
onQuerySuccess = { data ->
val documents = data.map { Document.parse(Json.encodeToString(it)) }
if (documents.isNotEmpty()) collection.insertMany(documents)
timers.update(timerKey)
}
)
}
getLocalData(
page = page,
size = size,
skipCount = skipCount,
collection = collection,
mapper = { documentToAnimeDirectoryEntity(it) },
onQuerySuccess = { data -> call.respond(HttpStatusCode.OK, Json.encodeToString(data)) }
)
}
}
private fun <T> getRemoteData(
filters: Bson,
mapper: (Document) -> T,
onQuerySuccess: (List<T>) -> Unit,
newCollection: MongoCollection<Document>,
remoteCollection: MongoCollection<Document>,
) {
newCollection.deleteMany(Document())
val query = remoteCollection
.find(filters)
.toList()
.map { mapper(it) }
onQuerySuccess(query)
}
private suspend fun <T> getLocalData(
page: Int,
size: Int,
skipCount: Int,
mapper: (Document) -> T,
collection: MongoCollection<Document>,
onQuerySuccess: suspend (PaginationResponse<T>) -> Unit
) {
val query = collection
.find()
.skip(skipCount)
.limit(size)
.toList()
.map { mapper(it) }
val paginate = PaginationResponse(
page = page,
data = query,
size = query.size
)
onQuerySuccess(paginate)
}

View File

@ -1,83 +0,0 @@
package com.jeluchu.features.anime.services
import com.jeluchu.core.enums.parseSeasons
import com.jeluchu.core.models.documentToSimpleAnimeEntity
import com.jeluchu.core.utils.Collections
import com.jeluchu.core.utils.SeasonCalendar
import com.jeluchu.features.anime.models.seasons.YearSeasons
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.Accumulators
import com.mongodb.client.model.Aggregates
import com.mongodb.client.model.Filters
import com.mongodb.client.model.Sorts
import io.ktor.http.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.bson.Document
import java.time.Year
class SeasonService(
private val database: MongoDatabase,
private val directory: MongoCollection<Document> = database.getCollection(Collections.ANIME_DIRECTORY)
) {
suspend fun getAnimeBySeason(call: RoutingCall) {
val year = call.request.queryParameters["year"]?.toInt() ?: SeasonCalendar.currentYear
val station = parseSeasons(call.request.queryParameters["station"] ?: SeasonCalendar.currentSeason.name)
?: SeasonCalendar.currentSeason
val query = directory.find(
Filters.and(
Filters.eq("season.year", year),
Filters.eq("season.station", station),
Filters.ne("type", "MUSIC"),
Filters.ne("type", "PV"),
)
)
.toList()
.map { documentToSimpleAnimeEntity(it) }
call.respond(HttpStatusCode.OK, Json.encodeToString(query))
}
suspend fun getYearsAndSeasons(call: RoutingCall) {
val currentYear = Year.now().value
val validSeasons = listOf("SUMMER", "FALL", "WINTER", "SPRING")
val pipeline = listOf(
Aggregates.match(
Document(
"\$and", listOf(
Document("season.year", Document("\$gt", 0)),
Document("season.year", Document("\$lte", currentYear)),
Document("season.station", Document("\$in", validSeasons))
)
)
),
Aggregates.group(
"\$season.year",
Accumulators.addToSet("seasons", "\$season.station")
),
Aggregates.project(
Document().apply {
put("year", "\$_id")
put("seasons", 1)
put("_id", 0)
}
),
Aggregates.sort(Sorts.descending("year"))
)
val results = directory.aggregate(pipeline).toList()
val index = results.map { document ->
YearSeasons(
year = document.getInteger("year"),
seasons = document.getList("seasons", String::class.java)
)
}
call.respond(HttpStatusCode.OK, Json.encodeToString(index))
}
}

View File

@ -1,55 +0,0 @@
package com.jeluchu.features.anime.services
import com.jeluchu.core.enums.AnimeStatusTypes
import com.jeluchu.core.enums.AnimeTypes
import com.jeluchu.core.models.documentToSimpleAnimeEntity
import com.jeluchu.core.utils.Collections
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.Filters
import io.ktor.http.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.bson.Document
class TagsService(
private val database: MongoDatabase,
private val directory: MongoCollection<Document> = database.getCollection(Collections.ANIME_DIRECTORY)
) {
suspend fun getAnimeByAnyTag(call: RoutingCall) {
val tags = call.request.queryParameters["tags"].orEmpty()
val nsfw = call.request.queryParameters["nsfw"].toBoolean()
val tagsList = if (tags.isNotEmpty()) {
tags.split(",").map { it.trim() }
} else emptyList()
if (tagsList.isEmpty()) {
call.respond(HttpStatusCode.BadRequest, "No tags provided")
return
}
val filters = mutableListOf<org.bson.conversions.Bson>().apply {
add(Filters.or(
Filters.`in`("tags.es", tagsList),
Filters.`in`("tags.en", tagsList)
))
add(Filters.`in`("status", listOf(AnimeStatusTypes.FINISHED, AnimeStatusTypes.ONGOING)))
add(Filters.ne("type", AnimeTypes.MUSIC))
add(Filters.ne("type", AnimeTypes.PV))
add(Filters.ne("type", AnimeTypes.CM))
if (!nsfw) add(Filters.eq("nsfw", false))
}
val query = directory.find(Filters.and(filters))
.toList()
.map { documentToSimpleAnimeEntity(it) }
call.respond(HttpStatusCode.OK, Json.encodeToString(query))
}
}

View File

@ -12,10 +12,6 @@ fun Route.rankingsEndpoints(
) = route(Routes.TOP) {
route(Routes.ANIME) {
getToJson { service.getAnimeRanking(call) }
route(Routes.TOP_TEN) {
getToJson { service.getAnimeTopTenRanking(call) }
}
}
route(Routes.MANGA) {
getToJson { service.getMangaRanking(call) }

View File

@ -43,7 +43,6 @@ class RankingsService(
private val mangaRanking = database.getCollection(Collections.MANGA_RANKING)
private val peopleRanking = database.getCollection(Collections.PEOPLE_RANKING)
private val characterRanking = database.getCollection(Collections.CHARACTER_RANKING)
private val animeRankingTopTen = database.getCollection(Collections.ANIME_RANKING_TOP_TEN)
suspend fun getAnimeRanking(call: RoutingCall) {
val filter = call.request.queryParameters["filter"] ?: "airing"
@ -328,65 +327,4 @@ class RankingsService(
call.respond(HttpStatusCode.OK, Json.encodeToString(response))
}
}
suspend fun getAnimeTopTenRanking(call: RoutingCall) {
val filter = call.request.queryParameters["filter"] ?: "airing"
val type = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopAnimeType.message)
if (parseAnimeType(type) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidTopAnimeType.message))
val timerKey = "${Collections.ANIME_RANKING}_${Collections.TOP_TEN}_${type}_${filter}"
val needsUpdate = timers.needsUpdate(
amount = 7,
key = timerKey,
unit = TimeUnit.DAY
)
if (needsUpdate) {
animeRankingTopTen.deleteMany(
Filters.and(
Filters.eq("type", type),
Filters.eq("subtype", filter)
)
)
val params = mutableListOf<String>()
params.add("type=$type")
params.add("filter=$filter")
val response = RestClient.request(
BaseUrls.JIKAN + Endpoints.TOP_ANIME + "?${params.joinToString("&")}",
AnimeSearch.serializer()
).data?.map { anime ->
anime.toAnimeTopEntity(
page = 0,
top = "anime",
type = type,
subType = filter
)
}.orEmpty().take(11).distinctBy { it.malId }
val documentsToInsert = parseDataToDocuments(response, AnimeTopEntity.serializer())
if (documentsToInsert.isNotEmpty()) animeRankingTopTen.insertMany(documentsToInsert)
timers.update(timerKey)
val elements = documentsToInsert.map { documentToAnimeTopEntity(it) }
call.respond(HttpStatusCode.OK, Json.encodeToString(elements))
} else {
val animes = animeRankingTopTen
.find(
Filters.and(
Filters.eq("type", type),
Filters.eq("subtype", filter)
)
)
.toList()
val elements = animes.map { documentToAnimeTopEntity(it) }
call.respond(HttpStatusCode.OK, Json.encodeToString(elements))
}
}
}

View File

@ -11,7 +11,6 @@ import com.jeluchu.core.models.ErrorResponse
import com.jeluchu.core.models.jikan.anime.AnimeData.Companion.toDayEntity
import com.jeluchu.core.utils.*
import com.jeluchu.features.anime.mappers.documentToScheduleDayEntity
import com.jeluchu.features.schedule.models.DayEntity
import com.jeluchu.features.schedule.models.ScheduleData
import com.jeluchu.features.schedule.models.ScheduleEntity
import com.mongodb.client.MongoDatabase
@ -38,20 +37,22 @@ class ScheduleService(
if (needsUpdate) {
schedules.deleteMany(Document())
val documents = mutableListOf<Document>()
Day.entries.forEach { day ->
val animes = getSchedule(day).data?.map { it.toDayEntity(day) }.orEmpty()
val documentsToInsert = parseDataToDocuments(animes, DayEntity.serializer())
if (documentsToInsert.isNotEmpty()) {
documents.addAll(documentsToInsert)
schedules.insertMany(documentsToInsert)
}
}
val response = ScheduleData(
sunday = getSchedule(Day.SUNDAY).data?.map { it.toDayEntity(Day.SUNDAY) }.orEmpty(),
friday = getSchedule(Day.FRIDAY).data?.map { it.toDayEntity(Day.FRIDAY) }.orEmpty(),
monday = getSchedule(Day.MONDAY).data?.map { it.toDayEntity(Day.MONDAY) }.orEmpty(),
tuesday = getSchedule(Day.TUESDAY).data?.map { it.toDayEntity(Day.TUESDAY) }.orEmpty(),
thursday = getSchedule(Day.THURSDAY).data?.map { it.toDayEntity(Day.THURSDAY) }.orEmpty(),
saturday = getSchedule(Day.SATURDAY).data?.map { it.toDayEntity(Day.SATURDAY) }.orEmpty(),
wednesday = getSchedule(Day.WEDNESDAY).data?.map { it.toDayEntity(Day.WEDNESDAY) }.orEmpty()
)
val elements = parseTopDataToDocuments(response)
if (elements.isNotEmpty()) schedules.insertMany(elements)
timers.update(TimerKey.SCHEDULE)
call.respond(HttpStatusCode.OK, documents.documentWeekMapper())
call.respond(HttpStatusCode.OK, elements.documentWeekMapper())
} else {
val elements = schedules.find().toList()
call.respond(HttpStatusCode.OK, elements.documentWeekMapper())
@ -77,16 +78,7 @@ class ScheduleService(
)
private fun List<Document>.documentWeekMapper(): String {
val elements = map { documentToScheduleDayEntity(it) }
return Json.encodeToString(ScheduleData(
monday = elements.filter { it.day == Day.MONDAY.name.lowercase() }.distinctBy { it.malId },
tuesday = elements.filter { it.day == Day.TUESDAY.name.lowercase() }.distinctBy { it.malId },
wednesday = elements.filter { it.day == Day.WEDNESDAY.name.lowercase() }.distinctBy { it.malId },
thursday = elements.filter { it.day == Day.THURSDAY.name.lowercase() }.distinctBy { it.malId },
friday = elements.filter { it.day == Day.FRIDAY.name.lowercase() }.distinctBy { it.malId },
saturday = elements.filter { it.day == Day.SATURDAY.name.lowercase() }.distinctBy { it.malId },
sunday = elements.filter { it.day == Day.SUNDAY.name.lowercase() }.distinctBy { it.malId }
))
val directory = map { documentToScheduleDayEntity(it) }
return Json.encodeToString(directory)
}
}