diff --git a/Dockerfile b/Dockerfile index 00a6c26..b00ed88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN gradle buildFatJar --no-daemon FROM amazoncorretto:21 AS runtime EXPOSE 8080/tcp RUN mkdir /app -COPY --from=build /home/gradle/src/build/libs/*.jar /app/app.jar -COPY entrypoint.sh /app/entrypoint.sh -RUN chmod +x /app/entrypoint.sh -ENTRYPOINT ["/app/entrypoint.sh"] +COPY --from=build /home/gradle/src/build/libs/aruppi-api-all.jar /app/app.jar +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh index 5f8cbff..f8719c2 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -2,14 +2,14 @@ # Check if MONGO_CONNECTION_STRING is defined if [ -z "$MONGO_CONNECTION_STRING" ]; then - echo "ERROR: The environment variable for the database, MONGO_CONNECTION_STRING is not defined." + >&2 echo -e "ERROR: The environment variable MONGO_CONNECTION_STRING is not defined.\n\tYou must provide a valid Connection String" exit 1 fi # Verificar si MONGO_DATABASE_NAME está definido if [ -z "$MONGO_DATABASE_NAME" ]; then - echo "WARNING: The environment variable for the database, MONGO_DATABASE_NAME, is not defined. It is using ‘mongodb’ as the default value." - MONGO_DATABASE_NAME="mongodb" + >&2 echo -e "WARNING: The environment variable for the database, MONGO_DATABASE_NAME, is not defined.\n\tUsing ‘aruppi’ as default value." + MONGO_DATABASE_NAME="aruppi" fi @@ -25,10 +25,12 @@ ktor: db: mongo: - connectionStrings: "${MONGO_CONNECTION_STRING}" + connectionStrings: ${MONGO_CONNECTION_STRING} database: - name: "${MONGO_DATABASE_NAME:-mongodb}" + name: ${MONGO_DATABASE_NAME:-mongodb} EOF +cd /app/ + # Run the application with the specified configuration exec java -Dconfig.file=/app/application.yaml -jar /app/app.jar \ No newline at end of file diff --git a/src/main/kotlin/com/jeluchu/core/extensions/CommonExtensions.kt b/src/main/kotlin/com/jeluchu/core/extensions/CommonExtensions.kt new file mode 100644 index 0000000..4ca21d6 --- /dev/null +++ b/src/main/kotlin/com/jeluchu/core/extensions/CommonExtensions.kt @@ -0,0 +1,115 @@ +package com.jeluchu.core.extensions + +import org.bson.Document +import java.text.SimpleDateFormat +import java.util.* + +fun Document.getStringSafe(key: String, defaultValue: String = ""): String { + return try { + when (val value = this[key]) { + is String -> value + is Number, is Boolean -> value.toString() + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +fun Document.getIntSafe(key: String, defaultValue: Int = 0): Int { + return try { + when (val value = this[key]) { + is Int -> value + is Number -> value.toInt() + is String -> value.toIntOrNull() ?: defaultValue + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +fun Document.getDoubleSafe(key: String, defaultValue: Double = 0.0): Double { + return try { + when (val value = this[key]) { + is Double -> value + is Number -> value.toDouble() + is String -> value.toDoubleOrNull() ?: defaultValue + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +fun Document.getFloatSafe(key: String, defaultValue: Float = 0.0f): Float { + return try { + when (val value = this[key]) { + is Float -> value + is Number -> value.toFloat() + is String -> value.toFloatOrNull() ?: defaultValue + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +fun Document.getLongSafe(key: String, defaultValue: Long = 0L): Long { + return try { + val value = this.get(key) + when (value) { + is Long -> value + is Number -> value.toLong() + is String -> value.toLongOrNull() ?: defaultValue + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +fun Document.getBooleanSafe(key: String, defaultValue: Boolean = false): Boolean { + return try { + when (val value = this[key]) { + is Boolean -> value + is String -> value.toBoolean() + is Number -> value.toInt() != 0 + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +fun Document.getDateSafe(key: String, defaultValue: Date = Date(0)): Date { + return try { + when (val value = this[key]) { + is Date -> value + is String -> SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").parse(value) ?: defaultValue + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +inline fun Document.getListSafe(key: String, defaultValue: List = emptyList()): List { + return try { + when (val value = this[key]) { + is List<*> -> value.filterIsInstance() + else -> defaultValue + } + } catch (e: Exception) { + defaultValue + } +} + +fun Document.getDocumentSafe(key: String): Document? { + return try { + val value = this[key] + if (value is Document) value else null + } catch (e: Exception) { + null + } +} \ 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 fa658dd..716d96f 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/mappers/AnimeMappers.kt @@ -1,149 +1,151 @@ package com.jeluchu.features.anime.mappers import com.example.models.* +import com.jeluchu.core.extensions.* +import kotlinx.serialization.json.Json import org.bson.Document fun documentToMoreInfoEntity(doc: Document): MoreInfoEntity { return MoreInfoEntity( - id = doc.getLong("id") ?: 0, - malId = doc.getInteger("malId", 0) ?: 0, - title = doc.getString("title") ?: "", - poster = doc.getString("poster") ?: "", - cover = doc.getString("cover") ?: "", - genres = doc.getList("genres", String::class.java) ?: emptyList(), - synopsis = doc.getString("synopsis") ?: "", - episodes = doc.getList("episodes", Document::class.java)?.map { documentToMergedEpisode(it) } ?: emptyList(), - episodesCount = doc.getInteger("episodesCount", 0) ?: 0, - score = doc.getString("score") ?: "", - staff = doc.getList("staff", Document::class.java)?.map { documentToStaff(it) } ?: emptyList(), - characters = doc.getList("characters", Document::class.java)?.map { documentToCharacter(it) } ?: emptyList(), - status = doc.getString("status") ?: "", - type = doc.getString("type") ?: "", - url = doc.getString("url") ?: "", - promo = doc.get("promo", Document::class.java)?.let { documentToVideoPromo(it) } ?: VideoPromo(), - source = doc.getString("source") ?: "", - duration = doc.getString("duration") ?: "", - rank = doc.getInteger("rank", 0) ?: 0, - titles = doc.getList("titles", Document::class.java)?.map { documentToAlternativeTitles(it) } ?: emptyList(), - airing = doc.getBoolean("airing", false) ?: false, - aired = doc.get("aired", Document::class.java)?.let { documentToAiringTime(it) } ?: AiringTime(), - broadcast = doc.get("broadcast", Document::class.java)?.let { documentToAnimeBroadcast(it) } ?: AnimeBroadcast(), - season = doc.getString("season") ?: "", - year = doc.getInteger("year", 0), - external = doc.getList("external", Document::class.java)?.map { documentToExternalLinks(it) } ?: emptyList(), - streaming = doc.getList("streaming", Document::class.java)?.map { documentToExternalLinks(it) } ?: emptyList(), - studios = doc.getList("studios", Document::class.java)?.map { documentToCompanies(it) } ?: emptyList(), - licensors = doc.getList("licensors", Document::class.java)?.map { documentToCompanies(it) } ?: emptyList(), - producers = doc.getList("producers", Document::class.java)?.map { documentToCompanies(it) } ?: emptyList(), - theme = doc.get("theme", Document::class.java)?.let { documentToThemes(it) } ?: Themes(), - relations = doc.getList("relations", Document::class.java)?.map { documentToRelated(it) } ?: emptyList(), - stats = doc.get("stats", Document::class.java)?.let { documentToStatistics(it) } ?: Statistics(), - gallery = doc.getList("gallery", Document::class.java)?.map { documentToImageMediaEntity(it) } ?: emptyList(), - episodeSource = doc.getString("episodeSource") ?: "" + id = doc.getLongSafe("id"), + malId = doc.getIntSafe("malId"), + title = doc.getStringSafe("title"), + poster = doc.getStringSafe("poster"), + 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") ?: "", + 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, + aired = doc.getDocumentSafe("aired")?.let { documentToAiringTime(it) } ?: AiringTime(), + broadcast = doc.getDocumentSafe("broadcast")?.let { documentToAnimeBroadcast(it) } ?: AnimeBroadcast(), + 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(), + theme = doc.getDocumentSafe("theme")?.let { documentToThemes(it) } ?: Themes(), + relations = doc.getListSafe("relations").map { documentToRelated(it) } ?: emptyList(), + stats = doc.getDocumentSafe("stats")?.let { documentToStatistics(it) } ?: Statistics(), + gallery = doc.getListSafe("gallery").map { documentToImageMediaEntity(it) } ?: emptyList(), + episodeSource = doc.getStringSafe("episodeSource") ?: "" ) } fun documentToActor(doc: Document): Actor { return Actor( - person = doc.get("person", Document::class.java)?.let { documentToIndividual(it) } ?: Individual(), - language = doc.getString("language") ?: "" + person = doc.getDocumentSafe("person")?.let { documentToIndividual(it) } ?: Individual(), + language = doc.getStringSafe("language") ?: "" ) } fun documentToAiringTime(doc: Document): AiringTime { return AiringTime( - from = doc.getString("from") ?: "", - to = doc.getString("to") ?: "" + from = doc.getStringSafe("from") ?: "", + to = doc.getStringSafe("to") ?: "" ) } fun documentToAlternativeTitles(doc: Document): AlternativeTitles { return AlternativeTitles( - title = doc.getString("title") ?: "", - type = doc.getString("type") ?: "" + title = doc.getStringSafe("title") ?: "", + type = doc.getStringSafe("type") ?: "" ) } fun documentToAnimeBroadcast(doc: Document): AnimeBroadcast { return AnimeBroadcast( - day = doc.getString("day") ?: "", - time = doc.getString("time") ?: "", - timezone = doc.getString("timezone") ?: "" + day = doc.getStringSafe("day") ?: "", + time = doc.getStringSafe("time") ?: "", + timezone = doc.getStringSafe("timezone") ?: "" ) } fun documentToAnimeSource(doc: Document): AnimeSource { return AnimeSource( - id = doc.getString("id") ?: "", - source = doc.getString("source") ?: "" + id = doc.getStringSafe("id") ?: "", + source = doc.getStringSafe("source") ?: "" ) } fun documentToCharacter(doc: Document): Character { return Character( - character = doc.get("character", Document::class.java)?.let { documentToIndividual(it) } ?: Individual(), - role = doc.getString("role") ?: "", - voiceActor = doc.getList("voiceActor", Document::class.java)?.map { documentToActor(it) } ?: emptyList() + character = doc.getDocumentSafe("character")?.let { documentToIndividual(it) } ?: Individual(), + role = doc.getStringSafe("role") ?: "", + voiceActor = doc.getListSafe("voiceActor").map { documentToActor(it) } ?: emptyList() ) } fun documentToCompanies(doc: Document): Companies { return Companies( - malId = doc.getInteger("malId", 0), - name = doc.getString("name") ?: "", - type = doc.getString("type") ?: "", - url = doc.getString("url") ?: "" + malId = doc.getIntSafe("malId", 0), + name = doc.getStringSafe("name") ?: "", + type = doc.getStringSafe("type") ?: "", + url = doc.getStringSafe("url") ?: "" ) } fun documentToExternalLinks(doc: Document): ExternalLinks { return ExternalLinks( - url = doc.getString("url") ?: "", - name = doc.getString("name") ?: "" + url = doc.getStringSafe("url") ?: "", + name = doc.getStringSafe("name") ?: "" ) } fun documentToImageMediaEntity(doc: Document): ImageMediaEntity { return ImageMediaEntity( - media = doc.getString("media") ?: "", - thumbnail = doc.getString("thumbnail") ?: "", - width = doc.getInteger("width", 0), - height = doc.getInteger("height", 0), - url = doc.getString("url") ?: "" + media = doc.getStringSafe("media") ?: "", + thumbnail = doc.getStringSafe("thumbnail") ?: "", + width = doc.getIntSafe("width", 0), + height = doc.getIntSafe("height", 0), + url = doc.getStringSafe("url") ?: "" ) } fun documentToImages(doc: Document): Images { return Images( - generic = doc.getString("generic") ?: "", - small = doc.getString("small") ?: "", - medium = doc.getString("medium") ?: "", - large = doc.getString("large") ?: "", - maximum = doc.getString("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.getInteger("malId", 0), - url = doc.getString("url") ?: "", - name = doc.getString("name") ?: "", - images = doc.getString("images") ?: "" + malId = doc.getIntSafe("malId", 0), + url = doc.getStringSafe("url") ?: "", + name = doc.getStringSafe("name") ?: "", + images = doc.getStringSafe("images") ?: "" ) } fun documentToMergedEpisode(doc: Document): MergedEpisode { return MergedEpisode( - number = doc.getInteger("number", 0), - ids = doc.getList("ids", Document::class.java)?.map { documentToAnimeSource(it) }?.toMutableList() ?: mutableListOf(), - nextEpisodeDate = doc.getString("nextEpisodeDate") ?: "" + number = doc.getIntSafe("number", 0), + ids = doc.getListSafe("ids").map { documentToAnimeSource(it) }.toMutableList() ?: mutableListOf(), + nextEpisodeDate = doc.getStringSafe("nextEpisodeDate") ?: "" ) } fun documentToRelated(doc: Document): Related { return Related( - entry = doc.getList("entry", Document::class.java)?.map { documentToCompanies(it) } ?: emptyList(), - relation = doc.getString("relation") ?: "" + entry = doc.getListSafe("entry").map { documentToCompanies(it) } ?: emptyList(), + relation = doc.getStringSafe("relation") ?: "" ) } @@ -154,42 +156,50 @@ fun documentToScore(doc: Document): Score { is Int -> value.toDouble() else -> 0.0 }, - score = doc.getInteger("score", 0), - votes = doc.getInteger("votes", 0) + score = doc.getIntSafe("score", 0), + votes = doc.getIntSafe("votes", 0) ) } fun documentToStaff(doc: Document): Staff { return Staff( person = doc.get("person", Document::class.java)?.let { documentToIndividual(it) } ?: Individual(), - positions = doc.getList("positions", String::class.java) ?: emptyList() + positions = doc.getListSafe("positions") ?: emptyList() ) } fun documentToStatistics(doc: Document): Statistics { return Statistics( - completed = doc.getInteger("completed"), - dropped = doc.getInteger("dropped"), - onHold = doc.getInteger("onHold"), - planToWatch = doc.getInteger("planToWatch"), - scores = doc.getList("scores", Document::class.java)?.map { documentToScore(it) } ?: emptyList(), - total = doc.getInteger("total"), - watching = doc.getInteger("watching") + 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 ) } fun documentToThemes(doc: Document): Themes { return Themes( - endings = doc.getList("endings", String::class.java) ?: emptyList(), - openings = doc.getList("openings", String::class.java) ?: emptyList() + endings = doc.getListSafe("endings"), + openings = doc.getListSafe("openings") ) } fun documentToVideoPromo(doc: Document): VideoPromo { return VideoPromo( - embedUrl = doc.getString("embedUrl") ?: "", - url = doc.getString("url") ?: "", - youtubeId = doc.getString("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/services/AnimeService.kt b/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt index 281729f..b2a20c9 100644 --- a/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt +++ b/src/main/kotlin/com/jeluchu/features/anime/services/AnimeService.kt @@ -3,6 +3,7 @@ package com.jeluchu.features.anime.services import com.jeluchu.core.messages.ErrorMessages import com.jeluchu.core.models.ErrorResponse 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.*