mirror of https://github.com/aruppi/aruppi-api.git
Initial preview API for v5 with 2 enpoints
parent
2308dd49f5
commit
ff7b55ce40
@ -0,0 +1,4 @@
|
||||
build
|
||||
.gradle
|
||||
.git
|
||||
*.md
|
@ -1,2 +1,50 @@
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
|
||||
# Environment Variables
|
||||
.env
|
||||
|
||||
# Docker
|
||||
docker-compose.override.yml
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
gradle/
|
||||
|
||||
gradlew
|
||||
|
||||
gradlew.bat
|
||||
|
@ -0,0 +1,29 @@
|
||||
# Stage 1: Cache Gradle dependencies
|
||||
FROM gradle:8-jdk21-corretto AS cache
|
||||
RUN mkdir -p /home/gradle/cache_home
|
||||
ENV GRADLE_USER_HOME /home/gradle/cache_home
|
||||
COPY build.gradle.kts /home/gradle/src/build.gradle.kts
|
||||
COPY settings.gradle.kts /home/gradle/src/settings.gradle.kts
|
||||
COPY gradle/libs.versions.toml /home/gradle/src/gradle/libs.versions.toml
|
||||
WORKDIR /home/gradle/src
|
||||
RUN gradle clean build -i --stacktrace
|
||||
|
||||
# Stage 2: Build Application
|
||||
FROM gradle:8-jdk21-corretto AS build
|
||||
COPY --from=cache /home/gradle/cache_home /home/gradle/.gradle
|
||||
COPY . /usr/src/app/
|
||||
WORKDIR /usr/src/app
|
||||
COPY --chown=gradle:gradle . /home/gradle/src
|
||||
WORKDIR /home/gradle/src
|
||||
# Build the fat JAR, Gradle also supports shadow
|
||||
# and boot JAR by default.
|
||||
RUN gradle buildFatJar --no-daemon
|
||||
|
||||
# Stage 3: Create the Runtime Image
|
||||
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"]
|
@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.ktor)
|
||||
alias(libs.plugins.kotlin.plugin.serialization)
|
||||
}
|
||||
|
||||
group = "com.jeluchu"
|
||||
version = "5.0.0"
|
||||
|
||||
application {
|
||||
mainClass.set("io.ktor.server.netty.EngineMain")
|
||||
|
||||
val isDevelopment: Boolean = project.ext.has("development")
|
||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.bson)
|
||||
implementation(libs.logback.classic)
|
||||
implementation(libs.ktor.server.core)
|
||||
implementation(libs.ktor.server.netty)
|
||||
implementation(libs.ktor.server.swagger)
|
||||
implementation(libs.mongodb.driver.core)
|
||||
implementation(libs.mongodb.driver.sync)
|
||||
implementation(libs.ktor.server.config.yaml)
|
||||
implementation(libs.ktor.server.status.pages)
|
||||
implementation(libs.ktor.serialization.kotlinx.json)
|
||||
implementation(libs.ktor.server.content.negotiation)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: ktor_app
|
||||
ports:
|
||||
- "8080:8080"
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
@ -0,0 +1,2 @@
|
||||
MONGO_CONNECTION_STRING=mongodb://user:password@host:port
|
||||
MONGO_DATABASE_NAME=aruppi
|
@ -0,0 +1,3 @@
|
||||
kotlin.code.style=official
|
||||
org.gradle.caching=true
|
||||
org.gradle.daemon=false
|
@ -0,0 +1 @@
|
||||
rootProject.name = "aruppi-api"
|
@ -0,0 +1,15 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package com.jeluchu
|
||||
|
||||
import com.jeluchu.core.configuration.initInstallers
|
||||
import com.jeluchu.core.configuration.initRoutes
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.netty.*
|
||||
|
||||
fun main(args: Array<String>) = EngineMain.main(args)
|
||||
|
||||
fun Application.module() {
|
||||
initInstallers()
|
||||
initRoutes()
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.jeluchu.core.configuration
|
||||
|
||||
import com.jeluchu.core.messages.ErrorMessages
|
||||
import com.jeluchu.core.models.ErrorResponse
|
||||
import io.ktor.http.*
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.plugins.statuspages.*
|
||||
import io.ktor.server.response.*
|
||||
|
||||
fun Application.initInstallers() {
|
||||
install(StatusPages) {
|
||||
status(HttpStatusCode.NotFound) { call, _ ->
|
||||
call.respond(HttpStatusCode.NotFound,
|
||||
ErrorResponse(ErrorMessages.NotFound.message)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.jeluchu.core.configuration
|
||||
|
||||
import com.mongodb.client.MongoClients
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.config.*
|
||||
|
||||
/**
|
||||
* Establishes connection with a MongoDB database.
|
||||
*
|
||||
* The following configuration properties (in application.yaml/application.conf) can be specified:
|
||||
* * `db.mongo.connectionStrings` connectionStrings for your MongoDB
|
||||
* * `db.mongo.database.name` name of the database
|
||||
*
|
||||
* IMPORTANT NOTE: in order to make MongoDB connection working, you have to start a MongoDB server first.
|
||||
* See the instructions here: https://www.mongodb.com/docs/manual/administration/install-community/
|
||||
* all the paramaters above
|
||||
*
|
||||
* @returns [MongoDatabase] instance
|
||||
* */
|
||||
fun Application.connectToMongoDB(): MongoDatabase {
|
||||
val connectionStrings = environment.config.tryGetString("db.mongo.connectionStrings") ?: "mongodb://"
|
||||
val databaseName = environment.config.tryGetString("db.mongo.database.name") ?: "aruppi"
|
||||
|
||||
val mongoClient = MongoClients.create(connectionStrings)
|
||||
val database = mongoClient.getDatabase(databaseName)
|
||||
|
||||
monitor.subscribe(ApplicationStopped) {
|
||||
mongoClient.close()
|
||||
}
|
||||
|
||||
return database
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.jeluchu.core.configuration
|
||||
|
||||
import com.jeluchu.features.anime.routes.animeEndpoints
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Application.initRoutes(
|
||||
mongoDatabase: MongoDatabase = connectToMongoDB()
|
||||
) = routing {
|
||||
route("api/v5") {
|
||||
initDocumentation()
|
||||
animeEndpoints(mongoDatabase)
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.jeluchu.core.configuration
|
||||
|
||||
import io.ktor.server.plugins.swagger.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Route.initDocumentation() {
|
||||
swaggerUI(
|
||||
path = "/",
|
||||
swaggerFile = "openapi/documentation.yaml"
|
||||
)
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.jeluchu.core.messages
|
||||
|
||||
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 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,6 @@
|
||||
package com.jeluchu.core.messages
|
||||
|
||||
sealed class GeneralMessages(val message: String) {
|
||||
data class Custom(val info: String) : GeneralMessages(info)
|
||||
data object DocumentationReference : GeneralMessages("More information on the Aruppi API can be found in the official documentation at: http://0.0.0.0:8080/swagger")
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.jeluchu.core.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ErrorResponse(
|
||||
val error: String
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package com.jeluchu.core.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MessageResponse(
|
||||
val message: String
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package com.jeluchu.core.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SuccessResponse(
|
||||
val success: String
|
||||
)
|
@ -0,0 +1,195 @@
|
||||
package com.jeluchu.features.anime.mappers
|
||||
|
||||
import com.example.models.*
|
||||
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") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToActor(doc: Document): Actor {
|
||||
return Actor(
|
||||
person = doc.get("person", Document::class.java)?.let { documentToIndividual(it) } ?: Individual(),
|
||||
language = doc.getString("language") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToAiringTime(doc: Document): AiringTime {
|
||||
return AiringTime(
|
||||
from = doc.getString("from") ?: "",
|
||||
to = doc.getString("to") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToAlternativeTitles(doc: Document): AlternativeTitles {
|
||||
return AlternativeTitles(
|
||||
title = doc.getString("title") ?: "",
|
||||
type = doc.getString("type") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToAnimeBroadcast(doc: Document): AnimeBroadcast {
|
||||
return AnimeBroadcast(
|
||||
day = doc.getString("day") ?: "",
|
||||
time = doc.getString("time") ?: "",
|
||||
timezone = doc.getString("timezone") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToAnimeSource(doc: Document): AnimeSource {
|
||||
return AnimeSource(
|
||||
id = doc.getString("id") ?: "",
|
||||
source = doc.getString("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()
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToCompanies(doc: Document): Companies {
|
||||
return Companies(
|
||||
malId = doc.getInteger("malId", 0),
|
||||
name = doc.getString("name") ?: "",
|
||||
type = doc.getString("type") ?: "",
|
||||
url = doc.getString("url") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToExternalLinks(doc: Document): ExternalLinks {
|
||||
return ExternalLinks(
|
||||
url = doc.getString("url") ?: "",
|
||||
name = doc.getString("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") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
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") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToIndividual(doc: Document): Individual {
|
||||
return Individual(
|
||||
malId = doc.getInteger("malId", 0),
|
||||
url = doc.getString("url") ?: "",
|
||||
name = doc.getString("name") ?: "",
|
||||
images = doc.getString("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") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToRelated(doc: Document): Related {
|
||||
return Related(
|
||||
entry = doc.getList("entry", Document::class.java)?.map { documentToCompanies(it) } ?: emptyList(),
|
||||
relation = doc.getString("relation") ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToScore(doc: Document): Score {
|
||||
return Score(
|
||||
percentage = when (val value = doc["percentage"]) {
|
||||
is Double -> value
|
||||
is Int -> value.toDouble()
|
||||
else -> 0.0
|
||||
},
|
||||
score = doc.getInteger("score", 0),
|
||||
votes = doc.getInteger("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()
|
||||
)
|
||||
}
|
||||
|
||||
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")
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToThemes(doc: Document): Themes {
|
||||
return Themes(
|
||||
endings = doc.getList("endings", String::class.java) ?: emptyList(),
|
||||
openings = doc.getList("openings", String::class.java) ?: emptyList()
|
||||
)
|
||||
}
|
||||
|
||||
fun documentToVideoPromo(doc: Document): VideoPromo {
|
||||
return VideoPromo(
|
||||
embedUrl = doc.getString("embedUrl") ?: "",
|
||||
url = doc.getString("url") ?: "",
|
||||
youtubeId = doc.getString("youtubeId") ?: "",
|
||||
images = doc.get("images", Document::class.java)?.let { documentToImages(it) } ?: Images()
|
||||
)
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Actor(
|
||||
/**
|
||||
* Person generic info.
|
||||
* @see Individual
|
||||
*/
|
||||
val person: Individual = Individual(),
|
||||
|
||||
/**
|
||||
* Language of the person.
|
||||
*/
|
||||
val language: String = ""
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AiringTime(
|
||||
/**
|
||||
* Start date airing.
|
||||
*/
|
||||
val from: String = "",
|
||||
|
||||
/**
|
||||
* End date airing.
|
||||
*/
|
||||
val to: String = ""
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AlternativeTitles(
|
||||
/**
|
||||
* Title for anime.
|
||||
*/
|
||||
val title: String = "",
|
||||
|
||||
/**
|
||||
* Title type for anime.
|
||||
*/
|
||||
val type: String = ""
|
||||
)
|
@ -0,0 +1,21 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AnimeBroadcast(
|
||||
/**
|
||||
* Day in broadcast.
|
||||
*/
|
||||
val day: String = "",
|
||||
|
||||
/**
|
||||
* Time date in broadcast.
|
||||
*/
|
||||
val time: String = "",
|
||||
|
||||
/**
|
||||
* Timezone in broadcast.
|
||||
*/
|
||||
val timezone: String = ""
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AnimeSource(
|
||||
val id: String,
|
||||
val source: String
|
||||
)
|
||||
|
@ -0,0 +1,10 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Character(
|
||||
var character: Individual = Individual(),
|
||||
var role: String = "",
|
||||
var voiceActor: List<Actor> = emptyList()
|
||||
)
|
@ -0,0 +1,26 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Companies(
|
||||
/**
|
||||
* ID associated with MyAnimeList.
|
||||
*/
|
||||
val malId: Int = 0,
|
||||
|
||||
/**
|
||||
* Name for company.
|
||||
*/
|
||||
val name: String = "",
|
||||
|
||||
/**
|
||||
* Type for company.
|
||||
*/
|
||||
val type: String = "",
|
||||
|
||||
/**
|
||||
* Url for company.
|
||||
*/
|
||||
val url: String = ""
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ExternalLinks(
|
||||
/**
|
||||
* Url for trailer.
|
||||
*/
|
||||
val url: String = "",
|
||||
|
||||
/**
|
||||
* Name of external info.
|
||||
*/
|
||||
val name: String = ""
|
||||
)
|
@ -0,0 +1,12 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ImageMediaEntity(
|
||||
val media: String,
|
||||
val thumbnail: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
val url: String
|
||||
)
|
@ -0,0 +1,12 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Images(
|
||||
val generic: String = "",
|
||||
val small: String = "",
|
||||
val medium: String = "",
|
||||
val large: String = "",
|
||||
val maximum: String = ""
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Individual(
|
||||
val malId: Int = 0,
|
||||
val url: String = "",
|
||||
val name: String = "",
|
||||
val images: String = ""
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class MergedEpisode(
|
||||
var number: Int,
|
||||
var ids: MutableList<AnimeSource> = mutableListOf(),
|
||||
var nextEpisodeDate: String = ""
|
||||
)
|
@ -0,0 +1,53 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.bson.Document
|
||||
|
||||
@Serializable
|
||||
data class MoreInfoEntity(
|
||||
val id: Long? = null,
|
||||
var malId: Int = 0,
|
||||
var title: String = "",
|
||||
var poster: String = "",
|
||||
var cover: String = "",
|
||||
var genres: List<String> = emptyList(),
|
||||
var synopsis: String = "",
|
||||
var episodes: List<MergedEpisode> = emptyList(),
|
||||
var episodesCount: Int = 0,
|
||||
var score: String = "",
|
||||
var staff: List<Staff> = emptyList(),
|
||||
var characters: List<Character> = emptyList(),
|
||||
var status: String = "",
|
||||
var type: String = "",
|
||||
val url: String = "",
|
||||
val promo: VideoPromo = VideoPromo(),
|
||||
val source: String = "",
|
||||
val duration: String = "",
|
||||
val rank: Int = 0,
|
||||
val titles: List<AlternativeTitles> = emptyList(),
|
||||
val airing: Boolean = false,
|
||||
val aired: AiringTime = AiringTime(),
|
||||
val broadcast: AnimeBroadcast = AnimeBroadcast(),
|
||||
val season: String = "",
|
||||
val year: Int = 0,
|
||||
val external: List<ExternalLinks> = emptyList(),
|
||||
val streaming: List<ExternalLinks> = emptyList(),
|
||||
val studios: List<Companies> = emptyList(),
|
||||
val licensors: List<Companies> = emptyList(),
|
||||
val producers: List<Companies> = emptyList(),
|
||||
val theme: Themes = Themes(),
|
||||
val relations: List<Related> = emptyList(),
|
||||
val stats: Statistics = Statistics(),
|
||||
val gallery: List<ImageMediaEntity> = emptyList(),
|
||||
val episodeSource: String = ""
|
||||
) {
|
||||
fun toDocument(): Document = Document.parse(Json.encodeToString(this))
|
||||
|
||||
companion object {
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
fun fromDocument(document: Document): MoreInfoEntity = json.decodeFromString(document.toJson())
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Related(
|
||||
/**
|
||||
* List of entries for relation in anime.
|
||||
* @see Companies
|
||||
*/
|
||||
val entry: List<Companies>,
|
||||
|
||||
/**
|
||||
* Relation for anime.
|
||||
*/
|
||||
val relation: String
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Score(
|
||||
val percentage: Double,
|
||||
val score: Int,
|
||||
val votes: Int
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Staff(
|
||||
var person: Individual = Individual(),
|
||||
var positions: List<String> = emptyList()
|
||||
)
|
@ -0,0 +1,14 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Statistics(
|
||||
val completed: Int? = null,
|
||||
val dropped: Int? = null,
|
||||
val onHold: Int? = null,
|
||||
val planToWatch: Int? = null,
|
||||
val scores: List<Score>? = emptyList(),
|
||||
val total: Int? = null,
|
||||
val watching: Int? = null
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
open class Themes(
|
||||
/**
|
||||
* List of endings.
|
||||
*/
|
||||
val endings: List<String> = emptyList(),
|
||||
|
||||
/**
|
||||
* List of openings.
|
||||
*/
|
||||
val openings: List<String> = emptyList()
|
||||
)
|
@ -0,0 +1,26 @@
|
||||
package com.example.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
open class VideoPromo(
|
||||
/**
|
||||
* Embed url for trailer.
|
||||
*/
|
||||
val embedUrl: String = "",
|
||||
|
||||
/**
|
||||
* Url for trailer.
|
||||
*/
|
||||
val url: String = "",
|
||||
|
||||
/**
|
||||
* Youtube id for trailer.
|
||||
*/
|
||||
val youtubeId: String = "",
|
||||
|
||||
/**
|
||||
* Images for trailer.
|
||||
*/
|
||||
val images: Images = Images()
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
package com.jeluchu.features.anime.routes
|
||||
|
||||
import com.jeluchu.features.anime.services.AnimeService
|
||||
import com.mongodb.client.MongoDatabase
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Route.animeEndpoints(
|
||||
mongoDatabase: MongoDatabase,
|
||||
service: AnimeService = AnimeService(mongoDatabase)
|
||||
) {
|
||||
get("/directory") { service.getDirectory(call) }
|
||||
get("/anime/{id}") { service.getAnimeByMalId(call) }
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
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.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
|
||||
|
||||
class AnimeService(
|
||||
database: MongoDatabase
|
||||
) {
|
||||
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 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
ktor:
|
||||
application:
|
||||
modules:
|
||||
- com.jeluchu.ApplicationKt.module
|
||||
deployment:
|
||||
port: 8080
|
||||
host: "0.0.0.0"
|
||||
|
||||
db:
|
||||
mongo:
|
||||
connectionStrings: ${?MONGO_CONNECTION_STRING}
|
||||
database:
|
||||
name: ${?MONGO_DATABASE_NAME}
|
@ -0,0 +1,36 @@
|
||||
openapi: "3.0.3"
|
||||
info:
|
||||
title: "Aruppi API"
|
||||
description: "Application API"
|
||||
version: "5.0.0-preview"
|
||||
servers:
|
||||
- url: "http://0.0.0.0:8080/api/v5"
|
||||
paths:
|
||||
/directory:
|
||||
get:
|
||||
description: "Hello World!"
|
||||
responses:
|
||||
"200":
|
||||
description: "OK"
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: "string"
|
||||
examples:
|
||||
Example#1:
|
||||
value: "Hello World!"
|
||||
/anime/{id}:
|
||||
get:
|
||||
description: "Hello World!"
|
||||
responses:
|
||||
"200":
|
||||
description: "OK"
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: "string"
|
||||
examples:
|
||||
Example#1:
|
||||
value: "Hello World!"
|
||||
components:
|
||||
schemas: {}
|
Loading…
Reference in New Issue