From a45a73c8bd33667a8f7f254b43c8c69f5a76b19f Mon Sep 17 00:00:00 2001 From: Gauthier Roebroeck Date: Mon, 31 May 2021 17:54:34 +0800 Subject: [PATCH] feat(api): search authors by name and role --- .../persistence/ReferentialRepository.kt | 7 ++ .../infrastructure/jooq/ReferentialDao.kt | 67 +++++++++++++++++++ .../interfaces/rest/ReferentialController.kt | 58 ++++++++++++---- 3 files changed, 119 insertions(+), 13 deletions(-) diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt index bb67e3296..8635676f1 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/ReferentialRepository.kt @@ -1,6 +1,8 @@ package org.gotson.komga.domain.persistence import org.gotson.komga.domain.model.Author +import org.springframework.data.domain.Page +import org.springframework.data.domain.Pageable import java.time.LocalDate interface ReferentialRepository { @@ -11,6 +13,11 @@ interface ReferentialRepository { fun findAuthorsNamesByName(search: String, filterOnLibraryIds: Collection?): List fun findAuthorsRoles(filterOnLibraryIds: Collection?): List + fun findAuthorsByName(search: String, role: String?, filterOnLibraryIds: Collection?, pageable: Pageable): Page + fun findAuthorsByNameAndLibrary(search: String, role: String?, libraryId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page + fun findAuthorsByNameAndCollection(search: String, role: String?, collectionId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page + fun findAuthorsByNameAndSeries(search: String, role: String?, seriesId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page + fun findAllGenres(filterOnLibraryIds: Collection?): Set fun findAllGenresByLibrary(libraryId: String, filterOnLibraryIds: Collection?): Set fun findAllGenresByCollection(collectionId: String, filterOnLibraryIds: Collection?): Set diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReferentialDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReferentialDao.kt index 2208ddd02..c99660215 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReferentialDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/ReferentialDao.kt @@ -8,6 +8,11 @@ import org.gotson.komga.jooq.tables.records.BookMetadataAuthorRecord import org.jooq.DSLContext import org.jooq.impl.DSL.lower import org.jooq.impl.DSL.select +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Sort import org.springframework.stereotype.Component import java.time.LocalDate @@ -71,6 +76,68 @@ class ReferentialDao( .fetchInto(bmaa) .map { it.toDomain() } + override fun findAuthorsByName(search: String, role: String?, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + return findAuthorsByName(search, role, filterOnLibraryIds, pageable, null) + } + + override fun findAuthorsByNameAndLibrary(search: String, role: String?, libraryId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + return findAuthorsByName(search, role, filterOnLibraryIds, pageable, FilterBy(FilterByType.LIBRARY, libraryId)) + } + + override fun findAuthorsByNameAndCollection(search: String, role: String?, collectionId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + return findAuthorsByName(search, role, filterOnLibraryIds, pageable, FilterBy(FilterByType.COLLECTION, collectionId)) + } + + override fun findAuthorsByNameAndSeries(search: String, role: String?, seriesId: String, filterOnLibraryIds: Collection?, pageable: Pageable): Page { + return findAuthorsByName(search, role, filterOnLibraryIds, pageable, FilterBy(FilterByType.SERIES, seriesId)) + } + + private enum class FilterByType { + LIBRARY, + COLLECTION, + SERIES, + } + + private data class FilterBy( + val type: FilterByType, + val id: String, + ) + + private fun findAuthorsByName(search: String, role: String?, filterOnLibraryIds: Collection?, pageable: Pageable, filterBy: FilterBy?): Page { + val query = dsl.selectDistinct(bmaa.NAME, bmaa.ROLE) + .from(bmaa) + .apply { if (filterOnLibraryIds != null || filterBy?.type == FilterByType.LIBRARY) leftJoin(s).on(bmaa.SERIES_ID.eq(s.ID)) } + .apply { if (filterBy?.type == FilterByType.COLLECTION) leftJoin(cs).on(bmaa.SERIES_ID.eq(cs.SERIES_ID)) } + .where(bmaa.NAME.containsIgnoreCase(search)) + .apply { role?.let { and(bmaa.ROLE.eq(role)) } } + .apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } } + .apply { + filterBy?.let { + when (it.type) { + FilterByType.LIBRARY -> and(s.LIBRARY_ID.eq(it.id)) + FilterByType.COLLECTION -> and(cs.COLLECTION_ID.eq(it.id)) + FilterByType.SERIES -> and(bmaa.SERIES_ID.eq(it.id)) + } + } + } + + val count = dsl.fetchCount(query) + + val items = query + .orderBy(bmaa.NAME, bmaa.ROLE) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } + .fetchInto(a) + .map { it.toDomain() } + + val pageSort = Sort.by("name") + return PageImpl( + items, + if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else PageRequest.of(0, maxOf(count, 20), pageSort), + count.toLong() + ) + } + override fun findAuthorsNamesByName(search: String, filterOnLibraryIds: Collection?): List = dsl.selectDistinct(a.NAME) .from(a) diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ReferentialController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ReferentialController.kt index 71e1bc97f..c6208dbce 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ReferentialController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/ReferentialController.kt @@ -1,9 +1,14 @@ package org.gotson.komga.interfaces.rest +import io.swagger.v3.oas.annotations.Parameter import org.gotson.komga.domain.persistence.ReferentialRepository import org.gotson.komga.infrastructure.security.KomgaPrincipal +import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam import org.gotson.komga.interfaces.rest.dto.AuthorDto import org.gotson.komga.interfaces.rest.dto.toDto +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable import org.springframework.http.MediaType import org.springframework.security.core.annotation.AuthenticationPrincipal import org.springframework.web.bind.annotation.GetMapping @@ -12,13 +17,13 @@ import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController @RestController -@RequestMapping("api/v1", produces = [MediaType.APPLICATION_JSON_VALUE]) +@RequestMapping("api", produces = [MediaType.APPLICATION_JSON_VALUE]) class ReferentialController( private val referentialRepository: ReferentialRepository ) { - @GetMapping("/authors") - fun getAuthors( + @GetMapping("v1/authors") + fun getAuthorsV1( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "search", defaultValue = "") search: String, @RequestParam(name = "library_id", required = false) libraryId: String?, @@ -33,20 +38,47 @@ class ReferentialController( else -> referentialRepository.findAuthorsByName(search, principal.user.getAuthorizedLibraryIds(null)) }.map { it.toDto() } - @GetMapping("/authors/names") + @PageableWithoutSortAsQueryParam + @GetMapping("v2/authors") + fun getAuthors( + @AuthenticationPrincipal principal: KomgaPrincipal, + @RequestParam(name = "search", defaultValue = "") search: String, + @RequestParam(name = "role") role: String?, + @RequestParam(name = "library_id", required = false) libraryId: String?, + @RequestParam(name = "collection_id", required = false) collectionId: String?, + @RequestParam(name = "series_id", required = false) seriesId: String?, + @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, + @Parameter(hidden = true) page: Pageable, + ): Page { + val pageRequest = + if (unpaged) Pageable.unpaged() + else PageRequest.of( + page.pageNumber, + page.pageSize, + ) + + return when { + libraryId != null -> referentialRepository.findAuthorsByNameAndLibrary(search, role, libraryId, principal.user.getAuthorizedLibraryIds(null), pageRequest) + collectionId != null -> referentialRepository.findAuthorsByNameAndCollection(search, role, collectionId, principal.user.getAuthorizedLibraryIds(null), pageRequest) + seriesId != null -> referentialRepository.findAuthorsByNameAndSeries(search, role, seriesId, principal.user.getAuthorizedLibraryIds(null), pageRequest) + else -> referentialRepository.findAuthorsByName(search, role, principal.user.getAuthorizedLibraryIds(null), pageRequest) + }.map { it.toDto() } + } + + @GetMapping("v1/authors/names") fun getAuthorsNames( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "search", defaultValue = "") search: String ): List = referentialRepository.findAuthorsNamesByName(search, principal.user.getAuthorizedLibraryIds(null)) - @GetMapping("/authors/roles") + @GetMapping("v1/authors/roles") fun getAuthorsRoles( @AuthenticationPrincipal principal: KomgaPrincipal, ): List = referentialRepository.findAuthorsRoles(principal.user.getAuthorizedLibraryIds(null)) - @GetMapping("/genres") + @GetMapping("v1/genres") fun getGenres( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "library_id", required = false) libraryId: String?, @@ -58,7 +90,7 @@ class ReferentialController( else -> referentialRepository.findAllGenres(principal.user.getAuthorizedLibraryIds(null)) } - @GetMapping("/tags") + @GetMapping("v1/tags") fun getTags( @AuthenticationPrincipal principal: KomgaPrincipal, // TODO: remove those parameters once Tachiyomi Extension is using the new /tags/series endpoint (changed in 0.87.4 - 21 Apr 2021) @@ -73,7 +105,7 @@ class ReferentialController( else -> referentialRepository.findAllSeriesAndBookTags(principal.user.getAuthorizedLibraryIds(null)) } - @GetMapping("/tags/book") + @GetMapping("v1/tags/book") fun getBookTags( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "series_id", required = false) seriesId: String?, @@ -83,7 +115,7 @@ class ReferentialController( else -> referentialRepository.findAllBookTags(principal.user.getAuthorizedLibraryIds(null)) } - @GetMapping("/tags/series") + @GetMapping("v1/tags/series") fun getSeriesTags( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "library_id", required = false) libraryId: String?, @@ -95,7 +127,7 @@ class ReferentialController( else -> referentialRepository.findAllSeriesTags(principal.user.getAuthorizedLibraryIds(null)) } - @GetMapping("/languages") + @GetMapping("v1/languages") fun getLanguages( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "library_id", required = false) libraryId: String?, @@ -107,7 +139,7 @@ class ReferentialController( else -> referentialRepository.findAllLanguages(principal.user.getAuthorizedLibraryIds(null)) } - @GetMapping("/publishers") + @GetMapping("v1/publishers") fun getPublishers( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "library_id", required = false) libraryId: String?, @@ -119,7 +151,7 @@ class ReferentialController( else -> referentialRepository.findAllPublishers(principal.user.getAuthorizedLibraryIds(null)) } - @GetMapping("/age-ratings") + @GetMapping("v1/age-ratings") fun getAgeRatings( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "library_id", required = false) libraryId: String?, @@ -131,7 +163,7 @@ class ReferentialController( else -> referentialRepository.findAllAgeRatings(principal.user.getAuthorizedLibraryIds(null)) }.map { it?.toString() ?: "None" }.toSet() - @GetMapping("/series/release-dates") + @GetMapping("v1/series/release-dates") fun getSeriesReleaseDates( @AuthenticationPrincipal principal: KomgaPrincipal, @RequestParam(name = "library_id", required = false) libraryId: String?,