diff --git a/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt b/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt index aeac368..0c87737 100644 --- a/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt +++ b/src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt @@ -8,10 +8,13 @@ sealed class ErrorMessages(val message: String) { data object InvalidMalId : ErrorMessages("The provided id of malId is invalid") data object InvalidDay : ErrorMessages("Invalid 'day' parameter. Valid values are: ${Day.entries.joinToString(", ") { it.name.lowercase() }}") data object InvalidAnimeType : ErrorMessages("Invalid 'type' parameter. Valid values are: ${AnimeTypes.entries.joinToString(", ") { it.name.lowercase() }}") + 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 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") data object InvalidInput : ErrorMessages("Invalid input provided") + data object InvalidValueTopPage : ErrorMessages("Value 26 is higher than the configured '25' max value") data object UnauthorizedMongo : ErrorMessages("Check the MongoDb Connection String to be able to correctly access this request.") } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/models/PaginacionResponse.kt b/src/main/kotlin/com/jeluchu/core/models/PaginacionResponse.kt new file mode 100644 index 0000000..658b004 --- /dev/null +++ b/src/main/kotlin/com/jeluchu/core/models/PaginacionResponse.kt @@ -0,0 +1,12 @@ +package com.jeluchu.core.models + +import kotlinx.serialization.Serializable + +@Serializable +data class PaginationResponse( + val page: Int = 0, + val size: Int = 0, + val totalPages: Int = 0, + val totalItems: Int = 0, + val data: List = emptyList() +) diff --git a/src/main/kotlin/com/jeluchu/core/models/jikan/character/CharacterData.kt b/src/main/kotlin/com/jeluchu/core/models/jikan/character/CharacterData.kt index 97d3c9c..1db3a31 100644 --- a/src/main/kotlin/com/jeluchu/core/models/jikan/character/CharacterData.kt +++ b/src/main/kotlin/com/jeluchu/core/models/jikan/character/CharacterData.kt @@ -37,7 +37,7 @@ data class CharacterData( top: String ) = CharacterTopEntity( malId = malId, - image = images?.webp?.large.orEmpty(), + image = images?.jpg?.generic.orEmpty(), name = name, nameKanji = nameKanji, top = top, diff --git a/src/main/kotlin/com/jeluchu/core/models/jikan/people/PeopleData.kt b/src/main/kotlin/com/jeluchu/core/models/jikan/people/PeopleData.kt index bf9e7b6..cea7601 100644 --- a/src/main/kotlin/com/jeluchu/core/models/jikan/people/PeopleData.kt +++ b/src/main/kotlin/com/jeluchu/core/models/jikan/people/PeopleData.kt @@ -46,7 +46,7 @@ data class PeopleData( top: String ) = PeopleTopEntity( malId = malId, - image = images?.webp?.large.orEmpty(), + image = images?.jpg?.generic.orEmpty(), name = name, givenName = givenName, familyName = familyName, diff --git a/src/main/kotlin/com/jeluchu/core/utils/Constants.kt b/src/main/kotlin/com/jeluchu/core/utils/Constants.kt index 1f68b07..ab7e2ad 100644 --- a/src/main/kotlin/com/jeluchu/core/utils/Constants.kt +++ b/src/main/kotlin/com/jeluchu/core/utils/Constants.kt @@ -29,7 +29,7 @@ object Routes { const val CHARACTER = "/characters" const val LAST_EPISODES = "/lastEpisodes" const val ID = "/{id}" - const val TYPE = "/{type}" + const val ANIME_TYPE = "/{type}" const val DAY = "/{day}" const val TOP_CHARACTER = "/top/character" const val RANKINGS = "/{type}/{filter}/{page}" 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 9733b7f..61dfd99 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt @@ -6,9 +6,11 @@ import com.jeluchu.features.anime.models.anime.* import com.jeluchu.features.anime.models.directory.AnimeDirectoryEntity import com.jeluchu.features.anime.models.directory.AnimeTypeEntity import com.jeluchu.features.rankings.models.AnimeTopEntity +import com.jeluchu.features.rankings.models.CharacterTopEntity +import com.jeluchu.features.rankings.models.MangaTopEntity +import com.jeluchu.features.rankings.models.PeopleTopEntity import com.jeluchu.features.schedule.models.DayEntity import org.bson.Document -import java.sql.Timestamp import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -231,7 +233,7 @@ fun documentToScheduleDayEntity(doc: Document) = DayEntity( title = doc.getStringSafe("title") ) -fun documentToTopEntity(doc: Document) = AnimeTopEntity( +fun documentToAnimeTopEntity(doc: Document) = AnimeTopEntity( malId = doc.getIntSafe("malId"), rank = doc.getIntSafe("rank"), score = doc.getFloatSafe("score"), @@ -247,6 +249,41 @@ fun documentToTopEntity(doc: Document) = AnimeTopEntity( page = doc.getIntSafe("page"), ) +fun documentToMangaTopEntity(doc: Document) = MangaTopEntity( + malId = doc.getIntSafe("malId"), + rank = doc.getIntSafe("rank"), + score = doc.getDoubleSafe("score"), + title = doc.getStringSafe("title"), + image = doc.getStringSafe("image"), + url = doc.getStringSafe("url"), + volumes = doc.getIntSafe("volumes"), + chapters = doc.getIntSafe("chapters"), + status = doc.getStringSafe("status"), + type = doc.getStringSafe("type"), + subtype = doc.getStringSafe("subtype"), + page = doc.getIntSafe("page"), +) + +fun documentToPeopleTopEntity(doc: Document) = PeopleTopEntity( + malId = doc.getIntSafe("malId"), + name = doc.getStringSafe("name"), + givenName = doc.getStringSafe("givenName"), + familyName = doc.getStringSafe("familyName"), + image = doc.getStringSafe("image"), + birthday = doc.getStringSafe("birthday"), + page = doc.getIntSafe("page"), + top = doc.getStringSafe("top"), +) + +fun documentToCharacterTopEntity(doc: Document) = CharacterTopEntity( + malId = doc.getIntSafe("malId"), + name = doc.getStringSafe("name"), + nameKanji = doc.getStringSafe("nameKanji"), + image = doc.getStringSafe("image"), + top = doc.getStringSafe("top"), + page = doc.getIntSafe("page"), +) + fun documentToAnimeTypeEntity(doc: Document) = AnimeTypeEntity( score = doc.getString("score"), malId = doc.getIntSafe("malId"), diff --git a/src/main/kotlin/com/jeluchu/features/anime/routes/AnimeRoutes.kt b/src/main/kotlin/com/jeluchu/features/anime/routes/AnimeRoutes.kt index 1ddb313..d2af6fb 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/routes/AnimeRoutes.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/routes/AnimeRoutes.kt @@ -19,6 +19,6 @@ fun Route.animeEndpoints( route(Routes.DIRECTORY) { getToJson { service.getDirectory(call) } - getToJson(Routes.TYPE) { directoryService.getAnimeByType(call) } + getToJson(Routes.ANIME_TYPE) { directoryService.getAnimeByType(call) } } } \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt b/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt index 7b03868..38101af 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt @@ -1,12 +1,15 @@ package com.jeluchu.features.anime.services import com.jeluchu.core.connection.RestClient +import com.jeluchu.core.enums.AnimeTypes import com.jeluchu.core.enums.Day import com.jeluchu.core.enums.TimeUnit +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.animeflv.lastepisodes.LastEpisodeData.Companion.toEpisodeEntity import com.jeluchu.core.models.animeflv.lastepisodes.LastEpisodes import com.jeluchu.core.models.jikan.anime.AnimeData.Companion.toDayEntity @@ -14,10 +17,7 @@ import com.jeluchu.core.utils.BaseUrls import com.jeluchu.core.utils.Collections import com.jeluchu.core.utils.Endpoints import com.jeluchu.core.utils.TimerKey -import com.jeluchu.features.anime.mappers.documentToAnimeDirectoryEntity -import com.jeluchu.features.anime.mappers.documentToLastEpisodesEntity -import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity -import com.jeluchu.features.anime.mappers.documentToScheduleDayEntity +import com.jeluchu.features.anime.mappers.* import com.jeluchu.features.schedule.models.ScheduleEntity import com.mongodb.client.MongoDatabase import com.mongodb.client.model.Filters @@ -36,10 +36,46 @@ class AnimeService( private val lastEpisodesCollection = database.getCollection(Collections.LAST_EPISODES) suspend fun getDirectory(call: RoutingCall) = try { - val elements = directoryCollection.find().toList() - val directory = elements.map { documentToAnimeDirectoryEntity(it) } - val json = Json.encodeToString(directory) - call.respond(HttpStatusCode.OK, json) + val type = call.request.queryParameters["type"].orEmpty() + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 10 + + if (page < 1 || size < 1) call.respond(HttpStatusCode.BadRequest, ErrorMessages.InvalidSizeAndPage.message) + val skipCount = (page - 1) * size + + if (parseAnimeType(type) == null) { + val animes = directoryCollection + .find() + .skip(skipCount) + .limit(size) + .toList() + + val elements = animes.map { documentToAnimeDirectoryEntity(it) } + + val response = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) + } else { + val animes = directoryCollection + .find(Filters.eq("type", type.uppercase())) + .skip(skipCount) + .limit(size) + .toList() + + val elements = animes.map { documentToAnimeTypeEntity(it) } + + val response = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) + } } catch (ex: Exception) { call.respond(HttpStatusCode.Unauthorized, ErrorResponse(ErrorMessages.UnauthorizedMongo.message)) } @@ -87,5 +123,10 @@ class AnimeService( val directory = map { documentToLastEpisodesEntity(it) } return Json.encodeToString(directory) } + + private fun List.documentAnimeTypeMapper(): String { + val directory = map { documentToAnimeTypeEntity(it) } + return Json.encodeToString(directory) + } } diff --git a/src/main/kotlin/com/jeluchu/features/anime/services/DirectoryService.kt b/src/main/kotlin/com/jeluchu/features/anime/services/DirectoryService.kt index 7e1aab0..72f50cf 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/services/DirectoryService.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/services/DirectoryService.kt @@ -6,6 +6,7 @@ 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.utils.Collections import com.jeluchu.core.utils.TimerKey import com.jeluchu.features.anime.mappers.documentToAnimeTypeEntity @@ -25,7 +26,13 @@ class DirectoryService( private val directory = database.getCollection(Collections.ANIME_DETAILS) suspend fun getAnimeByType(call: RoutingCall) { + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 10 val param = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidAnimeType.message) + + if (page < 1 || size < 1) call.respond(HttpStatusCode.BadRequest, ErrorMessages.InvalidSizeAndPage.message) + val skipCount = (page - 1) * size + if (parseAnimeType(param) == null) call.respond( HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidAnimeType.message) @@ -42,16 +49,39 @@ class DirectoryService( val collection = database.getCollection(timerKey) collection.deleteMany(Document()) - val animes = directory.find(Filters.eq("type", param.uppercase())).toList() + val animes = directory + .find(Filters.eq("type", param.uppercase())) + .skip(skipCount) + .limit(size) + .toList() + val animeTypes = animes.map { documentToAnimeTypeEntity(it) } val documents = animeTypes.map { anime -> Document.parse(Json.encodeToString(anime)) } if (documents.isNotEmpty()) collection.insertMany(documents) timers.update(timerKey) - call.respond(HttpStatusCode.OK, Json.encodeToString(animeTypes)) + val response = PaginationResponse( + page = page, + size = size, + data = animeTypes, + totalItems = directory.countDocuments().toInt() + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) } else { - val elements = directory.find().toList() - call.respond(HttpStatusCode.OK, elements.documentAnimeTypeMapper()) + val elements = directory.find() + .skip(skipCount) + .limit(size) + .toList() + + val response = PaginationResponse( + page = page, + size = size, + totalItems = directory.countDocuments().toInt(), + data = elements.map { documentToAnimeTypeEntity(it) } + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) } } 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 b6baea0..d62e74e 100644 --- a/src/main/kotlin/com/jeluchu/features/rankings/routes/RankingsRoutes.kt +++ b/src/main/kotlin/com/jeluchu/features/rankings/routes/RankingsRoutes.kt @@ -11,15 +11,15 @@ fun Route.rankingsEndpoints( service: RankingsService = RankingsService(mongoDatabase) ) = route(Routes.TOP) { route(Routes.ANIME) { - getToJson(Routes.RANKINGS) { service.getAnimeRanking(call) } + getToJson { service.getAnimeRanking(call) } } route(Routes.MANGA) { - getToJson(Routes.RANKINGS) { service.getMangaRanking(call) } + getToJson { service.getMangaRanking(call) } } route(Routes.PEOPLE) { - getToJson(Routes.PAGE) { service.getPeopleRanking(call) } + getToJson { service.getPeopleRanking(call) } } route(Routes.CHARACTER) { - getToJson(Routes.PAGE) { service.getCharacterRanking(call) } + getToJson { service.getCharacterRanking(call) } } } \ 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 fc913ab..c054865 100644 --- a/src/main/kotlin/com/jeluchu/features/rankings/services/RankingsService.kt +++ b/src/main/kotlin/com/jeluchu/features/rankings/services/RankingsService.kt @@ -6,6 +6,7 @@ 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.jikan.anime.AnimeData.Companion.toAnimeTopEntity import com.jeluchu.core.models.jikan.character.CharacterSearch import com.jeluchu.core.models.jikan.manga.MangaData.Companion.toMangaTopEntity @@ -17,7 +18,10 @@ import com.jeluchu.core.utils.BaseUrls import com.jeluchu.core.utils.Collections import com.jeluchu.core.utils.Endpoints import com.jeluchu.core.utils.parseDataToDocuments -import com.jeluchu.features.anime.mappers.documentToTopEntity +import com.jeluchu.features.anime.mappers.documentToAnimeTopEntity +import com.jeluchu.features.anime.mappers.documentToCharacterTopEntity +import com.jeluchu.features.anime.mappers.documentToMangaTopEntity +import com.jeluchu.features.anime.mappers.documentToPeopleTopEntity import com.jeluchu.features.rankings.models.AnimeTopEntity import com.jeluchu.features.rankings.models.CharacterTopEntity import com.jeluchu.features.rankings.models.MangaTopEntity @@ -41,13 +45,15 @@ class RankingsService( private val characterRanking = database.getCollection(Collections.CHARACTER_RANKING) suspend fun getAnimeRanking(call: RoutingCall) { - val paramType = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopAnimeType.message) - val paramPage = call.parameters["page"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) - val paramFilter = call.parameters["filter"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopAnimeFilterType.message) - if (parseAnimeType(paramType) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidTopAnimeType.message)) - if (parseAnimeFilterType(paramFilter) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidTopAnimeFilterType.message)) + val filter = call.request.queryParameters["filter"] ?: "airing" + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 25 + val type = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopAnimeType.message) - val timerKey = "${Collections.ANIME_RANKING}_${paramType}_${paramFilter}_${paramPage}" + if (size > 25) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidValueTopPage.message)) + if (parseAnimeType(type) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidTopAnimeType.message)) + + val timerKey = "${Collections.ANIME_RANKING}_${type}_${filter}_${page}" val needsUpdate = timers.needsUpdate( amount = 30, @@ -55,19 +61,22 @@ class RankingsService( unit = TimeUnit.DAY ) + if (page < 1 || size < 1) call.respond(HttpStatusCode.BadRequest, ErrorMessages.InvalidSizeAndPage.message) + val skipCount = (page - 1) * size + if (needsUpdate) { animeRanking.deleteMany( Filters.and( - Filters.eq("page", paramPage), - Filters.eq("type", paramType), - Filters.eq("subtype", paramFilter) + Filters.eq("page", page), + Filters.eq("type", type), + Filters.eq("subtype", filter) ) ) val params = mutableListOf() - params.add("type=$paramType") - params.add("page=$paramPage") - params.add("filter=$paramFilter") + params.add("type=$type") + params.add("page=$page") + params.add("filter=$filter") val response = RestClient.request( BaseUrls.JIKAN + Endpoints.TOP_ANIME + "?${params.joinToString("&")}", @@ -75,9 +84,9 @@ class RankingsService( ).data?.map { anime -> anime.toAnimeTopEntity( top = "anime", - page = paramPage, - type = paramType, - subType = paramFilter + page = page, + type = type, + subType = filter ) } @@ -85,42 +94,66 @@ class RankingsService( if (documentsToInsert.isNotEmpty()) animeRanking.insertMany(documentsToInsert) timers.update(timerKey) - call.respond(HttpStatusCode.OK, Json.encodeToString(response)) + val elements = documentsToInsert.map { documentToAnimeTopEntity(it) } + + val paginationResponse = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(paginationResponse)) } else { - val elements = animeRanking.find().toList() - val directory = elements.map { documentToTopEntity(it) } - val json = Json.encodeToString(directory) - call.respond(HttpStatusCode.OK, json) + val animes = animeRanking + .find(Filters.eq("type", type.lowercase())) + .skip(skipCount) + .limit(size) + .toList() + + val elements = animes.map { documentToAnimeTopEntity(it) } + val response = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) } } suspend fun getMangaRanking(call: RoutingCall) { - val paramType = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopMangaType.message) - val paramPage = call.parameters["page"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) - val paramFilter = call.parameters["filter"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopMangaFilterType.message) - if (parseMangaType(paramType) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidTopMangaType.message)) - if (parseMangaFilterType(paramFilter) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidTopMangaFilterType.message)) + val filter = call.request.queryParameters["filter"] ?: "publishing" + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 25 + val type = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidTopMangaType.message) + + if (size > 25) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidValueTopPage.message)) + if (parseMangaType(type) == null) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidTopAnimeType.message)) + + val timerKey = "${Collections.MANGA_RANKING}_${type}_${filter}_${page}" - val timerKey = "${Collections.MANGA_RANKING}_${paramType}_${paramFilter}_${paramPage}" val needsUpdate = timers.needsUpdate( amount = 30, key = timerKey, unit = TimeUnit.DAY ) + if (page < 1 || size < 1) call.respond(HttpStatusCode.BadRequest, ErrorMessages.InvalidSizeAndPage.message) + val skipCount = (page - 1) * size + if (needsUpdate) { mangaRanking.deleteMany( Filters.and( - Filters.eq("page", paramPage), - Filters.eq("type", paramType), - Filters.eq("subtype", paramFilter) + Filters.eq("page", page), + Filters.eq("type", type), + Filters.eq("subtype", filter) ) ) val params = mutableListOf() - params.add("type=$paramType") - params.add("page=$paramPage") - params.add("filter=$paramFilter") + params.add("type=$type") + params.add("page=$page") + params.add("filter=$filter") val response = RestClient.request( BaseUrls.JIKAN + Endpoints.TOP_MANGA + "?${params.joinToString("&")}", @@ -128,9 +161,9 @@ class RankingsService( ).data?.map { anime -> anime.toMangaTopEntity( top = "manga", - page = paramPage, - type = paramType, - subType = paramFilter + page = page, + type = type, + subType = filter ) } @@ -138,32 +171,58 @@ class RankingsService( if (documentsToInsert.isNotEmpty()) mangaRanking.insertMany(documentsToInsert) timers.update(timerKey) - call.respond(HttpStatusCode.OK, Json.encodeToString(response)) + val elements = documentsToInsert.map { documentToMangaTopEntity(it) } + + val paginationResponse = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(paginationResponse)) } else { - val elements = mangaRanking.find().toList() - val directory = elements.map { documentToTopEntity(it) } - val json = Json.encodeToString(directory) - call.respond(HttpStatusCode.OK, json) + val mangas = mangaRanking + .find(Filters.eq("type", type.lowercase())) + .skip(skipCount) + .limit(size) + .toList() + + val elements = mangas.map { documentToMangaTopEntity(it) } + val response = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) } } suspend fun getPeopleRanking(call: RoutingCall) { - val paramPage = call.parameters["page"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) - val timerKey = "${Collections.PEOPLE_RANKING}_${paramPage}" + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 25 + + if (size > 25) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidValueTopPage.message)) + val timerKey = "${Collections.PEOPLE_RANKING}_${page}" + val needsUpdate = timers.needsUpdate( amount = 30, key = timerKey, unit = TimeUnit.DAY ) - if (needsUpdate) { peopleRanking.deleteMany(Filters.and(Filters.eq("page", paramPage))) + if (page < 1 || size < 1) call.respond(HttpStatusCode.BadRequest, ErrorMessages.InvalidSizeAndPage.message) + val skipCount = (page - 1) * size + + if (needsUpdate) { + peopleRanking.deleteMany(Filters.and(Filters.eq("page", page))) val response = RestClient.request( - BaseUrls.JIKAN + Endpoints.TOP_PEOPLE + "?page=$paramPage", + BaseUrls.JIKAN + Endpoints.TOP_PEOPLE + "?page=$page", PeopleSearch.serializer() ).data?.map { anime -> anime.toPeopleTopEntity( top = "people", - page = paramPage + page = page ) } @@ -171,32 +230,58 @@ class RankingsService( if (documentsToInsert.isNotEmpty()) peopleRanking.insertMany(documentsToInsert) timers.update(timerKey) - call.respond(HttpStatusCode.OK, Json.encodeToString(response)) + val elements = documentsToInsert.map { documentToPeopleTopEntity(it) } + + val paginationResponse = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(paginationResponse)) } else { - val elements = peopleRanking.find().toList() - val directory = elements.map { documentToTopEntity(it) } - val json = Json.encodeToString(directory) - call.respond(HttpStatusCode.OK, json) + val peoples = peopleRanking + .find() + .skip(skipCount) + .limit(size) + .toList() + + val elements = peoples.map { documentToPeopleTopEntity(it) } + val response = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) } } suspend fun getCharacterRanking(call: RoutingCall) { - val paramPage = call.parameters["page"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) - val timerKey = "${Collections.CHARACTER_RANKING}_${paramPage}" + val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1 + val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 25 + + if (size > 25) call.respond(HttpStatusCode.BadRequest, ErrorResponse(ErrorMessages.InvalidValueTopPage.message)) + val timerKey = "${Collections.CHARACTER_RANKING}_${page}" + val needsUpdate = timers.needsUpdate( amount = 30, key = timerKey, unit = TimeUnit.DAY ) - if (needsUpdate) { characterRanking.deleteMany(Filters.and(Filters.eq("page", paramPage))) + if (page < 1 || size < 1) call.respond(HttpStatusCode.BadRequest, ErrorMessages.InvalidSizeAndPage.message) + val skipCount = (page - 1) * size + + if (needsUpdate) { + characterRanking.deleteMany(Filters.and(Filters.eq("page", page))) val response = RestClient.request( - BaseUrls.JIKAN + Endpoints.TOP_CHARACTER + "?page=$paramPage", + BaseUrls.JIKAN + Endpoints.TOP_CHARACTER + "?page=$page", CharacterSearch.serializer() ).data?.map { anime -> anime.toCharacterTopEntity( top = "character", - page = paramPage + page = page ) } @@ -204,12 +289,30 @@ class RankingsService( if (documentsToInsert.isNotEmpty()) characterRanking.insertMany(documentsToInsert) timers.update(timerKey) - call.respond(HttpStatusCode.OK, Json.encodeToString(response)) + val elements = documentsToInsert.map { documentToCharacterTopEntity(it) } + + val paginationResponse = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(paginationResponse)) } else { - val elements = characterRanking.find().toList() - val directory = elements.map { documentToTopEntity(it) } - val json = Json.encodeToString(directory) - call.respond(HttpStatusCode.OK, json) + val characters = characterRanking + .find() + .skip(skipCount) + .limit(size) + .toList() + + val elements = characters.map { documentToCharacterTopEntity(it) } + val response = PaginationResponse( + page = page, + size = size, + data = elements + ) + + call.respond(HttpStatusCode.OK, Json.encodeToString(response)) } } } \ No newline at end of file