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

Commit

Permalink
Merge pull request #211 from Z-Jais/master
Browse files Browse the repository at this point in the history
Added ProfileService and caching for improved performance
  • Loading branch information
Ziedelth authored Nov 20, 2023
2 parents c61b570 + 8838623 commit 9bd31d2
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 73 deletions.
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))
}

0 comments on commit 9bd31d2

Please sign in to comment.