mirror of
https://github.com/gotson/komga.git
synced 2025-12-22 00:13:30 +01:00
parent
02e916898e
commit
449a27e136
9 changed files with 153 additions and 71 deletions
|
|
@ -1,10 +1,12 @@
|
|||
package org.gotson.komga.domain.persistence
|
||||
|
||||
import org.gotson.komga.domain.model.SeriesCollection
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
|
||||
interface SeriesCollectionRepository {
|
||||
fun findByIdOrNull(collectionId: Long): SeriesCollection?
|
||||
fun findAll(): Collection<SeriesCollection>
|
||||
fun findAll(search: String? = null, pageable: Pageable): Page<SeriesCollection>
|
||||
|
||||
/**
|
||||
* Find one SeriesCollection by collectionId,
|
||||
|
|
@ -16,7 +18,7 @@ interface SeriesCollectionRepository {
|
|||
* Find all SeriesCollection with at least one Series belonging to the provided belongsToLibraryIds,
|
||||
* optionally with only seriesId filtered by the provided filterOnLibraryIds.
|
||||
*/
|
||||
fun findAllByLibraries(belongsToLibraryIds: Collection<Long>, filterOnLibraryIds: Collection<Long>?): Collection<SeriesCollection>
|
||||
fun findAllByLibraries(belongsToLibraryIds: Collection<Long>, filterOnLibraryIds: Collection<Long>?, search: String? = null, pageable: Pageable): Page<SeriesCollection>
|
||||
|
||||
/**
|
||||
* Find all SeriesCollection that contains the provided containsSeriesId,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ import org.gotson.komga.jooq.tables.records.CollectionRecord
|
|||
import org.jooq.DSLContext
|
||||
import org.jooq.Record
|
||||
import org.jooq.ResultQuery
|
||||
import org.jooq.impl.DSL
|
||||
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.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
|
@ -20,50 +25,79 @@ class SeriesCollectionDao(
|
|||
private val cs = Tables.COLLECTION_SERIES
|
||||
private val s = Tables.SERIES
|
||||
|
||||
private val groupFields = arrayOf(*c.fields(), *cs.fields())
|
||||
private val sorts = mapOf(
|
||||
"name" to DSL.lower(c.NAME)
|
||||
)
|
||||
|
||||
|
||||
override fun findByIdOrNull(collectionId: Long): SeriesCollection? =
|
||||
selectBase()
|
||||
.where(c.ID.eq(collectionId))
|
||||
.groupBy(*groupFields)
|
||||
.orderBy(cs.NUMBER.asc())
|
||||
.fetchAndMap()
|
||||
.fetchAndMap(null)
|
||||
.firstOrNull()
|
||||
|
||||
override fun findByIdOrNull(collectionId: Long, filterOnLibraryIds: Collection<Long>?): SeriesCollection? =
|
||||
selectBase()
|
||||
.where(c.ID.eq(collectionId))
|
||||
.also { step ->
|
||||
filterOnLibraryIds?.let { step.and(s.LIBRARY_ID.`in`(it)) }
|
||||
}
|
||||
.groupBy(*groupFields)
|
||||
.orderBy(cs.NUMBER.asc())
|
||||
.fetchAndMap()
|
||||
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
|
||||
.fetchAndMap(filterOnLibraryIds)
|
||||
.firstOrNull()
|
||||
|
||||
override fun findAll(): Collection<SeriesCollection> =
|
||||
selectBase()
|
||||
.groupBy(*groupFields)
|
||||
.orderBy(cs.NUMBER.asc())
|
||||
.fetchAndMap()
|
||||
override fun findAll(search: String?, pageable: Pageable): Page<SeriesCollection> {
|
||||
val conditions = search?.let { c.NAME.containsIgnoreCase(it) }
|
||||
?: DSL.trueCondition()
|
||||
|
||||
override fun findAllByLibraries(belongsToLibraryIds: Collection<Long>, filterOnLibraryIds: Collection<Long>?): Collection<SeriesCollection> {
|
||||
val count = dsl.selectCount()
|
||||
.from(c)
|
||||
.where(conditions)
|
||||
.fetchOne(0, Long::class.java)
|
||||
|
||||
val orderBy = pageable.sort.toOrderBy(sorts)
|
||||
|
||||
val items = selectBase()
|
||||
.where(conditions)
|
||||
.orderBy(orderBy)
|
||||
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
|
||||
.fetchAndMap(null)
|
||||
|
||||
return PageImpl(
|
||||
items,
|
||||
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageable.sort)
|
||||
else PageRequest.of(0, count.toInt(), pageable.sort),
|
||||
count.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
override fun findAllByLibraries(belongsToLibraryIds: Collection<Long>, filterOnLibraryIds: Collection<Long>?, search: String?, pageable: Pageable): Page<SeriesCollection> {
|
||||
val ids = dsl.select(c.ID)
|
||||
.from(c)
|
||||
.leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID))
|
||||
.leftJoin(s).on(cs.SERIES_ID.eq(s.ID))
|
||||
.where(s.LIBRARY_ID.`in`(belongsToLibraryIds))
|
||||
.apply { search?.let { and(c.NAME.containsIgnoreCase(it)) } }
|
||||
.fetch(0, Long::class.java)
|
||||
|
||||
return selectBase()
|
||||
val count = dsl.selectCount()
|
||||
.from(c)
|
||||
.where(c.ID.`in`(ids))
|
||||
.also { step ->
|
||||
filterOnLibraryIds?.let { step.and(s.LIBRARY_ID.`in`(it)) }
|
||||
}
|
||||
.groupBy(*groupFields)
|
||||
.orderBy(cs.NUMBER.asc())
|
||||
.fetchAndMap()
|
||||
.fetchOne(0, Long::class.java)
|
||||
|
||||
val orderBy = pageable.sort.toOrderBy(sorts)
|
||||
|
||||
val items = selectBase()
|
||||
.where(c.ID.`in`(ids))
|
||||
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
|
||||
.apply { search?.let { and(c.NAME.containsIgnoreCase(it)) } }
|
||||
.orderBy(orderBy)
|
||||
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
|
||||
.fetchAndMap(filterOnLibraryIds)
|
||||
|
||||
return PageImpl(
|
||||
items,
|
||||
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageable.sort)
|
||||
else PageRequest.of(0, count.toInt()),
|
||||
count.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
override fun findAllBySeries(containsSeriesId: Long, filterOnLibraryIds: Collection<Long>?): Collection<SeriesCollection> {
|
||||
|
|
@ -75,24 +109,27 @@ class SeriesCollectionDao(
|
|||
|
||||
return selectBase()
|
||||
.where(c.ID.`in`(ids))
|
||||
.also { step ->
|
||||
filterOnLibraryIds?.let { step.and(s.LIBRARY_ID.`in`(it)) }
|
||||
}
|
||||
.groupBy(*groupFields)
|
||||
.orderBy(cs.NUMBER.asc())
|
||||
.fetchAndMap()
|
||||
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
|
||||
.fetchAndMap(filterOnLibraryIds)
|
||||
}
|
||||
|
||||
private fun selectBase() =
|
||||
dsl.select(*groupFields)
|
||||
dsl.selectDistinct(*c.fields())
|
||||
.from(c)
|
||||
.leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID))
|
||||
.leftJoin(s).on(cs.SERIES_ID.eq(s.ID))
|
||||
|
||||
private fun ResultQuery<Record>.fetchAndMap() =
|
||||
fetchGroups({ it.into(c) }, { it.into(cs) })
|
||||
.map { (cr, csr) ->
|
||||
val seriesIds = csr.mapNotNull { it.seriesId }
|
||||
private fun ResultQuery<Record>.fetchAndMap(filterOnLibraryIds: Collection<Long>?): List<SeriesCollection> =
|
||||
fetchInto(c)
|
||||
.map { cr ->
|
||||
val seriesIds = dsl.select(*cs.fields())
|
||||
.from(cs)
|
||||
.leftJoin(s).on(cs.SERIES_ID.eq(s.ID))
|
||||
.where(cs.COLLECTION_ID.eq(cr.id))
|
||||
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
|
||||
.orderBy(cs.NUMBER.asc())
|
||||
.fetchInto(cs)
|
||||
.mapNotNull { it.seriesId }
|
||||
cr.toDomain(seriesIds)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
package org.gotson.komga.infrastructure.jooq
|
||||
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
|
||||
class UnpagedSorted(
|
||||
private val sort: Sort
|
||||
) : Pageable {
|
||||
|
||||
override fun getPageNumber(): Int {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun hasPrevious(): Boolean = false
|
||||
|
||||
override fun getSort(): Sort = sort
|
||||
|
||||
override fun isPaged(): Boolean = false
|
||||
|
||||
override fun next(): Pageable = this
|
||||
|
||||
override fun getPageSize(): Int {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun getOffset(): Long {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun first(): Pageable = this
|
||||
|
||||
override fun previousOrFirst(): Pageable = this
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import org.gotson.komga.domain.persistence.MediaRepository
|
|||
import org.gotson.komga.domain.persistence.SeriesCollectionRepository
|
||||
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
||||
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||
import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.interfaces.opds.dto.OpdsAuthor
|
||||
import org.gotson.komga.interfaces.opds.dto.OpdsEntryAcquisition
|
||||
|
|
@ -32,6 +33,7 @@ import org.gotson.komga.interfaces.opds.dto.OpdsLinkPageStreaming
|
|||
import org.gotson.komga.interfaces.opds.dto.OpdsLinkRel
|
||||
import org.gotson.komga.interfaces.opds.dto.OpdsLinkSearch
|
||||
import org.gotson.komga.interfaces.opds.dto.OpenSearchDescription
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
|
|
@ -223,11 +225,12 @@ class OpdsController(
|
|||
fun getCollections(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal
|
||||
): OpdsFeed {
|
||||
val pageRequest = UnpagedSorted(Sort.by(Sort.Order.asc("name")))
|
||||
val collections =
|
||||
if (principal.user.sharedAllLibraries) {
|
||||
collectionRepository.findAll()
|
||||
collectionRepository.findAll(pageable = pageRequest)
|
||||
} else {
|
||||
collectionRepository.findAllByLibraries(principal.user.sharedLibrariesIds, principal.user.sharedLibrariesIds)
|
||||
collectionRepository.findAllByLibraries(principal.user.sharedLibrariesIds, principal.user.sharedLibrariesIds, pageable = pageRequest)
|
||||
}
|
||||
return OpdsFeedNavigation(
|
||||
id = ID_COLLECTIONS_ALL,
|
||||
|
|
@ -238,7 +241,7 @@ class OpdsController(
|
|||
OpdsLinkFeedNavigation(OpdsLinkRel.SELF, "$routeBase$ROUTE_COLLECTIONS_ALL"),
|
||||
linkStart
|
||||
),
|
||||
entries = collections.map { it.toOpdsEntry() }
|
||||
entries = collections.content.map { it.toOpdsEntry() }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,10 @@ import org.gotson.komga.domain.model.SeriesCollection
|
|||
import org.gotson.komga.domain.persistence.SeriesCollectionRepository
|
||||
import org.gotson.komga.domain.service.SeriesCollectionLifecycle
|
||||
import org.gotson.komga.infrastructure.image.MosaicGenerator
|
||||
import org.gotson.komga.infrastructure.jooq.UnpagedSorted
|
||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
||||
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||
import org.gotson.komga.interfaces.rest.dto.CollectionCreationDto
|
||||
import org.gotson.komga.interfaces.rest.dto.CollectionDto
|
||||
import org.gotson.komga.interfaces.rest.dto.CollectionUpdateDto
|
||||
|
|
@ -20,7 +22,10 @@ import org.gotson.komga.interfaces.rest.dto.SeriesDto
|
|||
import org.gotson.komga.interfaces.rest.dto.restrictUrl
|
||||
import org.gotson.komga.interfaces.rest.dto.toDto
|
||||
import org.gotson.komga.interfaces.rest.persistence.SeriesDtoRepository
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
import org.springframework.http.CacheControl
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
|
|
@ -53,17 +58,30 @@ class SeriesCollectionController(
|
|||
private val mosaicGenerator: MosaicGenerator
|
||||
) {
|
||||
|
||||
@PageableWithoutSortAsQueryParam
|
||||
@GetMapping
|
||||
fun getAll(
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam(name = "library_id", required = false) libraryIds: List<Long>?
|
||||
): List<CollectionDto> =
|
||||
when {
|
||||
principal.user.sharedAllLibraries && libraryIds == null -> collectionRepository.findAll()
|
||||
principal.user.sharedAllLibraries && libraryIds != null -> collectionRepository.findAllByLibraries(libraryIds, null)
|
||||
!principal.user.sharedAllLibraries && libraryIds != null -> collectionRepository.findAllByLibraries(libraryIds, principal.user.sharedLibrariesIds)
|
||||
else -> collectionRepository.findAllByLibraries(principal.user.sharedLibrariesIds, principal.user.sharedLibrariesIds)
|
||||
}.sortedBy { it.name.toLowerCase() }.map { it.toDto() }
|
||||
@RequestParam(name = "search", required = false) searchTerm: String?,
|
||||
@RequestParam(name = "library_id", required = false) libraryIds: List<Long>?,
|
||||
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||
@Parameter(hidden = true) page: Pageable
|
||||
): Page<CollectionDto> {
|
||||
val pageRequest =
|
||||
if (unpaged) UnpagedSorted(Sort.by(Sort.Order.asc("name")))
|
||||
else PageRequest.of(
|
||||
page.pageNumber,
|
||||
page.pageSize,
|
||||
Sort.by(Sort.Order.asc("name"))
|
||||
)
|
||||
|
||||
return when {
|
||||
principal.user.sharedAllLibraries && libraryIds == null -> collectionRepository.findAll(searchTerm, pageable = pageRequest)
|
||||
principal.user.sharedAllLibraries && libraryIds != null -> collectionRepository.findAllByLibraries(libraryIds, null, searchTerm, pageable = pageRequest)
|
||||
!principal.user.sharedAllLibraries && libraryIds != null -> collectionRepository.findAllByLibraries(libraryIds, principal.user.sharedLibrariesIds, searchTerm, pageable = pageRequest)
|
||||
else -> collectionRepository.findAllByLibraries(principal.user.sharedLibrariesIds, principal.user.sharedLibrariesIds, searchTerm, pageable = pageRequest)
|
||||
}.map { it.toDto() }
|
||||
}
|
||||
|
||||
@GetMapping("{id}")
|
||||
fun getOne(
|
||||
|
|
|
|||
|
|
@ -12,12 +12,6 @@ import org.gotson.komga.Application
|
|||
@AnalyzeClasses(packagesOf = [Application::class], importOptions = [ImportOption.DoNotIncludeTests::class])
|
||||
class DomainDrivenDesignRulesTest {
|
||||
|
||||
@ArchTest
|
||||
val domain_persistence_can_only_contain_interfaces: ArchRule =
|
||||
classes()
|
||||
.that().resideInAPackage("..domain..persistence..")
|
||||
.should().beInterfaces()
|
||||
|
||||
@ArchTest
|
||||
val domain_model_should_not_access_other_packages: ArchRule =
|
||||
noClasses()
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@ import org.springframework.web.bind.annotation.RestController
|
|||
|
||||
class NamingConventionTest {
|
||||
|
||||
@ArchTest
|
||||
val domain_persistence_should_have_names_ending_with_repository: ArchRule =
|
||||
classes()
|
||||
.that().resideInAPackage("..domain..persistence..")
|
||||
.should().haveNameMatching(".*Repository")
|
||||
|
||||
@ArchTest
|
||||
val services_should_not_have_names_containing_service_or_manager: ArchRule =
|
||||
noClasses()
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import org.junit.jupiter.api.extension.ExtendWith
|
|||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
|
@ -164,11 +165,11 @@ class SeriesCollectionDaoTest(
|
|||
))
|
||||
|
||||
// when
|
||||
val foundLibrary1Filtered = collectionDao.findAllByLibraries(listOf(library.id), listOf(library.id))
|
||||
val foundLibrary1Unfiltered = collectionDao.findAllByLibraries(listOf(library.id), null)
|
||||
val foundLibrary2Filtered = collectionDao.findAllByLibraries(listOf(library2.id), listOf(library2.id))
|
||||
val foundLibrary2Unfiltered = collectionDao.findAllByLibraries(listOf(library2.id), null)
|
||||
val foundBothUnfiltered = collectionDao.findAllByLibraries(listOf(library.id, library2.id), null)
|
||||
val foundLibrary1Filtered = collectionDao.findAllByLibraries(listOf(library.id), listOf(library.id), pageable = Pageable.unpaged()).content
|
||||
val foundLibrary1Unfiltered = collectionDao.findAllByLibraries(listOf(library.id), null, pageable = Pageable.unpaged()).content
|
||||
val foundLibrary2Filtered = collectionDao.findAllByLibraries(listOf(library2.id), listOf(library2.id), pageable = Pageable.unpaged()).content
|
||||
val foundLibrary2Unfiltered = collectionDao.findAllByLibraries(listOf(library2.id), null, pageable = Pageable.unpaged()).content
|
||||
val foundBothUnfiltered = collectionDao.findAllByLibraries(listOf(library.id, library2.id), null, pageable = Pageable.unpaged()).content
|
||||
|
||||
// then
|
||||
assertThat(foundLibrary1Filtered).hasSize(2)
|
||||
|
|
|
|||
|
|
@ -114,10 +114,10 @@ class SeriesCollectionControllerTest(
|
|||
mockMvc.get("/api/v1/collections")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.length()") { value(3) }
|
||||
jsonPath("$[?(@.name == 'Lib1')].filtered") { value(false) }
|
||||
jsonPath("$[?(@.name == 'Lib2')].filtered") { value(false) }
|
||||
jsonPath("$[?(@.name == 'Lib1+2')].filtered") { value(false) }
|
||||
jsonPath("$.totalElements") { value(3) }
|
||||
jsonPath("$.content[?(@.name == 'Lib1')].filtered") { value(false) }
|
||||
jsonPath("$.content[?(@.name == 'Lib2')].filtered") { value(false) }
|
||||
jsonPath("$.content[?(@.name == 'Lib1+2')].filtered") { value(false) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,9 +129,9 @@ class SeriesCollectionControllerTest(
|
|||
mockMvc.get("/api/v1/collections")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.length()") { value(2) }
|
||||
jsonPath("$[?(@.name == 'Lib1')].filtered") { value(false) }
|
||||
jsonPath("$[?(@.name == 'Lib1+2')].filtered") { value(true) }
|
||||
jsonPath("$.totalElements") { value(2) }
|
||||
jsonPath("$.content[?(@.name == 'Lib1')].filtered") { value(false) }
|
||||
jsonPath("$.content[?(@.name == 'Lib1+2')].filtered") { value(true) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue