From 6cd0e8d59eb6f961860a1a8066e931b6a4a27b6b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 09:17:16 +0000 Subject: [PATCH 1/5] Bump junit-jupiter.version from 5.10.0 to 5.10.1 Bumps `junit-jupiter.version` from 5.10.0 to 5.10.1. Updates `org.junit.jupiter:junit-jupiter-engine` from 5.10.0 to 5.10.1 - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1) Updates `org.junit.jupiter:junit-jupiter-api` from 5.10.0 to 5.10.1 - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.1) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter-engine dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: org.junit.jupiter:junit-jupiter-api dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8089c25..c3acee8 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ UTF-8 true fr.ziedelth.ApplicationKt - 5.10.0 + 5.10.1 **/fr/ziedelth/dtos/**, From d26036809311553dfa9185afc5f4bb7858752724 Mon Sep 17 00:00:00 2001 From: Ziedelth Date: Mon, 30 Oct 2023 17:41:14 +0100 Subject: [PATCH 2/5] Refactor route annotations and improve error handling --- .../controllers/AbstractController.kt | 42 +- .../ziedelth/controllers/AnimeController.kt | 411 +++++++----------- .../controllers/AttachmentController.kt | 50 +-- .../ziedelth/controllers/AyaneController.kt | 46 +- .../ziedelth/controllers/CountryController.kt | 68 ++- .../ziedelth/controllers/EpisodeController.kt | 210 +++------ .../controllers/EpisodeTypeController.kt | 57 +-- .../ziedelth/controllers/GenreController.kt | 54 +-- .../controllers/LangTypeController.kt | 57 +-- .../controllers/PlatformController.kt | 57 +-- .../ziedelth/controllers/ProfileController.kt | 28 +- .../controllers/SimulcastController.kt | 25 +- .../ziedelth/dtos/{Ayane.kt => AyaneDto.kt} | 2 +- .../{MissingAnime.kt => MissingAnimeDto.kt} | 2 +- .../fr/ziedelth/events/AyaneReleaseEvent.kt | 4 +- .../kotlin/fr/ziedelth/plugins/Routing.kt | 189 +++++++- .../repositories/AbstractRepository.kt | 3 +- .../ziedelth/repositories/AnimeRepository.kt | 8 +- .../fr/ziedelth/services/AnimeService.kt | 6 +- .../fr/ziedelth/services/CountryService.kt | 2 +- .../fr/ziedelth/services/EpisodeService.kt | 4 +- .../ziedelth/services/EpisodeTypeService.kt | 2 +- .../fr/ziedelth/services/LangTypeService.kt | 2 +- .../fr/ziedelth/services/SimulcastService.kt | 2 +- .../kotlin/fr/ziedelth/utils/ImageCache.kt | 2 +- .../routes/{APIRoute.kt => Authorized.kt} | 2 +- .../kotlin/fr/ziedelth/utils/routes/Cached.kt | 5 + .../routes/{APIIgnore.kt => IgnorePath.kt} | 2 +- .../kotlin/fr/ziedelth/utils/routes/Path.kt | 5 + .../fr/ziedelth/utils/routes/Response.kt | 18 + .../fr/ziedelth/utils/routes/method/Delete.kt | 5 + .../fr/ziedelth/utils/routes/method/Get.kt | 5 + .../fr/ziedelth/utils/routes/method/Post.kt | 5 + .../fr/ziedelth/utils/routes/method/Put.kt | 5 + .../controllers/AnimeControllerTest.kt | 2 +- .../controllers/EpisodeControllerTest.kt | 22 +- .../controllers/ProfileControllerTest.kt | 36 ++ .../kotlin/fr/ziedelth/plugins/RoutingTest.kt | 31 +- .../fr/ziedelth/utils/ImageCacheTest.kt | 3 +- 39 files changed, 683 insertions(+), 796 deletions(-) rename src/main/kotlin/fr/ziedelth/dtos/{Ayane.kt => AyaneDto.kt} (85%) rename src/main/kotlin/fr/ziedelth/dtos/{MissingAnime.kt => MissingAnimeDto.kt} (85%) rename src/main/kotlin/fr/ziedelth/utils/routes/{APIRoute.kt => Authorized.kt} (79%) create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/Cached.kt rename src/main/kotlin/fr/ziedelth/utils/routes/{APIIgnore.kt => IgnorePath.kt} (79%) create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/Path.kt create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/Response.kt create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/method/Delete.kt create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/method/Get.kt create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/method/Post.kt create mode 100644 src/main/kotlin/fr/ziedelth/utils/routes/method/Put.kt create mode 100644 src/test/kotlin/fr/ziedelth/controllers/ProfileControllerTest.kt diff --git a/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt b/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt index b86272a..7c2c8d5 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/AbstractController.kt @@ -3,16 +3,9 @@ package fr.ziedelth.controllers import fr.ziedelth.utils.Constant import fr.ziedelth.utils.Decoder import fr.ziedelth.utils.Logger -import io.ktor.http.* -import io.ktor.server.application.* -import io.ktor.server.response.* -import io.ktor.util.pipeline.* -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.async import java.io.Serializable import java.lang.reflect.ParameterizedType import java.util.* -import java.util.logging.Level const val UNKNOWN_MESSAGE_ERROR = "Unknown error" const val MISSING_PARAMETERS_MESSAGE_ERROR = "Missing parameters" @@ -25,42 +18,11 @@ open class AbstractController(open val prefix: String) { val langTypes: List = listOf(), // Lang types wanted to see ) - val entityName: String = - ((javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*>).simpleName - val uuidRequest: UUID = UUID.randomUUID() + val entityName: String = ((javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*>).simpleName fun decode(watchlist: String): FilterData { val filterData = Constant.gson.fromJson(Decoder.fromGzip(watchlist), FilterData::class.java) - Logger.config("$watchlist - Episodes: ${filterData.episodes.size} - Animes: ${filterData.animes.size}") + Logger.config("Episodes: ${filterData.episodes.size} - Animes: ${filterData.animes.size}") return filterData } - - suspend fun printError(call: ApplicationCall, e: Exception) { - Logger.log(Level.SEVERE, e.message, e) - call.respond(HttpStatusCode.InternalServerError, e.message ?: UNKNOWN_MESSAGE_ERROR) - } - - protected fun PipelineContext.getPageAndLimit(): Pair { - val page = call.parameters["page"]!!.toIntOrNull() ?: throw IllegalArgumentException("Page is not valid") - val limit = call.parameters["limit"]!!.toIntOrNull() ?: throw IllegalArgumentException("Limit is not valid") - - require(!(page < 1 || limit < 1)) { "Page or limit is not valid" } - require(limit <= 30) { "Limit is too high" } - - return Pair(page, limit) - } - - protected fun PipelineContext.isUnauthorized(): Deferred = async { - if (!Constant.secureKey.isNullOrBlank()) { - val authorization = call.request.headers[HttpHeaders.Authorization] - - if (Constant.secureKey != authorization) { - Logger.warning("Unauthorized request") - call.respond(HttpStatusCode.Unauthorized, "Secure key not equals") - return@async true - } - } - - return@async false - } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt b/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt index fd2f3e2..5f9b7e4 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/AnimeController.kt @@ -11,12 +11,14 @@ import fr.ziedelth.services.AnimeService import fr.ziedelth.services.EpisodeService import fr.ziedelth.utils.ImageCache import fr.ziedelth.utils.Logger -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Delete +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post +import fr.ziedelth.utils.routes.method.Put 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.* private const val ANIME_NOT_FOUND_ERROR = "Anime not found" @@ -40,277 +42,182 @@ class AnimeController : AttachmentController("/animes") { @Inject private lateinit var simulcastRepository: SimulcastRepository - @APIRoute - private fun Route.searchByCountryAndHash() { - get("/country/{country}/search/hash/{hash}") { - val country = call.parameters["country"]!! - val hash = call.parameters["hash"]!! - Logger.info("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) - } + @Path("/country/{country}/search/hash/{hash}") + @Get + private fun searchByCountryAndHash(country: String, hash: String): Response { + val anime = animeRepository.findByHash(country, hash) ?: return Response(HttpStatusCode.NotFound) + return Response.ok(mapOf("uuid" to anime)) } - @APIRoute - private fun Route.searchByCountryAndName() { - get("/country/{country}/search/name/{name}") { - val country = call.parameters["country"]!! - val name = call.parameters["name"]!! - Logger.info("GET $prefix/country/$country/search/name/$name") - - try { - call.respond(animeService.findByName(country, name)) - } catch (e: Exception) { - e.printStackTrace() - } - } + @Path("/country/{country}/search/name/{name}") + @Get + private fun searchByCountryAndName(country: String, name: String): Response { + return Response.ok(animeService.findByName(country, name)) } - @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() - Logger.info("GET $prefix/country/$country/simulcast/$simulcast/page/$page/limit/$limit") - call.respond(animeService.getByPage(country, UUID.fromString(simulcast), page, limit)) - } catch (e: Exception) { - printError(call, e) - } - } + @Path("/country/{country}/simulcast/{simulcast}/page/{page}/limit/{limit}") + @Get + private fun paginationByCountryAndSimulcast(country: String, simulcast: UUID, page: Int, limit: Int): Response { + return Response.ok(animeService.getByPage(country, simulcast, page, limit)) } - @APIRoute - private fun Route.paginationMissing() { - post("/missing/page/{page}/limit/{limit}") { - try { - val watchlist = call.receive() - val (page, limit) = getPageAndLimit() - Logger.info("POST $prefix/missing/page/$page/limit/$limit") - val filterData = decode(watchlist) - call.respond(animeRepository.getMissingAnimes(filterData, page, limit)) - } catch (e: Exception) { - printError(call, e) - } - } + @Path("/diary/country/{country}/day/{day}") + @Get + private fun diaryByCountryAndDay(country: String, day: Int): Response { + var selectedDay = day + if (selectedDay == 0) selectedDay = 7 + if (selectedDay > 7) selectedDay = 1 + return Response.ok(animeService.getDiary(country, selectedDay)) } - @APIRoute - private fun Route.save() { - post { - Logger.info("POST $prefix") - if (isUnauthorized().await()) return@post - - try { - val anime = call.receive() - - anime.country = countryRepository.find(anime.country!!.uuid) ?: return@post run { - Logger.warning("Country not found") - - call.respond( - HttpStatusCode.BadRequest, - "Country not found" - ) - } - - if (anime.isNullOrNotValid()) { - Logger.warning(MISSING_PARAMETERS_MESSAGE_ERROR) - Logger.warning(anime.toString()) - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } - - if (animeRepository.findOneByName( - anime.country!!.tag!!, - anime.name!! - )?.country?.uuid == anime.country!!.uuid - ) { - Logger.warning("$entityName already exists") - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } - - val hash = anime.hash() - - if (animeRepository.findByHash(anime.country!!.tag!!, hash) != null) { - Logger.warning("$entityName already exists") - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } - - anime.hashes.add(hash) - val savedAnime = animeRepository.save(anime) - ImageCache.cache(savedAnime.uuid, savedAnime.image!!) - - animeService.invalidateAll() - call.respond(HttpStatusCode.Created, savedAnime) - } catch (e: Exception) { - printError(call, e) - } - } + @Path("/invalid/country/{country}") + @Get + @Authorized + private fun getAllInvalid(country: String): Response { + return Response.ok(animeRepository.getInvalidAnimes(country)) } - @APIRoute - private fun Route.update() { - put { - Logger.info("PUT $prefix") - if (isUnauthorized().await()) return@put - - try { - val anime = call.receive() - var savedAnime = animeRepository.find(anime.uuid) - - if (savedAnime == null) { - call.respond(HttpStatusCode.NotFound, ANIME_NOT_FOUND_ERROR) - return@put - } - - if (!anime.name.isNullOrBlank()) { - if (animeRepository.findOneByName(savedAnime.country!!.tag!!, anime.name!!) != null) { - call.respond(HttpStatusCode.Conflict, "Another anime with the name exist!") - return@put - } - - savedAnime.name = anime.name - } - - if (!anime.description.isNullOrBlank()) { - savedAnime.description = anime.description - } - - if (anime.simulcasts.isNotEmpty()) { - val savedSimulcasts = anime.simulcasts.mapNotNull { simulcastRepository.find(it.uuid) } - - savedAnime.simulcasts.clear() - savedAnime.simulcasts.addAll(savedSimulcasts) - } - - savedAnime = animeRepository.save(savedAnime) - animeService.invalidateAll() - episodeService.invalidateAll() - call.respond(HttpStatusCode.OK, savedAnime) - } catch (e: Exception) { - printError(call, e) - } - } + @Path("/missing/page/{page}/limit/{limit}") + @Post + private fun paginationMissing(body: String, page: Int, limit: Int): Response { + return Response.ok(animeRepository.getMissingAnimes(decode(body), page, limit)) } - @APIRoute - private fun Route.deleteAnime() { - delete("/{uuid}") { - try { - val uuid = UUID.fromString(call.parameters["uuid"]!!) - Logger.info("DELETE $prefix/$uuid") - if (isUnauthorized().await()) return@delete - val savedAnime = animeRepository.find(uuid) - - if (savedAnime == null) { - call.respond(HttpStatusCode.NotFound, ANIME_NOT_FOUND_ERROR) - return@delete - } - - animeRepository.delete(savedAnime) - animeService.invalidateAll() - episodeService.invalidateAll() - call.respond(HttpStatusCode.NoContent) - } catch (e: Exception) { - printError(call, e) - } + @Path + @Post + @Authorized + private fun save(body: Anime): Response { + val countryUuid = body.country!!.uuid + body.country = + countryRepository.find(countryUuid) ?: return Response(HttpStatusCode.BadRequest, "Country not found") + + val countryTag = body.country!!.tag!! + + if (body.isNullOrNotValid()) { + Logger.warning(MISSING_PARAMETERS_MESSAGE_ERROR) + Logger.warning(body.toString()) + return Response(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + } + + if (animeRepository.findOneByName(countryTag, body.name!!)?.country?.uuid == countryUuid) { + Logger.warning("$entityName already exists") + return Response(HttpStatusCode.Conflict, "$entityName already exists") } + + val hash = body.hash() + + if (animeRepository.findByHash(countryTag, hash) != null) { + Logger.warning("$entityName already exists") + return Response(HttpStatusCode.Conflict, "$entityName already exists") + } + + body.hashes.add(hash) + val savedAnime = animeRepository.save(body) + ImageCache.cache(savedAnime.uuid, savedAnime.image!!) + animeService.invalidateAll() + return Response.created(savedAnime) } - @APIRoute - private fun Route.merge() { - put("/merge") { - if (isUnauthorized().await()) return@put - - // Get list of uuids - val uuids = call.receive>().map { UUID.fromString(it) } - Logger.info("PUT $prefix/merge") - // Get anime - val animes = uuids.mapNotNull { animeRepository.find(it) } - - if (animes.isEmpty()) { - Logger.warning(ANIME_NOT_FOUND_ERROR) - call.respond(HttpStatusCode.NotFound, ANIME_NOT_FOUND_ERROR) - return@put + @Path + @Put + @Authorized + private fun update(body: Anime): Response { + var savedAnime = + animeRepository.find(body.uuid) ?: return Response(HttpStatusCode.NotFound, ANIME_NOT_FOUND_ERROR) + + if (!body.name.isNullOrBlank()) { + if (animeRepository.findOneByName(savedAnime.country!!.tag!!, body.name!!) != null) { + return Response(HttpStatusCode.Conflict, "Another anime with the name exist!") } - // Get all countries - val countries = animes.map { it.country }.distinctBy { it!!.uuid } + savedAnime.name = body.name + } - if (countries.size > 1) { - Logger.warning("Anime has different countries") - call.respond(HttpStatusCode.BadRequest, "Anime has different countries") - return@put - } + if (!body.description.isNullOrBlank()) { + savedAnime.description = body.description + } + + if (body.simulcasts.isNotEmpty()) { + val savedSimulcasts = body.simulcasts.mapNotNull { simulcastRepository.find(it.uuid) } - // Get all hashes - val hashes = animes.map { it.hashes }.flatten().distinct().toMutableSet() - // Get all genres - val genres = animes.map { it.genres }.flatten().distinctBy { it.uuid }.toMutableSet() - // Get all simulcasts - val simulcasts = animes.map { it.simulcasts }.flatten().distinctBy { it.uuid }.toMutableSet() - // Get all episodes - val episodes = - animes.map { episodeRepository.getAllBy("anime.uuid", it.uuid) }.flatten() - .distinctBy { it.uuid } - .toMutableSet() - - val firstAnime = animes.first() - - val savedAnime = animeRepository.find( - animeRepository.save( - Anime( - country = countries.first(), - name = "${animes.first().name} (${animes.size})", - releaseDate = firstAnime.releaseDate, - image = firstAnime.image, - description = firstAnime.description, - hashes = hashes, - genres = genres, - simulcasts = simulcasts - ) - ).uuid - )!! - - ImageCache.cache(savedAnime.uuid, savedAnime.image!!) - episodeRepository.saveAll(episodes.map { it.copy(anime = savedAnime) }) - - // Delete animes - animeRepository.deleteAll(animes) - - animeService.invalidateAll() - episodeService.invalidateAll() - call.respond(HttpStatusCode.OK, savedAnime) + savedAnime.simulcasts.clear() + savedAnime.simulcasts.addAll(savedSimulcasts) } + + savedAnime = animeRepository.save(savedAnime) + animeService.invalidateAll() + episodeService.invalidateAll() + return Response.ok(savedAnime) } - @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 - Logger.info("GET $prefix/diary/country/$country/day/$day") - call.respond(animeService.getDiary(country, day)) + @Path("/merge") + @Put + @Authorized + private fun merge(body: Array): Response { + // Get anime + val animes = body.mapNotNull { animeRepository.find(it) } + + if (animes.isEmpty()) { + Logger.warning(ANIME_NOT_FOUND_ERROR) + return Response(HttpStatusCode.NotFound, ANIME_NOT_FOUND_ERROR) } - } - @APIRoute - private fun Route.getAllInvalid() { - get("/invalid/country/{tag}") { - try { - val tag = call.parameters["tag"]!! - Logger.info("GET $prefix/invalid/country/$tag") - if (isUnauthorized().await()) return@get - val animes = animeRepository.getInvalidAnimes(tag) - call.respond(animes) - } catch (e: Exception) { - printError(call, e) - } + // Get all countries + val countries = animes.map { it.country }.distinctBy { it!!.uuid } + + if (countries.size > 1) { + Logger.warning("Anime has different countries") + return Response(HttpStatusCode.BadRequest, "Anime has different countries") } + + // Get all hashes + val hashes = animes.map { it.hashes }.flatten().distinct().toMutableSet() + // Get all genres + val genres = animes.map { it.genres }.flatten().distinctBy { it.uuid }.toMutableSet() + // Get all simulcasts + val simulcasts = animes.map { it.simulcasts }.flatten().distinctBy { it.uuid }.toMutableSet() + // Get all episodes + val episodes = + animes.map { episodeRepository.getAllBy("anime.uuid", it.uuid) }.flatten() + .distinctBy { it.uuid } + .toMutableSet() + + val firstAnime = animes.first() + + val savedAnime = animeRepository.find( + animeRepository.save( + Anime( + country = countries.first(), + name = "${animes.first().name} (${animes.size})", + releaseDate = firstAnime.releaseDate, + image = firstAnime.image, + description = firstAnime.description, + hashes = hashes, + genres = genres, + simulcasts = simulcasts + ) + ).uuid + )!! + + ImageCache.cache(savedAnime.uuid, savedAnime.image!!) + episodeRepository.saveAll(episodes.map { it.copy(anime = savedAnime) }) + + // Delete animes + animeRepository.deleteAll(animes) + + animeService.invalidateAll() + episodeService.invalidateAll() + return Response.ok(savedAnime) + } + + @Path("/{uuid}") + @Delete + @Authorized + private fun deleteAnime(uuid: UUID): Response { + val savedAnime = animeRepository.find(uuid) ?: return Response(HttpStatusCode.NotFound, ANIME_NOT_FOUND_ERROR) + animeRepository.delete(savedAnime) + animeService.invalidateAll() + episodeService.invalidateAll() + return Response(HttpStatusCode.NoContent) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt b/src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt index 7547eaf..82151c0 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/AttachmentController.kt @@ -2,47 +2,25 @@ package fr.ziedelth.controllers import fr.ziedelth.utils.ImageCache import fr.ziedelth.utils.Logger -import fr.ziedelth.utils.routes.APIIgnore +import fr.ziedelth.utils.routes.* +import fr.ziedelth.utils.routes.method.Get import io.ktor.http.* -import io.ktor.http.content.* -import io.ktor.server.application.* -import io.ktor.server.plugins.cachingheaders.* -import io.ktor.server.response.* -import io.ktor.server.routing.* import java.io.Serializable import java.util.* -@APIIgnore +@IgnorePath open class AttachmentController(override val prefix: String) : AbstractController(prefix) { - fun Route.attachmentByUUID() { - route("/attachment/{uuid}") { - install(CachingHeaders) { - options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = 172800)) } - } - - get { - 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)) { - Logger.warning("GET $prefix/attachment/$string : Invalid UUID") - return@get call.respond(HttpStatusCode.BadRequest) - } - - val uuid = UUID.fromString(string) - Logger.info("GET ${prefix}/attachment/$uuid") - - if (!ImageCache.contains(uuid)) { - Logger.warning("Attachment $uuid not found") - call.respond(HttpStatusCode.NoContent) - return@get - } - - val image = ImageCache.get(uuid)!! - Logger.config("Attachment $uuid found (${image.bytes.size} bytes)") - call.respondBytes(image.bytes, ContentType("image", image.type)) - } + @Path("/attachment/{uuid}") + @Get + @Cached(172800) + fun attachmentByUUID(uuid: UUID): Response { + if (!ImageCache.contains(uuid)) { + Logger.warning("Attachment $uuid not found") + return Response(HttpStatusCode.NoContent) } + + val image = ImageCache.get(uuid)!! + Logger.config("Attachment $uuid found (${image.bytes.size} bytes)") + return ResponseMultipart(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 3c0f74c..5ef91f7 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/AyaneController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/AyaneController.kt @@ -1,39 +1,27 @@ package fr.ziedelth.controllers -import fr.ziedelth.dtos.Ayane +import fr.ziedelth.dtos.AyaneDto import fr.ziedelth.events.AyaneReleaseEvent -import fr.ziedelth.utils.Logger import fr.ziedelth.utils.plugins.PluginManager -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Post 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 : AbstractController("/ayane") { - @APIRoute - private fun Route.save() { - post { - Logger.info("POST $prefix") - if (isUnauthorized().await()) return@post - - try { - val ayane = call.receive() - - if (ayane.message.isBlank() || ayane.images.isEmpty()) { - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } +class AyaneController : AbstractController("/ayane") { + @Path + @Post + @Authorized + private fun save(ayaneDto: AyaneDto): Response { + if (ayaneDto.message.isBlank() || ayaneDto.images.isEmpty()) { + return Response(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + } - call.respond(HttpStatusCode.Created, "OK") + Thread { + PluginManager.callEvent(AyaneReleaseEvent(ayaneDto)) + }.start() - Thread { - PluginManager.callEvent(AyaneReleaseEvent(ayane)) - }.start() - } catch (e: Exception) { - printError(call, e) - } - } + return Response.created(ayaneDto) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt b/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt index b2e4db8..6f985af 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/CountryController.kt @@ -5,13 +5,12 @@ import fr.ziedelth.entities.Country import fr.ziedelth.entities.isNullOrNotValid import fr.ziedelth.repositories.CountryRepository import fr.ziedelth.services.CountryService -import fr.ziedelth.utils.Logger -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post 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 : AbstractController("/countries") { @Inject @@ -20,43 +19,30 @@ class CountryController : AbstractController("/countries") { @Inject private lateinit var countryService: CountryService - @APIRoute - private fun Route.getAll() { - get { - println("GET $prefix") - call.respond(countryService.getAll()) - } + @Path + @Get + private fun getAll(): Response { + return Response.ok(countryService.getAll()) } - @APIRoute - private fun Route.save() { - post { - Logger.info("POST $prefix") - if (isUnauthorized().await()) return@post - - try { - val country = call.receive() - - if (country.isNullOrNotValid()) { - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } - - if (countryRepository.exists("tag", country.tag)) { - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } - - if (countryRepository.exists("name", country.name)) { - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } - - call.respond(HttpStatusCode.Created, countryRepository.save(country)) - countryService.invalidateAll() - } catch (e: Exception) { - printError(call, e) - } + @Path + @Post + @Authorized + private fun save(body: Country): Response { + if (body.isNullOrNotValid()) { + return Response(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + } + + if (countryRepository.exists("tag", body.tag)) { + return Response(HttpStatusCode.Conflict, "$entityName already exists") } + + if (countryRepository.exists("name", body.name)) { + return Response(HttpStatusCode.Conflict, "$entityName already exists") + } + + val savedCountry = countryRepository.save(body) + countryService.invalidateAll() + return Response.created(savedCountry) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/EpisodeController.kt b/src/main/kotlin/fr/ziedelth/controllers/EpisodeController.kt index fcea06d..9d39c11 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/EpisodeController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/EpisodeController.kt @@ -9,15 +9,18 @@ import fr.ziedelth.repositories.* import fr.ziedelth.services.AnimeService import fr.ziedelth.services.EpisodeService import fr.ziedelth.services.SimulcastService -import fr.ziedelth.utils.* +import fr.ziedelth.utils.CalendarConverter +import fr.ziedelth.utils.ImageCache +import fr.ziedelth.utils.SortType import fr.ziedelth.utils.plugins.PluginManager -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post +import fr.ziedelth.utils.routes.method.Put +import fr.ziedelth.utils.toISO8601 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 io.ktor.util.pipeline.* import java.util.* class EpisodeController : AttachmentController("/episodes") { @@ -48,65 +51,22 @@ class EpisodeController : AttachmentController("/episodes") { @Inject private lateinit var episodeService: EpisodeService - @APIRoute - private fun Route.paginationByCountry() { - get("/country/{country}/page/{page}/limit/{limit}") { - try { - val country = call.parameters["country"]!! - val (page, limit) = getPageAndLimit() - Logger.info("GET $prefix/country/$country/page/$page/limit/$limit") - call.respond(episodeService.getByPage(country, page, limit)) - } catch (e: Exception) { - printError(call, e) - } - } - } - - @APIRoute - private fun Route.paginationAnime() { - get("/anime/{uuid}/page/{page}/limit/{limit}") { - try { - val animeUuid = call.parameters["uuid"]!! - val (page, limit) = getPageAndLimit() - val sortType = SortType.valueOf(call.request.queryParameters["sortType"] ?: SortType.SEASON_NUMBER.name) - Logger.info("GET $prefix/anime/$animeUuid/page/$page/limit/$limit") - call.respond(episodeService.getByPageWithAnime(UUID.fromString(animeUuid), sortType, page, limit)) - } catch (e: Exception) { - printError(call, e) - } - } + @Path("/country/{country}/page/{page}/limit/{limit}") + @Get + private fun paginationByCountry(country: String, page: Int, limit: Int): Response { + return Response.ok(episodeService.getByPage(country, page, limit)) } - private suspend fun filterWatchlistByPageAndLimit( - pipelineContext: PipelineContext, - episodeController: EpisodeController, - routePrefix: String, - ) { - try { - val watchlist = pipelineContext.call.receive() - val (page, limit) = pipelineContext.getPageAndLimit() - Logger.info("POST $prefix/${routePrefix}/page/$page/limit/$limit") - val filterData = decode(watchlist) - - pipelineContext.call.respond(episodeRepository.getByPageWithListFilter(filterData, page, limit)) - } catch (e: Exception) { - episodeController.printError(pipelineContext.call, e) - } - } - - @APIRoute - private fun Route.paginationWatchlist() { - post("/watchlist/page/{page}/limit/{limit}") { - filterWatchlistByPageAndLimit(this, this@EpisodeController, "watchlist") - } + @Path("/anime/{uuid}/page/{page}/limit/{limit}") + @Get + private fun paginationAnime(uuid: UUID, page: Int, limit: Int): Response { + return Response.ok(episodeService.getByPageWithAnime(uuid, SortType.SEASON_NUMBER, page, limit)) } - @APIRoute - @Deprecated(message = "Use /watchlist as replace") - private fun Route.paginationWatchlistFilter() { - post("/watchlist_filter/page/{page}/limit/{limit}") { - filterWatchlistByPageAndLimit(this, this@EpisodeController, "watchlist_filter") - } + @Path("/watchlist/page/{page}/limit/{limit}") + @Post + private fun paginationWatchlist(body: String, page: Int, limit: Int): Response { + return Response.ok(episodeRepository.getByPageWithListFilter(decode(body), page, limit)) } private fun merge(episode: Episode) { @@ -163,96 +123,64 @@ class EpisodeController : AttachmentController("/episodes") { } } - @APIRoute - private fun Route.saveMultiple() { - post("/multiple") { - Logger.info("POST $prefix/multiple") - if (isUnauthorized().await()) return@post - - try { - val episodes = call.receive>().filter { !episodeRepository.exists("hash", it.hash!!) } - - if (episodes.isEmpty()) { - call.respond(HttpStatusCode.NoContent, "All requested episodes already exists!") - return@post - } - - val savedEpisodes = mutableListOf() - - episodes.forEach { - merge(it) - val savedEpisode = episodeRepository.save(it) - savedEpisodes.add(savedEpisode) - ImageCache.cache(savedEpisode.uuid, savedEpisode.image!!) - } - - episodeService.invalidateAll() - animeService.invalidateAll() - simulcastService.invalidateAll() - call.respond(HttpStatusCode.Created, savedEpisodes) - - if (savedEpisodes.size <= 5) { - Thread { - PluginManager.callEvent(EpisodesReleaseEvent(savedEpisodes)) - }.start() - } - } catch (e: Exception) { - printError(call, e) - } - } - } - - @APIRoute - private fun Route.update() { - put { - Logger.info("PUT $prefix") - if (isUnauthorized().await()) return@put + @Path("/multiple") + @Post + @Authorized + private fun saveMultiple(body: Array): Response { + val episodes = body.filter { !episodeRepository.exists("hash", it.hash!!) } - try { - val episode = call.receive() - var savedEpisode = episodeRepository.find(episode.uuid) + if (episodes.isEmpty()) { + return Response(HttpStatusCode.NoContent, "All requested episodes already exists!") + } - if (savedEpisode == null) { - call.respond(HttpStatusCode.NotFound, "Episode not found") - return@put - } + val savedEpisodes = mutableListOf() - if (episode.episodeType?.uuid != null) { - val foundEpisodeType = episodeTypeRepository.find(episode.episodeType!!.uuid) + episodes.forEach { + merge(it) + val savedEpisode = episodeRepository.save(it) + savedEpisodes.add(savedEpisode) + ImageCache.cache(savedEpisode.uuid, savedEpisode.image!!) + } - if (foundEpisodeType == null) { - call.respond(HttpStatusCode.NotFound, "Episode type not found") - return@put - } + episodeService.invalidateAll() + animeService.invalidateAll() + simulcastService.invalidateAll() - savedEpisode.episodeType = foundEpisodeType - } + if (savedEpisodes.size <= 5) { + Thread { + PluginManager.callEvent(EpisodesReleaseEvent(savedEpisodes)) + }.start() + } - if (episode.langType?.uuid != null) { - val foundLangType = langTypeRepository.find(episode.langType!!.uuid) + return Response.created(savedEpisodes) + } - if (foundLangType == null) { - call.respond(HttpStatusCode.NotFound, "Lang type not found") - return@put - } + @Path + @Put + @Authorized + private fun update(body: Episode): Response { + var savedEpisode = episodeRepository.find(body.uuid) ?: return Response(HttpStatusCode.NotFound, "Episode not found") - savedEpisode.langType = foundLangType - } + if (body.episodeType?.uuid != null) { + val foundEpisodeType = episodeTypeRepository.find(body.episodeType!!.uuid) ?: return Response(HttpStatusCode.NotFound, "Episode type not found") + savedEpisode.episodeType = foundEpisodeType + } - if (episode.season != null) { - savedEpisode.season = episode.season - } + if (body.langType?.uuid != null) { + val foundLangType = langTypeRepository.find(body.langType!!.uuid) ?: return Response(HttpStatusCode.NotFound, "Lang type not found") + savedEpisode.langType = foundLangType + } - if (episode.duration != -1L) { - savedEpisode.duration = episode.duration - } + if (body.season != null) { + savedEpisode.season = body.season + } - savedEpisode = episodeRepository.save(savedEpisode) - episodeService.invalidateAll() - call.respond(HttpStatusCode.OK, savedEpisode) - } catch (e: Exception) { - printError(call, e) - } + if (body.duration != -1L) { + savedEpisode.duration = body.duration } + + savedEpisode = episodeRepository.save(savedEpisode) + episodeService.invalidateAll() + return Response.ok(savedEpisode) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt b/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt index 3f0b2ef..8502cfc 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/EpisodeTypeController.kt @@ -5,13 +5,12 @@ import fr.ziedelth.entities.EpisodeType import fr.ziedelth.entities.isNullOrNotValid import fr.ziedelth.repositories.EpisodeTypeRepository import fr.ziedelth.services.EpisodeTypeService -import fr.ziedelth.utils.Logger -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post 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 : AbstractController("/episodetypes") { @Inject @@ -20,38 +19,26 @@ class EpisodeTypeController : AbstractController("/episodetypes") { @Inject private lateinit var episodeTypeService: EpisodeTypeService - @APIRoute - private fun Route.getAll() { - get { - Logger.info("GET $prefix") - call.respond(episodeTypeService.getAll()) - } + @Path + @Get + private fun getAll(): Response { + return Response.ok(episodeTypeService.getAll()) } - @APIRoute - private fun Route.save() { - post { - Logger.info("POST $prefix") - if (isUnauthorized().await()) return@post - - try { - val episodeType = call.receive() - - if (episodeType.isNullOrNotValid()) { - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } - - if (episodeTypeRepository.exists("name", episodeType.name)) { - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } + @Path + @Post + @Authorized + private fun save(body: EpisodeType): Response { + if (body.isNullOrNotValid()) { + return Response(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + } - call.respond(HttpStatusCode.Created, episodeTypeRepository.save(episodeType)) - episodeTypeService.invalidateAll() - } catch (e: Exception) { - printError(call, e) - } + if (episodeTypeRepository.exists("name", body.name)) { + return Response(HttpStatusCode.Conflict, "$entityName already exists") } + + val savedEpisodeType = episodeTypeRepository.save(body) + episodeTypeService.invalidateAll() + return Response.created(savedEpisodeType) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt b/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt index 61c9721..53fd309 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/GenreController.kt @@ -4,49 +4,35 @@ import com.google.inject.Inject import fr.ziedelth.entities.Genre import fr.ziedelth.entities.isNullOrNotValid import fr.ziedelth.repositories.GenreRepository -import fr.ziedelth.utils.Logger -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post 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 : AbstractController("/genres") { @Inject private lateinit var genreRepository: GenreRepository - @APIRoute - private fun Route.getAll() { - get { - Logger.info("GET $prefix") - call.respond(genreRepository.getAll()) - } + @Path + @Get + private fun getAll(): Response { + return Response.ok(genreRepository.getAll()) } - @APIRoute - private fun Route.save() { - post { - Logger.info("POST $prefix") - if (isUnauthorized().await()) return@post - - try { - val genre = call.receive() - - if (genre.isNullOrNotValid()) { - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } - - if (genreRepository.exists("name", genre.name)) { - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } + @Path + @Post + @Authorized + private fun save(body: Genre): Response { + if (body.isNullOrNotValid()) { + return Response(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + } - call.respond(HttpStatusCode.Created, genreRepository.save(genre)) - } catch (e: Exception) { - printError(call, e) - } + if (genreRepository.exists("name", body.name)) { + return Response(HttpStatusCode.Conflict, "$entityName already exists") } + + return Response.created(genreRepository.save(body)) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt b/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt index 1d9647f..5c932cd 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/LangTypeController.kt @@ -5,13 +5,12 @@ import fr.ziedelth.entities.LangType import fr.ziedelth.entities.isNullOrNotValid import fr.ziedelth.repositories.LangTypeRepository import fr.ziedelth.services.LangTypeService -import fr.ziedelth.utils.Logger -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post 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 : AbstractController("/langtypes") { @Inject @@ -20,38 +19,26 @@ class LangTypeController : AbstractController("/langtypes") { @Inject private lateinit var langTypeService: LangTypeService - @APIRoute - private fun Route.getAll() { - get { - Logger.info("GET $prefix") - call.respond(langTypeService.getAll()) - } + @Path + @Get + private fun getAll(): Response { + return Response.ok(langTypeService.getAll()) } - @APIRoute - private fun Route.save() { - post { - Logger.info("POST $prefix") - if (isUnauthorized().await()) return@post - - try { - val langType = call.receive() - - if (langType.isNullOrNotValid()) { - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } - - if (langTypeRepository.exists("name", langType.name)) { - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } + @Path + @Post + @Authorized + private fun save(body: LangType): Response { + if (body.isNullOrNotValid()) { + return Response(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + } - call.respond(HttpStatusCode.Created, langTypeRepository.save(langType)) - langTypeService.invalidateAll() - } catch (e: Exception) { - printError(call, e) - } + if (langTypeRepository.exists("name", body.name)) { + return Response(HttpStatusCode.Conflict, "$entityName already exists") } + + val savedLangType = langTypeRepository.save(body) + langTypeService.invalidateAll() + return Response.created(savedLangType) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/PlatformController.kt b/src/main/kotlin/fr/ziedelth/controllers/PlatformController.kt index fa67126..ff48f59 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/PlatformController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/PlatformController.kt @@ -5,50 +5,37 @@ import fr.ziedelth.entities.Platform import fr.ziedelth.entities.isNullOrNotValid import fr.ziedelth.repositories.PlatformRepository import fr.ziedelth.utils.ImageCache -import fr.ziedelth.utils.Logger -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.Authorized +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post 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 PlatformController : AttachmentController("/platforms") { @Inject private lateinit var platformRepository: PlatformRepository - @APIRoute - private fun Route.getAll() { - get { - Logger.info("GET $prefix") - call.respond(platformRepository.getAll()) - } + @Path + @Get + private fun getAll(): Response { + return Response.ok(platformRepository.getAll()) } - @APIRoute - private fun Route.save() { - post { - Logger.info("POST $prefix") - if (isUnauthorized().await()) return@post - - try { - val platform = call.receive() - - if (platform.isNullOrNotValid()) { - call.respond(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) - return@post - } - - if (platformRepository.exists("name", platform.name)) { - call.respond(HttpStatusCode.Conflict, "$entityName already exists") - return@post - } + @Path + @Post + @Authorized + private fun save(body: Platform): Response { + if (body.isNullOrNotValid()) { + return Response(HttpStatusCode.BadRequest, MISSING_PARAMETERS_MESSAGE_ERROR) + } - call.respond(HttpStatusCode.Created, platformRepository.save(platform)) - ImageCache.cache(platform.uuid, platform.image!!) - } catch (e: Exception) { - printError(call, e) - } + if (platformRepository.exists("name", body.name)) { + return Response(HttpStatusCode.Conflict, "$entityName already exists") } + + val savedPlatform = platformRepository.save(body) + ImageCache.cache(savedPlatform.uuid, savedPlatform.image!!) + return Response.created(savedPlatform) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt b/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt index d8fd8f4..0662a48 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/ProfileController.kt @@ -2,31 +2,19 @@ package fr.ziedelth.controllers import com.google.inject.Inject import fr.ziedelth.repositories.EpisodeRepository -import fr.ziedelth.utils.Logger -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 fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Post import java.io.Serializable class ProfileController : AbstractController("/profile") { @Inject private lateinit var episodeRepository: EpisodeRepository - @APIRoute - private fun Route.totalDuration() { - post("/total-duration") { - try { - val watchlist = call.receive() - Logger.info("POST $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) - } - } + @Path("/total-duration") + @Post + private fun getTotalDuration(body: String): Response { + val filterData = decode(body) + return Response.ok(mapOf("total-duration" to episodeRepository.getTotalDurationSeen(filterData.episodes))) } } diff --git a/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt b/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt index 260703c..43e5d03 100644 --- a/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt +++ b/src/main/kotlin/fr/ziedelth/controllers/SimulcastController.kt @@ -3,28 +3,17 @@ package fr.ziedelth.controllers import com.google.inject.Inject import fr.ziedelth.entities.Simulcast import fr.ziedelth.services.SimulcastService -import fr.ziedelth.utils.Logger -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.* +import fr.ziedelth.utils.routes.Path +import fr.ziedelth.utils.routes.Response +import fr.ziedelth.utils.routes.method.Get class SimulcastController : AbstractController("/simulcasts") { @Inject private lateinit var simulcastService: SimulcastService - @APIRoute - private fun Route.getByCountry() { - get("/country/{country}") { - try { - val country = call.parameters["country"]!! - Logger.info("GET $prefix/country/$country") - call.respond(simulcastService.getAll(country)) - } catch (e: Exception) { - e.printStackTrace() - call.respond(HttpStatusCode.InternalServerError, e) - } - } + @Path("/country/{country}") + @Get + private fun getByCountry(country: String): Response { + return Response.ok(simulcastService.getAll(country)) } } diff --git a/src/main/kotlin/fr/ziedelth/dtos/Ayane.kt b/src/main/kotlin/fr/ziedelth/dtos/AyaneDto.kt similarity index 85% rename from src/main/kotlin/fr/ziedelth/dtos/Ayane.kt rename to src/main/kotlin/fr/ziedelth/dtos/AyaneDto.kt index 713ad4f..dcb998c 100644 --- a/src/main/kotlin/fr/ziedelth/dtos/Ayane.kt +++ b/src/main/kotlin/fr/ziedelth/dtos/AyaneDto.kt @@ -2,7 +2,7 @@ package fr.ziedelth.dtos import java.io.Serializable -data class Ayane( +data class AyaneDto( val message: String, val images: List ) : Serializable diff --git a/src/main/kotlin/fr/ziedelth/dtos/MissingAnime.kt b/src/main/kotlin/fr/ziedelth/dtos/MissingAnimeDto.kt similarity index 85% rename from src/main/kotlin/fr/ziedelth/dtos/MissingAnime.kt rename to src/main/kotlin/fr/ziedelth/dtos/MissingAnimeDto.kt index d62e213..c01879d 100644 --- a/src/main/kotlin/fr/ziedelth/dtos/MissingAnime.kt +++ b/src/main/kotlin/fr/ziedelth/dtos/MissingAnimeDto.kt @@ -2,7 +2,7 @@ package fr.ziedelth.dtos import fr.ziedelth.entities.Anime -data class MissingAnime( +data class MissingAnimeDto( val anime: Anime? = null, val episodeCount: Long = 0, val lastEpisode: String? = null, diff --git a/src/main/kotlin/fr/ziedelth/events/AyaneReleaseEvent.kt b/src/main/kotlin/fr/ziedelth/events/AyaneReleaseEvent.kt index 2e09d13..7144b9b 100644 --- a/src/main/kotlin/fr/ziedelth/events/AyaneReleaseEvent.kt +++ b/src/main/kotlin/fr/ziedelth/events/AyaneReleaseEvent.kt @@ -1,6 +1,6 @@ package fr.ziedelth.events -import fr.ziedelth.dtos.Ayane +import fr.ziedelth.dtos.AyaneDto import fr.ziedelth.utils.plugins.events.Event -data class AyaneReleaseEvent(val ayane: Ayane) : Event \ No newline at end of file +data class AyaneReleaseEvent(val ayane: AyaneDto) : Event \ No newline at end of file diff --git a/src/main/kotlin/fr/ziedelth/plugins/Routing.kt b/src/main/kotlin/fr/ziedelth/plugins/Routing.kt index 6c918af..6be6a37 100644 --- a/src/main/kotlin/fr/ziedelth/plugins/Routing.kt +++ b/src/main/kotlin/fr/ziedelth/plugins/Routing.kt @@ -2,19 +2,39 @@ package fr.ziedelth.plugins import com.google.inject.AbstractModule import com.google.inject.Guice +import com.google.inject.Injector import fr.ziedelth.controllers.AbstractController import fr.ziedelth.controllers.AttachmentController +import fr.ziedelth.dtos.AyaneDto +import fr.ziedelth.entities.* +import fr.ziedelth.entities.Platform import fr.ziedelth.repositories.AbstractRepository import fr.ziedelth.services.AbstractService +import fr.ziedelth.utils.Constant import fr.ziedelth.utils.Database -import fr.ziedelth.utils.routes.APIIgnore -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.Logger +import fr.ziedelth.utils.routes.* +import fr.ziedelth.utils.routes.method.Delete +import fr.ziedelth.utils.routes.method.Get +import fr.ziedelth.utils.routes.method.Post +import fr.ziedelth.utils.routes.method.Put +import io.ktor.http.* +import io.ktor.http.content.* import io.ktor.server.application.* +import io.ktor.server.plugins.cachingheaders.* +import io.ktor.server.request.* +import io.ktor.server.response.* import io.ktor.server.routing.* +import io.ktor.util.* import org.reflections.Reflections -import kotlin.reflect.full.findAnnotations -import kotlin.reflect.full.memberExtensionFunctions -import kotlin.reflect.jvm.javaMethod +import java.util.* +import java.util.logging.Level +import kotlin.reflect.KFunction +import kotlin.reflect.full.* +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.javaType + +private const val AN_ERROR_WAS_OCCURRED = "An error was occurred" class DatabaseModule(private val reflections: Reflections, private val database: Database) : AbstractModule() { override fun configure() { @@ -35,29 +55,158 @@ fun Application.configureRouting(database: Database) { val injector = Guice.createInjector(DatabaseModule(reflections, database)) routing { - reflections.getSubTypesOf(AbstractController::class.java).forEach { - if (it.isAnnotationPresent(APIIgnore::class.java)) { - return@forEach + createRoutes(reflections, injector) + } +} + +private fun Routing.createRoutes(reflections: Reflections, injector: Injector) { + reflections.getSubTypesOf(AbstractController::class.java).forEach { controllerClass -> + if (controllerClass.isAnnotationPresent(IgnorePath::class.java)) { + return@forEach + } + + val controller = injector.getInstance(controllerClass) + createControllerRoutes(controller) + } +} + +private suspend fun isAuthorized(method: KFunction<*>, call: ApplicationCall): Boolean { + if (method.hasAnnotation() && !Constant.secureKey.isNullOrBlank()) { + val authorization = call.request.headers[HttpHeaders.Authorization] + + if (Constant.secureKey != authorization) { + Logger.warning("Unauthorized request") + call.respond(HttpStatusCode.Unauthorized, "Secure key not equals") + return false + } + } + + return true +} + +private suspend fun handleRequest( + httpMethod: String, + call: ApplicationCall, + method: KFunction<*>, + controller: AbstractController<*>, + path: String +) { + if (!isAuthorized(method, call)) return + + val parameters = call.parameters.toMap() + val replacedPath = replacePathWithParameters("${controller.prefix}$path", parameters) + + Logger.info("$httpMethod $replacedPath") + + try { + val response = callMethodWithParameters(method, controller, call, parameters) + + if (response is ResponseMultipart) { + call.respondBytes(response.image, response.contentType) + } else { + call.respond(response.status, response.data ?: "") + } + } catch (e: Exception) { + Logger.log(Level.SEVERE, AN_ERROR_WAS_OCCURRED, e) + call.respond(HttpStatusCode.BadRequest) + } +} + +fun Routing.createControllerRoutes(controller: AbstractController<*>) { + val isAttachmentController = controller::class.isSubclassOf(AttachmentController::class) + val kMethods = controller::class.declaredFunctions.filter { it.hasAnnotation() }.toMutableSet() + + if (isAttachmentController) { + kMethods.addAll(controller::class.superclasses.first().declaredFunctions.filter { it.hasAnnotation() }) + } + + route(controller.prefix) { + kMethods.forEach { method -> + val path = method.findAnnotation()!!.value + + if (method.hasAnnotation()) { + val cached = method.findAnnotation()!!.maxAgeSeconds + + install(CachingHeaders) { + options { _, _ -> CachingOptions(CacheControl.MaxAge(maxAgeSeconds = cached)) } + } + } + + if (method.hasAnnotation()) { + get(path) { + handleRequest("GET", call, method, controller, path) + } } - val controller = injector.getInstance(it) - val isAttachmentController = controller::class.java.superclass == AttachmentController::class.java + if (method.hasAnnotation()) { + post(path) { + handleRequest("POST", call, method, controller, path) + } + } - val kFunctions = controller::class.memberExtensionFunctions.filter { kFunction -> - kFunction.findAnnotations(APIRoute::class).isNotEmpty() - }.toMutableList() + if (method.hasAnnotation()) { + put(path) { + handleRequest("PUT", call, method, controller, path) + } + } - route(controller.prefix) { - kFunctions.forEach { kFunction -> - val javaMethod = kFunction.javaMethod!! - javaMethod.isAccessible = true - javaMethod.invoke(controller, this) + if (method.hasAnnotation()) { + delete(path) { + handleRequest("DELETE", call, method, controller, path) } + } + } + } +} + +private fun replacePathWithParameters(path: String, parameters: Map>): String = + parameters.keys.fold(path) { acc, param -> + acc.replace("{$param}", parameters[param]!!.joinToString(", ")) + } - if (isAttachmentController) { - controller.javaClass.getMethod("attachmentByUUID", Route::class.java).invoke(controller, this) +private suspend fun callMethodWithParameters( + method: KFunction<*>, + controller: AbstractController<*>, + call: ApplicationCall, + parameters: Map> +): Response { + val methodParams = method.parameters.associateWith { kParameter -> + if (kParameter.name.isNullOrBlank()) { + controller + } else if (kParameter.name == "body") { + when (kParameter.type.javaType) { + Anime::class.java -> call.receive() + Array::class.java -> call.receive>() + AyaneDto::class.java -> call.receive() + Country::class.java -> call.receive() + Array::class.java -> call.receive>() + EpisodeType::class.java -> call.receive() + Genre::class.java -> call.receive() + LangType::class.java -> call.receive() + Platform::class.java -> call.receive() + else -> call.receive() + } + } else { + val value = parameters[kParameter.name]!!.first() + + val parsedValue = when (kParameter.type.javaType) { + UUID::class.java -> UUID.fromString(value) + Int::class.java -> value.toInt() + else -> value + } + + when (kParameter.name) { + "page" -> require(parsedValue as Int >= 1) { "Page is not valid" } + "limit" -> { + val i = parsedValue as Int + require(i in 1..30) { "Limit is not valid" } } } + + parsedValue } } + + method.isAccessible = true + return method.callBy(methodParams) as Response } diff --git a/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt index 1bd2a15..49b95da 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/AbstractRepository.kt @@ -9,7 +9,8 @@ open class AbstractRepository { @Inject protected lateinit var database: Database - private val entityClass: Class = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class // NOSONAR + private val entityClass: Class = + (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class // NOSONAR private val entityName: String = entityClass.simpleName fun find(uuid: UUID): T? { diff --git a/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt b/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt index 0ff2a28..ac6d194 100644 --- a/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt +++ b/src/main/kotlin/fr/ziedelth/repositories/AnimeRepository.kt @@ -1,7 +1,7 @@ package fr.ziedelth.repositories import fr.ziedelth.controllers.AbstractController -import fr.ziedelth.dtos.MissingAnime +import fr.ziedelth.dtos.MissingAnimeDto import fr.ziedelth.entities.Anime import fr.ziedelth.entities.Episode import fr.ziedelth.utils.unaccent @@ -87,18 +87,18 @@ class AnimeRepository : AbstractRepository() { filterData: AbstractController.FilterData, page: Int, limit: Int - ): List { + ): List { val whereString = """WHERE e.anime = a AND ${if (filterData.episodes.isEmpty()) "" else "e.uuid NOT IN :episodeUuids AND"} e.episodeType.uuid IN :episodeTypeUuids AND e.langType.uuid IN :langTypeUuids""" return super.getByPage( - MissingAnime::class.java, + MissingAnimeDto::class.java, page, limit, """ - SELECT new fr.ziedelth.dtos.MissingAnime( + SELECT new fr.ziedelth.dtos.MissingAnimeDto( a, ( SELECT count(e) diff --git a/src/main/kotlin/fr/ziedelth/services/AnimeService.kt b/src/main/kotlin/fr/ziedelth/services/AnimeService.kt index f03d572..449d301 100644 --- a/src/main/kotlin/fr/ziedelth/services/AnimeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/AnimeService.kt @@ -19,7 +19,7 @@ class AnimeService : AbstractService() { private val paginationSimulcastCountryCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: PaginationSimulcastCountryCacheKey): List { - Logger.info("Updating anime pagination cache") + Logger.config("Updating anime pagination cache") return repository.getByPage(key.tag, key.simulcast, key.page, key.limit) } }) @@ -27,7 +27,7 @@ class AnimeService : AbstractService() { private val dayCountryCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: DayCountryCacheKey): List { - Logger.info("Updating anime day pagination cache") + Logger.config("Updating anime day pagination cache") return repository.getDiary(key.tag, key.day) } }) @@ -35,7 +35,7 @@ class AnimeService : AbstractService() { private val searchCountryCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: SearchCountryCacheKey): List { - Logger.info("Updating anime day search cache") + Logger.config("Updating anime day search cache") return repository.findByName(key.tag, key.search) } }) diff --git a/src/main/kotlin/fr/ziedelth/services/CountryService.kt b/src/main/kotlin/fr/ziedelth/services/CountryService.kt index 215e037..d51052f 100644 --- a/src/main/kotlin/fr/ziedelth/services/CountryService.kt +++ b/src/main/kotlin/fr/ziedelth/services/CountryService.kt @@ -14,7 +14,7 @@ class CountryService : AbstractService() { private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: String): List { - Logger.info("Updating country cache") + Logger.config("Updating country cache") return repository.getAll() } }) diff --git a/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt b/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt index 12f5f2f..0204983 100644 --- a/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/EpisodeService.kt @@ -18,7 +18,7 @@ class EpisodeService : AbstractService() { private val paginationCountryCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: PaginationCountryCacheKey): List { - Logger.info("Updating episode pagination cache") + Logger.config("Updating episode pagination cache") return repository.getByPage(key.tag, key.page, key.limit) } }) @@ -26,7 +26,7 @@ class EpisodeService : AbstractService() { private val paginationAnimeCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: PaginationAnimeCacheKey): List { - Logger.info("Updating episode anime pagination cache") + Logger.config("Updating episode anime pagination cache") return repository.getByPageWithAnime(key.anime, key.sortType, key.page, key.limit) } }) diff --git a/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt b/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt index dc6d18f..2a66dc9 100644 --- a/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/EpisodeTypeService.kt @@ -14,7 +14,7 @@ class EpisodeTypeService : AbstractService() { private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: String): List { - Logger.info("Updating episode type cache") + Logger.config("Updating episode type cache") return repository.getAll() } }) diff --git a/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt b/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt index 1762093..f2d8bc6 100644 --- a/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt +++ b/src/main/kotlin/fr/ziedelth/services/LangTypeService.kt @@ -14,7 +14,7 @@ class LangTypeService : AbstractService() { private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: String): List { - Logger.info("Updating lang type cache") + Logger.config("Updating lang type cache") return repository.getAll() } }) diff --git a/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt b/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt index 158f8f9..3e1deda 100644 --- a/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt +++ b/src/main/kotlin/fr/ziedelth/services/SimulcastService.kt @@ -15,7 +15,7 @@ class SimulcastService : AbstractService() { private val loadingCache = CacheBuilder.newBuilder() .build(object : CacheLoader>() { override fun load(key: CountryCacheKey): List { - Logger.info("Updating simulcast cache") + Logger.config("Updating simulcast cache") return repository.getAll(key.tag) } }) diff --git a/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt b/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt index 784f5f3..296f5f7 100644 --- a/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt +++ b/src/main/kotlin/fr/ziedelth/utils/ImageCache.kt @@ -86,7 +86,7 @@ object ImageCache { val combinedImages = platforms + animes + episodes - combinedImages.parallelStream().forEach { + combinedImages.forEach { val uuid = it[0] as UUID val url = it[1] as String cache(uuid, url) diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/APIRoute.kt b/src/main/kotlin/fr/ziedelth/utils/routes/Authorized.kt similarity index 79% rename from src/main/kotlin/fr/ziedelth/utils/routes/APIRoute.kt rename to src/main/kotlin/fr/ziedelth/utils/routes/Authorized.kt index d80f3a5..e3d4795 100644 --- a/src/main/kotlin/fr/ziedelth/utils/routes/APIRoute.kt +++ b/src/main/kotlin/fr/ziedelth/utils/routes/Authorized.kt @@ -2,4 +2,4 @@ package fr.ziedelth.utils.routes @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION) -annotation class APIRoute +annotation class Authorized diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/Cached.kt b/src/main/kotlin/fr/ziedelth/utils/routes/Cached.kt new file mode 100644 index 0000000..1a4c757 --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/Cached.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class Cached(val maxAgeSeconds: Int) diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/APIIgnore.kt b/src/main/kotlin/fr/ziedelth/utils/routes/IgnorePath.kt similarity index 79% rename from src/main/kotlin/fr/ziedelth/utils/routes/APIIgnore.kt rename to src/main/kotlin/fr/ziedelth/utils/routes/IgnorePath.kt index f962c6d..d9b4428 100644 --- a/src/main/kotlin/fr/ziedelth/utils/routes/APIIgnore.kt +++ b/src/main/kotlin/fr/ziedelth/utils/routes/IgnorePath.kt @@ -2,4 +2,4 @@ package fr.ziedelth.utils.routes @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.CLASS) -annotation class APIIgnore +annotation class IgnorePath diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/Path.kt b/src/main/kotlin/fr/ziedelth/utils/routes/Path.kt new file mode 100644 index 0000000..f4f5bc5 --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/Path.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class Path(val value: String = "") diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/Response.kt b/src/main/kotlin/fr/ziedelth/utils/routes/Response.kt new file mode 100644 index 0000000..27c32df --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/Response.kt @@ -0,0 +1,18 @@ +package fr.ziedelth.utils.routes + +import io.ktor.http.* + +open class Response( + val status: HttpStatusCode = HttpStatusCode.OK, + val data: Any? = null, +) { + companion object { + fun ok(data: Any?): Response = Response(HttpStatusCode.OK, data) + fun created(data: Any?): Response = Response(HttpStatusCode.Created, data) + } +} + +open class ResponseMultipart( + val image: ByteArray, + val contentType: ContentType, +) : Response() \ No newline at end of file diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/method/Delete.kt b/src/main/kotlin/fr/ziedelth/utils/routes/method/Delete.kt new file mode 100644 index 0000000..94fb1fa --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/method/Delete.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes.method + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class Delete diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/method/Get.kt b/src/main/kotlin/fr/ziedelth/utils/routes/method/Get.kt new file mode 100644 index 0000000..529f816 --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/method/Get.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes.method + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class Get diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/method/Post.kt b/src/main/kotlin/fr/ziedelth/utils/routes/method/Post.kt new file mode 100644 index 0000000..e91da82 --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/method/Post.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes.method + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class Post diff --git a/src/main/kotlin/fr/ziedelth/utils/routes/method/Put.kt b/src/main/kotlin/fr/ziedelth/utils/routes/method/Put.kt new file mode 100644 index 0000000..4044a97 --- /dev/null +++ b/src/main/kotlin/fr/ziedelth/utils/routes/method/Put.kt @@ -0,0 +1,5 @@ +package fr.ziedelth.utils.routes.method + +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.FUNCTION) +annotation class Put diff --git a/src/test/kotlin/fr/ziedelth/controllers/AnimeControllerTest.kt b/src/test/kotlin/fr/ziedelth/controllers/AnimeControllerTest.kt index 7075e1f..4ea5375 100644 --- a/src/test/kotlin/fr/ziedelth/controllers/AnimeControllerTest.kt +++ b/src/test/kotlin/fr/ziedelth/controllers/AnimeControllerTest.kt @@ -105,7 +105,7 @@ internal class AnimeControllerTest : AbstractAPITest() { val responseError = client.get("/animes/country/${country.tag}/simulcast/${simulcast.uuid}/page/ae/limit/12") - expect(HttpStatusCode.InternalServerError) { responseError.status } + expect(HttpStatusCode.BadRequest) { responseError.status } } } diff --git a/src/test/kotlin/fr/ziedelth/controllers/EpisodeControllerTest.kt b/src/test/kotlin/fr/ziedelth/controllers/EpisodeControllerTest.kt index 3873c70..f915603 100644 --- a/src/test/kotlin/fr/ziedelth/controllers/EpisodeControllerTest.kt +++ b/src/test/kotlin/fr/ziedelth/controllers/EpisodeControllerTest.kt @@ -61,11 +61,11 @@ internal class EpisodeControllerTest : AbstractAPITest() { // ERROR - expect(HttpStatusCode.InternalServerError) { client.get("/episodes/country/${country.tag}/page/ae/limit/12").status } - expect(HttpStatusCode.InternalServerError) { client.get("/episodes/country/${country.tag}/page/1/limit/ae").status } - expect(HttpStatusCode.InternalServerError) { client.get("/episodes/country/${country.tag}/page/0/limit/12").status } - expect(HttpStatusCode.InternalServerError) { client.get("/episodes/country/${country.tag}/page/1/limit/0").status } - expect(HttpStatusCode.InternalServerError) { client.get("/episodes/country/${country.tag}/page/1/limit/31").status } + expect(HttpStatusCode.BadRequest) { client.get("/episodes/country/${country.tag}/page/ae/limit/12").status } + expect(HttpStatusCode.BadRequest) { client.get("/episodes/country/${country.tag}/page/1/limit/ae").status } + expect(HttpStatusCode.BadRequest) { client.get("/episodes/country/${country.tag}/page/0/limit/12").status } + expect(HttpStatusCode.BadRequest) { client.get("/episodes/country/${country.tag}/page/1/limit/0").status } + expect(HttpStatusCode.BadRequest) { client.get("/episodes/country/${country.tag}/page/1/limit/31").status } } } @@ -103,7 +103,7 @@ internal class EpisodeControllerTest : AbstractAPITest() { val responseError = client.get("/episodes/anime/${anime.uuid}/page/ae/limit/12") - expect(HttpStatusCode.InternalServerError) { responseError.status } + expect(HttpStatusCode.BadRequest) { responseError.status } } } @@ -417,7 +417,7 @@ internal class EpisodeControllerTest : AbstractAPITest() { configureRoutingTest() } - expect(HttpStatusCode.InternalServerError) { + expect(HttpStatusCode.BadRequest) { client.post("/episodes/multiple") { contentType(ContentType.Application.Json) setBody( @@ -439,7 +439,7 @@ internal class EpisodeControllerTest : AbstractAPITest() { val platform = platformRepository.getAll().first() - expect(HttpStatusCode.InternalServerError) { + expect(HttpStatusCode.BadRequest) { client.post("/episodes/multiple") { contentType(ContentType.Application.Json) setBody( @@ -461,7 +461,7 @@ internal class EpisodeControllerTest : AbstractAPITest() { val anime = animeRepository.getAll().first() - expect(HttpStatusCode.InternalServerError) { + expect(HttpStatusCode.BadRequest) { client.post("/episodes/multiple") { contentType(ContentType.Application.Json) setBody( @@ -483,7 +483,7 @@ internal class EpisodeControllerTest : AbstractAPITest() { val episodeType = episodeTypeRepository.getAll().first() - expect(HttpStatusCode.InternalServerError) { + expect(HttpStatusCode.BadRequest) { client.post("/episodes/multiple") { contentType(ContentType.Application.Json) setBody( @@ -505,7 +505,7 @@ internal class EpisodeControllerTest : AbstractAPITest() { val langType = langTypeRepository.getAll().first() - expect(HttpStatusCode.InternalServerError) { + expect(HttpStatusCode.BadRequest) { client.post("/episodes/multiple") { contentType(ContentType.Application.Json) setBody( diff --git a/src/test/kotlin/fr/ziedelth/controllers/ProfileControllerTest.kt b/src/test/kotlin/fr/ziedelth/controllers/ProfileControllerTest.kt new file mode 100644 index 0000000..e5626b8 --- /dev/null +++ b/src/test/kotlin/fr/ziedelth/controllers/ProfileControllerTest.kt @@ -0,0 +1,36 @@ +package fr.ziedelth.controllers + +import com.google.gson.JsonObject +import fr.ziedelth.AbstractAPITest +import fr.ziedelth.plugins.configureHTTP +import fr.ziedelth.plugins.configureRoutingTest +import fr.ziedelth.utils.Constant +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.server.testing.* +import org.junit.jupiter.api.Test +import kotlin.test.assertNotSame +import kotlin.test.expect + +internal class ProfileControllerTest : AbstractAPITest() { + @Test + fun getTotalDuration() { + testApplication { + application { + configureHTTP() + configureRoutingTest() + } + + val response = client.post("/profile/total-duration") { setBody(getFilterDataEncoded()) } + + expect(HttpStatusCode.OK) { response.status } + + val json = Constant.gson.fromJson(response.bodyAsText(), JsonObject::class.java) + val totalDuration = json.getAsJsonPrimitive("total-duration").asInt + + assertNotSame(0, totalDuration) + expect(1440) { totalDuration } + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt b/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt index 91bf712..b740594 100644 --- a/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt +++ b/src/test/kotlin/fr/ziedelth/plugins/RoutingTest.kt @@ -2,17 +2,12 @@ package fr.ziedelth.plugins import com.google.inject.Guice import fr.ziedelth.controllers.AbstractController -import fr.ziedelth.controllers.AttachmentController import fr.ziedelth.repositories.* import fr.ziedelth.utils.DatabaseTest -import fr.ziedelth.utils.routes.APIIgnore -import fr.ziedelth.utils.routes.APIRoute +import fr.ziedelth.utils.routes.IgnorePath 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") @@ -29,29 +24,13 @@ val episodeRepository: EpisodeRepository = injector.getInstance(EpisodeRepositor fun Application.configureRoutingTest() { routing { - reflections.getSubTypesOf(AbstractController::class.java).forEach { - if (it.isAnnotationPresent(APIIgnore::class.java)) { + reflections.getSubTypesOf(AbstractController::class.java).forEach { controllerClass -> + if (controllerClass.isAnnotationPresent(IgnorePath::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) - } - } + val controller = injector.getInstance(controllerClass) + createControllerRoutes(controller) } } } \ No newline at end of file diff --git a/src/test/kotlin/fr/ziedelth/utils/ImageCacheTest.kt b/src/test/kotlin/fr/ziedelth/utils/ImageCacheTest.kt index b319048..4358b13 100644 --- a/src/test/kotlin/fr/ziedelth/utils/ImageCacheTest.kt +++ b/src/test/kotlin/fr/ziedelth/utils/ImageCacheTest.kt @@ -17,7 +17,8 @@ internal class ImageCacheTest { @Test fun cache() { val uuid = UUID.randomUUID() - val imageUrl = "https://www.crunchyroll.com/imgsrv/display/thumbnail/480x720/catalog/crunchyroll/26afbe6a1577d35adb996205f563e34b.jpe" + val imageUrl = + "https://www.crunchyroll.com/imgsrv/display/thumbnail/480x720/catalog/crunchyroll/26afbe6a1577d35adb996205f563e34b.jpe" ImageCache.cache(uuid, imageUrl) assertTrue(ImageCache.contains(uuid)) From bc032806f871f3491128c8d92488741df42b506c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:39:20 +0000 Subject: [PATCH 3/5] Bump ktor_version from 2.3.5 to 2.3.6 Bumps `ktor_version` from 2.3.5 to 2.3.6. Updates `io.ktor:ktor-server-test-host-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-server-core-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-server-compression-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-server-cors-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-server-content-negotiation-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-client-content-negotiation-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-serialization-gson-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-server-netty-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) Updates `io.ktor:ktor-server-caching-headers-jvm` from 2.3.5 to 2.3.6 - [Release notes](https://github.com/ktorio/ktor/releases) - [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md) - [Commits](https://github.com/ktorio/ktor/commits) --- updated-dependencies: - dependency-name: io.ktor:ktor-server-test-host-jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-server-core-jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-server-compression-jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-server-cors-jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-server-content-negotiation-jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-client-content-negotiation-jvm dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-serialization-gson-jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-server-netty-jvm dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: io.ktor:ktor-server-caching-headers-jvm dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c3acee8..37cded9 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ api - 2.3.5 + 2.3.6 official 1.9.20 2.0 From 33b798091b51f4ff735dd16ce986082c8168f225 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:39:45 +0000 Subject: [PATCH 4/5] Bump org.apache.maven.plugins:maven-surefire-plugin from 3.2.1 to 3.2.2 Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.1...surefire-3.2.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 37cded9..4407c70 100644 --- a/pom.xml +++ b/pom.xml @@ -190,7 +190,7 @@ maven-surefire-plugin - 3.2.1 + 3.2.2 maven-failsafe-plugin From 57c9e25976a2bad74eb563de690043d9ea4eca01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:38:47 +0000 Subject: [PATCH 5/5] Bump org.apache.maven.plugins:maven-failsafe-plugin from 3.2.1 to 3.2.2 Bumps [org.apache.maven.plugins:maven-failsafe-plugin](https://github.com/apache/maven-surefire) from 3.2.1 to 3.2.2. - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.1...surefire-3.2.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-failsafe-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4407c70..c853f17 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ maven-failsafe-plugin - 3.2.1 + 3.2.2 org.codehaus.mojo