diff --git a/src/main/kotlin/com/jeluchu/core/enums/AnimeFilterTypes.kt b/src/main/kotlin/com/jeluchu/core/enums/AnimeFilterTypes.kt new file mode 100644 index 0000000..1b97dbc --- /dev/null +++ b/src/main/kotlin/com/jeluchu/core/enums/AnimeFilterTypes.kt @@ -0,0 +1,13 @@ +package com.jeluchu.core.enums + +import kotlinx.serialization.Serializable + +@Serializable +enum class AnimeFilterTypes { + AIRING, + UPCOMING, + BYPOPULARITY, + FAVORITE, +} + +fun parseFilterType(type: String) = AnimeFilterTypes.entries.firstOrNull { it.name.equals(type, ignoreCase = true) } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/enums/AnimeTypes.kt b/src/main/kotlin/com/jeluchu/core/enums/AnimeTypes.kt new file mode 100644 index 0000000..e7fa87a --- /dev/null +++ b/src/main/kotlin/com/jeluchu/core/enums/AnimeTypes.kt @@ -0,0 +1,18 @@ +package com.jeluchu.core.enums + +import kotlinx.serialization.Serializable + +@Serializable +enum class AnimeTypes { + TV, + MOVIE, + OVA, + SPECIAL, + ONA, + MUSIC, + CM, + PV, + TV_SPECIAL, +} + +fun parseType(type: String) = AnimeTypes.entries.firstOrNull { it.name.equals(type, ignoreCase = true) } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/utils/Day.kt b/src/main/kotlin/com/jeluchu/core/enums/Day.kt similarity index 79% rename from src/main/kotlin/com/jeluchu/core/utils/Day.kt rename to src/main/kotlin/com/jeluchu/core/enums/Day.kt index ed18d38..5f1c48e 100644 --- a/src/main/kotlin/com/jeluchu/core/utils/Day.kt +++ b/src/main/kotlin/com/jeluchu/core/enums/Day.kt @@ -1,4 +1,4 @@ -package com.jeluchu.core.utils +package com.jeluchu.core.enums import kotlinx.serialization.Serializable diff --git a/src/main/kotlin/com/jeluchu/core/utils/TimeUnit.kt b/src/main/kotlin/com/jeluchu/core/enums/TimeUnit.kt similarity index 67% rename from src/main/kotlin/com/jeluchu/core/utils/TimeUnit.kt rename to src/main/kotlin/com/jeluchu/core/enums/TimeUnit.kt index 13c3b93..c23321a 100644 --- a/src/main/kotlin/com/jeluchu/core/utils/TimeUnit.kt +++ b/src/main/kotlin/com/jeluchu/core/enums/TimeUnit.kt @@ -1,4 +1,4 @@ -package com.jeluchu.core.utils +package com.jeluchu.core.enums enum class TimeUnit { DAY, diff --git a/src/main/kotlin/com/jeluchu/core/extensions/UpdatingExtensions.kt b/src/main/kotlin/com/jeluchu/core/extensions/UpdatingExtensions.kt index 36cd4ab..32ca6f4 100644 --- a/src/main/kotlin/com/jeluchu/core/extensions/UpdatingExtensions.kt +++ b/src/main/kotlin/com/jeluchu/core/extensions/UpdatingExtensions.kt @@ -1,6 +1,6 @@ package com.jeluchu.core.extensions -import com.jeluchu.core.utils.TimeUnit +import com.jeluchu.core.enums.TimeUnit import com.jeluchu.core.utils.TimerKey import com.mongodb.client.MongoCollection import com.mongodb.client.model.Filters.eq diff --git a/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt b/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt index e886e39..6e7c3f3 100644 --- a/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt +++ b/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt @@ -1,6 +1,6 @@ package com.jeluchu.core.messages -import com.jeluchu.core.utils.Day +import com.jeluchu.core.enums.Day sealed class ErrorMessages(val message: String) { data object NotFound : ErrorMessages("Nyaaaaaaaan! This request has not been found by our alpaca-neko") diff --git a/src/main/kotlin/com/jeluchu/core/models/jikan/anime/AnimeData.kt b/src/main/kotlin/com/jeluchu/core/models/jikan/anime/AnimeData.kt index 6d6742d..116b9b7 100644 --- a/src/main/kotlin/com/jeluchu/core/models/jikan/anime/AnimeData.kt +++ b/src/main/kotlin/com/jeluchu/core/models/jikan/anime/AnimeData.kt @@ -1,6 +1,8 @@ package com.jeluchu.core.models.jikan.anime -import com.jeluchu.core.utils.Day +import com.jeluchu.core.enums.Day +import com.jeluchu.core.utils.toVideoPromo +import com.jeluchu.features.rankings.models.TopEntity import com.jeluchu.features.schedule.models.DayEntity import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -281,5 +283,26 @@ data class AnimeData( image = images?.webp?.large.orEmpty(), title = titles?.first()?.title.orEmpty() ) + + fun AnimeData.toTopEntity( + page: Int, + rank: Int, + type: String, + subType: String, + ) = TopEntity( + malId = malId, + rank = rank, + score = score, + image = images?.webp?.large.orEmpty(), + title = titles?.first()?.title.orEmpty(), + url = url, + promo = trailer?.toVideoPromo(), + season = season, + year = year, + airing = airing, + type = type, + subtype = subType, + page = page + ) } } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/utils/Constants.kt b/src/main/kotlin/com/jeluchu/core/utils/Constants.kt index 763c3c4..7c6df1b 100644 --- a/src/main/kotlin/com/jeluchu/core/utils/Constants.kt +++ b/src/main/kotlin/com/jeluchu/core/utils/Constants.kt @@ -6,22 +6,26 @@ object BaseUrls { object Endpoints { const val SCHEDULES = "schedules" + const val TOP_ANIME = "top/anime" } object Routes { + const val TOP = "/top" + const val ANIME = "/anime" const val SCHEDULE = "/schedule" const val DIRECTORY = "/directory" - const val TOP_ANIME = "/top/anime" const val TOP_MANGA = "/top/manga" const val TOP_PEOPLE = "/top/people" const val ANIME_DETAILS = "/anime/{id}" const val SCHEDULE_DAY = "/schedule/{day}" const val TOP_CHARACTER = "/top/character" + const val TOP_ANIME = "/{type}/{filter}/{page}" } object TimerKey { const val KEY = "key" + const val RANKING = "ranking" const val SCHEDULE = "schedule" const val LAST_UPDATED = "lastUpdated" } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/utils/Mappers.kt b/src/main/kotlin/com/jeluchu/core/utils/Mappers.kt new file mode 100644 index 0000000..d130464 --- /dev/null +++ b/src/main/kotlin/com/jeluchu/core/utils/Mappers.kt @@ -0,0 +1,21 @@ +package com.jeluchu.core.utils + +import com.example.models.VideoPromo +import com.jeluchu.core.models.jikan.anime.ImageFormat +import com.jeluchu.core.models.jikan.anime.Trailer +import com.jeluchu.features.anime.models.anime.Images + +fun Trailer.toVideoPromo() = VideoPromo( + url = url.orEmpty(), + youtubeId = youtubeId.orEmpty(), + embedUrl = embedUrl.orEmpty(), + images = images?.toImages() ?: Images() +) + +fun ImageFormat.toImages() = Images( + generic = generic.orEmpty(), + small = small.orEmpty(), + medium = medium.orEmpty(), + large = large.orEmpty(), + maximum = maximum.orEmpty() +) \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/utils/parseJsonArrayToDocuments.kt b/src/main/kotlin/com/jeluchu/core/utils/ParseDocuments.kt similarity index 68% rename from src/main/kotlin/com/jeluchu/core/utils/parseJsonArrayToDocuments.kt rename to src/main/kotlin/com/jeluchu/core/utils/ParseDocuments.kt index a708842..514a9b5 100644 --- a/src/main/kotlin/com/jeluchu/core/utils/parseJsonArrayToDocuments.kt +++ b/src/main/kotlin/com/jeluchu/core/utils/ParseDocuments.kt @@ -1,5 +1,6 @@ package com.jeluchu.core.utils +import com.jeluchu.features.rankings.models.TopEntity import com.jeluchu.features.schedule.models.DayEntity import com.jeluchu.features.schedule.models.ScheduleData import kotlinx.serialization.encodeToString @@ -24,5 +25,15 @@ fun parseScheduleDataToDocuments(data: ScheduleData): List { processDay(data.saturday) processDay(data.sunday) + return documents +} + +fun parseScheduleDataToDocuments(data: List?): List { + val documents = mutableListOf() + data?.forEach { animeData -> + val animeJsonString = Json.encodeToString(animeData) + val document = Document.parse(animeJsonString) + documents.add(document) + } return documents } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt b/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt index 7657a5e..f86a6f7 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt @@ -4,6 +4,7 @@ import com.example.models.* import com.jeluchu.core.extensions.* import com.jeluchu.features.anime.models.anime.Images import com.jeluchu.features.anime.models.directory.AnimeDirectoryEntity +import com.jeluchu.features.rankings.models.TopEntity import com.jeluchu.features.schedule.models.DayEntity import org.bson.Document @@ -219,4 +220,20 @@ fun documentToScheduleDayEntity(doc: Document) = DayEntity( malId = doc.getIntSafe("malId"), image = doc.getStringSafe("image"), title = doc.getStringSafe("title") +) + +fun documentToTopEntity(doc: Document) = TopEntity( + malId = doc.getIntSafe("malId"), + rank = doc.getIntSafe("rank"), + score = doc.getFloatSafe("score"), + title = doc.getStringSafe("title"), + image = doc.getStringSafe("image"), + url = doc.getStringSafe("url"), + promo = doc.getDocumentSafe("promo")?.let { documentToVideoPromo(it) } ?: VideoPromo(), + season = doc.getStringSafe("season"), + year = doc.getIntSafe("year"), + airing = doc.getBooleanSafe("airing"), + type = doc.getStringSafe("type"), + subtype = doc.getStringSafe("subtype"), + page = doc.getIntSafe("page"), ) \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/features/rankings/models/TopEntity.kt b/src/main/kotlin/com/jeluchu/features/rankings/models/TopEntity.kt new file mode 100644 index 0000000..ad749f9 --- /dev/null +++ b/src/main/kotlin/com/jeluchu/features/rankings/models/TopEntity.kt @@ -0,0 +1,21 @@ +package com.jeluchu.features.rankings.models + +import com.example.models.VideoPromo +import kotlinx.serialization.Serializable + +@Serializable +data class TopEntity( + val malId: Int? = 0, + val rank: Int? = 0, + val score: Float? = 0f, + val title: String? = "", + val image: String? = "", + val url: String? = "", + val promo: VideoPromo? = VideoPromo(), + val season: String? = "", + val year: Int? = 0, + val airing: Boolean? = false, + val type: String? = "", + val subtype: String? = "", + val page: Int? = 0 +) \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/features/rankings/routes/RankingsRoutes.kt b/src/main/kotlin/com/jeluchu/features/rankings/routes/RankingsRoutes.kt index b5b40a8..6254e92 100644 --- a/src/main/kotlin/com/jeluchu/features/rankings/routes/RankingsRoutes.kt +++ b/src/main/kotlin/com/jeluchu/features/rankings/routes/RankingsRoutes.kt @@ -10,6 +10,11 @@ fun Route.rankingsEndpoints( mongoDatabase: MongoDatabase, service: RankingsService = RankingsService(mongoDatabase) ) { - getToJson(Routes.TOP_ANIME) { service.getAnimeByMalId(call) } - getToJson(Routes.TOP_MANGA) { service.getDirectory(call) } + route(Routes.TOP) { + route(Routes.ANIME) { + getToJson(Routes.TOP_ANIME) { service.getAnimeRanking(call) } + } + } + + getToJson(Routes.TOP_MANGA) { } } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/features/rankings/services/RankingsService.kt b/src/main/kotlin/com/jeluchu/features/rankings/services/RankingsService.kt index 83eb506..db2f2bc 100644 --- a/src/main/kotlin/com/jeluchu/features/rankings/services/RankingsService.kt +++ b/src/main/kotlin/com/jeluchu/features/rankings/services/RankingsService.kt @@ -1,16 +1,16 @@ package com.jeluchu.features.rankings.services import com.jeluchu.core.connection.RestClient +import com.jeluchu.core.enums.* 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.jikan.anime.AnimeData.Companion.toDayEntity +import com.jeluchu.core.models.jikan.anime.AnimeData.Companion.toTopEntity +import com.jeluchu.core.models.jikan.search.Search import com.jeluchu.core.utils.* -import com.jeluchu.features.anime.mappers.documentToAnimeDirectoryEntity -import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity import com.jeluchu.features.anime.mappers.documentToScheduleDayEntity -import com.jeluchu.features.schedule.models.ScheduleData +import com.jeluchu.features.anime.mappers.documentToTopEntity import com.jeluchu.features.schedule.models.ScheduleEntity import com.mongodb.client.MongoDatabase import com.mongodb.client.model.Filters @@ -25,52 +25,54 @@ class RankingsService( database: MongoDatabase ) { private val timers = database.getCollection("timers") - private val schedules = database.getCollection("schedule") + private val ranking = database.getCollection("ranking") suspend fun getAnimeRanking(call: RoutingCall) { + val paramType = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) + val paramFilter = call.parameters["filter"] ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) + val paramPage = call.parameters["page"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) + if (parseType(paramType) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidDay.message)) + if (parseFilterType(paramFilter) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidDay.message)) + + val timerKey = "${TimerKey.RANKING}_${paramType}_${paramFilter}_${paramPage}" + val needsUpdate = timers.needsUpdate( - amount = 30, - unit = TimeUnit.DAY, - key = TimerKey.SCHEDULE + amount = 1, + key = timerKey, + unit = TimeUnit.MINUTE ) if (needsUpdate) { - schedules.deleteMany(Document()) + ranking.deleteMany(Document()) - 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 params = mutableListOf() + params.add("type=$paramType") + params.add("page=$paramPage") + params.add("filter=$paramFilter") + + val response = RestClient.request( + BaseUrls.JIKAN + Endpoints.TOP_ANIME + "?${params.joinToString("&")}", + Search.serializer() + ).data?.mapIndexed { index, anime -> + anime.toTopEntity( + page = paramPage, + rank = index + 1, + type = paramType, + subType = paramFilter + ) + } val documentsToInsert = parseScheduleDataToDocuments(response) - if (documentsToInsert.isNotEmpty()) schedules.insertMany(documentsToInsert) - timers.update(TimerKey.SCHEDULE) + if (documentsToInsert.isNotEmpty()) ranking.insertMany(documentsToInsert) + timers.update(timerKey) call.respond(HttpStatusCode.OK, Json.encodeToString(response)) } else { - val elements = schedules.find().toList() - val directory = elements.map { documentToScheduleDayEntity(it) } + val elements = ranking.find().toList() + val directory = elements.map { documentToTopEntity(it) } val json = Json.encodeToString(directory) call.respond(HttpStatusCode.OK, json) } } - - suspend fun getScheduleByDay(call: RoutingCall) { - val param = call.parameters["day"] ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) - if (parseDay(param) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidDay.message)) - - val elements = schedules.find(Filters.eq("day", param.lowercase())).toList() - val directory = elements.map { documentToScheduleDayEntity(it) } - val json = Json.encodeToString(directory) - call.respond(HttpStatusCode.OK, json) - } - - private suspend fun getSchedule(day: Day) = - RestClient.request(BaseUrls.JIKAN + Endpoints.SCHEDULES + "/" + day, ScheduleEntity.serializer()) } diff --git a/src/main/kotlin/com/jeluchu/features/schedule/services/ScheduleService.kt b/src/main/kotlin/com/jeluchu/features/schedule/services/ScheduleService.kt index b2b0f70..da3c66f 100644 --- a/src/main/kotlin/com/jeluchu/features/schedule/services/ScheduleService.kt +++ b/src/main/kotlin/com/jeluchu/features/schedule/services/ScheduleService.kt @@ -1,6 +1,9 @@ package com.jeluchu.features.schedule.services import com.jeluchu.core.connection.RestClient +import com.jeluchu.core.enums.Day +import com.jeluchu.core.enums.TimeUnit +import com.jeluchu.core.enums.parseDay import com.jeluchu.core.extensions.needsUpdate import com.jeluchu.core.extensions.update import com.jeluchu.core.messages.ErrorMessages