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

Commit

Permalink
Refactor controllers and repositories for dependency injection
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziedelth committed Sep 27, 2023
1 parent 9b863bd commit c6524a2
Show file tree
Hide file tree
Showing 35 changed files with 478 additions and 369 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>7.0.0</version>
</dependency>
</dependencies>

<build>
Expand Down
33 changes: 3 additions & 30 deletions src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<T : Serializable>(val prefix: String) {
open class AbstractController<T : Serializable>(open val prefix: String) {
data class FilterData(
val animes: List<UUID> = listOf(),
val episodes: List<UUID> = listOf(),
Expand All @@ -27,7 +25,8 @@ open class IController<T : Serializable>(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<Unit, ApplicationCall>.getPageAndLimit(): Pair<Int, Int> {
val page = call.parameters["page"]!!.toIntOrNull() ?: throw IllegalArgumentException("Page is not valid")
Expand All @@ -43,30 +42,4 @@ open class IController<T : Serializable>(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))
}
}
}
117 changes: 63 additions & 54 deletions src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt
Original file line number Diff line number Diff line change
@@ -1,89 +1,96 @@
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.*
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<Anime>("/animes") {
fun getRoutes(routing: Routing) {
routing.route(prefix) {
search()
getByPage()
getAttachment()
getMissing()
create()
merge()
diary()
class AnimeController : AttachmentController<Anime>("/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<String>()
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")

Expand All @@ -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
Expand All @@ -118,31 +125,32 @@ 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)
}
}
}

@APIRoute
private fun Route.merge() {
put("/merge") {
// Get list of uuids
val uuids = call.receive<List<String>>().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")
Expand All @@ -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})",
Expand All @@ -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))
}
}
}
39 changes: 39 additions & 0 deletions src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt
Original file line number Diff line number Diff line change
@@ -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<T : Serializable>(override val prefix: String) : AbstractController<T>(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))
}
}
}
Loading

0 comments on commit c6524a2

Please sign in to comment.