mirror of https://github.com/aruppi/aruppi-api.git
Include new endpoints, Anitakume for Spanish Podcasts and Gallery
parent
6f56611f18
commit
c54a28967b
@ -0,0 +1,35 @@
|
|||||||
|
package com.jeluchu.features.anitakume.mappers
|
||||||
|
|
||||||
|
import com.jeluchu.core.extensions.getStringSafe
|
||||||
|
import com.jeluchu.core.extensions.parseRssDate
|
||||||
|
import com.jeluchu.features.anitakume.models.AnitakumeEntity
|
||||||
|
import com.prof18.rssparser.model.RssChannel
|
||||||
|
import org.bson.Document
|
||||||
|
|
||||||
|
fun RssChannel.toPodcast(
|
||||||
|
list: MutableList<AnitakumeEntity>
|
||||||
|
) = list.apply{
|
||||||
|
items.forEach { item ->
|
||||||
|
add(
|
||||||
|
AnitakumeEntity(
|
||||||
|
image = item.itunesItemData?.image.orEmpty(),
|
||||||
|
title = item.title?.substringAfter(" · ").orEmpty(),
|
||||||
|
description = item.description.orEmpty(),
|
||||||
|
date = item.pubDate?.parseRssDate().orEmpty(),
|
||||||
|
link = item.link.orEmpty(),
|
||||||
|
audio = item.audio.orEmpty(),
|
||||||
|
duration = item.itunesItemData?.duration.orEmpty()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun documentToAnitakumeEntity(doc: Document) = AnitakumeEntity(
|
||||||
|
image = doc.getStringSafe("image"),
|
||||||
|
title = doc.getStringSafe("title"),
|
||||||
|
description = doc.getStringSafe("description"),
|
||||||
|
date = doc.getStringSafe("date"),
|
||||||
|
link = doc.getStringSafe("link"),
|
||||||
|
audio = doc.getStringSafe("audio"),
|
||||||
|
duration = doc.getStringSafe("duration")
|
||||||
|
)
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.jeluchu.features.anitakume.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AnitakumeEntity(
|
||||||
|
val image: String,
|
||||||
|
val title: String,
|
||||||
|
val description: String,
|
||||||
|
val date: String,
|
||||||
|
val link: String,
|
||||||
|
val audio: String,
|
||||||
|
val duration: String
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.jeluchu.features.anitakume.routes
|
||||||
|
|
||||||
|
import com.jeluchu.core.extensions.getToJson
|
||||||
|
import com.jeluchu.core.utils.Routes
|
||||||
|
import com.jeluchu.features.anitakume.services.AnitakumeService
|
||||||
|
import com.jeluchu.features.news.services.NewsService
|
||||||
|
import com.mongodb.client.MongoDatabase
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Route.anitakumeEndpoints(
|
||||||
|
mongoDatabase: MongoDatabase,
|
||||||
|
service: AnitakumeService = AnitakumeService(mongoDatabase)
|
||||||
|
) = route(Routes.ANITAKUME) {
|
||||||
|
getToJson { service.getEpisodes(call) }
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.jeluchu.features.anitakume.services
|
||||||
|
|
||||||
|
import com.jeluchu.core.enums.TimeUnit
|
||||||
|
import com.jeluchu.core.extensions.needsUpdate
|
||||||
|
import com.jeluchu.core.extensions.update
|
||||||
|
import com.jeluchu.core.utils.Collections
|
||||||
|
import com.jeluchu.core.utils.RssSources
|
||||||
|
import com.jeluchu.core.utils.RssUrls
|
||||||
|
import com.jeluchu.core.utils.parseDataToDocuments
|
||||||
|
import com.jeluchu.features.anitakume.mappers.documentToAnitakumeEntity
|
||||||
|
import com.jeluchu.features.anitakume.mappers.toPodcast
|
||||||
|
import com.jeluchu.features.anitakume.models.AnitakumeEntity
|
||||||
|
import com.jeluchu.features.news.mappers.documentToNewsEntity
|
||||||
|
import com.jeluchu.features.news.mappers.toNews
|
||||||
|
import com.jeluchu.features.news.models.NewEntity
|
||||||
|
import com.mongodb.client.MongoDatabase
|
||||||
|
import com.prof18.rssparser.RssParser
|
||||||
|
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 AnitakumeService(
|
||||||
|
database: MongoDatabase
|
||||||
|
) {
|
||||||
|
private val timers = database.getCollection(Collections.TIMERS)
|
||||||
|
private val anitakume = database.getCollection(Collections.ANITAKUME)
|
||||||
|
|
||||||
|
suspend fun getEpisodes(call: RoutingCall) {
|
||||||
|
val needsUpdate = timers.needsUpdate(
|
||||||
|
amount = 1,
|
||||||
|
key = Collections.ANITAKUME,
|
||||||
|
unit = TimeUnit.DAY
|
||||||
|
)
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
anitakume.deleteMany(Document())
|
||||||
|
|
||||||
|
val response = mutableListOf<AnitakumeEntity>().apply {
|
||||||
|
RssParser().getRssChannel(RssUrls.ANITAKUME).toPodcast(this@apply)
|
||||||
|
}
|
||||||
|
|
||||||
|
val documentsToInsert = parseDataToDocuments(response, AnitakumeEntity.serializer())
|
||||||
|
if (documentsToInsert.isNotEmpty()) anitakume.insertMany(documentsToInsert)
|
||||||
|
timers.update(Collections.ANITAKUME)
|
||||||
|
|
||||||
|
val elements = documentsToInsert.map { documentToAnitakumeEntity(it) }
|
||||||
|
call.respond(HttpStatusCode.OK, Json.encodeToString(elements))
|
||||||
|
} else {
|
||||||
|
val animes = anitakume
|
||||||
|
.find()
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val elements = animes.map { documentToNewsEntity(it) }
|
||||||
|
call.respond(HttpStatusCode.OK, Json.encodeToString(elements))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.jeluchu.features.gallery.mappers
|
||||||
|
|
||||||
|
import com.jeluchu.core.extensions.getBooleanSafe
|
||||||
|
import com.jeluchu.core.extensions.getDocumentSafe
|
||||||
|
import com.jeluchu.core.extensions.getFloatSafe
|
||||||
|
import com.jeluchu.core.extensions.getIntSafe
|
||||||
|
import com.jeluchu.core.extensions.getListSafe
|
||||||
|
import com.jeluchu.core.extensions.getStringSafe
|
||||||
|
import com.jeluchu.core.extensions.parseRssDate
|
||||||
|
import com.jeluchu.features.anime.mappers.documentToVideoPromo
|
||||||
|
import com.jeluchu.features.anime.models.anime.VideoPromo
|
||||||
|
import com.jeluchu.features.gallery.models.PostsResponse
|
||||||
|
import com.jeluchu.features.gallery.models.ProcessedPost
|
||||||
|
import com.jeluchu.features.news.models.NewEntity
|
||||||
|
import com.prof18.rssparser.model.RssChannel
|
||||||
|
import org.bson.Document
|
||||||
|
|
||||||
|
fun PostsResponse.Post.toProcessedPost(page: Int): ProcessedPost {
|
||||||
|
val imageUrl = "https://oimages.anime-pictures.net/${md5.take(3)}/$md5$ext"
|
||||||
|
|
||||||
|
return ProcessedPost(
|
||||||
|
id = id,
|
||||||
|
image = imageUrl,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
pubtime = pubtime,
|
||||||
|
size = size,
|
||||||
|
erotics = erotics,
|
||||||
|
spoiler = spoiler,
|
||||||
|
haveAlpha = haveAlpha,
|
||||||
|
page = page
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PostsResponse.Post.toProcessedPostQuery(page: Int, query: String): ProcessedPost {
|
||||||
|
val imageUrl = "https://oimages.anime-pictures.net/${md5.take(3)}/$md5$ext"
|
||||||
|
|
||||||
|
return ProcessedPost(
|
||||||
|
id = id,
|
||||||
|
image = imageUrl,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
pubtime = pubtime,
|
||||||
|
size = size,
|
||||||
|
erotics = erotics,
|
||||||
|
spoiler = spoiler,
|
||||||
|
haveAlpha = haveAlpha,
|
||||||
|
page = page,
|
||||||
|
query = query
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun documentToProcessedPost(doc: Document) = ProcessedPost(
|
||||||
|
id = doc.getIntSafe("id"),
|
||||||
|
image = doc.getStringSafe("image"),
|
||||||
|
width = doc.getIntSafe("width"),
|
||||||
|
height = doc.getIntSafe("height"),
|
||||||
|
pubtime = doc.getStringSafe("pubtime"),
|
||||||
|
size = doc.getIntSafe("size"),
|
||||||
|
erotics = doc.getIntSafe("erotics"),
|
||||||
|
spoiler = doc.getBooleanSafe("spoiler"),
|
||||||
|
haveAlpha = doc.getBooleanSafe("haveAlpha")
|
||||||
|
)
|
@ -0,0 +1,97 @@
|
|||||||
|
package com.jeluchu.features.gallery.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PostsResponse(
|
||||||
|
@SerialName("exclusive_tag")
|
||||||
|
val exclusiveTag: String? = null,
|
||||||
|
|
||||||
|
@SerialName("posts_per_page")
|
||||||
|
val postsPerPage: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("response_posts_count")
|
||||||
|
val responsePostsCount: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("page_number")
|
||||||
|
val pageNumber: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("posts")
|
||||||
|
val posts: List<Post> = emptyList(),
|
||||||
|
|
||||||
|
@SerialName("posts_count")
|
||||||
|
val postsCount: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("max_pages")
|
||||||
|
val maxPages: Int = 0
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Post(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("md5")
|
||||||
|
val md5: String = "",
|
||||||
|
|
||||||
|
@SerialName("md5_pixels")
|
||||||
|
val md5Pixels: String = "",
|
||||||
|
|
||||||
|
@SerialName("width")
|
||||||
|
val width: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("height")
|
||||||
|
val height: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("pubtime")
|
||||||
|
val pubtime: String = "",
|
||||||
|
|
||||||
|
@SerialName("datetime")
|
||||||
|
val datetime: String = "",
|
||||||
|
|
||||||
|
@SerialName("score")
|
||||||
|
val score: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("score_number")
|
||||||
|
val scoreNumber: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("size")
|
||||||
|
val size: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("download_count")
|
||||||
|
val downloadCount: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("erotics")
|
||||||
|
val erotics: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("color")
|
||||||
|
val color: List<Int> = emptyList(),
|
||||||
|
|
||||||
|
@SerialName("ext")
|
||||||
|
val ext: String = "",
|
||||||
|
|
||||||
|
@SerialName("status")
|
||||||
|
val status: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("status_type")
|
||||||
|
val statusType: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("redirect_id")
|
||||||
|
val redirectId: String? = null,
|
||||||
|
|
||||||
|
@SerialName("spoiler")
|
||||||
|
val spoiler: Boolean = false,
|
||||||
|
|
||||||
|
@SerialName("have_alpha")
|
||||||
|
val haveAlpha: Boolean = false,
|
||||||
|
|
||||||
|
@SerialName("tags_count")
|
||||||
|
val tagsCount: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("artefacts_degree")
|
||||||
|
val artefactsDegree: Double = 0.0,
|
||||||
|
|
||||||
|
@SerialName("smooth_degree")
|
||||||
|
val smoothDegree: Double = 0.0
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.jeluchu.features.gallery.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ProcessedPost(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
|
||||||
|
@SerialName("image")
|
||||||
|
val image: String,
|
||||||
|
|
||||||
|
@SerialName("width")
|
||||||
|
val width: Int,
|
||||||
|
|
||||||
|
@SerialName("height")
|
||||||
|
val height: Int,
|
||||||
|
|
||||||
|
@SerialName("pubtime")
|
||||||
|
val pubtime: String,
|
||||||
|
|
||||||
|
@SerialName("size")
|
||||||
|
val size: Int,
|
||||||
|
|
||||||
|
@SerialName("erotics")
|
||||||
|
val erotics: Int,
|
||||||
|
|
||||||
|
@SerialName("spoiler")
|
||||||
|
val spoiler: Boolean,
|
||||||
|
|
||||||
|
@SerialName("have_alpha")
|
||||||
|
val haveAlpha: Boolean,
|
||||||
|
|
||||||
|
@SerialName("page")
|
||||||
|
val page: Int = 0,
|
||||||
|
|
||||||
|
@SerialName("query")
|
||||||
|
val query: String = "0"
|
||||||
|
)
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.jeluchu.features.gallery.routes
|
||||||
|
|
||||||
|
import com.jeluchu.core.extensions.getToJson
|
||||||
|
import com.jeluchu.core.utils.Routes
|
||||||
|
import com.jeluchu.features.gallery.services.GalleryService
|
||||||
|
import com.jeluchu.features.news.services.NewsService
|
||||||
|
import com.mongodb.client.MongoDatabase
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
|
||||||
|
fun Route.galleryEndpoints(
|
||||||
|
mongoDatabase: MongoDatabase,
|
||||||
|
service: GalleryService = GalleryService(mongoDatabase)
|
||||||
|
) = route(Routes.GALLERY) {
|
||||||
|
route(Routes.LAST_POST) {
|
||||||
|
getToJson { service.getLastPosts(call) }
|
||||||
|
}
|
||||||
|
|
||||||
|
getToJson { service.getQueryImages(call) }
|
||||||
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
package com.jeluchu.features.gallery.services
|
||||||
|
|
||||||
|
import com.jeluchu.core.connection.RestClient
|
||||||
|
import com.jeluchu.core.enums.TimeUnit
|
||||||
|
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.PaginationResponse
|
||||||
|
import com.jeluchu.core.utils.BaseUrls
|
||||||
|
import com.jeluchu.core.utils.Collections
|
||||||
|
import com.jeluchu.core.utils.Endpoints
|
||||||
|
import com.jeluchu.core.utils.parseDataToDocuments
|
||||||
|
import com.jeluchu.features.gallery.mappers.documentToProcessedPost
|
||||||
|
import com.jeluchu.features.gallery.mappers.toProcessedPost
|
||||||
|
import com.jeluchu.features.gallery.mappers.toProcessedPostQuery
|
||||||
|
import com.jeluchu.features.gallery.models.PostsResponse
|
||||||
|
import com.jeluchu.features.gallery.models.ProcessedPost
|
||||||
|
import com.mongodb.client.MongoDatabase
|
||||||
|
import com.mongodb.client.model.Filters
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.server.response.*
|
||||||
|
import io.ktor.server.response.respond
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okio.IOException
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
class GalleryService(
|
||||||
|
database: MongoDatabase
|
||||||
|
) {
|
||||||
|
private val timers = database.getCollection(Collections.TIMERS)
|
||||||
|
private val queryPosts = database.getCollection(Collections.ANIME_PICTURES_QUERY)
|
||||||
|
private val recentPosts = database.getCollection(Collections.ANIME_PICTURES_RECENT)
|
||||||
|
|
||||||
|
suspend fun getLastPosts(call: RoutingCall) {
|
||||||
|
val size = 80
|
||||||
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
|
||||||
|
val timerKey = "${Collections.ANIME_PICTURES_RECENT}_${page}"
|
||||||
|
val skipCount = (page - 1) * size
|
||||||
|
|
||||||
|
val needsUpdate = timers.needsUpdate(
|
||||||
|
amount = 30,
|
||||||
|
key = timerKey,
|
||||||
|
unit = TimeUnit.DAY
|
||||||
|
)
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
recentPosts.deleteMany(Filters.and(Filters.eq("page", page)))
|
||||||
|
val response = RestClient.request(
|
||||||
|
BaseUrls.ANIME_PICTURES + Endpoints.POSTS + "?page=$page",
|
||||||
|
PostsResponse.serializer()
|
||||||
|
).posts.map { it.toProcessedPost(page) }
|
||||||
|
|
||||||
|
val documentsToInsert = parseDataToDocuments(response, ProcessedPost.serializer())
|
||||||
|
if (documentsToInsert.isNotEmpty()) recentPosts.insertMany(documentsToInsert)
|
||||||
|
timers.update(timerKey)
|
||||||
|
|
||||||
|
val elements = documentsToInsert.map { documentToProcessedPost(it) }
|
||||||
|
|
||||||
|
val paginationResponse = PaginationResponse(
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
data = elements
|
||||||
|
)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK, Json.encodeToString(paginationResponse))
|
||||||
|
} else {
|
||||||
|
val posts = recentPosts
|
||||||
|
.find(Filters.and(Filters.eq("page", page)))
|
||||||
|
.skip(skipCount)
|
||||||
|
.limit(size)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val elements = posts.map { documentToProcessedPost(it) }
|
||||||
|
val response = PaginationResponse(
|
||||||
|
page = page,
|
||||||
|
size = size,
|
||||||
|
data = elements
|
||||||
|
)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK, Json.encodeToString(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getQueryImages(call: RoutingCall) {
|
||||||
|
val size = 80
|
||||||
|
val query = call.request.queryParameters["query"].orEmpty()
|
||||||
|
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
|
||||||
|
val timerKey = "${Collections.ANIME_PICTURES_QUERY}_${query}_${page}"
|
||||||
|
val skipCount = (page - 1) * size
|
||||||
|
val fixedPage = page + 1
|
||||||
|
|
||||||
|
val needsUpdate = timers.needsUpdate(
|
||||||
|
amount = 30,
|
||||||
|
key = timerKey,
|
||||||
|
unit = TimeUnit.DAY
|
||||||
|
)
|
||||||
|
|
||||||
|
if (needsUpdate) {
|
||||||
|
queryPosts.deleteMany(Filters.and(Filters.eq("page", fixedPage)))
|
||||||
|
|
||||||
|
try {
|
||||||
|
val response = RestClient.request(
|
||||||
|
BaseUrls.ANIME_PICTURES + Endpoints.POSTS + "?search_tag=${URLEncoder.encode(query, "UTF-8")}" + "&lang=en&type=json_v3" + "&page=$page",
|
||||||
|
PostsResponse.serializer()
|
||||||
|
).posts.map { it.toProcessedPostQuery(fixedPage, query) }
|
||||||
|
|
||||||
|
val documentsToInsert = parseDataToDocuments(response, ProcessedPost.serializer())
|
||||||
|
if (documentsToInsert.isNotEmpty()) queryPosts.insertMany(documentsToInsert)
|
||||||
|
timers.update(timerKey)
|
||||||
|
|
||||||
|
val elements = documentsToInsert.map { documentToProcessedPost(it) }
|
||||||
|
|
||||||
|
val paginationResponse = PaginationResponse(
|
||||||
|
page = fixedPage,
|
||||||
|
size = size,
|
||||||
|
data = elements
|
||||||
|
)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK, Json.encodeToString(paginationResponse))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
val paginationResponse = PaginationResponse(
|
||||||
|
page = fixedPage,
|
||||||
|
size = size,
|
||||||
|
data = emptyList<ProcessedPost>()
|
||||||
|
)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK, Json.encodeToString(paginationResponse))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val posts = queryPosts
|
||||||
|
.find(Filters.and(Filters.eq("page", fixedPage)))
|
||||||
|
.skip(skipCount)
|
||||||
|
.limit(size)
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val elements = posts.map { documentToProcessedPost(it) }
|
||||||
|
val response = PaginationResponse(
|
||||||
|
page = fixedPage,
|
||||||
|
size = size,
|
||||||
|
data = elements
|
||||||
|
)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.OK, Json.encodeToString(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue