diff --git a/src/main/kotlin/com/jeluchu/core/extensions/RoutesExtensions.kt b/src/main/kotlin/com/jeluchu/core/extensions/RoutesExtensions.kt new file mode 100644 index 0000000..c0021cd --- /dev/null +++ b/src/main/kotlin/com/jeluchu/core/extensions/RoutesExtensions.kt @@ -0,0 +1,14 @@ +package com.jeluchu.core.extensions + +import io.ktor.http.* +import io.ktor.server.routing.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +fun Route.getToJson( + path: String, + request: suspend RoutingContext.() -> Unit +): Route = get(path) { + call.response.headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString()) + withContext(Dispatchers.IO) { request() } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/utils/Constants.kt b/src/main/kotlin/com/jeluchu/core/utils/Constants.kt new file mode 100644 index 0000000..08c1154 --- /dev/null +++ b/src/main/kotlin/com/jeluchu/core/utils/Constants.kt @@ -0,0 +1,6 @@ +package com.jeluchu.core.utils + +object Routes { + const val DIRECTORY = "/directory" + const val ANIME_DETAILS = "/anime/{id}" +} \ No newline at end of file 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 716d96f..9208655 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt @@ -2,9 +2,25 @@ package com.jeluchu.features.anime.mappers import com.example.models.* import com.jeluchu.core.extensions.* -import kotlinx.serialization.json.Json +import com.jeluchu.features.anime.models.directory.AnimeDirectoryEntity import org.bson.Document +fun documentToAnimeDirectoryEntity(doc: Document) = AnimeDirectoryEntity( + rank = doc.getIntSafe("rank"), + year = doc.getIntSafe("year"), + url = doc.getStringSafe("url"), + malId = doc.getIntSafe("malId"), + type = doc.getStringSafe("type"), + score = doc.getStringSafe("score"), + title = doc.getStringSafe("title"), + status = doc.getStringSafe("status"), + season = doc.getStringSafe("season"), + poster = doc.getStringSafe("poster"), + airing = doc.getBooleanSafe("airing"), + genres = doc.getListSafe("genres"), + episodesCount = doc.getIntSafe("episodesCount") +) + fun documentToMoreInfoEntity(doc: Document): MoreInfoEntity { return MoreInfoEntity( id = doc.getLongSafe("id"), @@ -14,138 +30,138 @@ fun documentToMoreInfoEntity(doc: Document): MoreInfoEntity { cover = doc.getStringSafe("cover"), genres = doc.getListSafe("genres"), synopsis = doc.getStringSafe("synopsis"), - episodes = doc.getListSafe("episodes").map { documentToMergedEpisode(it) } ?: emptyList(), - episodesCount = doc.getIntSafe("episodesCount", 0) ?: 0, - score = doc.getStringSafe("score") ?: "", - staff = doc.getListSafe("staff").map { documentToStaff(it) } ?: emptyList(), - characters = doc.getListSafe("characters").map { documentToCharacter(it) } ?: emptyList(), - status = doc.getStringSafe("status") ?: "", - type = doc.getStringSafe("type") ?: "", - url = doc.getStringSafe("url") ?: "", + episodes = doc.getListSafe("episodes").map { documentToMergedEpisode(it) }, + episodesCount = doc.getIntSafe("episodesCount", 0), + score = doc.getStringSafe("score"), + staff = doc.getListSafe("staff").map { documentToStaff(it) }, + characters = doc.getListSafe("characters").map { documentToCharacter(it) }, + status = doc.getStringSafe("status"), + type = doc.getStringSafe("type"), + url = doc.getStringSafe("url"), promo = doc.getDocumentSafe("promo")?.let { documentToVideoPromo(it) } ?: VideoPromo(), - source = doc.getStringSafe("source") ?: "", - duration = doc.getStringSafe("duration") ?: "", - rank = doc.getIntSafe("rank", 0) ?: 0, - titles = doc.getListSafe("titles").map { documentToAlternativeTitles(it) } ?: emptyList(), - airing = doc.getBooleanSafe("airing") ?: false, + source = doc.getStringSafe("source"), + duration = doc.getStringSafe("duration"), + rank = doc.getIntSafe("rank", 0), + titles = doc.getListSafe("titles").map { documentToAlternativeTitles(it) }, + airing = doc.getBooleanSafe("airing"), aired = doc.getDocumentSafe("aired")?.let { documentToAiringTime(it) } ?: AiringTime(), broadcast = doc.getDocumentSafe("broadcast")?.let { documentToAnimeBroadcast(it) } ?: AnimeBroadcast(), - season = doc.getStringSafe("season") ?: "", + season = doc.getStringSafe("season"), year = doc.getIntSafe("year", 0), - external = doc.getListSafe("external").map { documentToExternalLinks(it) } ?: emptyList(), - streaming = doc.getListSafe("streaming").map { documentToExternalLinks(it) } ?: emptyList(), - studios = doc.getListSafe("studios").map { documentToCompanies(it) } ?: emptyList(), - licensors = doc.getListSafe("licensors").map { documentToCompanies(it) } ?: emptyList(), - producers = doc.getListSafe("producers").map { documentToCompanies(it) } ?: emptyList(), + external = doc.getListSafe("external").map { documentToExternalLinks(it) }, + streaming = doc.getListSafe("streaming").map { documentToExternalLinks(it) }, + studios = doc.getListSafe("studios").map { documentToCompanies(it) }, + licensors = doc.getListSafe("licensors").map { documentToCompanies(it) }, + producers = doc.getListSafe("producers").map { documentToCompanies(it) }, theme = doc.getDocumentSafe("theme")?.let { documentToThemes(it) } ?: Themes(), - relations = doc.getListSafe("relations").map { documentToRelated(it) } ?: emptyList(), + relations = doc.getListSafe("relations").map { documentToRelated(it) }, stats = doc.getDocumentSafe("stats")?.let { documentToStatistics(it) } ?: Statistics(), - gallery = doc.getListSafe("gallery").map { documentToImageMediaEntity(it) } ?: emptyList(), - episodeSource = doc.getStringSafe("episodeSource") ?: "" + gallery = doc.getListSafe("gallery").map { documentToImageMediaEntity(it) }, + episodeSource = doc.getStringSafe("episodeSource") ) } fun documentToActor(doc: Document): Actor { return Actor( person = doc.getDocumentSafe("person")?.let { documentToIndividual(it) } ?: Individual(), - language = doc.getStringSafe("language") ?: "" + language = doc.getStringSafe("language") ) } fun documentToAiringTime(doc: Document): AiringTime { return AiringTime( - from = doc.getStringSafe("from") ?: "", - to = doc.getStringSafe("to") ?: "" + from = doc.getStringSafe("from"), + to = doc.getStringSafe("to") ) } fun documentToAlternativeTitles(doc: Document): AlternativeTitles { return AlternativeTitles( - title = doc.getStringSafe("title") ?: "", - type = doc.getStringSafe("type") ?: "" + title = doc.getStringSafe("title"), + type = doc.getStringSafe("type") ) } fun documentToAnimeBroadcast(doc: Document): AnimeBroadcast { return AnimeBroadcast( - day = doc.getStringSafe("day") ?: "", - time = doc.getStringSafe("time") ?: "", - timezone = doc.getStringSafe("timezone") ?: "" + day = doc.getStringSafe("day"), + time = doc.getStringSafe("time"), + timezone = doc.getStringSafe("timezone") ) } fun documentToAnimeSource(doc: Document): AnimeSource { return AnimeSource( - id = doc.getStringSafe("id") ?: "", - source = doc.getStringSafe("source") ?: "" + id = doc.getStringSafe("id"), + source = doc.getStringSafe("source") ) } fun documentToCharacter(doc: Document): Character { return Character( character = doc.getDocumentSafe("character")?.let { documentToIndividual(it) } ?: Individual(), - role = doc.getStringSafe("role") ?: "", - voiceActor = doc.getListSafe("voiceActor").map { documentToActor(it) } ?: emptyList() + role = doc.getStringSafe("role"), + voiceActor = doc.getListSafe("voiceActor").map { documentToActor(it) } ) } fun documentToCompanies(doc: Document): Companies { return Companies( malId = doc.getIntSafe("malId", 0), - name = doc.getStringSafe("name") ?: "", - type = doc.getStringSafe("type") ?: "", - url = doc.getStringSafe("url") ?: "" + name = doc.getStringSafe("name"), + type = doc.getStringSafe("type"), + url = doc.getStringSafe("url") ) } fun documentToExternalLinks(doc: Document): ExternalLinks { return ExternalLinks( - url = doc.getStringSafe("url") ?: "", - name = doc.getStringSafe("name") ?: "" + url = doc.getStringSafe("url"), + name = doc.getStringSafe("name") ) } fun documentToImageMediaEntity(doc: Document): ImageMediaEntity { return ImageMediaEntity( - media = doc.getStringSafe("media") ?: "", - thumbnail = doc.getStringSafe("thumbnail") ?: "", + media = doc.getStringSafe("media"), + thumbnail = doc.getStringSafe("thumbnail"), width = doc.getIntSafe("width", 0), height = doc.getIntSafe("height", 0), - url = doc.getStringSafe("url") ?: "" + url = doc.getStringSafe("url") ) } fun documentToImages(doc: Document): Images { return Images( - generic = doc.getStringSafe("generic") ?: "", - small = doc.getStringSafe("small") ?: "", - medium = doc.getStringSafe("medium") ?: "", - large = doc.getStringSafe("large") ?: "", - maximum = doc.getStringSafe("maximum") ?: "" + generic = doc.getStringSafe("generic"), + small = doc.getStringSafe("small"), + medium = doc.getStringSafe("medium"), + large = doc.getStringSafe("large"), + maximum = doc.getStringSafe("maximum") ) } fun documentToIndividual(doc: Document): Individual { return Individual( malId = doc.getIntSafe("malId", 0), - url = doc.getStringSafe("url") ?: "", - name = doc.getStringSafe("name") ?: "", - images = doc.getStringSafe("images") ?: "" + url = doc.getStringSafe("url"), + name = doc.getStringSafe("name"), + images = doc.getStringSafe("images") ) } fun documentToMergedEpisode(doc: Document): MergedEpisode { return MergedEpisode( number = doc.getIntSafe("number", 0), - ids = doc.getListSafe("ids").map { documentToAnimeSource(it) }.toMutableList() ?: mutableListOf(), - nextEpisodeDate = doc.getStringSafe("nextEpisodeDate") ?: "" + ids = doc.getListSafe("ids").map { documentToAnimeSource(it) }.toMutableList(), + nextEpisodeDate = doc.getStringSafe("nextEpisodeDate") ) } fun documentToRelated(doc: Document): Related { return Related( - entry = doc.getListSafe("entry").map { documentToCompanies(it) } ?: emptyList(), - relation = doc.getStringSafe("relation") ?: "" + entry = doc.getListSafe("entry").map { documentToCompanies(it) }, + relation = doc.getStringSafe("relation") ) } @@ -164,19 +180,19 @@ fun documentToScore(doc: Document): Score { fun documentToStaff(doc: Document): Staff { return Staff( person = doc.get("person", Document::class.java)?.let { documentToIndividual(it) } ?: Individual(), - positions = doc.getListSafe("positions") ?: emptyList() + positions = doc.getListSafe("positions") ) } fun documentToStatistics(doc: Document): Statistics { return Statistics( - completed = doc.getIntSafe("completed") ?: 0, - dropped = doc.getIntSafe("dropped") ?: 0, - onHold = doc.getIntSafe("onHold") ?: 0, - planToWatch = doc.getIntSafe("planToWatch") ?: 0, - scores = doc.getListSafe("scores").map { documentToScore(it) } ?: emptyList(), - total = doc.getIntSafe("total") ?: 0, - watching = doc.getIntSafe("watching") ?: 0 + completed = doc.getIntSafe("completed"), + dropped = doc.getIntSafe("dropped"), + onHold = doc.getIntSafe("onHold"), + planToWatch = doc.getIntSafe("planToWatch"), + scores = doc.getListSafe("scores").map { documentToScore(it) }, + total = doc.getIntSafe("total"), + watching = doc.getIntSafe("watching") ) } @@ -189,17 +205,9 @@ fun documentToThemes(doc: Document): Themes { fun documentToVideoPromo(doc: Document): VideoPromo { return VideoPromo( - embedUrl = doc.getStringSafe("embedUrl") ?: "", - url = doc.getStringSafe("url") ?: "", - youtubeId = doc.getStringSafe("youtubeId") ?: "", + embedUrl = doc.getStringSafe("embedUrl"), + url = doc.getStringSafe("url"), + youtubeId = doc.getStringSafe("youtubeId"), images = doc.get("images", Document::class.java)?.let { documentToImages(it) } ?: Images() ) } - - -fun List.toMoreInfoEntity(): List { - val json = Json { ignoreUnknownKeys = true } - val jsonStrings = map { it.toJson() } - return jsonStrings.map { json.decodeFromString(it) } -} - diff --git a/src/main/kotlin/com/jeluchu/features/anime/models/directory/AnimeDirectoryEntity.kt b/src/main/kotlin/com/jeluchu/features/anime/models/directory/AnimeDirectoryEntity.kt new file mode 100644 index 0000000..a703e99 --- /dev/null +++ b/src/main/kotlin/com/jeluchu/features/anime/models/directory/AnimeDirectoryEntity.kt @@ -0,0 +1,20 @@ +package com.jeluchu.features.anime.models.directory + +import kotlinx.serialization.Serializable + +@Serializable +data class AnimeDirectoryEntity( + val rank: Int = 0, + val year: Int = 0, + var malId: Int = 0, + val url: String = "", + var type: String = "", + var score: String = "", + var title: String = "", + var status: String = "", + val season: String = "", + var poster: String = "", + var episodesCount: Int = 0, + val airing: Boolean = false, + var genres: List = emptyList() +) \ No newline at end of file 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 dcc4f5d..d820204 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/routes/AnimeRoutes.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/routes/AnimeRoutes.kt @@ -1,5 +1,7 @@ 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.mongodb.client.MongoDatabase import io.ktor.server.routing.* @@ -8,6 +10,6 @@ fun Route.animeEndpoints( mongoDatabase: MongoDatabase, service: AnimeService = AnimeService(mongoDatabase) ) { - get("/directory") { service.getDirectory(call) } - get("/anime/{id}") { service.getAnimeByMalId(call) } + getToJson(Routes.DIRECTORY) { service.getDirectory(call) } + getToJson(Routes.ANIME_DETAILS) { service.getAnimeByMalId(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 b2a20c9..3d26e84 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt @@ -2,15 +2,13 @@ package com.jeluchu.features.anime.services import com.jeluchu.core.messages.ErrorMessages import com.jeluchu.core.models.ErrorResponse +import com.jeluchu.features.anime.mappers.documentToAnimeDirectoryEntity import com.jeluchu.features.anime.mappers.documentToMoreInfoEntity -import com.jeluchu.features.anime.mappers.toMoreInfoEntity 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.coroutines.Dispatchers -import kotlinx.coroutines.withContext import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -19,35 +17,23 @@ class AnimeService( ) { private val directoryCollection = database.getCollection("animedetails") - suspend fun getDirectory( - call: RoutingCall - ) = withContext(Dispatchers.IO) { - call.response.headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString()) - - try { - val elements = directoryCollection.find().toList() - val directory = elements.map { documentToMoreInfoEntity(it) } - val json = Json.encodeToString(directory) - call.respond(HttpStatusCode.OK, json) - } catch (ex: Exception) { - call.respond(HttpStatusCode.Unauthorized, ErrorResponse(ErrorMessages.UnauthorizedMongo.message)) - } + 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) + } catch (ex: Exception) { + call.respond(HttpStatusCode.Unauthorized, ErrorResponse(ErrorMessages.UnauthorizedMongo.message)) } - suspend fun getAnimeByMalId( - call: RoutingCall - ) = withContext(Dispatchers.IO) { - call.response.headers.append(HttpHeaders.ContentType, ContentType.Application.Json.toString()) - - try { - val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) - directoryCollection.find(Filters.eq("malId", id)).firstOrNull()?.let { anime -> - val info = documentToMoreInfoEntity(anime) - call.respond(HttpStatusCode.OK, Json.encodeToString(info)) - } ?: call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.AnimeNotFound.message)) - } catch (ex: Exception) { - call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message)) - } + suspend fun getAnimeByMalId(call: RoutingCall) = try { + val id = call.parameters["id"]?.toInt() ?: throw IllegalArgumentException(ErrorMessages.InvalidMalId.message) + directoryCollection.find(Filters.eq("malId", id)).firstOrNull()?.let { anime -> + val info = documentToMoreInfoEntity(anime) + call.respond(HttpStatusCode.OK, Json.encodeToString(info)) + } ?: call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.AnimeNotFound.message)) + } catch (ex: Exception) { + call.respond(HttpStatusCode.NotFound, ErrorResponse(ErrorMessages.InvalidInput.message)) } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..a0f55c8 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %date %-5level [%thread] %logger{0}: %msg%n + + + + + + +