Include filter directory by types and last episodes endpoint added

v5 v5.2.0
Jéluchu 7 months ago
parent 56b8c1108a
commit 5d1b33e1e8

@ -7,6 +7,7 @@ sealed class ErrorMessages(val message: String) {
data object AnimeNotFound : ErrorMessages("This malId is not in our database") data object AnimeNotFound : ErrorMessages("This malId is not in our database")
data object InvalidMalId : ErrorMessages("The provided id of malId is invalid") 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 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 InvalidTopAnimeType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeTypesErrorList") data object InvalidTopAnimeType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeTypesErrorList")
data object InvalidTopAnimeFilterType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeFilterTypesErrorList") data object InvalidTopAnimeFilterType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeFilterTypesErrorList")
data object InvalidTopMangaType : ErrorMessages("Invalid 'type' parameter. Valid values are: $mangaTypesErrorList") data object InvalidTopMangaType : ErrorMessages("Invalid 'type' parameter. Valid values are: $mangaTypesErrorList")

@ -0,0 +1,10 @@
package com.jeluchu.core.models.animeflv.lastepisodes
import kotlinx.serialization.Serializable
@Serializable
data class EpisodeEntity(
var number: Int,
var image: String,
var title: String
)

@ -0,0 +1,19 @@
package com.jeluchu.core.models.animeflv.lastepisodes
import kotlinx.serialization.Serializable
@Serializable
data class LastEpisodeData(
val cover: String?,
val number: Int?,
val title: String?,
val url: String?
) {
companion object {
fun LastEpisodeData.toEpisodeEntity() = EpisodeEntity(
number = number ?: 0,
image = cover.orEmpty(),
title = title.orEmpty()
)
}
}

@ -0,0 +1,9 @@
package com.jeluchu.core.models.animeflv.lastepisodes
import kotlinx.serialization.Serializable
@Serializable
data class LastEpisodes(
val data: List<LastEpisodeData>?,
val success: Boolean?
)

