Skip to content

Commit 5d1b33e

Browse files
committed
Include filter directory by types and last episodes endpoint added
1 parent 56b8c11 commit 5d1b33e

File tree

10 files changed

+199
-2
lines changed

10 files changed

+199
-2
lines changed

src/main/kotlin/com/jeluchu/core/messages/ErrorMessages.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ sealed class ErrorMessages(val message: String) {
77
data object AnimeNotFound : ErrorMessages("This malId is not in our database")
88
data object InvalidMalId : ErrorMessages("The provided id of malId is invalid")
99
data object InvalidDay : ErrorMessages("Invalid 'day' parameter. Valid values are: ${Day.entries.joinToString(", ") { it.name.lowercase() }}")
10+
data object InvalidAnimeType : ErrorMessages("Invalid 'type' parameter. Valid values are: ${AnimeTypes.entries.joinToString(", ") { it.name.lowercase() }}")
1011
data object InvalidTopAnimeType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeTypesErrorList")
1112
data object InvalidTopAnimeFilterType : ErrorMessages("Invalid 'type' parameter. Valid values are: $animeFilterTypesErrorList")
1213
data object InvalidTopMangaType : ErrorMessages("Invalid 'type' parameter. Valid values are: $mangaTypesErrorList")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.jeluchu.core.models.animeflv.lastepisodes
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class EpisodeEntity(
7+
var number: Int,
8+
var image: String,
9+
var title: String
10+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.jeluchu.core.models.animeflv.lastepisodes
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class LastEpisodeData(
7+
val cover: String?,
8+
val number: Int?,
9+
val title: String?,
10+
val url: String?
11+
) {
12+
companion object {
13+
fun LastEpisodeData.toEpisodeEntity() = EpisodeEntity(
14+
number = number ?: 0,
15+
image = cover.orEmpty(),
16+
title = title.orEmpty()
17+
)
18+
}
19+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.jeluchu.core.models.animeflv.lastepisodes
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class LastEpisodes(
7+
val data: List<LastEpisodeData>?,
8+
val success: Boolean?
9+
)

src/main/kotlin/com/jeluchu/core/utils/Constants.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.jeluchu.core.utils
22

33
object BaseUrls {
44
const val JIKAN = "https://api.jikan.moe/v4/"
5+
const val ANIME_FLV = "https://animeflv.ahmedrangel.com/api/"
56
}
67

78
object Endpoints {
@@ -14,6 +15,7 @@ object Endpoints {
1415
const val STATISTICS = "statistics"
1516
const val CHARACTERS = "characters"
1617
const val TOP_CHARACTER = "top/characters"
18+
const val LAST_EPISODES = "list/latest-episodes"
1719
}
1820

1921
object Routes {
@@ -25,7 +27,9 @@ object Routes {
2527
const val SCHEDULE = "/schedule"
2628
const val DIRECTORY = "/directory"
2729
const val CHARACTER = "/characters"
28-
const val ANIME_DETAILS = "/anime/{id}"
30+
const val LAST_EPISODES = "/lastEpisodes"
31+
const val ID = "/{id}"
32+
const val TYPE = "/{type}"
2933
const val DAY = "/{day}"
3034
const val TOP_CHARACTER = "/top/character"
3135
const val RANKINGS = "/{type}/{filter}/{page}"
@@ -36,12 +40,16 @@ object TimerKey {
3640
const val RANKING = "ranking"
3741
const val SCHEDULE = "schedule"
3842
const val LAST_UPDATED = "lastUpdated"
43+
const val ANIME_TYPE = "anime_"
44+
const val LAST_EPISODES = "last_episodes"
3945
}
4046

4147
object Collections {
4248
const val TIMERS = "timers"
4349
const val SCHEDULES = "schedule"
50+
const val ANIME_TYPE = "anime_"
4451
const val ANIME_DETAILS = "anime_details"
52+
const val LAST_EPISODES = "last_episodes"
4553
const val ANIME_RANKING = "anime_ranking"
4654
const val MANGA_RANKING = "manga_ranking"
4755
const val PEOPLE_RANKING = "people_ranking"

src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.jeluchu.features.anime.mappers
22

33
import com.jeluchu.core.extensions.*
4+
import com.jeluchu.core.models.animeflv.lastepisodes.EpisodeEntity
45
import com.jeluchu.features.anime.models.anime.*
56
import com.jeluchu.features.anime.models.directory.AnimeDirectoryEntity
7+
import com.jeluchu.features.anime.models.directory.AnimeTypeEntity
68
import com.jeluchu.features.rankings.models.AnimeTopEntity
79
import com.jeluchu.features.schedule.models.DayEntity
810
import org.bson.Document
@@ -243,4 +245,19 @@ fun documentToTopEntity(doc: Document) = AnimeTopEntity(
243245
type = doc.getStringSafe("type"),
244246
subtype = doc.getStringSafe("subtype"),
245247
page = doc.getIntSafe("page"),
248+
)
249+
250+
fun documentToAnimeTypeEntity(doc: Document) = AnimeTypeEntity(
251+
score = doc.getString("score"),
252+
malId = doc.getIntSafe("malId"),
253+
type = doc.getStringSafe("type"),
254+
title = doc.getStringSafe("title"),
255+
image = doc.getStringSafe("poster"),
256+
episodes = doc.getListSafe<Document>("episodes").size
257+
)
258+
259+
fun documentToLastEpisodesEntity(doc: Document) = EpisodeEntity(
260+
number = doc.getIntSafe("number"),
261+
title = doc.getStringSafe("title"),
262+
image = doc.getStringSafe("image")
246263
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.jeluchu.features.anime.models.directory
2+
3+
import kotlinx.serialization.Serializable
4+
5+
@Serializable
6+
data class AnimeTypeEntity(
7+
val malId: Int? = 0,
8+
val type: String? = "",
9+
val episodes: Int? = 0,
10+
val title: String? = "",
11+
val image: String? = "",
12+
val score: String? = ""
13+
)

src/main/kotlin/com/jeluchu/features/anime/routes/AnimeRoutes.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@ package com.jeluchu.features.anime.routes
33
import com.jeluchu.core.extensions.getToJson
44
import com.jeluchu.core.utils.Routes
55
import com.jeluchu.features.anime.services.AnimeService
6+
import com.jeluchu.features.anime.services.DirectoryService
67
import com.mongodb.client.MongoDatabase
78
import io.ktor.server.routing.*
89

910
fun Route.animeEndpoints(
1011
mongoDatabase: MongoDatabase,
1112
service: AnimeService = AnimeService(mongoDatabase),
13+
directoryService: DirectoryService = DirectoryService(mongoDatabase),
1214
) {
13-
getToJson(Routes.ANIME_DETAILS) { service.getAnimeByMalId(call) }
15+
route(Routes.ANIME) {
16+
getToJson(Routes.ID) { service.getAnimeByMalId(call) }
17+
getToJson(Routes.LAST_EPISODES) { service.getLastEpisodes(call) }
18+
}
19+
1420
route(Routes.DIRECTORY) {
1521
getToJson { service.getDirectory(call) }
22+
getToJson(Routes.TYPE) { directoryService.getAnimeByType(call) }
1623
}
1724
}

src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
package com.jeluchu.features.anime.services
22

3+
import com.jeluchu.core.connection.RestClient
4+
import com.jeluchu.core.enums.Day
5+
import com.jeluchu.core.enums.TimeUnit
6+
import com.jeluchu.core.extensions.needsUpdate
7+
import com.jeluchu.core.extensions.update
38
import com.jeluchu.core.messages.ErrorMessages
49
import com.jeluchu.core.models.ErrorResponse
10+
import com.jeluchu.core.models.animeflv.lastepisodes.LastEpisodeData.Companion.toEpisodeEntity
11+
import com.jeluchu.core.models.animeflv.lastepisodes.LastEpisodes
12+
import com.jeluchu.core.models.jikan.anime.AnimeData.Companion.toDayEntity
13+
import com.jeluchu.core.utils.BaseUrls
514
import com.jeluchu.core.utils.Collections
15+
import com.jeluchu.core.utils.Endpoints
16+
import com.jeluchu.core.utils.TimerKey
617
import com.jeluchu.features.anime.mappers.documentToAnimeDirectoryEntity
18+
import com.jeluchu.features.anime.mappers.documentToLastEpisodesEntity
719
import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity
20+
import com.jeluchu.features.anime.mappers.documentToScheduleDayEntity
21+
import com.jeluchu.features.schedule.models.ScheduleEntity
822
import com.mongodb.client.MongoDatabase
923
import com.mongodb.client.model.Filters
1024
import io.ktor.http.*
1125
import io.ktor.server.response.*
1226
import io.ktor.server.routing.*
1327
import kotlinx.serialization.encodeToString
1428
import kotlinx.serialization.json.Json
29+
import org.bson.Document
1530

1631
class AnimeService(
1732
database: MongoDatabase
1833
) {
34+
private val timers = database.getCollection(Collections.TIMERS)
1935
private val directoryCollection = database.getCollection(Collections.ANIME_DETAILS)
36+
private val lastEpisodesCollection = database.getCollection(Collections.LAST_EPISODES)
2037

2138
suspend fun getDirectory(call: RoutingCall) = try {
2239
val elements = directoryCollection.find().toList()
@@ -36,5 +53,39 @@ class AnimeService(
3653
} catch (ex: Exception) {
3754
call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message))
3855
}
56+
57+
suspend fun getLastEpisodes(call: RoutingCall) = try {
58+
val needsUpdate = timers.needsUpdate(
59+
amount = 3,
60+
unit = TimeUnit.HOUR,
61+
key = TimerKey.LAST_EPISODES
62+
)
63+
64+
if (needsUpdate) {
65+
lastEpisodesCollection.deleteMany(Document())
66+
67+
val episodes = getLastedEpisodes().data?.map { it.toEpisodeEntity() }.orEmpty()
68+
val documents = episodes.map { anime -> Document.parse(Json.encodeToString(anime)) }
69+
if (documents.isNotEmpty()) lastEpisodesCollection.insertMany(documents)
70+
timers.update(TimerKey.LAST_EPISODES)
71+
72+
call.respond(HttpStatusCode.OK, Json.encodeToString(episodes))
73+
} else {
74+
val elements = lastEpisodesCollection.find().toList()
75+
call.respond(HttpStatusCode.OK, elements.documentToLastEpisodesEntity())
76+
}
77+
} catch (ex: Exception) {
78+
call.respond(HttpStatusCode.Unauthorized, ErrorResponse(ErrorMessages.UnauthorizedMongo.message))
79+
}
80+
81+
private suspend fun getLastedEpisodes() = RestClient.requestWithDelay(
82+
url = BaseUrls.ANIME_FLV + Endpoints.LAST_EPISODES,
83+
deserializer = LastEpisodes.serializer()
84+
)
85+
86+
private fun List<Document>.documentToLastEpisodesEntity(): String {
87+
val directory = map { documentToLastEpisodesEntity(it) }
88+
return Json.encodeToString(directory)
89+
}
3990
}
4091

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.jeluchu.features.anime.services
2+
3+
import com.jeluchu.core.enums.TimeUnit
4+
import com.jeluchu.core.enums.parseAnimeType
5+
import com.jeluchu.core.extensions.needsUpdate
6+
import com.jeluchu.core.extensions.update
7+
import com.jeluchu.core.messages.ErrorMessages
8+
import com.jeluchu.core.models.ErrorResponse
9+
import com.jeluchu.core.utils.Collections
10+
import com.jeluchu.core.utils.TimerKey
11+
import com.jeluchu.features.anime.mappers.documentToAnimeTypeEntity
12+
import com.mongodb.client.MongoDatabase
13+
import com.mongodb.client.model.Filters
14+
import io.ktor.http.*
15+
import io.ktor.server.response.*
16+
import io.ktor.server.routing.*
17+
import kotlinx.serialization.encodeToString
18+
import kotlinx.serialization.json.Json
19+
import org.bson.Document
20+
21+
class DirectoryService(
22+
private val database: MongoDatabase
23+
) {
24+
private val timers = database.getCollection(Collections.TIMERS)
25+
private val directory = database.getCollection(Collections.ANIME_DETAILS)
26+
27+
suspend fun getAnimeByType(call: RoutingCall) {
28+
val param = call.parameters["type"] ?: throw IllegalArgumentException(ErrorMessages.InvalidAnimeType.message)
29+
if (parseAnimeType(param) == null) call.respond(
30+
HttpStatusCode.BadRequest,
31+
ErrorResponse(ErrorMessages.InvalidAnimeType.message)
32+
)
33+
34+
val timerKey = "${TimerKey.ANIME_TYPE}${param.lowercase()}"
35+
val needsUpdate = timers.needsUpdate(
36+
amount = 30,
37+
key = timerKey,
38+
unit = TimeUnit.DAY,
39+
)
40+
41+
if (needsUpdate) {
42+
val collection = database.getCollection(timerKey)
43+
collection.deleteMany(Document())
44+
45+
val animes = directory.find(Filters.eq("type", param.uppercase())).toList()
46+
val animeTypes = animes.map { documentToAnimeTypeEntity(it) }
47+
val documents = animeTypes.map { anime -> Document.parse(Json.encodeToString(anime)) }
48+
if (documents.isNotEmpty()) collection.insertMany(documents)
49+
timers.update(timerKey)
50+
51+
call.respond(HttpStatusCode.OK, Json.encodeToString(animeTypes))
52+
} else {
53+
val elements = directory.find().toList()
54+
call.respond(HttpStatusCode.OK, elements.documentAnimeTypeMapper())
55+
}
56+
}
57+
58+
private fun List<Document>.documentAnimeTypeMapper(): String {
59+
val directory = map { documentToAnimeTypeEntity(it) }
60+
return Json.encodeToString(directory)
61+
}
62+
}

0 commit comments

Comments
 (0)