diff --git a/komga-webui/src/components/CollectionAddToDialog.vue b/komga-webui/src/components/CollectionAddToDialog.vue index a895d7bc9..8964030e6 100644 --- a/komga-webui/src/components/CollectionAddToDialog.vue +++ b/komga-webui/src/components/CollectionAddToDialog.vue @@ -106,7 +106,7 @@ export default Vue.extend({ this.modal = val if (val) { this.newCollection = '' - this.collections = (await this.$komgaCollections.getCollections(undefined, undefined, true)).content + this.collections = (await this.$komgaCollections.getCollections(undefined, { unpaged: true } as PageRequest)).content } }, modal (val) { diff --git a/komga-webui/src/components/CollectionEditDialog.vue b/komga-webui/src/components/CollectionEditDialog.vue index 91c964490..4e771ce0a 100644 --- a/komga-webui/src/components/CollectionEditDialog.vue +++ b/komga-webui/src/components/CollectionEditDialog.vue @@ -117,7 +117,7 @@ export default Vue.extend({ async dialogReset (collection: CollectionDto) { this.form.name = collection.name this.form.ordered = collection.ordered - this.collections = (await this.$komgaCollections.getCollections(undefined, undefined, true)).content + this.collections = (await this.$komgaCollections.getCollections(undefined, { unpaged: true } as PageRequest)).content }, dialogCancel () { this.$emit('input', false) diff --git a/komga-webui/src/components/SearchBox.vue b/komga-webui/src/components/SearchBox.vue index ec7e6832a..52c9489c7 100644 --- a/komga-webui/src/components/SearchBox.vue +++ b/komga-webui/src/components/SearchBox.vue @@ -113,7 +113,7 @@ export default Vue.extend({ this.loading = true this.series = (await this.$komgaSeries.getSeries(undefined, { size: this.pageSize }, query)).content this.books = (await this.$komgaBooks.getBooks(undefined, { size: this.pageSize }, query)).content - this.collections = (await this.$komgaCollections.getCollections(undefined, { size: this.pageSize }, false, query)).content + this.collections = (await this.$komgaCollections.getCollections(undefined, { size: this.pageSize }, query)).content this.showResults = true this.loading = false } else { diff --git a/komga-webui/src/services/komga-collections.service.ts b/komga-webui/src/services/komga-collections.service.ts index 2f79839e0..b1195c807 100644 --- a/komga-webui/src/services/komga-collections.service.ts +++ b/komga-webui/src/services/komga-collections.service.ts @@ -11,11 +11,10 @@ export default class KomgaCollectionsService { this.http = http } - async getCollections (libraryIds?: number[], pageRequest?: PageRequest, unpaged?: boolean, search?: string): Promise> { + async getCollections (libraryIds?: number[], pageRequest?: PageRequest, search?: string): Promise> { try { const params = { ...pageRequest } as any if (libraryIds) params.library_id = libraryIds - if (unpaged) params.unpaged = unpaged if (search) params.search = search return (await this.http.get(API_COLLECTIONS, { @@ -79,7 +78,7 @@ export default class KomgaCollectionsService { } } - async getSeries (collectionId: number, pageRequest?: PageRequest): Promise { + async getSeries (collectionId: number, pageRequest?: PageRequest): Promise> { try { return (await this.http.get(`${API_COLLECTIONS}/${collectionId}/series`)).data } catch (e) { diff --git a/komga-webui/src/types/pageable.ts b/komga-webui/src/types/pageable.ts index 76c18f30e..18e1f6e6c 100644 --- a/komga-webui/src/types/pageable.ts +++ b/komga-webui/src/types/pageable.ts @@ -30,7 +30,8 @@ interface Sort { interface PageRequest { size?: number, page?: number, - sort?: string[] + sort?: string[], + unpaged?: boolean } interface SortOption { diff --git a/komga-webui/src/views/BrowseCollection.vue b/komga-webui/src/views/BrowseCollection.vue index e3991b19a..de2a7303e 100644 --- a/komga-webui/src/views/BrowseCollection.vue +++ b/komga-webui/src/views/BrowseCollection.vue @@ -233,7 +233,7 @@ export default Vue.extend({ methods: { async loadCollection (collectionId: number) { this.collection = await this.$komgaCollections.getOneCollection(collectionId) - this.series = await this.$komgaCollections.getSeries(collectionId) + this.series = (await this.$komgaCollections.getSeries(collectionId, { unpaged: true } as PageRequest)).content this.seriesCopy = [...this.series] }, singleEdit (series: SeriesDto) { diff --git a/komga-webui/src/views/BrowseSeries.vue b/komga-webui/src/views/BrowseSeries.vue index ccd73fb76..dca6b767f 100644 --- a/komga-webui/src/views/BrowseSeries.vue +++ b/komga-webui/src/views/BrowseSeries.vue @@ -412,7 +412,7 @@ export default Vue.extend({ this.series = await this.$komgaSeries.getOneSeries(seriesId) this.collections = await this.$komgaSeries.getCollections(seriesId) for (const c of this.collections) { - this.collectionsContent[c.id] = await this.$komgaCollections.getSeries(c.id) + this.collectionsContent[c.id] = (await this.$komgaCollections.getSeries(c.id, { unpaged: true } as PageRequest)).content } await this.loadPage(seriesId, this.page, this.sortActive) }, diff --git a/komga-webui/src/views/Search.vue b/komga-webui/src/views/Search.vue index b44fe353b..0a30df9b2 100644 --- a/komga-webui/src/views/Search.vue +++ b/komga-webui/src/views/Search.vue @@ -121,7 +121,7 @@ export default Vue.extend({ this.series = (await this.$komgaSeries.getSeries(undefined, { size: this.pageSize }, search)).content this.books = (await this.$komgaBooks.getBooks(undefined, { size: this.pageSize }, search)).content - this.collections = (await this.$komgaCollections.getCollections(undefined, { size: this.pageSize }, undefined, search)).content + this.collections = (await this.$komgaCollections.getCollections(undefined, { size: this.pageSize }, search)).content this.loading = false } else { diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt index ac1ea5b2b..eb611156d 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt @@ -13,6 +13,7 @@ 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.LocalDateTime @@ -60,16 +61,17 @@ class SeriesCollectionDao( .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } .fetchAndMap(null) + val pageSort = if (orderBy.size > 1) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageable.sort) - else PageRequest.of(0, count.toInt(), pageable.sort), + if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else PageRequest.of(0, count.toInt(), pageSort), count.toLong() ) } override fun findAllByLibraries(belongsToLibraryIds: Collection, filterOnLibraryIds: Collection?, search: String?, pageable: Pageable): Page { - val ids = dsl.select(c.ID) + val ids = dsl.selectDistinct(c.ID) .from(c) .leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) .leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) @@ -77,10 +79,7 @@ class SeriesCollectionDao( .apply { search?.let { and(c.NAME.containsIgnoreCase(it)) } } .fetch(0, Long::class.java) - val count = dsl.selectCount() - .from(c) - .where(c.ID.`in`(ids)) - .fetchOne(0, Long::class.java) + val count = ids.size val orderBy = pageable.sort.toOrderBy(sorts) @@ -92,10 +91,11 @@ class SeriesCollectionDao( .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } .fetchAndMap(filterOnLibraryIds) + val pageSort = if (orderBy.size > 1) pageable.sort else Sort.unsorted() return PageImpl( items, - if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageable.sort) - else PageRequest.of(0, count.toInt()), + if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else PageRequest.of(0, count.toInt(), pageSort), count.toLong() ) } diff --git a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt index b185369b4..5d8a3a819 100644 --- a/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt +++ b/komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesDtoDao.kt @@ -20,6 +20,7 @@ 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.math.BigDecimal import java.net.URL @@ -56,7 +57,8 @@ class SeriesDtoDao( "createdDate" to s.CREATED_DATE, "created" to s.CREATED_DATE, "lastModifiedDate" to s.LAST_MODIFIED_DATE, - "lastModified" to s.LAST_MODIFIED_DATE + "lastModified" to s.LAST_MODIFIED_DATE, + "collection.number" to cs.NUMBER ) override fun findAll(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page { @@ -67,6 +69,13 @@ class SeriesDtoDao( return findAll(conditions, having, userId, pageable) } + override fun findByCollectionId(collectionId: Long, userId: Long, pageable: Pageable): Page { + val conditions = cs.COLLECTION_ID.eq(collectionId) + val having = DSL.trueCondition() + + return findAll(conditions, having, userId, pageable, true) + } + override fun findRecentlyUpdated(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page { val conditions = search.toCondition() .and(s.CREATED_DATE.ne(s.LAST_MODIFIED_DATE)) @@ -83,19 +92,25 @@ class SeriesDtoDao( .fetchAndMap(userId) .firstOrNull() - override fun findByIds(seriesIds: Collection, userId: Long): List = - selectBase(userId) - .where(s.ID.`in`(seriesIds)) - .groupBy(*groupFields) - .fetchAndMap(userId) - - private fun findAll(conditions: Condition, having: Condition, userId: Long, pageable: Pageable): Page { - val count = dsl.select(s.ID) + private fun selectBase(userId: Long, selectCollectionNumber: Boolean = false): SelectOnConditionStep = + dsl.selectDistinct(*groupFields) + .select(DSL.countDistinct(b.ID).`as`(BOOKS_COUNT)) + .apply { if (selectCollectionNumber) select(cs.NUMBER) } .from(s) .leftJoin(b).on(s.ID.eq(b.SERIES_ID)) .leftJoin(d).on(s.ID.eq(d.SERIES_ID)) .leftJoin(r).on(b.ID.eq(r.BOOK_ID)) + .and(readProgressCondition(userId)) + .leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) + + private fun findAll(conditions: Condition, having: Condition, userId: Long, pageable: Pageable, selectCollectionNumber: Boolean = false): Page { + val count = dsl.selectDistinct(s.ID) + .from(s) + .leftJoin(b).on(s.ID.eq(b.SERIES_ID)) + .leftJoin(d).on(s.ID.eq(d.SERIES_ID)) + .leftJoin(r).on(b.ID.eq(r.BOOK_ID)) + .and(readProgressCondition(userId)) .leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) .where(conditions) .groupBy(s.ID) @@ -105,32 +120,23 @@ class SeriesDtoDao( val orderBy = pageable.sort.toOrderBy(sorts) - val dtos = selectBase(userId) + val dtos = selectBase(userId, selectCollectionNumber) .where(conditions) .groupBy(*groupFields) .having(having) .orderBy(orderBy) - .limit(pageable.pageSize) - .offset(pageable.offset) + .apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) } .fetchAndMap(userId) + val pageSort = if (orderBy.size > 1) pageable.sort else Sort.unsorted() return PageImpl( dtos, - PageRequest.of(pageable.pageNumber, pageable.pageSize, pageable.sort), + if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort) + else PageRequest.of(0, count.toInt(), pageSort), count.toLong() ) } - private fun selectBase(userId: Long): SelectOnConditionStep = - dsl.selectDistinct(*groupFields) - .select(DSL.countDistinct(b.ID).`as`(BOOKS_COUNT)) - .from(s) - .leftJoin(b).on(s.ID.eq(b.SERIES_ID)) - .leftJoin(d).on(s.ID.eq(d.SERIES_ID)) - .leftJoin(r).on(b.ID.eq(r.BOOK_ID)) - .leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) - .and(readProgressCondition(userId)) - private fun readProgressCondition(userId: Long): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull) private fun ResultQuery.fetchAndMap(userId: Long) = diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt index 50ec09c04..c067094df 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesCollectionController.kt @@ -13,7 +13,6 @@ 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 @@ -162,21 +161,29 @@ class SeriesCollectionController( } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } - @PageableAsQueryParam + @PageableWithoutSortAsQueryParam @GetMapping("{id}/series") fun getSeriesForCollection( @PathVariable id: Long, @AuthenticationPrincipal principal: KomgaPrincipal, + @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable - ): List = + ): Page = collectionRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { collection -> - if (collection.ordered) { - // use map to ensure the order is conserved - collection.seriesIds.mapNotNull { seriesDtoRepository.findByIdOrNull(it, principal.user.id) } - } else { - seriesDtoRepository.findByIds(collection.seriesIds, principal.user.id) - .sortedBy { it.metadata.titleSort } - }.map { it.restrictUrl(!principal.user.roleAdmin) } + val sort = + if (collection.ordered) Sort.by(Sort.Order.asc("collection.number")) + else Sort.by(Sort.Order.asc("metadata.titleSort")) + + val pageRequest = + if (unpaged) UnpagedSorted(sort) + else PageRequest.of( + page.pageNumber, + page.pageSize, + sort + ) + + seriesDtoRepository.findByCollectionId(collection.id, principal.user.id, pageRequest) + .map { it.restrictUrl(!principal.user.roleAdmin) } } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND) } diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt index cd3dc1418..576a4d1cd 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/SeriesController.kt @@ -18,6 +18,7 @@ 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.domain.service.BookLifecycle +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 @@ -76,14 +77,20 @@ class SeriesController( @RequestParam(name = "collection_id", required = false) collectionIds: List?, @RequestParam(name = "status", required = false) metadataStatus: List?, @RequestParam(name = "read_status", required = false) readStatus: List?, + @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable ): Page { - val pageRequest = PageRequest.of( - page.pageNumber, - page.pageSize, - if (page.sort.isSorted) Sort.by(page.sort.map { it.ignoreCase() }.toList()) - else Sort.by(Sort.Order.asc("metadata.titleSort").ignoreCase()) - ) + val sort = + if (page.sort.isSorted) page.sort + else Sort.by(Sort.Order.asc("metadata.titleSort")) + + val pageRequest = + if (unpaged) UnpagedSorted(sort) + else PageRequest.of( + page.pageNumber, + page.pageSize, + sort + ) val seriesSearch = SeriesSearchWithReadProgress( libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds), @@ -102,13 +109,18 @@ class SeriesController( @GetMapping("/latest") fun getLatestSeries( @AuthenticationPrincipal principal: KomgaPrincipal, + @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable ): Page { - val pageRequest = PageRequest.of( - page.pageNumber, - page.pageSize, - Sort.by(Sort.Direction.DESC, "lastModifiedDate") - ) + val sort = Sort.by(Sort.Order.desc("lastModified")) + + val pageRequest = + if (unpaged) UnpagedSorted(sort) + else PageRequest.of( + page.pageNumber, + page.pageSize, + sort + ) return seriesDtoRepository.findAll( SeriesSearchWithReadProgress(principal.user.getAuthorizedLibraryIds(null)), @@ -122,13 +134,18 @@ class SeriesController( @GetMapping("/new") fun getNewSeries( @AuthenticationPrincipal principal: KomgaPrincipal, + @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable ): Page { - val pageRequest = PageRequest.of( - page.pageNumber, - page.pageSize, - Sort.by(Sort.Direction.DESC, "createdDate") - ) + val sort = Sort.by(Sort.Order.desc("created")) + + val pageRequest = + if (unpaged) UnpagedSorted(sort) + else PageRequest.of( + page.pageNumber, + page.pageSize, + sort + ) return seriesDtoRepository.findAll( SeriesSearchWithReadProgress(principal.user.getAuthorizedLibraryIds(null)), @@ -142,13 +159,18 @@ class SeriesController( @GetMapping("/updated") fun getUpdatedSeries( @AuthenticationPrincipal principal: KomgaPrincipal, + @RequestParam(name = "unpaged", required = false) unpaged: Boolean = false, @Parameter(hidden = true) page: Pageable ): Page { - val pageRequest = PageRequest.of( - page.pageNumber, - page.pageSize, - Sort.by(Sort.Direction.DESC, "lastModifiedDate") - ) + val sort = Sort.by(Sort.Order.desc("lastModified")) + + val pageRequest = + if (unpaged) UnpagedSorted(sort) + else PageRequest.of( + page.pageNumber, + page.pageSize, + sort + ) return seriesDtoRepository.findRecentlyUpdated( SeriesSearchWithReadProgress(principal.user.getAuthorizedLibraryIds(null)), diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt index 153cece31..381e7d640 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/rest/persistence/SeriesDtoRepository.kt @@ -7,7 +7,7 @@ import org.springframework.data.domain.Pageable interface SeriesDtoRepository { fun findAll(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page + fun findByCollectionId(collectionId: Long, userId: Long, pageable: Pageable): Page fun findRecentlyUpdated(search: SeriesSearchWithReadProgress, userId: Long, pageable: Pageable): Page fun findByIdOrNull(seriesId: Long, userId: Long): SeriesDto? - fun findByIds(seriesIds: Collection, userId: Long): List }