@ -2,6 +2,7 @@ package com.jeluchu.core.utils
object BaseUrls { object BaseUrls {
const val JIKAN = "https://api.jikan.moe/v4/" const val JIKAN = "https://api.jikan.moe/v4/"
const val ANIME_FLV = "https://animeflv.ahmedrangel.com/api/"
} }
object Endpoints { object Endpoints {
@ -14,6 +15,7 @@ object Endpoints {
const val STATISTICS = "statistics" const val STATISTICS = "statistics"
const val CHARACTERS = "characters" const val CHARACTERS = "characters"
const val TOP_CHARACTER = "top/characters" const val TOP_CHARACTER = "top/characters"
const val LAST_EPISODES = "list/latest-episodes"
} }
object Routes { object Routes {
@ -25,7 +27,9 @@ object Routes {
const val SCHEDULE = "/schedule" const val SCHEDULE = "/schedule"
const val DIRECTORY = "/directory" const val DIRECTORY = "/directory"
const val CHARACTER = "/characters" const val CHARACTER = "/characters"
const val ANIME_DETAILS = "/anime/{id}" const val LAST_EPISODES = "/lastEpisodes"
const val ID = "/{id}"
const val TYPE = "/{type}"
const val DAY = "/{day}" const val DAY = "/{day}"
const val TOP_CHARACTER = "/top/character" const val TOP_CHARACTER = "/top/character"
const val RANKINGS = "/{type}/{filter}/{page}" const val RANKINGS = "/{type}/{filter}/{page}"
@ -36,12 +40,16 @@ object TimerKey {
const val RANKING = "ranking" const val RANKING = "ranking"
const val SCHEDULE = "schedule" const val SCHEDULE = "schedule"
const val LAST_UPDATED = "lastUpdated" const val LAST_UPDATED = "lastUpdated"
const val ANIME_TYPE = "anime_"
const val LAST_EPISODES = "last_episodes"
} }
object Collections { object Collections {
const val TIMERS = "timers" const val TIMERS = "timers"
const val SCHEDULES = "schedule" const val SCHEDULES = "schedule"
const val ANIME_TYPE = "anime_"
const val ANIME_DETAILS = "anime_details" const val ANIME_DETAILS = "anime_details"
const val LAST_EPISODES = "last_episodes"
const val ANIME_RANKING = "anime_ranking" const val ANIME_RANKING = "anime_ranking"
const val MANGA_RANKING = "manga_ranking" const val MANGA_RANKING = "manga_ranking"
const val PEOPLE_RANKING = "people_ranking" const val PEOPLE_RANKING = "people_ranking"

@ -1,8 +1,10 @@
package com.jeluchu.features.anime.mappers package com.jeluchu.features.anime.mappers
import com.jeluchu.core.extensions.* import com.jeluchu.core.extensions.*
import com.jeluchu.core.models.animeflv.lastepisodes.EpisodeEntity
import com.jeluchu.features.anime.models.anime.* import com.jeluchu.features.anime.models.anime.*
import com.jeluchu.features.anime.models.directory.AnimeDirectoryEntity 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.AnimeTopEntity
import com.jeluchu.features.schedule.models.DayEntity import com.jeluchu.features.schedule.models.DayEntity
import org.bson.Document import org.bson.Document
@ -243,4 +245,19 @@ fun documentToTopEntity(doc: Document) = AnimeTopEntity(
type = doc.getStringSafe("type"), type = doc.getStringSafe("type"),
subtype = doc.getStringSafe("subtype"), subtype = doc.getStringSafe("subtype"),
page = doc.getIntSafe("page"), page = doc.getIntSafe("page"),
)
fun documentToAnimeTypeEntity(doc: Document) = AnimeTypeEntity(
score = doc.getString("score"),
malId = doc.getIntSafe("malId"),
type = doc.getStringSafe("type"),
title = doc.getStringSafe("title"),
image = doc.getStringSafe("poster"),
episodes = doc.getListSafe<Document>("episodes").size
)
fun documentToLastEpisodesEntity(doc: Document) = EpisodeEntity(
number = doc.getIntSafe("number"),
title = doc.getStringSafe("title"),
image = doc.getStringSafe("image")
) )

@ -0,0 +1,13 @@
package com.jeluchu.features.anime.models.directory
import kotlinx.serialization.Serializable
@Serializable
data class AnimeTypeEntity(
val malId: Int? = 0,
val type: String? = "",
val episodes: Int? = 0,
val title: String? = "",
val image: String? = "",
val score: String? = ""
)

@ -3,15 +3,22 @@ package com.jeluchu.features.anime.routes
import com.jeluchu.core.extensions.getToJson import com.jeluchu.core.extensions.getToJson
import com.jeluchu.core.utils.Routes import com.jeluchu.core.utils.Routes
import com.jeluchu.features.anime.services.AnimeService import com.jeluchu.features.anime.services.AnimeService
import com.jeluchu.features.anime.services.DirectoryService
import com.mongodb.client.MongoDatabase import com.mongodb.client.MongoDatabase
import io.ktor.server.routing.* import io.ktor.server.routing.*
fun Route.animeEndpoints( fun Route.animeEndpoints(
mongoDatabase: MongoDatabase, mongoDatabase: MongoDatabase,
service: AnimeService = AnimeService(mongoDatabase), service: AnimeService = AnimeService(mongoDatabase),
directoryService: DirectoryService = DirectoryService(mongoDatabase),
) { ) {
getToJson(Routes.ANIME_DETAILS) { service.getAnimeByMalId(call) } route(Routes.ANIME) {
getToJson(Routes.ID) { service.getAnimeByMalId(call) }
getToJson(Routes.LAST_EPISODES) { service.getLastEpisodes(call) }
}
route(Routes.DIRECTORY) { route(Routes.DIRECTORY) {
getToJson { service.getDirectory(call) } getToJson { service.getDirectory(call) }
getToJson(Routes.TYPE) { directoryService.getAnimeByType(call) }
} }
} }

@ -1,10 +1,24 @@
package com.jeluchu.features.anime.services package com.jeluchu.features.anime.services
import com.jeluchu.core.connection.RestClient
import com.jeluchu.core.enums.Day
import com.jeluchu.core.enums.TimeUnit
import com.jeluchu.core.extensions.needsUpdate
import com.jeluchu.core.extensions.update
import com.jeluchu.core.messages.ErrorMessages import com.jeluchu.core.messages.ErrorMessages
import com.jeluchu.core.models.ErrorResponse import com.jeluchu.core.models.ErrorResponse
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
import com.jeluchu.core.utils.BaseUrls
import com.jeluchu.core.utils.Collections 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.documentToAnimeDirectoryEntity
import com.jeluchu.features.anime.mappers.documentToLastEpisodesEntity
import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity
import com.jeluchu.features.anime.mappers.documentToScheduleDayEntity
import com.jeluchu.features.schedule.models.ScheduleEntity
import com.mongodb.client.MongoDatabase import com.mongodb.client.MongoDatabase
import com.mongodb.client.model.Filters import com.mongodb.client.model.Filters
import io.ktor.http.* import io.ktor.http.*
@ -12,11 +26,14 @@ import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.bson.Document
class AnimeService( class AnimeService(
database: MongoDatabase database: MongoDatabase
) { ) {
private val timers = database.getCollection(Collections.TIMERS)
private val directoryCollection = database.getCollection(Collections.ANIME_DETAILS) private val directoryCollection = database.getCollection(Collections.ANIME_DETAILS)
private val lastEpisodesCollection = database.getCollection(Collections.LAST_EPISODES)
suspend fun getDirectory(call: RoutingCall) = try { suspend fun getDirectory(call: RoutingCall) = try {
val elements = directoryCollection.find().toList() val elements = directoryCollection.find().toList()
@ -36,5 +53,39 @@ class AnimeService(
} catch (ex: Exception) { } catch (ex: Exception) {
call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message)) call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message))
} }
suspend fun getLastEpisodes(call: RoutingCall) = try {
val needsUpdate = timers.needsUpdate(
amount = 3,
unit = TimeUnit.HOUR,
key = TimerKey.LAST_EPISODES
)
if (needsUpdate) {
lastEpisodesCollection.deleteMany(Document())
val episodes = getLastedEpisodes().data?.map { it.toEpisodeEntity() }.orEmpty()
val documents = episodes.map { anime -> Document.parse(Json.encodeToString(anime)) }
if (documents.isNotEmpty()) lastEpisodesCollection.insertMany(documents)
timers.update(TimerKey.LAST_EPISODES)
call.respond(HttpStatusCode.OK, Json.encodeToString(episodes))
} else {
val elements = lastEpisodesCollection.find().toList()
call.respond(HttpStatusCode.OK, elements.documentToLastEpisodesEntity())
}
} catch (ex: Exception) {
call.respond(HttpStatusCode.Unauthorized, ErrorResponse(ErrorMessages.UnauthorizedMongo.message))
}
private suspend fun getLastedEpisodes() = RestClient.requestWithDelay(
url = BaseUrls.ANIME_FLV + Endpoints.LAST_EPISODES,
deserializer = LastEpisodes.serializer()
)
private fun List<Document>.documentToLastEpisodesEntity(): String {
val directory = map { documentToLastEpisodesEntity(it) }
return Json.encodeToString(directory)
}
} }

@ -0,0 +1,62 @@
package com.jeluchu.features.anime.services
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.utils.Collections
import com.jeluchu.core.utils.TimerKey
import com.jeluchu.features.anime.mappers.documentToAnimeTypeEntity
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 DirectoryService(
private val database: MongoDatabase
) {
private val timers = database.getCollection(Collections.TIMERS)
private val directory = database.getCollection(Collections.ANIME_DETAILS)
suspend fun getAnimeByType(call: RoutingCall) {
val param = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidAnimeType.message)
if (parseAnimeType(param) == null) call.respond(
HttpStatusCode.BadRequest,
ErrorResponse(ErrorMessages.InvalidAnimeType.message)
)
val timerKey = "${TimerKey.ANIME_TYPE}${param.lowercase()}"
val needsUpdate = timers.needsUpdate(
amount = 30,
key = timerKey,
unit = TimeUnit.DAY,
)
if (needsUpdate) {
val collection = database.getCollection(timerKey)
collection.deleteMany(Document())
val animes = directory.find(Filters.eq("type", param.uppercase())).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))
} else {
val elements = directory.find().toList()
call.respond(HttpStatusCode.OK, elements.documentAnimeTypeMapper())
}
}
private fun List<Document>.documentAnimeTypeMapper(): String {
val directory = map { documentToAnimeTypeEntity(it) }
return Json.encodeToString(directory)
}
}
Loading…
Cancel
Save