From 9b863bdddb905a648831ba856065d864c1eeae10 Mon Sep 17 00:00:00 2001 From: Ziedelth Date: Wed, 27 Sep 2023 15:44:00 +0200 Subject: [PATCH 1/2] Update docker compose commands in push workflow --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 08f1428..572e2e0 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -98,4 +98,4 @@ jobs: - name: SSH Commands run: | - ssh -C -p 2222 ${{ secrets.SSH_DESTINATION }} "docker pull localhost:5000/jais-api:latest && cd ${{ secrets.SSH_FOLDER }} && docker compose down jais-api && docker compose up jais-api -d" + ssh -C -p 2222 ${{ secrets.SSH_DESTINATION }} "docker pull localhost:5000/jais-api:latest && cd ${{ secrets.SSH_FOLDER }} && docker compose down jais-api && docker compose up -d" From c6524a25b42210d82442cbc7e065dd9d46b10e62 Mon Sep 17 00:00:00 2001 From: Ziedelth Date: Wed, 27 Sep 2023 21:35:35 +0200 Subject: [PATCH 2/2] Refactor controllers and repositories for dependency injection --- pom.xml | 5 + .../controllers/AbstractController.kt | 33 +---- .../ziedelth/controllers/AnimeController.kt | 117 ++++++++++-------- .../controllers/AttachmentController.kt | 39 ++++++ .../ziedelth/controllers/AyaneController.kt | 36 +++--- .../ziedelth/controllers/CountryController.kt | 32 ++--- .../ziedelth/controllers/EpisodeController.kt | 86 +++++++------ .../controllers/EpisodeTypeController.kt | 31 ++--- .../ziedelth/controllers/GenreController.kt | 18 +-- .../controllers/LangTypeController.kt | 30 +++-- .../controllers/PlatformController.kt | 21 ++-- .../ziedelth/controllers/ProfileController.kt | 30 +++-- .../controllers/SimulcastController.kt | 29 +++-- .../fr/ziedelth/listeners/EpisodesRelease.kt | 5 +- .../kotlin/fr/ziedelth/plugins/Routing.kt | 89 +++++++------ .../repositories/AbstractRepository.kt | 6 +- .../ziedelth/repositories/AnimeRepository.kt | 7 +- .../repositories/CountryRepository.kt | 3 +- .../repositories/EpisodeRepository.kt | 7 +- .../repositories/EpisodeTypeRepository.kt | 3 +- .../ziedelth/repositories/GenreRepository.kt | 3 +- .../repositories/LangTypeRepository.kt | 3 +- .../repositories/PlatformRepository.kt | 3 +- .../repositories/SimulcastRepository.kt | 3 +- .../fr/ziedelth/services/AbstractService.kt | 3 + .../fr/ziedelth/services/AnimeService.kt | 6 +- .../fr/ziedelth/services/CountryService.kt | 6 +- .../fr/ziedelth/services/EpisodeService.kt | 6 +- .../ziedelth/services/EpisodeTypeService.kt | 6 +- .../fr/ziedelth/services/LangTypeService.kt | 6 +- .../fr/ziedelth/services/SimulcastService.kt | 6 +- .../kotlin/fr/ziedelth/utils/ImageCache.kt | 84 ++++++------- .../fr/ziedelth/utils/routes/APIIgnore.kt | 5 + .../fr/ziedelth/utils/routes/APIRoute.kt | 5 + .../kotlin/fr/ziedelth/plugins/RoutingTest.kt | 75 ++++++----- 35 files changed, 478 insertions(+), 369 deletions(-) create mode 100644 src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt create mode 100644 src/main/kotlin/fr/ziedelth/services/AbstractService.kt create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/APIIgnore.kt create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/APIRoute.kt diff --git a/pom.xml b/pom.xml index 61f0e05..60d33e0 100644 --- a/pom.xml +++ b/pom.xml @@ -142,6 +142,11 @@ guava 32.1.2-jre + + com.google.inject + guice + 7.0.0 + diff --git a/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt b/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt index 87a87f0..adc2807 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt @@ -2,11 +2,9 @@ package fr.ziedelth.controllers import fr.ziedelth.utils.Constant import fr.ziedelth.utils.Decoder -import fr.ziedelth.utils.ImageCache import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* -import io.ktor.server.routing.* import io.ktor.util.pipeline.* import java.io.Serializable import java.lang.reflect.ParameterizedType @@ -15,7 +13,7 @@ import java.util.* const val UNKNOWN_MESSAGE_ERROR = "Unknown error" const val MISSING_PARAMETERS_MESSAGE_ERROR = "Missing parameters" -open class IController(val prefix: String) { +open class AbstractController(open val prefix: String) { data class FilterData( val animes: List = listOf(), val episodes: List = listOf(), @@ -27,7 +25,8 @@ open class IController(val prefix: String) { ((javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*>).simpleName val uuidRequest: UUID = UUID.randomUUID() - fun decode(watchlist: String) = Constant.gson.fromJson(Decoder.fromGzip(watchlist), FilterData::class.java) + fun decode(watchlist: String): FilterData = + Constant.gson.fromJson(Decoder.fromGzip(watchlist), FilterData::class.java) fun PipelineContext.getPageAndLimit(): Pair { val page = call.parameters["page"]!!.toIntOrNull() ?: throw IllegalArgumentException("Page is not valid") @@ -43,30 +42,4 @@ open class IController(val prefix: String) { e.printStackTrace() call.respond(HttpStatusCode.InternalServerError, e.message ?: UNKNOWN_MESSAGE_ERROR) } - - fun Route.getAttachment() { - get("/attachment/{uuid}") { - val string = call.parameters["uuid"]!! - val uuidRegex = - "^[0-9(a-f|A-F)]{8}-[0-9(a-f|A-F)]{4}-4[0-9(a-f|A-F)]{3}-[89ab][0-9(a-f|A-F)]{3}-[0-9(a-f|A-F)]{12}\$".toRegex() - - if (!uuidRegex.matches(string)) { - println("GET $prefix/attachment/$string : Invalid UUID") - return@get call.respond(HttpStatusCode.BadRequest) - } - - val uuid = UUID.fromString(string) - println("GET ${prefix}/attachment/$uuid") - - if (!ImageCache.contains(uuid)) { - println("Attachment $uuid not found") - call.respond(HttpStatusCode.NoContent) - return@get - } - - val image = ImageCache.get(uuid)!! - println("Attachment $uuid found (${image.bytes.size} bytes)") - call.respondBytes(image.bytes, ContentType("image", image.type)) - } - } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt b/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt index d39dd24..398a1ac 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt @@ -1,11 +1,15 @@ package fr.ziedelth.controllers +import com.google.inject.Inject import fr.ziedelth.entities.Anime import fr.ziedelth.entities.isNullOrNotValid +import fr.ziedelth.repositories.AnimeRepository import fr.ziedelth.repositories.CountryRepository +import fr.ziedelth.repositories.EpisodeRepository import fr.ziedelth.services.AnimeService import fr.ziedelth.services.EpisodeService import fr.ziedelth.utils.ImageCache +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* @@ -13,77 +17,80 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import java.util.* -class AnimeController( - private val countryRepository: CountryRepository, - private val service: AnimeService, - private val episodeService: EpisodeService -) : - IController("/animes") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - search() - getByPage() - getAttachment() - getMissing() - create() - merge() - diary() +class AnimeController : AttachmentController("/animes") { + @Inject + private lateinit var countryRepository: CountryRepository + + @Inject + private lateinit var episodeRepository: EpisodeRepository + + @Inject + private lateinit var episodeService: EpisodeService + + @Inject + private lateinit var animeRepository: AnimeRepository + + @Inject + private lateinit var animeService: AnimeService + + @APIRoute + private fun Route.searchByCountryAndHash() { + get("/country/{country}/search/hash/{hash}") { + val country = call.parameters["country"]!! + val hash = call.parameters["hash"]!! + println("GET $prefix/country/$country/search/hash/$hash") + val anime = animeRepository.findByHash(country, hash) + call.respond(if (anime != null) mapOf("uuid" to anime) else HttpStatusCode.NotFound) } } - private fun Route.search() { - route("/country/{country}/search") { - get("/hash/{hash}") { - val country = call.parameters["country"]!! - val hash = call.parameters["hash"]!! - println("GET $prefix/country/$country/search/hash/$hash") - val anime = service.repository.findByHash(country, hash) - call.respond(if (anime != null) mapOf("uuid" to anime) else HttpStatusCode.NotFound) - } - - get("/name/{name}") { - val country = call.parameters["country"]!! - val name = call.parameters["name"]!! - println("GET $prefix/country/$country/search/name/$name") + @APIRoute + private fun Route.searchByCountryAndName() { + get("/country/{country}/search/name/{name}") { + val country = call.parameters["country"]!! + val name = call.parameters["name"]!! + println("GET $prefix/country/$country/search/name/$name") - try { - call.respond(service.findByName(country, name)) - } catch (e: Exception) { - e.printStackTrace() - } + try { + call.respond(animeService.findByName(country, name)) + } catch (e: Exception) { + e.printStackTrace() } } } - private fun Route.getByPage() { + @APIRoute + private fun Route.paginationByCountryAndSimulcast() { get("/country/{country}/simulcast/{simulcast}/page/{page}/limit/{limit}") { try { val country = call.parameters["country"]!! val simulcast = call.parameters["simulcast"]!! val (page, limit) = getPageAndLimit() println("GET $prefix/country/$country/simulcast/$simulcast/page/$page/limit/$limit") - call.respond(service.getByPage(country, UUID.fromString(simulcast), page, limit)) + call.respond(animeService.getByPage(country, UUID.fromString(simulcast), page, limit)) } catch (e: Exception) { printError(call, e) } } } - private fun Route.getMissing() { + @APIRoute + private fun Route.paginationMissing() { post("/missing/page/{page}/limit/{limit}") { try { val watchlist = call.receive() val (page, limit) = getPageAndLimit() println("POST $prefix/missing/page/$page/limit/$limit") val filterData = decode(watchlist) - call.respond(service.repository.getMissingAnimes(filterData, page, limit)) + call.respond(animeRepository.getMissingAnimes(filterData, page, limit)) } catch (e: Exception) { printError(call, e) } } } - private fun Route.create() { + @APIRoute + private fun Route.save() { post { println("POST $prefix") @@ -106,7 +113,7 @@ class AnimeController( return@post } - if (service.repository.findOneByName( + if (animeRepository.findOneByName( anime.country!!.tag!!, anime.name!! )?.country?.uuid == anime.country!!.uuid @@ -118,17 +125,17 @@ class AnimeController( val hash = anime.hash() - if (service.repository.findByHash(anime.country!!.tag!!, hash) != null) { + if (animeRepository.findByHash(anime.country!!.tag!!, hash) != null) { println("$entityName already exists") call.respond(HttpStatusCode.Conflict, "$entityName already exists") return@post } anime.hashes.add(hash) - val savedAnime = service.repository.save(anime) - ImageCache.cachingNetworkImage(savedAnime.uuid, savedAnime.image!!) + val savedAnime = animeRepository.save(anime) + ImageCache.cache(savedAnime.uuid, savedAnime.image!!) - service.invalidateAll() + animeService.invalidateAll() call.respond(HttpStatusCode.Created, savedAnime) } catch (e: Exception) { printError(call, e) @@ -136,13 +143,14 @@ class AnimeController( } } + @APIRoute private fun Route.merge() { put("/merge") { // Get list of uuids val uuids = call.receive>().map { UUID.fromString(it) } println("PUT $prefix/merge") // Get anime - val animes = uuids.mapNotNull { service.repository.find(it) } + val animes = uuids.mapNotNull { animeRepository.find(it) } if (animes.isEmpty()) { println("Anime not found") @@ -167,14 +175,14 @@ class AnimeController( val simulcasts = animes.map { it.simulcasts }.flatten().distinctBy { it.uuid }.toMutableSet() // Get all episodes val episodes = - animes.map { episodeService.repository.getAllBy("anime.uuid", it.uuid) }.flatten() + animes.map { episodeRepository.getAllBy("anime.uuid", it.uuid) }.flatten() .distinctBy { it.uuid } .toMutableSet() val firstAnime = animes.first() - val savedAnime = service.repository.find( - service.repository.save( + val savedAnime = animeRepository.find( + animeRepository.save( Anime( country = countries.first(), name = "${animes.first().name} (${animes.size})", @@ -188,26 +196,27 @@ class AnimeController( ).uuid )!! - ImageCache.cachingNetworkImage(savedAnime.uuid, savedAnime.image!!) - episodeService.repository.saveAll(episodes.map { it.copy(anime = savedAnime) }) + ImageCache.cache(savedAnime.uuid, savedAnime.image!!) + episodeRepository.saveAll(episodes.map { it.copy(anime = savedAnime) }) // Delete animes - service.repository.deleteAll(animes) + animeRepository.deleteAll(animes) - service.invalidateAll() + animeService.invalidateAll() episodeService.invalidateAll() call.respond(HttpStatusCode.OK, savedAnime) } } - private fun Route.diary() { + @APIRoute + private fun Route.diaryByCountryAndDay() { get("/diary/country/{country}/day/{day}") { val country = call.parameters["country"]!! var day = call.parameters["day"]!!.toIntOrNull() ?: return@get call.respond(HttpStatusCode.BadRequest) if (day == 0) day = 7 if (day > 7) day = 1 println("GET $prefix/diary/country/$country/day/$day") - call.respond(service.getDiary(country, day)) + call.respond(animeService.getDiary(country, day)) } } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt b/src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt new file mode 100644 index 0000000..e43d5ec --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt @@ -0,0 +1,39 @@ +package fr.ziedelth.controllers + +import fr.ziedelth.utils.ImageCache +import fr.ziedelth.utils.routes.APIIgnore +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* +import java.io.Serializable +import java.util.* + +@APIIgnore +open class AttachmentController(override val prefix: String) : AbstractController(prefix) { + fun Route.attachmentByUUID() { + get("/attachment/{uuid}") { + val string = call.parameters["uuid"]!! + val uuidRegex = + "^[0-9(a-f|A-F)]{8}-[0-9(a-f|A-F)]{4}-4[0-9(a-f|A-F)]{3}-[89ab][0-9(a-f|A-F)]{3}-[0-9(a-f|A-F)]{12}\$".toRegex() + + if (!uuidRegex.matches(string)) { + println("GET $prefix/attachment/$string : Invalid UUID") + return@get call.respond(HttpStatusCode.BadRequest) + } + + val uuid = UUID.fromString(string) + println("GET ${prefix}/attachment/$uuid") + + if (!ImageCache.contains(uuid)) { + println("Attachment $uuid not found") + call.respond(HttpStatusCode.NoContent) + return@get + } + + val image = ImageCache.get(uuid)!! + println("Attachment $uuid found (${image.bytes.size} bytes)") + call.respondBytes(image.bytes, ContentType("image", image.type)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/fr/ziedelth/controllers/AyaneController.kt b/src/main/kotlin/fr/ziedelth/controllers/AyaneController.kt index 190f4af..5857c7c 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/AyaneController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/AyaneController.kt @@ -3,34 +3,34 @@ package fr.ziedelth.controllers import fr.ziedelth.dtos.Ayane import fr.ziedelth.events.AyaneReleaseEvent import fr.ziedelth.utils.plugins.PluginManager +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -class AyaneController : IController("/ayane") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - post { - println("POST $prefix") +class AyaneController : AbstractController("/ayane") { + @APIRoute + private fun Route.save() { + post { + println("POST $prefix") - try { - val ayane = call.receive() + try { + val ayane = call.receive() - if (ayane.message.isBlank() || ayane.images.isEmpty()) { - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } + if (ayane.message.isBlank() || ayane.images.isEmpty()) { + call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + return@post + } - call.respond(HttpStatusCode.Created, "OK") + call.respond(HttpStatusCode.Created, "OK") - Thread { - PluginManager.callEvent(AyaneReleaseEvent(ayane)) - }.start() - } catch (e: Exception) { - printError(call, e) - } + Thread { + PluginManager.callEvent(AyaneReleaseEvent(ayane)) + }.start() + } catch (e: Exception) { + printError(call, e) } } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt b/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt index 125c50e..dc7ee02 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt @@ -1,30 +1,34 @@ package fr.ziedelth.controllers +import com.google.inject.Inject import fr.ziedelth.entities.Country import fr.ziedelth.entities.isNullOrNotValid +import fr.ziedelth.repositories.CountryRepository import fr.ziedelth.services.CountryService +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -class CountryController(private val service: CountryService) : IController("/countries") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - getAll() - create() - } - } +class CountryController : AbstractController("/countries") { + @Inject + private lateinit var countryRepository: CountryRepository + + @Inject + private lateinit var countryService: CountryService - fun Route.getAll() { + @APIRoute + private fun Route.getAll() { get { println("GET $prefix") - call.respond(service.getAll()) + call.respond(countryService.getAll()) } } - private fun Route.create() { + @APIRoute + private fun Route.save() { post { println("POST $prefix") @@ -36,18 +40,18 @@ class CountryController(private val service: CountryService) : IController("/episodes") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - getWithPage() - getAnimeWithPage() - getWatchlist() - getWatchlistFilter() - getAttachment() - create() - } - } +class EpisodeController : AttachmentController("/episodes") { + @Inject + private lateinit var platformRepository: PlatformRepository + + @Inject + private lateinit var animeRepository: AnimeRepository + + @Inject + private lateinit var animeService: AnimeService + + @Inject + private lateinit var simulcastRepository: SimulcastRepository + + @Inject + private lateinit var simulcastService: SimulcastService + + @Inject + private lateinit var episodeTypeRepository: EpisodeTypeRepository + + @Inject + private lateinit var langTypeRepository: LangTypeRepository + + @Inject + private lateinit var episodeRepository: EpisodeRepository + + @Inject + private lateinit var episodeService: EpisodeService - private fun Route.getWithPage() { + @APIRoute + private fun Route.paginationByCountry() { get("/country/{country}/page/{page}/limit/{limit}") { try { val country = call.parameters["country"]!! val (page, limit) = getPageAndLimit() println("GET $prefix/country/$country/page/$page/limit/$limit") - call.respond(service.getByPage(country, page, limit)) + call.respond(episodeService.getByPage(country, page, limit)) } catch (e: Exception) { printError(call, e) } } } - private fun Route.getAnimeWithPage() { + @APIRoute + private fun Route.paginationAnime() { get("/anime/{uuid}/page/{page}/limit/{limit}") { try { val animeUuid = call.parameters["uuid"]!! val (page, limit) = getPageAndLimit() println("GET $prefix/anime/$animeUuid/page/$page/limit/$limit") - call.respond(service.getByPageWithAnime(UUID.fromString(animeUuid), page, limit)) + call.respond(episodeService.getByPageWithAnime(UUID.fromString(animeUuid), page, limit)) } catch (e: Exception) { printError(call, e) } @@ -76,20 +87,22 @@ class EpisodeController( println("POST $prefix/${routePrefix}/page/$page/limit/$limit") val filterData = decode(watchlist) - pipelineContext.call.respond(service.repository.getByPageWithListFilter(filterData, page, limit)) + pipelineContext.call.respond(episodeRepository.getByPageWithListFilter(filterData, page, limit)) } catch (e: Exception) { episodeController.printError(pipelineContext.call, e) } } - private fun Route.getWatchlist() { + @APIRoute + private fun Route.paginationWatchlist() { post("/watchlist/page/{page}/limit/{limit}") { filterWatchlistByPageAndLimit(this, this@EpisodeController, "watchlist") } } + @APIRoute @Deprecated(message = "Use /watchlist as replace") - private fun Route.getWatchlistFilter() { + private fun Route.paginationWatchlistFilter() { post("/watchlist_filter/page/{page}/limit/{limit}") { filterWatchlistByPageAndLimit(this, this@EpisodeController, "watchlist_filter") } @@ -97,7 +110,7 @@ class EpisodeController( private fun merge(episode: Episode) { episode.platform = platformRepository.find(episode.platform!!.uuid) ?: throw Exception("Platform not found") - episode.anime = animeService.repository.find(episode.anime!!.uuid) ?: throw Exception("Anime not found") + episode.anime = animeRepository.find(episode.anime!!.uuid) ?: throw Exception("Anime not found") episode.episodeType = episodeTypeRepository.find(episode.episodeType!!.uuid) ?: throw Exception("EpisodeType not found") episode.langType = langTypeRepository.find(episode.langType!!.uuid) ?: throw Exception("LangType not found") @@ -107,25 +120,26 @@ class EpisodeController( } if (episode.number == -1) { - episode.number = service.repository.getLastNumber(episode) + 1 + episode.number = episodeRepository.getLastNumber(episode) + 1 } val tmpSimulcast = Simulcast.getSimulcast(episode.releaseDate.split("-")[0].toInt(), episode.releaseDate.split("-")[1].toInt()) val simulcast = - simulcastService.repository.findBySeasonAndYear(tmpSimulcast.season!!, tmpSimulcast.year!!) ?: tmpSimulcast + simulcastRepository.findBySeasonAndYear(tmpSimulcast.season!!, tmpSimulcast.year!!) ?: tmpSimulcast if (episode.anime!!.simulcasts.isEmpty() || episode.anime!!.simulcasts.none { it.uuid == simulcast.uuid }) { episode.anime!!.simulcasts.add(simulcast) } } - private fun Route.create() { + @APIRoute + private fun Route.saveMultiple() { post("/multiple") { println("POST $prefix/multiple") try { - val episodes = call.receive>().filter { !service.repository.exists("hash", it.hash!!) } + val episodes = call.receive>().filter { !episodeRepository.exists("hash", it.hash!!) } if (episodes.isEmpty()) { call.respond(HttpStatusCode.NoContent, "All requested episodes already exists!") @@ -136,12 +150,12 @@ class EpisodeController( episodes.forEach { merge(it) - val savedEpisode = service.repository.save(it) + val savedEpisode = episodeRepository.save(it) savedEpisodes.add(savedEpisode) - ImageCache.cachingNetworkImage(savedEpisode.uuid, savedEpisode.image!!) + ImageCache.cache(savedEpisode.uuid, savedEpisode.image!!) } - service.invalidateAll() + episodeService.invalidateAll() animeService.invalidateAll() simulcastService.invalidateAll() call.respond(HttpStatusCode.Created, savedEpisodes) diff --git a/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt b/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt index 165708c..41e8cfe 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt @@ -1,31 +1,34 @@ package fr.ziedelth.controllers +import com.google.inject.Inject import fr.ziedelth.entities.EpisodeType import fr.ziedelth.entities.isNullOrNotValid +import fr.ziedelth.repositories.EpisodeTypeRepository import fr.ziedelth.services.EpisodeTypeService +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -class EpisodeTypeController(private val service: EpisodeTypeService) : - IController("/episodetypes") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - getAll() - create() - } - } +class EpisodeTypeController : AbstractController("/episodetypes") { + @Inject + private lateinit var episodeTypeRepository: EpisodeTypeRepository + + @Inject + private lateinit var episodeTypeService: EpisodeTypeService - fun Route.getAll() { + @APIRoute + private fun Route.getAll() { get { println("GET $prefix") - call.respond(service.getAll()) + call.respond(episodeTypeService.getAll()) } } - private fun Route.create() { + @APIRoute + private fun Route.save() { post { println("POST $prefix") @@ -37,13 +40,13 @@ class EpisodeTypeController(private val service: EpisodeTypeService) : return@post } - if (service.repository.exists("name", episodeType.name)) { + if (episodeTypeRepository.exists("name", episodeType.name)) { call.respond(HttpStatusCode.Conflict, "$entityName already exists") return@post } - call.respond(HttpStatusCode.Created, service.repository.save(episodeType)) - service.invalidateAll() + call.respond(HttpStatusCode.Created, episodeTypeRepository.save(episodeType)) + episodeTypeService.invalidateAll() } catch (e: Exception) { printError(call, e) } diff --git a/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt b/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt index 24cfcb4..3751ac1 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt @@ -1,30 +1,30 @@ package fr.ziedelth.controllers +import com.google.inject.Inject import fr.ziedelth.entities.Genre import fr.ziedelth.entities.isNullOrNotValid import fr.ziedelth.repositories.GenreRepository +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -class GenreController(private val genreRepository: GenreRepository) : IController("/genres") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - getAll() - create() - } - } +class GenreController : AbstractController("/genres") { + @Inject + private lateinit var genreRepository: GenreRepository - fun Route.getAll() { + @APIRoute + private fun Route.getAll() { get { println("GET $prefix") call.respond(genreRepository.getAll()) } } - private fun Route.create() { + @APIRoute + private fun Route.save() { post { println("POST $prefix") diff --git a/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt b/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt index b03980b..c299fb1 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt @@ -1,30 +1,34 @@ package fr.ziedelth.controllers +import com.google.inject.Inject import fr.ziedelth.entities.LangType import fr.ziedelth.entities.isNullOrNotValid +import fr.ziedelth.repositories.LangTypeRepository import fr.ziedelth.services.LangTypeService +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* -class LangTypeController(private val service: LangTypeService) : IController("/langtypes") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - getAll() - create() - } - } +class LangTypeController : AbstractController("/langtypes") { + @Inject + private lateinit var langTypeRepository: LangTypeRepository + + @Inject + private lateinit var langTypeService: LangTypeService - fun Route.getAll() { + @APIRoute + private fun Route.getAll() { get { println("GET $prefix") - call.respond(service.getAll()) + call.respond(langTypeService.getAll()) } } - private fun Route.create() { + @APIRoute + private fun Route.save() { post { println("POST $prefix") @@ -36,13 +40,13 @@ class LangTypeController(private val service: LangTypeService) : IController("/platforms") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - getAll() - getAttachment() - create() - } - } +class PlatformController : AttachmentController("/platforms") { + @Inject + private lateinit var platformRepository: PlatformRepository - fun Route.getAll() { + @APIRoute + private fun Route.getAll() { get { println("GET $prefix") call.respond(platformRepository.getAll()) } } - private fun Route.create() { + @APIRoute + private fun Route.save() { post { println("POST $prefix") @@ -43,6 +43,7 @@ class PlatformController(private val platformRepository: PlatformRepository) : I } call.respond(HttpStatusCode.Created, platformRepository.save(platform)) + ImageCache.cache(platform.uuid, platform.image!!) } catch (e: Exception) { printError(call, e) } diff --git a/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt b/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt index 2197a44..19a94fa 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt @@ -1,6 +1,8 @@ package fr.ziedelth.controllers +import com.google.inject.Inject import fr.ziedelth.repositories.EpisodeRepository +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.request.* @@ -8,19 +10,21 @@ import io.ktor.server.response.* import io.ktor.server.routing.* import java.io.Serializable -class ProfileController(private val episodeRepository: EpisodeRepository) : IController("/profile") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - post("/total-duration") { - try { - val watchlist = call.receive() - println("GET $prefix/total-duration") - val filterData = decode(watchlist) - call.respond(mapOf("total-duration" to episodeRepository.getTotalDurationSeen(filterData.episodes))) - } catch (e: Exception) { - e.printStackTrace() - call.respond(HttpStatusCode.InternalServerError, e) - } +class ProfileController : AbstractController("/profile") { + @Inject + private lateinit var episodeRepository: EpisodeRepository + + @APIRoute + private fun Route.totalDuration() { + post("/total-duration") { + try { + val watchlist = call.receive() + println("GET $prefix/total-duration") + val filterData = decode(watchlist) + call.respond(mapOf("total-duration" to episodeRepository.getTotalDurationSeen(filterData.episodes))) + } catch (e: Exception) { + e.printStackTrace() + call.respond(HttpStatusCode.InternalServerError, e) } } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt b/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt index 1ed5712..cd5e46f 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt @@ -1,25 +1,28 @@ package fr.ziedelth.controllers +import com.google.inject.Inject import fr.ziedelth.entities.Simulcast import fr.ziedelth.services.SimulcastService +import fr.ziedelth.utils.routes.APIRoute import io.ktor.http.* import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* -class SimulcastController(private val service: SimulcastService) : - IController("/simulcasts") { - fun getRoutes(routing: Routing) { - routing.route(prefix) { - get("/country/{country}") { - try { - val country = call.parameters["country"]!! - println("GET $prefix/country/$country") - call.respond(service.getAll(country)) - } catch (e: Exception) { - e.printStackTrace() - call.respond(HttpStatusCode.InternalServerError, e) - } +class SimulcastController : AbstractController("/simulcasts") { + @Inject + private lateinit var simulcastService: SimulcastService + + @APIRoute + private fun Route.getByCountry() { + get("/country/{country}") { + try { + val country = call.parameters["country"]!! + println("GET $prefix/country/$country") + call.respond(simulcastService.getAll(country)) + } catch (e: Exception) { + e.printStackTrace() + call.respond(HttpStatusCode.InternalServerError, e) } } } diff --git a/src/main/kotlin/fr/ziedelth/listeners/EpisodesRelease.kt b/src/main/kotlin/fr/ziedelth/listeners/EpisodesRelease.kt index 0223fdc..0ac90fc 100644 --- a/src/main/kotlin/fr/ziedelth/listeners/EpisodesRelease.kt +++ b/src/main/kotlin/fr/ziedelth/listeners/EpisodesRelease.kt @@ -12,7 +12,7 @@ class EpisodesRelease : Listener { private var lastDaySend = 0 private var lastSend = mutableListOf() - fun toString(triple: Triple): String { + fun toString(triple: Triple): String { val etName = when (triple.first.name) { "EPISODE" -> "Épisode" "SPECIAL" -> "Spécial" @@ -38,7 +38,8 @@ class EpisodesRelease : Listener { lastSend.clear() } - val animes = event.episodes.map { it.anime to toString(Triple(it.episodeType!!, it.number!!, it.langType!!)) }.distinctBy { it.first?.uuid }.filter { !lastSend.contains(it.first?.uuid) } + val animes = event.episodes.map { it.anime to toString(Triple(it.episodeType!!, it.number!!, it.langType!!)) } + .distinctBy { it.first?.uuid }.filter { !lastSend.contains(it.first?.uuid) } if (animes.isEmpty()) return lastSend.addAll(animes.map { it.first!!.uuid }) diff --git a/src/main/kotlin/fr/ziedelth/plugins/Routing.kt b/src/main/kotlin/fr/ziedelth/plugins/Routing.kt index 51458cb..6c918af 100644 --- a/src/main/kotlin/fr/ziedelth/plugins/Routing.kt +++ b/src/main/kotlin/fr/ziedelth/plugins/Routing.kt @@ -1,46 +1,63 @@ package fr.ziedelth.plugins -import fr.ziedelth.controllers.* -import fr.ziedelth.repositories.* -import fr.ziedelth.services.* +import com.google.inject.AbstractModule +import com.google.inject.Guice +import fr.ziedelth.controllers.AbstractController +import fr.ziedelth.controllers.AttachmentController +import fr.ziedelth.repositories.AbstractRepository +import fr.ziedelth.services.AbstractService import fr.ziedelth.utils.Database +import fr.ziedelth.utils.routes.APIIgnore +import fr.ziedelth.utils.routes.APIRoute import io.ktor.server.application.* import io.ktor.server.routing.* +import org.reflections.Reflections +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.full.memberExtensionFunctions +import kotlin.reflect.jvm.javaMethod + +class DatabaseModule(private val reflections: Reflections, private val database: Database) : AbstractModule() { + override fun configure() { + bind(Database::class.java).toInstance(database) + + reflections.getSubTypesOf(AbstractRepository::class.java).forEach { + bind(it).asEagerSingleton() + } + + reflections.getSubTypesOf(AbstractService::class.java).forEach { + bind(it).asEagerSingleton() + } + } +} fun Application.configureRouting(database: Database) { + val reflections = Reflections("fr.ziedelth") + val injector = Guice.createInjector(DatabaseModule(reflections, database)) + routing { - val countryRepository = CountryRepository(database) - val platformRepository = PlatformRepository(database) - val simulcastRepository = SimulcastRepository(database) - val genreRepository = GenreRepository(database) - val animeRepository = AnimeRepository(database) - val episodeTypeRepository = EpisodeTypeRepository(database) - val langTypeRepository = LangTypeRepository(database) - val episodeRepository = EpisodeRepository(database) - - val countryService = CountryService(countryRepository) - val episodeTypeService = EpisodeTypeService(episodeTypeRepository) - val langTypeService = LangTypeService(langTypeRepository) - val simulcastService = SimulcastService(simulcastRepository) - val animeService = AnimeService(animeRepository) - val episodeService = EpisodeService(episodeRepository) - - CountryController(countryService).getRoutes(this) - PlatformController(platformRepository).getRoutes(this) - SimulcastController(simulcastService).getRoutes(this) - GenreController(genreRepository).getRoutes(this) - AnimeController(countryRepository, animeService, episodeService).getRoutes(this) - EpisodeTypeController(episodeTypeService).getRoutes(this) - LangTypeController(langTypeService).getRoutes(this) - EpisodeController( - platformRepository, - animeService, - simulcastService, - episodeTypeRepository, - langTypeRepository, - episodeService - ).getRoutes(this) - AyaneController().getRoutes(this) - ProfileController(episodeRepository).getRoutes(this) + reflections.getSubTypesOf(AbstractController::class.java).forEach { + if (it.isAnnotationPresent(APIIgnore::class.java)) { + return@forEach + } + + val controller = injector.getInstance(it) + val isAttachmentController = controller::class.java.superclass == AttachmentController::class.java + + val kFunctions = controller::class.memberExtensionFunctions.filter { kFunction -> + kFunction.findAnnotations(APIRoute::class).isNotEmpty() + }.toMutableList() + + route(controller.prefix) { + kFunctions.forEach { kFunction -> + val javaMethod = kFunction.javaMethod!! + javaMethod.isAccessible = true + javaMethod.invoke(controller, this) + } + + if (isAttachmentController) { + controller.javaClass.getMethod("attachmentByUUID", Route::class.java).invoke(controller, this) + } + } + } } } diff --git a/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt index 696452d..a74797c 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt @@ -1,10 +1,14 @@ package fr.ziedelth.repositories +import com.google.inject.Inject import fr.ziedelth.utils.Database import java.lang.reflect.ParameterizedType import java.util.* -open class AbstractRepository(val database: Database) { +open class AbstractRepository { + @Inject + protected lateinit var database: Database + private val entityClass: Class = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class private val entityName: String = entityClass.simpleName diff --git a/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt index 9bb67d5..d0c7450 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt @@ -1,17 +1,16 @@ package fr.ziedelth.repositories -import fr.ziedelth.controllers.IController +import fr.ziedelth.controllers.AbstractController import fr.ziedelth.dtos.MissingAnime import fr.ziedelth.entities.Anime import fr.ziedelth.entities.Episode -import fr.ziedelth.utils.Database import fr.ziedelth.utils.unaccent import java.time.DayOfWeek import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.* -class AnimeRepository(database: Database) : AbstractRepository(database), +class AnimeRepository : AbstractRepository(), IPageRepository { fun findByHash(tag: String, hash: String): UUID? { return database.inTransaction { @@ -95,7 +94,7 @@ class AnimeRepository(database: Database) : AbstractRepository(database), } fun getMissingAnimes( - filterData: IController.FilterData, + filterData: AbstractController.FilterData, page: Int, limit: Int ): List { diff --git a/src/main/kotlin/fr/ziedelth/repositories/CountryRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/CountryRepository.kt index 533514e..3e142f8 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/CountryRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/CountryRepository.kt @@ -1,6 +1,5 @@ package fr.ziedelth.repositories import fr.ziedelth.entities.Country -import fr.ziedelth.utils.Database -class CountryRepository(database: Database) : AbstractRepository(database) \ No newline at end of file +class CountryRepository : AbstractRepository() \ No newline at end of file diff --git a/src/main/kotlin/fr/ziedelth/repositories/EpisodeRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/EpisodeRepository.kt index 76568bc..b563874 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/EpisodeRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/EpisodeRepository.kt @@ -1,14 +1,13 @@ package fr.ziedelth.repositories -import fr.ziedelth.controllers.IController +import fr.ziedelth.controllers.AbstractController import fr.ziedelth.entities.Episode -import fr.ziedelth.utils.Database import java.util.* private const val ORDER = "ORDER BY releaseDate DESC, anime.name, season DESC, number DESC, episodeType.name, langType.name" -class EpisodeRepository(database: Database) : AbstractRepository(database), +class EpisodeRepository : AbstractRepository(), IPageRepository { override fun getByPage(tag: String, page: Int, limit: Int): List { return super.getByPage( @@ -38,7 +37,7 @@ class EpisodeRepository(database: Database) : AbstractRepository(databa } fun getByPageWithListFilter( - filterData: IController.FilterData, + filterData: AbstractController.FilterData, page: Int, limit: Int ): List { diff --git a/src/main/kotlin/fr/ziedelth/repositories/EpisodeTypeRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/EpisodeTypeRepository.kt index e04e00a..9f3f560 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/EpisodeTypeRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/EpisodeTypeRepository.kt @@ -1,6 +1,5 @@ package fr.ziedelth.repositories import fr.ziedelth.entities.EpisodeType -import fr.ziedelth.utils.Database -class EpisodeTypeRepository(database: Database) : AbstractRepository(database) +class EpisodeTypeRepository : AbstractRepository() diff --git a/src/main/kotlin/fr/ziedelth/repositories/GenreRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/GenreRepository.kt index 46cf8cf..e67d958 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/GenreRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/GenreRepository.kt @@ -1,6 +1,5 @@ package fr.ziedelth.repositories import fr.ziedelth.entities.Genre -import fr.ziedelth.utils.Database -class GenreRepository(database: Database) : AbstractRepository(database) \ No newline at end of file +class GenreRepository : AbstractRepository() \ No newline at end of file diff --git a/src/main/kotlin/fr/ziedelth/repositories/LangTypeRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/LangTypeRepository.kt index 62415e0..60ceb84 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/LangTypeRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/LangTypeRepository.kt @@ -1,6 +1,5 @@ package fr.ziedelth.repositories import fr.ziedelth.entities.LangType -import fr.ziedelth.utils.Database -class LangTypeRepository(database: Database) : AbstractRepository(database) +class LangTypeRepository : AbstractRepository() diff --git a/src/main/kotlin/fr/ziedelth/repositories/PlatformRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/PlatformRepository.kt index be58351..77b0df5 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/PlatformRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/PlatformRepository.kt @@ -1,6 +1,5 @@ package fr.ziedelth.repositories import fr.ziedelth.entities.Platform -import fr.ziedelth.utils.Database -class PlatformRepository(database: Database) : AbstractRepository(database) \ No newline at end of file +class PlatformRepository : AbstractRepository() \ No newline at end of file diff --git a/src/main/kotlin/fr/ziedelth/repositories/SimulcastRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/SimulcastRepository.kt index 02dffda..4baa041 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/SimulcastRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/SimulcastRepository.kt @@ -2,9 +2,8 @@ package fr.ziedelth.repositories import fr.ziedelth.entities.Simulcast import fr.ziedelth.utils.Constant -import fr.ziedelth.utils.Database -class SimulcastRepository(database: Database) : AbstractRepository(database) { +class SimulcastRepository : AbstractRepository() { fun getAll(tag: String?): List { val list = database.inTransaction { val query = it.createQuery("SELECT simulcasts FROM Anime WHERE country.tag = :tag", Simulcast::class.java) diff --git a/src/main/kotlin/fr/ziedelth/services/AbstractService.kt b/src/main/kotlin/fr/ziedelth/services/AbstractService.kt new file mode 100644 index 0000000..309c0ef --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/services/AbstractService.kt @@ -0,0 +1,3 @@ +package fr.ziedelth.services + +abstract class AbstractService \ No newline at end of file diff --git a/src/main/kotlin/fr/ziedelth/services/AnimeService.kt b/src/main/kotlin/fr/ziedelth/services/AnimeService.kt index 1e02b55..7e163df 100644 --- a/src/main/kotlin/fr/ziedelth/services/AnimeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/AnimeService.kt @@ -2,6 +2,7 @@ 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.DayCountryCacheKey import fr.ziedelth.caches.PaginationSimulcastCountryCacheKey import fr.ziedelth.caches.SearchCountryCacheKey @@ -10,7 +11,10 @@ import fr.ziedelth.repositories.AnimeRepository import fr.ziedelth.utils.unaccent import java.util.* -class AnimeService(val repository: AnimeRepository) { +class AnimeService : AbstractService() { + @Inject + private lateinit var repository: AnimeRepository + private val paginationSimulcastCountryCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: PaginationSimulcastCountryCacheKey): List { diff --git a/src/main/kotlin/fr/ziedelth/services/CountryService.kt b/src/main/kotlin/fr/ziedelth/services/CountryService.kt index 8b76621..211fb01 100644 --- a/src/main/kotlin/fr/ziedelth/services/CountryService.kt +++ b/src/main/kotlin/fr/ziedelth/services/CountryService.kt @@ -2,10 +2,14 @@ package fr.ziedelth.services import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader +import com.google.inject.Inject import fr.ziedelth.entities.Country import fr.ziedelth.repositories.CountryRepository -class CountryService(val repository: CountryRepository) { +class CountryService : AbstractService() { + @Inject + private lateinit var repository: CountryRepository + private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: String): List { diff --git a/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt b/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt index 60310b1..dc6017e 100644 --- a/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt @@ -2,13 +2,17 @@ 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.PaginationAnimeCacheKey import fr.ziedelth.caches.PaginationCountryCacheKey import fr.ziedelth.entities.Episode import fr.ziedelth.repositories.EpisodeRepository import java.util.* -class EpisodeService(val repository: EpisodeRepository) { +class EpisodeService : AbstractService() { + @Inject + private lateinit var repository: EpisodeRepository + private val paginationCountryCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: PaginationCountryCacheKey): List { diff --git a/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt b/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt index cbfd938..b629275 100644 --- a/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt @@ -2,10 +2,14 @@ package fr.ziedelth.services import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader +import com.google.inject.Inject import fr.ziedelth.entities.EpisodeType import fr.ziedelth.repositories.EpisodeTypeRepository -class EpisodeTypeService(val repository: EpisodeTypeRepository) { +class EpisodeTypeService : AbstractService() { + @Inject + private lateinit var repository: EpisodeTypeRepository + private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: String): List { diff --git a/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt b/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt index 6b47dfb..d5c74a4 100644 --- a/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt @@ -2,10 +2,14 @@ package fr.ziedelth.services import com.google.common.cache.CacheBuilder import com.google.common.cache.CacheLoader +import com.google.inject.Inject import fr.ziedelth.entities.LangType import fr.ziedelth.repositories.LangTypeRepository -class LangTypeService(val repository: LangTypeRepository) { +class LangTypeService : AbstractService() { + @Inject + private lateinit var repository: LangTypeRepository + private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: String): List { diff --git a/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt b/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt index e09db7b..ac4be8a 100644 --- a/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt +++ b/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt @@ -2,11 +2,15 @@ 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.CountryCacheKey import fr.ziedelth.entities.Simulcast import fr.ziedelth.repositories.SimulcastRepository -class SimulcastService(val repository: SimulcastRepository) { +class SimulcastService : AbstractService() { + @Inject + private lateinit var repository: SimulcastRepository + private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: CountryCacheKey): List { diff --git a/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt b/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt index e196aea..3ebae2a 100644 --- a/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt +++ b/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt @@ -10,7 +10,7 @@ import java.nio.file.Files import java.util.* object ImageCache { - data class Image(val bytes: ByteArray, val type: String) { + data class Image(val url: String, val bytes: ByteArray = byteArrayOf(), val type: String = "jpg") { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -25,11 +25,35 @@ object ImageCache { } } - private val cache = mutableMapOf() - private var totalSize = 0 + private val cache = mutableMapOf>() fun contains(uuid: UUID) = cache.containsKey(uuid) - fun get(uuid: UUID) = cache[uuid] + + fun get(uuid: UUID): Image? { + val pair = cache[uuid] ?: return null + + if (pair.second) { + return pair.first + } + + println("Encoding image to WebP") + val url = pair.first.url + var bytes: ByteArray? = null + + try { + bytes = saveImage(url).readBytes() + val webp = encodeToWebP(bytes) + cache[uuid] = Image(url, webp, "webp") to true + } catch (e: Exception) { + if (bytes != null) { + cache[uuid] = Image(url, bytes, "jpg") to true + } else { + println("Failed to load image $url : ${e.message}") + } + } + + return cache[uuid]?.first + } private fun encodeToWebP(image: ByteArray): ByteArray { val matImage = Imgcodecs.imdecode(MatOfByte(*image), Imgcodecs.IMREAD_UNCHANGED) @@ -51,46 +75,12 @@ object ImageCache { return tmpFile.inputStream() } - fun cachingNetworkImage(uuid: UUID, url: String) { - if (contains(uuid)) return - var bytes: ByteArray? = null - - try { - bytes = saveImage(url).readBytes() - val webp = encodeToWebP(bytes) - cache[uuid] = Image(webp, "webp") - } catch (e: Exception) { - if (bytes != null) { - cache[uuid] = Image(bytes, "jpg") - } else { - println("Failed to load image $url : ${e.message}") - this.totalSize-- - } - } - } - - private fun startPrintProgressThread() { - val thread = Thread { - val marginError = 5 // In percent - var totalSize: Double - var isRunning = true - - while (isRunning) { - totalSize = this.totalSize * (1 - marginError / 100.0) - isRunning = cache.size < totalSize - println("Progress : ${cache.size}/${this.totalSize} (${totalSize.toInt()} with $marginError% margin error)") - if (!isRunning) println("Done") - Thread.sleep(5000) - } - } - - thread.isDaemon = true - thread.start() + fun cache(uuid: UUID, url: String) { + cache[uuid] = Image(url) to false } fun invalidCache(database: Database) { cache.clear() - totalSize = 0 try { database.inTransaction { session -> @@ -108,14 +98,12 @@ object ImageCache { println("Episodes : ${episodes.size}") val combinedImages = platforms + animes + episodes - totalSize = combinedImages.size - startPrintProgressThread() - - Thread { - combinedImages.parallelStream().forEach { - cachingNetworkImage(it[0] as UUID, it[1] as String) - } - }.start() + + combinedImages.parallelStream().forEach { + val uuid = it[0] as UUID + val url = it[1] as String + cache(uuid, url) + } } } catch (e: Exception) { e.printStackTrace() diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/APIIgnore.kt b/src/main/kotlin/fr/ziedelth/utils/routes/APIIgnore.kt new file mode 100644 index 0000000..f962c6d --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/APIIgnore.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.CLASS) +annotation class APIIgnore diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/APIRoute.kt b/src/main/kotlin/fr/ziedelth/utils/routes/APIRoute.kt new file mode 100644 index 0000000..d80f3a5 --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/APIRoute.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class APIRoute diff --git a/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt b/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt index 9c45684..91bf712 100644 --- a/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt +++ b/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt @@ -1,46 +1,57 @@ package fr.ziedelth.plugins -import fr.ziedelth.controllers.* +import com.google.inject.Guice +import fr.ziedelth.controllers.AbstractController +import fr.ziedelth.controllers.AttachmentController import fr.ziedelth.repositories.* -import fr.ziedelth.services.* import fr.ziedelth.utils.DatabaseTest +import fr.ziedelth.utils.routes.APIIgnore +import fr.ziedelth.utils.routes.APIRoute import io.ktor.server.application.* import io.ktor.server.routing.* +import org.reflections.Reflections +import kotlin.reflect.full.findAnnotations +import kotlin.reflect.full.memberExtensionFunctions +import kotlin.reflect.jvm.javaMethod val databaseTest = DatabaseTest() +private val reflections = Reflections("fr.ziedelth") +private val injector = Guice.createInjector(DatabaseModule(reflections, databaseTest)) -val countryRepository = CountryRepository(databaseTest) -val platformRepository = PlatformRepository(databaseTest) -val simulcastRepository = SimulcastRepository(databaseTest) -val genreRepository = GenreRepository(databaseTest) -val animeRepository = AnimeRepository(databaseTest) -val episodeTypeRepository = EpisodeTypeRepository(databaseTest) -val langTypeRepository = LangTypeRepository(databaseTest) -val episodeRepository = EpisodeRepository(databaseTest) - -val countryService = CountryService(countryRepository) -val episodeTypeService = EpisodeTypeService(episodeTypeRepository) -val langTypeService = LangTypeService(langTypeRepository) -val simulcastService = SimulcastService(simulcastRepository) -val animeService = AnimeService(animeRepository) -val episodeService = EpisodeService(episodeRepository) +val countryRepository: CountryRepository = injector.getInstance(CountryRepository::class.java) +val platformRepository: PlatformRepository = injector.getInstance(PlatformRepository::class.java) +val simulcastRepository: SimulcastRepository = injector.getInstance(SimulcastRepository::class.java) +val genreRepository: GenreRepository = injector.getInstance(GenreRepository::class.java) +val animeRepository: AnimeRepository = injector.getInstance(AnimeRepository::class.java) +val episodeTypeRepository: EpisodeTypeRepository = injector.getInstance(EpisodeTypeRepository::class.java) +val langTypeRepository: LangTypeRepository = injector.getInstance(LangTypeRepository::class.java) +val episodeRepository: EpisodeRepository = injector.getInstance(EpisodeRepository::class.java) fun Application.configureRoutingTest() { routing { - CountryController(countryService).getRoutes(this) - PlatformController(platformRepository).getRoutes(this) - SimulcastController(simulcastService).getRoutes(this) - GenreController(genreRepository).getRoutes(this) - AnimeController(countryRepository, animeService, episodeService).getRoutes(this) - EpisodeTypeController(episodeTypeService).getRoutes(this) - LangTypeController(langTypeService).getRoutes(this) - EpisodeController( - platformRepository, - animeService, - simulcastService, - episodeTypeRepository, - langTypeRepository, - episodeService - ).getRoutes(this) + reflections.getSubTypesOf(AbstractController::class.java).forEach { + if (it.isAnnotationPresent(APIIgnore::class.java)) { + return@forEach + } + + val controller = injector.getInstance(it) + val isAttachmentController = controller::class.java.superclass == AttachmentController::class.java + + val kFunctions = controller::class.memberExtensionFunctions.filter { kFunction -> + kFunction.findAnnotations(APIRoute::class).isNotEmpty() + }.toMutableList() + + route(controller.prefix) { + kFunctions.forEach { kFunction -> + val javaMethod = kFunction.javaMethod!! + javaMethod.isAccessible = true + javaMethod.invoke(controller, this) + } + + if (isAttachmentController) { + controller.javaClass.getMethod("attachmentByUUID", Route::class.java).invoke(controller, this) + } + } + } } } \ No newline at end of file