Skip to content
This repository has been archived by the owner on Mar 28, 2024. It is now read-only.

Added ProfileService and caching for improved performance #211

Merged
merged 1 commit into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/main/kotlin/fr/ziedelth/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import nu.pattern.OpenCV
import java.util.*

private lateinit var database: Database
private var isDebug = false

fun main(args: Array<String>) {
val isDebug = args.isNotEmpty() && args[0] == "debug"
isDebug = args.isNotEmpty() && args[0] == "debug"
val loadImage = !isDebug || (args.size > 1 && args[1] == "loadImage")

if (isDebug) {
Expand Down Expand Up @@ -71,7 +72,7 @@ private fun handleCommands() {

fun Application.myApplicationModule() {
Logger.info("Configure server...")
configureHTTP()
configureHTTP(isDebug)
Logger.info("Configure routing...")
configureRouting(database)
Logger.info("Server configured and ready")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fr.ziedelth.caches

import java.util.*

data class ProfileEpisodeTypesLangTypesPaginationCacheKey(
override val profile: UUID,
val episodeTypes: List<UUID>,
val langTypes: List<UUID>,
override val page: Int,
override val limit: Int,
) : ProfilePaginationCacheKey(profile, page, limit)
27 changes: 27 additions & 0 deletions src/main/kotlin/fr/ziedelth/caches/ProfilePaginationCacheKey.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fr.ziedelth.caches

import java.util.*

open class ProfilePaginationCacheKey(
open val profile: UUID,
open val page: Int,
open val limit: Int,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ProfilePaginationCacheKey) return false

if (profile != other.profile) return false
if (page != other.page) return false
if (limit != other.limit) return false

return true
}

override fun hashCode(): Int {
var result = profile.hashCode()
result = 31 * result + page
result = 31 * result + limit
return result
}
}
27 changes: 10 additions & 17 deletions src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import fr.ziedelth.repositories.EpisodeRepository
import fr.ziedelth.repositories.ProfileRepository
import fr.ziedelth.services.EpisodeTypeService
import fr.ziedelth.services.LangTypeService
import fr.ziedelth.services.ProfileService
import fr.ziedelth.utils.Constant
import fr.ziedelth.utils.routes.*
import fr.ziedelth.utils.routes.method.Delete
Expand All @@ -27,6 +28,9 @@ class ProfileController : AbstractController<Serializable>("/profile") {
@Inject
private lateinit var profileRepository: ProfileRepository

@Inject
private lateinit var profileService: ProfileService

@Inject
private lateinit var episodeTypeService: EpisodeTypeService

Expand Down Expand Up @@ -98,6 +102,7 @@ class ProfileController : AbstractController<Serializable>("/profile") {
@Authenticated
private fun addToWatchlist(@JWTUser jwtUser: UUID, @QueryParam anime: String?, @QueryParam episode: String?): Response {
profileRepository.addToWatchlist(profileRepository.find(jwtUser)!!, anime, episode) ?: return Response(HttpStatusCode.BadRequest)
profileService.invalidateProfile(jwtUser)
return Response.ok()
}

Expand All @@ -114,6 +119,7 @@ class ProfileController : AbstractController<Serializable>("/profile") {
@Authenticated
private fun deleteToWatchlist(@JWTUser jwtUser: UUID, @QueryParam anime: String?, @QueryParam episode: String?): Response {
profileRepository.removeToWatchlist(profileRepository.find(jwtUser)!!, anime, episode) ?: return Response(HttpStatusCode.BadRequest)
profileService.invalidateProfile(jwtUser)
return Response.ok()
}

Expand Down Expand Up @@ -149,21 +155,7 @@ class ProfileController : AbstractController<Serializable>("/profile") {
limit: Int
): Response {
val (episodeTypesUuid, langTypesUuid) = getEpisodeAndLangTypesUuid(episodeTypes, langTypes)
return Response.ok(profileRepository.getMissingAnimes(jwtUser, episodeTypesUuid, langTypesUuid, page, limit))
}

@Path("/watchlist/episodes/missing/page/{page}/limit/{limit}")
@Get
@Authenticated
private fun paginationWatchlistEpisodesMissing(
@JWTUser jwtUser: UUID,
@QueryParam episodeTypes: String?,
@QueryParam langTypes: String?,
page: Int,
limit: Int
): Response {
val (episodeTypesUuid, langTypesUuid) = getEpisodeAndLangTypesUuid(episodeTypes, langTypes)
return Response.ok(profileRepository.getMissingEpisodes(jwtUser, episodeTypesUuid, langTypesUuid, page, limit))
return Response.ok(profileService.getMissingAnimes(jwtUser, episodeTypesUuid, langTypesUuid, page, limit))
}

/**
Expand All @@ -178,7 +170,7 @@ class ProfileController : AbstractController<Serializable>("/profile") {
@Get
@Authenticated
private fun getWatchlistAnimesByPageAndLimit(@JWTUser jwtUser: UUID, page: Int, limit: Int): Response {
return Response.ok(profileRepository.getWatchlistAnimes(jwtUser, page, limit))
return Response.ok(profileService.getWatchlistAnimes(jwtUser, page, limit))
}

/**
Expand All @@ -193,14 +185,15 @@ class ProfileController : AbstractController<Serializable>("/profile") {
@Get
@Authenticated
private fun getWatchlistEpisodesByPageAndLimit(@JWTUser jwtUser: UUID, page: Int, limit: Int): Response {
return Response.ok(profileRepository.getWatchlistEpisodes(jwtUser, page, limit))
return Response.ok(profileService.getWatchlistEpisodes(jwtUser, page, limit))
}

@Path
@Delete
@Authenticated
private fun deleteProfile(@JWTUser jwtUser: UUID): Response {
profileRepository.delete(profileRepository.find(jwtUser)!!)
profileService.invalidateProfile(jwtUser)
return Response.ok()
}
}
22 changes: 12 additions & 10 deletions src/main/kotlin/fr/ziedelth/plugins/HTTP.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.response.*

fun Application.configureHTTP() {
fun Application.configureHTTP(isDebug: Boolean = false) {
install(Compression) {
gzip {
priority = 1.0
Expand Down Expand Up @@ -59,15 +59,17 @@ fun Application.configureHTTP() {
}
}

install(SwaggerUI) {
swagger {
swaggerUrl = "swagger"
forwardRoot = true
}
info {
title = "API"
version = "latest"
description = "API for testing and demonstration purposes."
if (isDebug) {
install(SwaggerUI) {
swagger {
swaggerUrl = "swagger"
forwardRoot = true
}
info {
title = "API"
version = "latest"
description = "API for testing and demonstration purposes."
}
}
}
}
46 changes: 2 additions & 44 deletions src/main/kotlin/fr/ziedelth/repositories/ProfileRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,48 +163,6 @@ class ProfileRepository : AbstractRepository<Profile>() {
)
}

fun getMissingEpisodes(
uuid: UUID,
episodeTypes: List<UUID>,
langTypes: List<UUID>,
page: Int,
limit: Int
): List<Episode> {
val (animes, episodes) = database.inReadOnlyTransaction {
val animes = it.createQuery(
"SELECT a.anime.uuid FROM Profile p JOIN p.animes a WHERE p.uuid = :uuid",
UUID::class.java
).setParameter("uuid", uuid).resultList

val episodes = it.createQuery(
"SELECT e.episode.uuid FROM Profile p JOIN p.episodes e WHERE p.uuid = :uuid",
UUID::class.java
).setParameter("uuid", uuid).resultList

animes to episodes
}

val exclusionCondition = if (episodes.isEmpty()) "" else "e.uuid NOT IN :episodes AND"

return super.getByPage(
Episode::class.java,
page,
limit,
"""
FROM Episode e
WHERE e.anime.uuid IN :animes
AND $exclusionCondition
e.episodeType.uuid IN :episodeTypes
AND e.langType.uuid IN :langTypes
ORDER BY releaseDate DESC, anime.name, season DESC, number DESC, episodeType.name, langType.name
""".trimIndent(),
"animes" to animes,
if (episodes.isEmpty()) null else "episodes" to episodes,
"episodeTypes" to episodeTypes,
"langTypes" to langTypes
)
}

/**
* Retrieves a list of Anime objects from the watchlist of a profile identified by the given UUID.
*
Expand Down Expand Up @@ -238,9 +196,9 @@ class ProfileRepository : AbstractRepository<Profile>() {
* @param limit The maximum number of episodes to retrieve per page.
* @return A list of Anime objects representing the episodes in the watchlist.
*/
fun getWatchlistEpisodes(uuid: UUID, page: Int, limit: Int): List<Anime> {
fun getWatchlistEpisodes(uuid: UUID, page: Int, limit: Int): List<Episode> {
return super.getByPage(
Anime::class.java,
Episode::class.java,
page,
limit,
"""
Expand Down
64 changes: 64 additions & 0 deletions src/main/kotlin/fr/ziedelth/services/ProfileService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package fr.ziedelth.services

import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.inject.Inject
import fr.ziedelth.caches.ProfileEpisodeTypesLangTypesPaginationCacheKey
import fr.ziedelth.caches.ProfilePaginationCacheKey
import fr.ziedelth.dtos.MissingAnimeDto
import fr.ziedelth.entities.Anime
import fr.ziedelth.entities.Episode
import fr.ziedelth.repositories.ProfileRepository
import fr.ziedelth.utils.Logger
import java.util.*

class ProfileService : AbstractService() {
@Inject
private lateinit var repository: ProfileRepository

private val missingAnimesLoadingCache = CacheBuilder.newBuilder()
.build(object : CacheLoader<ProfileEpisodeTypesLangTypesPaginationCacheKey, List<MissingAnimeDto>>() {
override fun load(key: ProfileEpisodeTypesLangTypesPaginationCacheKey): List<MissingAnimeDto> {
Logger.config("Updating profile missing animes cache")
return repository.getMissingAnimes(key.profile, key.episodeTypes, key.langTypes, key.page, key.limit)
}
})

private val watchlistAnimesLoadingCache = CacheBuilder.newBuilder()
.build(object : CacheLoader<ProfilePaginationCacheKey, List<Anime>>() {
override fun load(key: ProfilePaginationCacheKey): List<Anime> {
Logger.config("Updating profile animes cache")
return repository.getWatchlistAnimes(key.profile, key.page, key.limit)
}
})

private val watchlistEpisodesLoadingCache = CacheBuilder.newBuilder()
.build(object : CacheLoader<ProfilePaginationCacheKey, List<Episode>>() {
override fun load(key: ProfilePaginationCacheKey): List<Episode> {
Logger.config("Updating profile episodes cache")
return repository.getWatchlistEpisodes(key.profile, key.page, key.limit)
}
})

fun invalidateProfile(profile: UUID) {
Logger.warning("Invalidate all profile cache for profile $profile")

val missingAnimesKeys = missingAnimesLoadingCache.asMap().keys.filter { it.profile == profile }
missingAnimesLoadingCache.invalidateAll(missingAnimesKeys)

val watchlistAnimesKeys = watchlistAnimesLoadingCache.asMap().keys.filter { it.profile == profile }
watchlistAnimesLoadingCache.invalidateAll(watchlistAnimesKeys)

val watchlistEpisodesKeys = watchlistEpisodesLoadingCache.asMap().keys.filter { it.profile == profile }
watchlistEpisodesLoadingCache.invalidateAll(watchlistEpisodesKeys)
}

fun getMissingAnimes(profile: UUID, episodeTypes: List<UUID>, langTypes: List<UUID>, page: Int, limit: Int): List<MissingAnimeDto> =
missingAnimesLoadingCache.getUnchecked(ProfileEpisodeTypesLangTypesPaginationCacheKey(profile, episodeTypes, langTypes, page, limit))

fun getWatchlistAnimes(profile: UUID, page: Int, limit: Int): List<Anime> =
watchlistAnimesLoadingCache.getUnchecked(ProfilePaginationCacheKey(profile, page, limit))

fun getWatchlistEpisodes(profile: UUID, page: Int, limit: Int): List<Episode> =
watchlistEpisodesLoadingCache.getUnchecked(ProfilePaginationCacheKey(profile, page, limit))
}