mirror of https://github.com/aruppi/aruppi-api
Include Schedule endpoints and prepare for Ranking endpoints
This commit is contained in:
parent
1976c82603
commit
dfc1b5a2b8
|
@ -20,7 +20,9 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation(libs.bson)
|
||||
implementation(libs.ktor.client.cio)
|
||||
implementation(libs.logback.classic)
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.server.core)
|
||||
implementation(libs.ktor.server.netty)
|
||||
implementation(libs.ktor.server.swagger)
|
||||
|
|
|
@ -6,6 +6,8 @@ logback-version = "1.4.14"
|
|||
mongo-version = "4.10.2"
|
||||
|
||||
[libraries]
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-version" }
|
||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor-version" }
|
||||
ktor-server-core = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor-version" }
|
||||
ktor-server-swagger = { module = "io.ktor:ktor-server-swagger-jvm", version.ref = "ktor-version" }
|
||||
ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json-jvm", version.ref = "ktor-version" }
|
||||
|
@ -16,8 +18,6 @@ bson = { module = "org.mongodb:bson", version.ref = "mongo-version" }
|
|||
ktor-server-netty = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor-version" }
|
||||
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback-version" }
|
||||
ktor-server-config-yaml = { module = "io.ktor:ktor-server-config-yaml-jvm", version.ref = "ktor-version" }
|
||||
ktor-server-test-host = { module = "io.ktor:ktor-server-test-host-jvm", version.ref = "ktor-version" }
|
||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin-version" }
|
||||
ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages", version.ref = "kotlin-version" }
|
||||
|
||||
[plugins]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.jeluchu.core.configuration
|
||||
|
||||
import com.jeluchu.features.anime.routes.animeEndpoints
|
||||
import com.jeluchu.features.rankings.routes.rankingsEndpoints
|
||||
import com.jeluchu.features.schedule.routes.scheduleEndpoints
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
|
@ -11,5 +13,7 @@ fun Application.initRoutes(
|
|||
route("api/v5") {
|
||||
initDocumentation()
|
||||
animeEndpoints(mongoDatabase)
|
||||
rankingsEndpoints(mongoDatabase)
|
||||
scheduleEndpoints(mongoDatabase)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.jeluchu.core.connection
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
object RestClient {
|
||||
private val client = HttpClient(CIO)
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
suspend fun <T> request(
|
||||
url: String,
|
||||
deserializer: DeserializationStrategy<T>
|
||||
): T {
|
||||
return runCatching {
|
||||
val response = client.get(url) {
|
||||
headers { append(HttpHeaders.Accept, ContentType.Application.Json.toString()) }
|
||||
}
|
||||
|
||||
json.decodeFromString(deserializer, response.bodyAsText())
|
||||
}.getOrElse { throwable -> throw throwable }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.jeluchu.core.extensions
|
||||
|
||||
import com.jeluchu.core.utils.TimeUnit
|
||||
import com.jeluchu.core.utils.TimerKey
|
||||
import com.mongodb.client.MongoCollection
|
||||
import com.mongodb.client.model.Filters.eq
|
||||
import com.mongodb.client.model.ReplaceOptions
|
||||
import org.bson.Document
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
fun MongoCollection<Document>.needsUpdate(
|
||||
key: String,
|
||||
amount: Long = 5,
|
||||
unit: TimeUnit = TimeUnit.HOUR
|
||||
): Boolean {
|
||||
val currentTime = Instant.now()
|
||||
val timestampEntry = find(eq(TimerKey.KEY, key)).firstOrNull()
|
||||
|
||||
return if (timestampEntry == null) true else {
|
||||
val lastUpdatedDate = timestampEntry.getDate(TimerKey.LAST_UPDATED)
|
||||
val lastUpdated = lastUpdatedDate.toInstant()
|
||||
val duration = Duration.between(lastUpdated, currentTime)
|
||||
|
||||
when (unit) {
|
||||
TimeUnit.DAY -> duration.toDays() >= amount
|
||||
TimeUnit.HOUR -> duration.toHours() >= amount
|
||||
TimeUnit.MINUTE -> duration.toMinutes() >= amount
|
||||
TimeUnit.SECOND -> duration.toSeconds() >= amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MongoCollection<Document>.update(key: String) {
|
||||
val currentTime = Instant.now()
|
||||
val newTimestampDocument = Document(TimerKey.KEY, key)
|
||||
.append(TimerKey.LAST_UPDATED, Date.from(currentTime))
|
||||
|
||||
replaceOne(
|
||||
eq(TimerKey.KEY, TimerKey.SCHEDULE),
|
||||
newTimestampDocument,
|
||||
ReplaceOptions().upsert(true)
|
||||
)
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
package com.jeluchu.core.messages
|
||||
|
||||
import com.jeluchu.core.utils.Day
|
||||
|
||||
sealed class ErrorMessages(val message: String) {
|
||||
data class Custom(val error: String) : ErrorMessages(error)
|
||||
data object NotFound : ErrorMessages("Nyaaaaaaaan! This request has not been found by our alpaca-neko")
|
||||
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 InvalidInput : ErrorMessages("Invalid input provided")
|
||||
data object UnauthorizedMongo : ErrorMessages("Check the MongoDb Connection String to be able to correctly access this request.")
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.jeluchu.core.models
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
data class TimestampEntry(
|
||||
val key: String,
|
||||
val lastUpdated: Instant
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import com.jeluchu.core.models.jikan.anime.Prop
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Aired data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Aired(
|
||||
/**
|
||||
* Start date in ISO8601 format.
|
||||
*/
|
||||
@SerialName("from")
|
||||
val from: String? = "",
|
||||
|
||||
/**
|
||||
* @see Prop for the detail.
|
||||
*/
|
||||
@SerialName("prop")
|
||||
val prop: Prop? = Prop(),
|
||||
|
||||
/**
|
||||
* End date in ISO8601 format.
|
||||
*/
|
||||
@SerialName("to")
|
||||
val to: String? = ""
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
|
||||
/**
|
||||
* Anime data class.
|
||||
*/
|
||||
data class Anime(
|
||||
|
||||
/**
|
||||
* Data for anime requested.
|
||||
*/
|
||||
@SerialName("data")
|
||||
val data: AnimeData
|
||||
)
|
|
@ -0,0 +1,285 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import com.jeluchu.core.utils.Day
|
||||
import com.jeluchu.features.schedule.models.DayEntity
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* AnimeInfo data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class AnimeData(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int? = 0,
|
||||
|
||||
/**
|
||||
* Anime's MyAnimeList link.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String? = "",
|
||||
|
||||
/**
|
||||
* Anime's MyAnimeList cover/image link.
|
||||
* @see Images for the detail.
|
||||
*/
|
||||
@SerialName("images")
|
||||
val images: Images? = Images(),
|
||||
|
||||
/**
|
||||
* Anime's official trailer URL.
|
||||
* @see Trailer for the detail.
|
||||
*/
|
||||
@SerialName("trailer")
|
||||
val trailer: Trailer? = Trailer(),
|
||||
|
||||
/**
|
||||
* When entry is pending approval on MAL.
|
||||
*/
|
||||
@SerialName("approved")
|
||||
val approved: Boolean? = false,
|
||||
|
||||
/**
|
||||
* Title of the anime.
|
||||
* @see Title for the detail.
|
||||
*/
|
||||
@SerialName("titles")
|
||||
val titles: List<Title>? = emptyList(),
|
||||
|
||||
/**
|
||||
* Title of the anime.
|
||||
*/
|
||||
@Deprecated("Use 'titles: List<Title>' to get the title")
|
||||
@SerialName("title")
|
||||
val title: String? = "",
|
||||
|
||||
/**
|
||||
* Title of the anime in English.
|
||||
*/
|
||||
@Deprecated("Use 'titles: List<Title>' to get the title")
|
||||
@SerialName("title_english")
|
||||
val titleEnglish: String? = "",
|
||||
|
||||
/**
|
||||
* Title of the anime in Japanese.
|
||||
*/
|
||||
@Deprecated("Use 'titles: List<Title>' to get the title")
|
||||
@SerialName("title_japanese")
|
||||
val titleJapanese: String? = "",
|
||||
|
||||
/**
|
||||
* List of anime's synonyms.
|
||||
* @return null if there's none.
|
||||
*/
|
||||
@Deprecated("Use 'titles: List<Title>' to get the title")
|
||||
@SerialName("title_synonyms")
|
||||
val titleSynonyms: List<String>? = emptyList(),
|
||||
|
||||
/**
|
||||
* Type of the anime.
|
||||
* @see AnimeType for the detail.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String? = "",
|
||||
|
||||
/**
|
||||
* Source of the anime.
|
||||
*/
|
||||
@SerialName("source")
|
||||
val source : String? = "",
|
||||
|
||||
/**
|
||||
* Total episode(s) of the anime.
|
||||
*/
|
||||
@SerialName("episodes")
|
||||
val episodes: Int? = 0,
|
||||
|
||||
/**
|
||||
* Status of the anime (e.g "Airing", "Not yet airing", etc).
|
||||
*/
|
||||
@SerialName("status")
|
||||
val status : String? = "",
|
||||
|
||||
/**
|
||||
* Whether the anime is currently airing or not.
|
||||
*/
|
||||
@SerialName("airing")
|
||||
val airing: Boolean? = false,
|
||||
|
||||
/**
|
||||
* Interval of airing time in ISO8601 format.
|
||||
* @see Aired for the detail.
|
||||
* @return null if there's none
|
||||
*/
|
||||
@SerialName("aired")
|
||||
val aired: Aired? = Aired(),
|
||||
|
||||
/**
|
||||
* Duration per episode.
|
||||
*/
|
||||
@SerialName("duration")
|
||||
val duration : String? = "",
|
||||
|
||||
/**
|
||||
* Age rating of the anime.
|
||||
*/
|
||||
@SerialName("rating")
|
||||
val rating : String? = "",
|
||||
|
||||
/**
|
||||
* Score at MyAnimeList. Formatted up to 2 decimal places.
|
||||
*/
|
||||
@SerialName("score")
|
||||
val score: Float? = 0f,
|
||||
|
||||
/**
|
||||
* Number of people/users that scored the anime.
|
||||
*/
|
||||
@SerialName("scored_by")
|
||||
val scoredBy: Int? = 0,
|
||||
|
||||
/**
|
||||
* Anime's score rank on MyAnimeList.
|
||||
*/
|
||||
@SerialName("rank")
|
||||
val rank: Int? = 0,
|
||||
|
||||
/**
|
||||
* Anime's popularity rank on MyAnimeList.
|
||||
*/
|
||||
@SerialName("popularity")
|
||||
val popularity: Int? = 0,
|
||||
|
||||
/**
|
||||
* Anime's members count on MyAnimeList.
|
||||
*/
|
||||
@SerialName("members")
|
||||
val members: Int? = 0,
|
||||
|
||||
/**
|
||||
* Anime's favorites count on MyAnimeList.
|
||||
*/
|
||||
@SerialName("favorites")
|
||||
val favorites: Int? = 0,
|
||||
|
||||
/**
|
||||
* Synopsis of the anime.
|
||||
*/
|
||||
@SerialName("synopsis")
|
||||
val synopsis : String? = "",
|
||||
|
||||
/**
|
||||
* Background info of the anime.
|
||||
*/
|
||||
@SerialName("background")
|
||||
val background : String? = "",
|
||||
|
||||
/**
|
||||
* Season where anime premiered.
|
||||
*/
|
||||
@SerialName("season")
|
||||
val season: String? = "",
|
||||
|
||||
/**
|
||||
* Year where anime premiered.
|
||||
*/
|
||||
@SerialName("year")
|
||||
val year: Int? = 0,
|
||||
|
||||
/**
|
||||
* Broadcast date of the anime (day and time).
|
||||
* @see Broadcast for the detail.
|
||||
*/
|
||||
@SerialName("broadcast")
|
||||
val broadcast: Broadcast? = Broadcast(),
|
||||
|
||||
/**
|
||||
* List of producers of this anime.
|
||||
* @see Producer for the detail.
|
||||
*/
|
||||
@SerialName("producers")
|
||||
val producers: List<Producer>? = emptyList(),
|
||||
|
||||
/**
|
||||
* List of licensors of this anime.
|
||||
* @see Licensor for the detail.
|
||||
*/
|
||||
@SerialName("licensors")
|
||||
val licensors: List<Licensor>? = emptyList(),
|
||||
|
||||
/**
|
||||
* List of studios of this anime.
|
||||
* @see Studio for the detail.
|
||||
*
|
||||
*/
|
||||
@SerialName("studios")
|
||||
val studios: List<Studio>? = emptyList(),
|
||||
|
||||
/**
|
||||
* List of genre of this anime.
|
||||
* @see Genre for the detail.
|
||||
*/
|
||||
@SerialName("genres")
|
||||
val genres: List<Genre>? = emptyList(),
|
||||
|
||||
/**
|
||||
* List of explicit genre of this anime.
|
||||
* @see ExplicitGenre for the detail.
|
||||
*/
|
||||
@SerialName("explicit_genres")
|
||||
val explicitGenres: List<ExplicitGenre>? = emptyList(),
|
||||
|
||||
/**
|
||||
* List of themes of this anime.
|
||||
* @see Themes for the detail.
|
||||
*/
|
||||
@SerialName("themes")
|
||||
val themes: List<Themes>? = emptyList(),
|
||||
|
||||
/**
|
||||
* Demographic of this anime.
|
||||
* @see Demographic for the detail.
|
||||
*/
|
||||
@SerialName("demographics")
|
||||
val demographics: List<Demographic>? = emptyList(),
|
||||
|
||||
/**
|
||||
* Relation of this anime.
|
||||
* @see Relation for the detail.
|
||||
*/
|
||||
@SerialName("relations")
|
||||
val relations: List<Relation>? = emptyList(),
|
||||
|
||||
/**
|
||||
* Theme of this anime.
|
||||
* @see Theme for the detail.
|
||||
*/
|
||||
@SerialName("theme")
|
||||
val theme: Theme? = Theme(),
|
||||
|
||||
/**
|
||||
* Theme of this anime.
|
||||
* @see External for the detail.
|
||||
*/
|
||||
@SerialName("external")
|
||||
val external: List<External>? = emptyList(),
|
||||
|
||||
/**
|
||||
* Theme of this anime.
|
||||
* @see Streaming for the detail.
|
||||
*/
|
||||
@SerialName("streaming")
|
||||
val streaming: List<Streaming>? = emptyList()
|
||||
) {
|
||||
companion object {
|
||||
fun AnimeData.toDayEntity(day: Day) = DayEntity(
|
||||
malId = malId ?: 0,
|
||||
day = day.name.lowercase(),
|
||||
image = images?.webp?.large.orEmpty(),
|
||||
title = titles?.first()?.title.orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
|
||||
data class AnimeStatistics(
|
||||
@SerialName("data")
|
||||
val data: Statistics?
|
||||
) {
|
||||
data class Statistics(
|
||||
@SerialName("completed")
|
||||
val completed: Int?,
|
||||
|
||||
@SerialName("dropped")
|
||||
val dropped: Int?,
|
||||
|
||||
@SerialName("on_hold")
|
||||
val onHold: Int?,
|
||||
|
||||
@SerialName("plan_to_watch")
|
||||
val planToWatch: Int?,
|
||||
|
||||
@SerialName("scores")
|
||||
val scores: List<Score>?,
|
||||
|
||||
@SerialName("total")
|
||||
val total: Int?,
|
||||
|
||||
@SerialName("watching")
|
||||
val watching: Int?
|
||||
)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Broadcast data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Broadcast(
|
||||
/**
|
||||
* Day in broadcast.
|
||||
*/
|
||||
@SerialName("day")
|
||||
val day: String? = "",
|
||||
|
||||
/**
|
||||
* String date in broadcast.
|
||||
*/
|
||||
@SerialName("string")
|
||||
val string: String? = "",
|
||||
|
||||
/**
|
||||
* Time date in broadcast.
|
||||
*/
|
||||
@SerialName("time")
|
||||
val time: String? = "",
|
||||
|
||||
/**
|
||||
* Timezone in broadcast.
|
||||
*/
|
||||
@SerialName("timezone")
|
||||
val timezone: String? = ""
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* DateProp data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class DateProp(
|
||||
/**
|
||||
* Day in date.
|
||||
*/
|
||||
@SerialName("day")
|
||||
val day: Int? = 0,
|
||||
|
||||
/**
|
||||
* Month in date.
|
||||
*/
|
||||
@SerialName("month")
|
||||
val month: Int? = 0,
|
||||
|
||||
/**
|
||||
* Year in date.
|
||||
*/
|
||||
@SerialName("year")
|
||||
val year: Int? = 0
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Demographic data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Demographic(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for demographic.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for demographic.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for demographic.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Entry data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Entry(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for entry.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for entry.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for entry.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* ExplicitGenre data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class ExplicitGenre(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for explicit genre.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for explicit genre.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for explicit genre.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* External data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class External(
|
||||
/**
|
||||
* Name of external info.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Url of external info.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Genre data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Genre(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for genre.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for genre.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for genre.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ImageFormat(
|
||||
@SerialName("image_url")
|
||||
val generic: String? = "",
|
||||
|
||||
@SerialName("small_image_url")
|
||||
val small: String? = "",
|
||||
|
||||
@SerialName("medium_image_url")
|
||||
val medium: String? = "",
|
||||
|
||||
@SerialName("large_image_url")
|
||||
val large: String? = "",
|
||||
|
||||
@SerialName("maximum_image_url")
|
||||
val maximum: String? = ""
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Images data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Images(
|
||||
/**
|
||||
* Images for jpg image type.
|
||||
*/
|
||||
@SerialName("jpg")
|
||||
val jpg: ImageFormat? = null,
|
||||
|
||||
/**
|
||||
* Images for webp image type.
|
||||
*/
|
||||
@SerialName("webp")
|
||||
val webp: ImageFormat? = null
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Licensor data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Licensor(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for licensor.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for licensor.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for licensor.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Producer data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Producer(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for producer.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for producer.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for producer.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Prop data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Prop(
|
||||
/**
|
||||
* Start date.
|
||||
* @see DateProp for the detail.
|
||||
*/
|
||||
@SerialName("from")
|
||||
val from: DateProp? = DateProp(),
|
||||
|
||||
/**
|
||||
* String with date.
|
||||
*/
|
||||
@SerialName("string")
|
||||
val string: String? = "",
|
||||
|
||||
/**
|
||||
* End date.
|
||||
* @see DateProp for the detail.
|
||||
*/
|
||||
@SerialName("to")
|
||||
val to: DateProp? = DateProp()
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Relation data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Relation(
|
||||
/**
|
||||
* List of entries for relation in anime.
|
||||
* @see Entry for the detail.
|
||||
*/
|
||||
@SerialName("entry")
|
||||
val entry: List<Entry>?,
|
||||
|
||||
/**
|
||||
* Relation for anime
|
||||
*/
|
||||
@SerialName("relation")
|
||||
val relation: String?
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
|
||||
data class Score(
|
||||
@SerialName("percentage")
|
||||
val percentage: Double,
|
||||
|
||||
@SerialName("score")
|
||||
val score: Int,
|
||||
|
||||
@SerialName("votes")
|
||||
val votes: Int
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Streaming data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Streaming(
|
||||
/**
|
||||
* Name of streaming info.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Url of streaming info.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Studio data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Studio(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for studio.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for studio.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for studio.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Theme data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Theme(
|
||||
/**
|
||||
* List of endings.
|
||||
*/
|
||||
@SerialName("endings")
|
||||
val endings: List<String>? = emptyList(),
|
||||
|
||||
/**
|
||||
* List of openings.
|
||||
*/
|
||||
@SerialName("openings")
|
||||
val openings: List<String>? = emptyList()
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Themes data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Themes(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
@SerialName("mal_id")
|
||||
val malId: Int?,
|
||||
|
||||
/**
|
||||
* Name for themes.
|
||||
*/
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
/**
|
||||
* Type for themes.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?,
|
||||
|
||||
/**
|
||||
* Url for themes.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String?
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Title data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Title(
|
||||
/**
|
||||
* Title for anime.
|
||||
*/
|
||||
@SerialName("title")
|
||||
val title: String?,
|
||||
|
||||
/**
|
||||
* Title type for anime.
|
||||
*/
|
||||
@SerialName("type")
|
||||
val type: String?
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.anime
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Trailer data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Trailer(
|
||||
/**
|
||||
* Embed url for trailer.
|
||||
*/
|
||||
@SerialName("embed_url")
|
||||
val embedUrl: String? = "",
|
||||
|
||||
/**
|
||||
* Url for trailer.
|
||||
*/
|
||||
@SerialName("url")
|
||||
val url: String? = "",
|
||||
|
||||
/**
|
||||
* Youtube id for trailer.
|
||||
*/
|
||||
@SerialName("youtube_id")
|
||||
val youtubeId: String? = "",
|
||||
|
||||
/**
|
||||
* Images for trailer.
|
||||
*/
|
||||
@SerialName("images")
|
||||
val images: ImageFormat? = ImageFormat()
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
package com.jeluchu.core.models.jikan.search
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* ItemsPage data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class ItemsPage(
|
||||
/**
|
||||
* Count page.
|
||||
*/
|
||||
@SerialName("count")
|
||||
val count: Int? = 0,
|
||||
|
||||
/**
|
||||
* Total items availables.
|
||||
*/
|
||||
@SerialName("total")
|
||||
val total: Int? = 0,
|
||||
|
||||
/**
|
||||
* Total items per page.
|
||||
*/
|
||||
@SerialName("per_page")
|
||||
val itemsPerPage: Int? = 0
|
||||
)
|
|
@ -0,0 +1,34 @@
|
|||
package com.jeluchu.core.models.jikan.search
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Pagination data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Pagination(
|
||||
/**
|
||||
* Current page available.
|
||||
*/
|
||||
@SerialName("current_page")
|
||||
val currentPage: Int? = 0,
|
||||
|
||||
/**
|
||||
* Last page available.
|
||||
*/
|
||||
@SerialName("last_visible_page")
|
||||
val lastPage: Int? = 0,
|
||||
|
||||
/**
|
||||
* Items information.
|
||||
*/
|
||||
@SerialName("items")
|
||||
val itemsPage: ItemsPage? = ItemsPage(),
|
||||
|
||||
/**
|
||||
* Request hast next page or not.
|
||||
*/
|
||||
@SerialName("has_next_page")
|
||||
val hasNextPage: Boolean? = null
|
||||
)
|
|
@ -0,0 +1,23 @@
|
|||
package com.jeluchu.core.models.jikan.search
|
||||
|
||||
import com.jeluchu.core.models.jikan.anime.AnimeData
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Search data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class Search(
|
||||
/**
|
||||
* Pagination info for request
|
||||
*/
|
||||
@SerialName("pagination")
|
||||
val pagination: Pagination? = Pagination(),
|
||||
|
||||
/**
|
||||
* Data list of all anime found.
|
||||
*/
|
||||
@SerialName("data")
|
||||
val data: List<AnimeData>? = emptyList()
|
||||
)
|
|
@ -1,6 +1,27 @@
|
|||
package com.jeluchu.core.utils
|
||||
|
||||
object BaseUrls {
|
||||
const val JIKAN = "https://api.jikan.moe/v4/"
|
||||
}
|
||||
|
||||
object Endpoints {
|
||||
const val SCHEDULES = "schedules"
|
||||
}
|
||||
|
||||
|
||||
object Routes {
|
||||
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"
|
||||
}
|
||||
|
||||
object TimerKey {
|
||||
const val KEY = "key"
|
||||
const val SCHEDULE = "schedule"
|
||||
const val LAST_UPDATED = "lastUpdated"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.jeluchu.core.utils
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class Day {
|
||||
MONDAY,
|
||||
TUESDAY,
|
||||
WEDNESDAY,
|
||||
THURSDAY,
|
||||
FRIDAY,
|
||||
SATURDAY,
|
||||
SUNDAY,
|
||||
}
|
||||
|
||||
fun parseDay(day: String) = Day.entries.firstOrNull { it.name.equals(day, ignoreCase = true) }
|
|
@ -0,0 +1,8 @@
|
|||
package com.jeluchu.core.utils
|
||||
|
||||
enum class TimeUnit {
|
||||
DAY,
|
||||
HOUR,
|
||||
MINUTE,
|
||||
SECOND
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.jeluchu.core.utils
|
||||
|
||||
import com.jeluchu.features.schedule.models.DayEntity
|
||||
import com.jeluchu.features.schedule.models.ScheduleData
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.bson.Document
|
||||
|
||||
fun parseScheduleDataToDocuments(data: ScheduleData): List<Document> {
|
||||
val documents = mutableListOf<Document>()
|
||||
fun processDay(dayList: List<DayEntity>?) {
|
||||
dayList?.forEach { animeData ->
|
||||
val animeJsonString = Json.encodeToString(animeData)
|
||||
val document = Document.parse(animeJsonString)
|
||||
documents.add(document)
|
||||
}
|
||||
}
|
||||
|
||||
processDay(data.monday)
|
||||
processDay(data.tuesday)
|
||||
processDay(data.wednesday)
|
||||
processDay(data.thursday)
|
||||
processDay(data.friday)
|
||||
processDay(data.saturday)
|
||||
processDay(data.sunday)
|
||||
|
||||
return documents
|
||||
}
|
|
@ -2,7 +2,9 @@ package com.jeluchu.features.anime.mappers
|
|||
|
||||
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.schedule.models.DayEntity
|
||||
import org.bson.Document
|
||||
|
||||
fun documentToAnimeDirectoryEntity(doc: Document) = AnimeDirectoryEntity(
|
||||
|
@ -211,3 +213,10 @@ fun documentToVideoPromo(doc: Document): VideoPromo {
|
|||
images = doc.get("images", Document::class.java)?.let { documentToImages(it) } ?: Images()
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToScheduleDayEntity(doc: Document) = DayEntity(
|
||||
day = doc.getStringSafe("day"),
|
||||
malId = doc.getIntSafe("malId"),
|
||||
image = doc.getStringSafe("image"),
|
||||
title = doc.getStringSafe("title")
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package com.example.models
|
||||
package com.jeluchu.features.anime.models.anime
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.example.models
|
||||
|
||||
import com.jeluchu.features.anime.models.anime.Images
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.jeluchu.features.rankings.routes
|
||||
|
||||
import com.jeluchu.core.extensions.getToJson
|
||||
import com.jeluchu.core.utils.Routes
|
||||
import com.jeluchu.features.rankings.services.RankingsService
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Route.rankingsEndpoints(
|
||||
mongoDatabase: MongoDatabase,
|
||||
service: RankingsService = RankingsService(mongoDatabase)
|
||||
) {
|
||||
getToJson(Routes.TOP_ANIME) { service.getAnimeByMalId(call) }
|
||||
getToJson(Routes.TOP_MANGA) { service.getDirectory(call) }
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package com.jeluchu.features.rankings.services
|
||||
|
||||
import com.jeluchu.core.connection.RestClient
|
||||
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.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.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 RankingsService(
|
||||
database: MongoDatabase
|
||||
) {
|
||||
private val timers = database.getCollection("timers")
|
||||
private val schedules = database.getCollection("schedule")
|
||||
|
||||
suspend fun getAnimeRanking(call: RoutingCall) {
|
||||
val needsUpdate = timers.needsUpdate(
|
||||
amount = 30,
|
||||
unit = TimeUnit.DAY,
|
||||
key = TimerKey.SCHEDULE
|
||||
)
|
||||
|
||||
if (needsUpdate) {
|
||||
schedules.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 documentsToInsert = parseScheduleDataToDocuments(response)
|
||||
if (documentsToInsert.isNotEmpty()) schedules.insertMany(documentsToInsert)
|
||||
timers.update(TimerKey.SCHEDULE)
|
||||
|
||||
call.respond(HttpStatusCode.OK, Json.encodeToString(response))
|
||||
} else {
|
||||
val elements = schedules.find().toList()
|
||||
val directory = elements.map { documentToScheduleDayEntity(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())
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.jeluchu.features.schedule.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DayEntity(
|
||||
val malId: Int,
|
||||
val day: String,
|
||||
val title: String,
|
||||
val image: String,
|
||||
)
|
|
@ -0,0 +1,49 @@
|
|||
package com.jeluchu.features.schedule.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ScheduleData(
|
||||
/**
|
||||
* All current season entries scheduled for Monday.
|
||||
*/
|
||||
@SerialName("monday")
|
||||
val monday: List<DayEntity>? = emptyList(),
|
||||
|
||||
/**
|
||||
* All current season entries scheduled for Tuesday.
|
||||
*/
|
||||
@SerialName("tuesday")
|
||||
val tuesday: List<DayEntity>? = emptyList(),
|
||||
|
||||
/**
|
||||
* All current season entries scheduled for Wednesday.
|
||||
*/
|
||||
@SerialName("wednesday")
|
||||
val wednesday: List<DayEntity>? = emptyList(),
|
||||
|
||||
/**
|
||||
* All current season entries scheduled for Thursday.
|
||||
*/
|
||||
@SerialName("thursday")
|
||||
val thursday: List<DayEntity>? = emptyList(),
|
||||
|
||||
/**
|
||||
* All current season entries scheduled for Friday.
|
||||
*/
|
||||
@SerialName("friday")
|
||||
val friday: List<DayEntity>? = emptyList(),
|
||||
|
||||
/**
|
||||
* All current season entries scheduled for Saturday.
|
||||
*/
|
||||
@SerialName("saturday")
|
||||
val saturday: List<DayEntity>? = emptyList(),
|
||||
|
||||
/**
|
||||
* All current season entries scheduled for Sunday.
|
||||
*/
|
||||
@SerialName("sunday")
|
||||
val sunday: List<DayEntity>? = emptyList()
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
package com.jeluchu.features.schedule.models
|
||||
|
||||
import com.jeluchu.core.models.jikan.search.Pagination
|
||||
import com.jeluchu.core.models.jikan.anime.AnimeData
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Schedule data class.
|
||||
*/
|
||||
@Serializable
|
||||
data class ScheduleEntity(
|
||||
/**
|
||||
* Pagination info for request
|
||||
*/
|
||||
@SerialName("pagination")
|
||||
val pagination: Pagination? = Pagination(),
|
||||
|
||||
/**
|
||||
* Data for anime requested.
|
||||
*/
|
||||
@SerialName("data")
|
||||
val data: List<AnimeData>? = emptyList()
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
package com.jeluchu.features.schedule.routes
|
||||
|
||||
import com.jeluchu.core.extensions.getToJson
|
||||
import com.jeluchu.core.utils.Routes
|
||||
import com.jeluchu.features.schedule.services.ScheduleService
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Route.scheduleEndpoints(
|
||||
mongoDatabase: MongoDatabase,
|
||||
service: ScheduleService = ScheduleService(mongoDatabase)
|
||||
) {
|
||||
getToJson(Routes.SCHEDULE) { service.getSchedule(call) }
|
||||
getToJson(Routes.SCHEDULE_DAY) { service.getScheduleByDay(call) }
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.jeluchu.features.schedule.services
|
||||
|
||||
import com.jeluchu.core.connection.RestClient
|
||||
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.utils.*
|
||||
import com.jeluchu.features.anime.mappers.documentToScheduleDayEntity
|
||||
import com.jeluchu.features.schedule.models.ScheduleData
|
||||
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 ScheduleService(
|
||||
database: MongoDatabase
|
||||
) {
|
||||
private val timers = database.getCollection("timers")
|
||||
private val schedules = database.getCollection("schedule")
|
||||
|
||||
suspend fun getSchedule(call: RoutingCall) {
|
||||
val needsUpdate = timers.needsUpdate(
|
||||
amount = 7,
|
||||
unit = TimeUnit.DAY,
|
||||
key = TimerKey.SCHEDULE
|
||||
)
|
||||
|
||||
if (needsUpdate) {
|
||||
schedules.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 documentsToInsert = parseScheduleDataToDocuments(response)
|
||||
if (documentsToInsert.isNotEmpty()) schedules.insertMany(documentsToInsert)
|
||||
timers.update(TimerKey.SCHEDULE)
|
||||
|
||||
call.respond(HttpStatusCode.OK, Json.encodeToString(response))
|
||||
} else {
|
||||
val elements = schedules.find().toList()
|
||||
val directory = elements.map { documentToScheduleDayEntity(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())
|
||||
}
|
Loading…
Reference in New Issue