Skip to content

Commit

Permalink
Include filter directory by types and last episodes endpoint added
Browse files Browse the repository at this point in the history
  • Loading branch information
jeluchu committed Jan 10, 2025
1 parent 56b8c11 commit 5d1b33e
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ sealed class ErrorMessages(val message: String) {
data object AnimeNotFound : ErrorMessages("This malId is not in our database")
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 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -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()
)
}
}
Original file line number Diff line number Diff line change
@@ -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?
)
10 changes: 9 additions & 1 deletion src/main/kotlin/com/jeluchu/core/utils/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.jeluchu.core.utils

object BaseUrls {
const val JIKAN = "https://api.jikan.moe/v4/"
const val ANIME_FLV = "https://animeflv.ahmedrangel.com/api/"
}

object Endpoints {
Expand All @@ -14,6 +15,7 @@ object Endpoints {
const val STATISTICS = "statistics"
const val CHARACTERS = "characters"
const val TOP_CHARACTER = "top/characters"
const val LAST_EPISODES = "list/latest-episodes"
}

object Routes {
Expand All @@ -25,7 +27,9 @@ object Routes {
const val SCHEDULE = "/schedule"
const val DIRECTORY = "/directory"
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 TOP_CHARACTER = "/top/character"
const val RANKINGS = "/{type}/{filter}/{page}"
Expand All @@ -36,12 +40,16 @@ object TimerKey {
const val RANKING = "ranking"
const val SCHEDULE = "schedule"
const val LAST_UPDATED = "lastUpdated"
const val ANIME_TYPE = "anime_"
const val LAST_EPISODES = "last_episodes"
}

object Collections {
const val TIMERS = "timers"
const val SCHEDULES = "schedule"
const val ANIME_TYPE = "anime_"
const val ANIME_DETAILS = "anime_details"
const val LAST_EPISODES = "last_episodes"
const val ANIME_RANKING = "anime_ranking"
const val MANGA_RANKING = "manga_ranking"
const val PEOPLE_RANKING = "people_ranking"
Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.jeluchu.features.anime.mappers

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.directory.AnimeDirectoryEntity
import com.jeluchu.features.anime.models.directory.AnimeTypeEntity
import com.jeluchu.features.rankings.models.AnimeTopEntity
import com.jeluchu.features.schedule.models.DayEntity
import org.bson.Document
Expand Down Expand Up @@ -243,4 +245,19 @@ fun documentToTopEntity(doc: Document) = AnimeTopEntity(
type = doc.getStringSafe("type"),
subtype = doc.getStringSafe("subtype"),
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")
)
Original file line number Diff line number Diff line change
@@ -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? = ""
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ package com.jeluchu.features.anime.routes
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.mongodb.client.MongoDatabase
import io.ktor.server.routing.*

fun Route.animeEndpoints(
mongoDatabase: 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) {
getToJson { service.getDirectory(call) }
getToJson(Routes.TYPE) { directoryService.getAnimeByType(call) }
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
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.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.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.schedule.models.ScheduleEntity
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 AnimeService(
database: MongoDatabase
) {
private val timers = database.getCollection(Collections.TIMERS)
private val directoryCollection = database.getCollection(Collections.ANIME_DETAILS)
private val lastEpisodesCollection = database.getCollection(Collections.LAST_EPISODES)

suspend fun getDirectory(call: RoutingCall) = try {
val elements = directoryCollection.find().toList()
Expand All @@ -36,5 +53,39 @@ class AnimeService(
} catch (ex: Exception) {
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)
}
}

Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 5d1b33e

Please sign in to comment.