mirror of
https://github.com/gotson/komga.git
synced 2025-12-21 07:56:57 +01:00
perf: precompute series book counts
This commit is contained in:
parent
ae671709fe
commit
c3b352aca0
13 changed files with 183 additions and 79 deletions
|
|
@ -0,0 +1,29 @@
|
|||
alter table SERIES
|
||||
add column BOOK_COUNT int NOT NULL DEFAULT 0;
|
||||
|
||||
update SERIES
|
||||
set BOOK_COUNT = (
|
||||
SELECT COUNT(b.ID)
|
||||
FROM BOOK b
|
||||
WHERE b.SERIES_ID = SERIES.ID
|
||||
);
|
||||
|
||||
CREATE TABLE READ_PROGRESS_SERIES
|
||||
(
|
||||
SERIES_ID varchar NOT NULL,
|
||||
USER_ID varchar NOT NULL,
|
||||
READ_COUNT int NOT NULL,
|
||||
IN_PROGRESS_COUNT int NOT NULL,
|
||||
PRIMARY KEY (SERIES_ID, USER_ID),
|
||||
FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID),
|
||||
FOREIGN KEY (USER_ID) REFERENCES USER (ID)
|
||||
);
|
||||
|
||||
insert into READ_PROGRESS_SERIES
|
||||
select BOOK.SERIES_ID,
|
||||
READ_PROGRESS.USER_ID,
|
||||
sum(case when READ_PROGRESS.COMPLETED = 1 then 1 else 0 end) as READ_COUNT,
|
||||
sum(case when READ_PROGRESS.COMPLETED = 0 then 1 else 0 end) as IN_PROGRESS_COUNT
|
||||
from BOOK
|
||||
inner join READ_PROGRESS on (BOOK.ID = READ_PROGRESS.BOOK_ID)
|
||||
group by BOOK.SERIES_ID, READ_PROGRESS.USER_ID;
|
||||
|
|
@ -13,6 +13,7 @@ data class Series(
|
|||
|
||||
val id: String = TsidCreator.getTsid256().toString(),
|
||||
val libraryId: String = "",
|
||||
val bookCount: Int = 0,
|
||||
|
||||
override val createdDate: LocalDateTime = LocalDateTime.now(),
|
||||
override val lastModifiedDate: LocalDateTime = LocalDateTime.now()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import org.gotson.komga.domain.model.Media
|
|||
|
||||
interface MediaRepository {
|
||||
fun findById(bookId: String): Media
|
||||
fun getPagesSize(bookId: String): Int
|
||||
fun getPagesSizes(bookIds: Collection<String>): Collection<Pair<String, Int>>
|
||||
|
||||
fun insert(media: Media)
|
||||
fun insertMany(medias: Collection<Media>)
|
||||
|
|
|
|||
|
|
@ -9,10 +9,12 @@ interface ReadProgressRepository {
|
|||
fun findByBookId(bookId: String): Collection<ReadProgress>
|
||||
|
||||
fun save(readProgress: ReadProgress)
|
||||
fun saveAll(readProgresses: Collection<ReadProgress>)
|
||||
|
||||
fun delete(bookId: String, userId: String)
|
||||
fun deleteByUserId(userId: String)
|
||||
fun deleteByBookId(bookId: String)
|
||||
fun deleteByBookIds(bookIds: Collection<String>)
|
||||
fun deleteByBookIdsAndUserId(bookIds: Collection<String>, userId: String)
|
||||
fun deleteAll()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,10 +214,10 @@ class BookLifecycle(
|
|||
}
|
||||
|
||||
fun markReadProgress(book: Book, user: KomgaUser, page: Int) {
|
||||
val media = mediaRepository.findById(book.id)
|
||||
require(page >= 1 && page <= media.pages.size) { "Page argument ($page) must be within 1 and book page count (${media.pages.size})" }
|
||||
val pages = mediaRepository.getPagesSize(book.id)
|
||||
require(page in 1..pages) { "Page argument ($page) must be within 1 and book page count ($pages)" }
|
||||
|
||||
readProgressRepository.save(ReadProgress(book.id, user.id, page, page == media.pages.size))
|
||||
readProgressRepository.save(ReadProgress(book.id, user.id, page, page == pages))
|
||||
}
|
||||
|
||||
fun markReadProgressCompleted(bookId: String, user: KomgaUser) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import org.gotson.komga.domain.model.Book
|
|||
import org.gotson.komga.domain.model.BookMetadata
|
||||
import org.gotson.komga.domain.model.BookMetadataAggregation
|
||||
import org.gotson.komga.domain.model.BookMetadataPatchCapability
|
||||
import org.gotson.komga.domain.model.KomgaUser
|
||||
import org.gotson.komga.domain.model.Media
|
||||
import org.gotson.komga.domain.model.ReadProgress
|
||||
import org.gotson.komga.domain.model.Series
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.domain.model.ThumbnailSeries
|
||||
|
|
@ -16,6 +18,7 @@ import org.gotson.komga.domain.persistence.BookMetadataAggregationRepository
|
|||
import org.gotson.komga.domain.persistence.BookMetadataRepository
|
||||
import org.gotson.komga.domain.persistence.BookRepository
|
||||
import org.gotson.komga.domain.persistence.MediaRepository
|
||||
import org.gotson.komga.domain.persistence.ReadProgressRepository
|
||||
import org.gotson.komga.domain.persistence.SeriesCollectionRepository
|
||||
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
||||
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||
|
|
@ -24,7 +27,6 @@ import org.springframework.stereotype.Service
|
|||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.Comparator
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val natSortComparator: Comparator<String> = CaseInsensitiveSimpleNaturalComparator.getInstance()
|
||||
|
|
@ -40,6 +42,7 @@ class SeriesLifecycle(
|
|||
private val seriesMetadataRepository: SeriesMetadataRepository,
|
||||
private val bookMetadataAggregationRepository: BookMetadataAggregationRepository,
|
||||
private val collectionRepository: SeriesCollectionRepository,
|
||||
private val readProgressRepository: ReadProgressRepository,
|
||||
private val taskReceiver: TaskReceiver
|
||||
) {
|
||||
|
||||
|
|
@ -69,13 +72,16 @@ class SeriesLifecycle(
|
|||
}
|
||||
bookMetadataRepository.updateMany(oldToNew.map { it.second })
|
||||
|
||||
// refresh metadata to reimport book number, else the series resorting would overwritei t
|
||||
// refresh metadata to reimport book number, else the series resorting would overwrite it
|
||||
oldToNew.forEach { (old, new) ->
|
||||
if (old.number != new.number || old.numberSort != new.numberSort) {
|
||||
logger.debug { "Metadata numbering has changed, refreshing metadata for book ${new.bookId} " }
|
||||
taskReceiver.refreshBookMetadata(new.bookId, listOf(BookMetadataPatchCapability.NUMBER, BookMetadataPatchCapability.NUMBER_SORT))
|
||||
}
|
||||
}
|
||||
|
||||
// update book count for series
|
||||
seriesRepository.update(series.copy(bookCount = books.size))
|
||||
}
|
||||
|
||||
fun addBooks(series: Series, booksToAdd: Collection<Book>) {
|
||||
|
|
@ -147,6 +153,17 @@ class SeriesLifecycle(
|
|||
seriesRepository.deleteAll(seriesIds)
|
||||
}
|
||||
|
||||
fun markReadProgressCompleted(seriesId: String, user: KomgaUser) {
|
||||
val progresses = mediaRepository.getPagesSizes(bookRepository.findAllIdBySeriesId(seriesId))
|
||||
.map { (bookId, pageSize) -> ReadProgress(bookId, user.id, pageSize, true) }
|
||||
|
||||
readProgressRepository.saveAll(progresses)
|
||||
}
|
||||
|
||||
fun deleteReadProgress(seriesId: String, user: KomgaUser) {
|
||||
readProgressRepository.deleteByBookIdsAndUserId(bookRepository.findAllIdBySeriesId(seriesId), user.id)
|
||||
}
|
||||
|
||||
fun getThumbnail(seriesId: String): ThumbnailSeries? {
|
||||
val selected = thumbnailsSeriesRepository.findSelectedBySeriesId(seriesId)
|
||||
|
||||
|
|
@ -205,6 +222,5 @@ class SeriesLifecycle(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ThumbnailSeries.exists(): Boolean = Files.exists(Paths.get(url.toURI()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,20 @@ class MediaDao(
|
|||
override fun findById(bookId: String): Media =
|
||||
find(dsl, bookId)
|
||||
|
||||
override fun getPagesSize(bookId: String): Int =
|
||||
dsl.select(m.PAGE_COUNT)
|
||||
.from(m)
|
||||
.where(m.BOOK_ID.eq(bookId))
|
||||
.fetch(m.PAGE_COUNT)
|
||||
.first()
|
||||
|
||||
override fun getPagesSizes(bookIds: Collection<String>): Collection<Pair<String, Int>> =
|
||||
dsl.select(m.BOOK_ID, m.PAGE_COUNT)
|
||||
.from(m)
|
||||
.where(m.BOOK_ID.`in`(bookIds))
|
||||
.fetch()
|
||||
.map { Pair(it[m.BOOK_ID], it[m.PAGE_COUNT]) }
|
||||
|
||||
private fun find(dsl: DSLContext, bookId: String): Media =
|
||||
dsl.select(*groupFields)
|
||||
.from(m)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import org.gotson.komga.domain.persistence.ReadProgressRepository
|
|||
import org.gotson.komga.jooq.Tables
|
||||
import org.gotson.komga.jooq.tables.records.ReadProgressRecord
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.Query
|
||||
import org.jooq.impl.DSL
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
|
@ -15,6 +17,8 @@ class ReadProgressDao(
|
|||
) : ReadProgressRepository {
|
||||
|
||||
private val r = Tables.READ_PROGRESS
|
||||
private val rs = Tables.READ_PROGRESS_SERIES
|
||||
private val b = Tables.BOOK
|
||||
|
||||
override fun findAll(): Collection<ReadProgress> =
|
||||
dsl.selectFrom(r)
|
||||
|
|
@ -40,41 +44,96 @@ class ReadProgressDao(
|
|||
.map { it.toDomain() }
|
||||
|
||||
override fun save(readProgress: ReadProgress) {
|
||||
dsl.insertInto(r, r.BOOK_ID, r.USER_ID, r.PAGE, r.COMPLETED)
|
||||
dsl.transaction { config ->
|
||||
config.dsl().saveQuery(readProgress).execute()
|
||||
config.dsl().aggregateSeriesProgress(listOf(readProgress.bookId), readProgress.userId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun saveAll(readProgresses: Collection<ReadProgress>) {
|
||||
dsl.transaction { config ->
|
||||
val queries = readProgresses.map { config.dsl().saveQuery(it) }
|
||||
config.dsl().batch(queries).execute()
|
||||
|
||||
readProgresses.groupBy { it.userId }
|
||||
.forEach { (userId, readProgresses) ->
|
||||
config.dsl().aggregateSeriesProgress(readProgresses.map { it.bookId }, userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DSLContext.saveQuery(readProgress: ReadProgress): Query =
|
||||
this.insertInto(r, r.BOOK_ID, r.USER_ID, r.PAGE, r.COMPLETED)
|
||||
.values(readProgress.bookId, readProgress.userId, readProgress.page, readProgress.completed)
|
||||
.onDuplicateKeyUpdate()
|
||||
.set(r.PAGE, readProgress.page)
|
||||
.set(r.COMPLETED, readProgress.completed)
|
||||
.set(r.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
|
||||
.execute()
|
||||
}
|
||||
|
||||
override fun delete(bookId: String, userId: String) {
|
||||
dsl.deleteFrom(r)
|
||||
.where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId)))
|
||||
.execute()
|
||||
dsl.transaction { config ->
|
||||
config.dsl().deleteFrom(r).where(r.BOOK_ID.eq(bookId).and(r.USER_ID.eq(userId))).execute()
|
||||
config.dsl().aggregateSeriesProgress(listOf(bookId), userId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteByUserId(userId: String) {
|
||||
dsl.deleteFrom(r)
|
||||
.where(r.USER_ID.eq(userId))
|
||||
.execute()
|
||||
dsl.transaction { config ->
|
||||
config.dsl().deleteFrom(r).where(r.USER_ID.eq(userId)).execute()
|
||||
config.dsl().deleteFrom(rs).where(rs.USER_ID.eq(userId)).execute()
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteByBookId(bookId: String) {
|
||||
dsl.deleteFrom(r)
|
||||
.where(r.BOOK_ID.eq(bookId))
|
||||
.execute()
|
||||
dsl.transaction { config ->
|
||||
config.dsl().deleteFrom(r).where(r.BOOK_ID.eq(bookId)).execute()
|
||||
config.dsl().aggregateSeriesProgress(listOf(bookId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteByBookIds(bookIds: Collection<String>) {
|
||||
dsl.deleteFrom(r)
|
||||
.where(r.BOOK_ID.`in`(bookIds))
|
||||
.execute()
|
||||
dsl.transaction { config ->
|
||||
config.dsl().deleteFrom(r).where(r.BOOK_ID.`in`(bookIds)).execute()
|
||||
config.dsl().aggregateSeriesProgress(bookIds)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteByBookIdsAndUserId(bookIds: Collection<String>, userId: String) {
|
||||
dsl.transaction { config ->
|
||||
config.dsl().deleteFrom(r).where(r.BOOK_ID.`in`(bookIds)).and(r.USER_ID.eq(userId)).execute()
|
||||
config.dsl().aggregateSeriesProgress(bookIds, userId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteAll() {
|
||||
dsl.deleteFrom(r).execute()
|
||||
dsl.transaction { config ->
|
||||
config.dsl().deleteFrom(r).execute()
|
||||
config.dsl().deleteFrom(rs).execute()
|
||||
}
|
||||
}
|
||||
|
||||
private fun DSLContext.aggregateSeriesProgress(bookIds: Collection<String>, userId: String? = null) {
|
||||
val seriesIds = this.select(b.SERIES_ID)
|
||||
.from(b)
|
||||
.where(b.ID.`in`(bookIds))
|
||||
.fetch(b.SERIES_ID)
|
||||
|
||||
this.deleteFrom(rs)
|
||||
.where(rs.SERIES_ID.`in`(seriesIds))
|
||||
.apply { userId?.let { and(rs.USER_ID.eq(it)) } }
|
||||
.execute()
|
||||
|
||||
this.insertInto(rs)
|
||||
.select(
|
||||
this.select(b.SERIES_ID, r.USER_ID)
|
||||
.select(DSL.sum(DSL.`when`(r.COMPLETED.isTrue, 1).otherwise(0)))
|
||||
.select(DSL.sum(DSL.`when`(r.COMPLETED.isFalse, 1).otherwise(0)))
|
||||
.from(b)
|
||||
.innerJoin(r).on(b.ID.eq(r.BOOK_ID))
|
||||
.where(b.SERIES_ID.`in`(seriesIds))
|
||||
.apply { userId?.let { and(r.USER_ID.eq(it)) } }
|
||||
.groupBy(b.SERIES_ID, r.USER_ID)
|
||||
).execute()
|
||||
}
|
||||
|
||||
private fun ReadProgressRecord.toDomain() =
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ class SeriesDao(
|
|||
.set(s.URL, series.url.toString())
|
||||
.set(s.FILE_LAST_MODIFIED, series.fileLastModified)
|
||||
.set(s.LIBRARY_ID, series.libraryId)
|
||||
.set(s.BOOK_COUNT, series.bookCount)
|
||||
.set(s.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
|
||||
.where(s.ID.eq(series.id))
|
||||
.execute()
|
||||
|
|
@ -143,6 +144,7 @@ class SeriesDao(
|
|||
fileLastModified = fileLastModified,
|
||||
id = id,
|
||||
libraryId = libraryId,
|
||||
bookCount = bookCount,
|
||||
createdDate = createdDate.toCurrentTimeZone(),
|
||||
lastModifiedDate = lastModifiedDate.toCurrentTimeZone()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ import org.jooq.Record
|
|||
import org.jooq.ResultQuery
|
||||
import org.jooq.SelectOnConditionStep
|
||||
import org.jooq.impl.DSL
|
||||
import org.jooq.impl.DSL.field
|
||||
import org.jooq.impl.DSL.inline
|
||||
import org.jooq.impl.DSL.lower
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.PageImpl
|
||||
|
|
@ -43,9 +41,9 @@ class SeriesDtoDao(
|
|||
|
||||
companion object {
|
||||
private val s = Tables.SERIES
|
||||
private val b = Tables.BOOK
|
||||
private val d = Tables.SERIES_METADATA
|
||||
private val r = Tables.READ_PROGRESS
|
||||
private val rs = Tables.READ_PROGRESS_SERIES
|
||||
private val cs = Tables.COLLECTION_SERIES
|
||||
private val g = Tables.SERIES_METADATA_GENRE
|
||||
private val st = Tables.SERIES_METADATA_TAG
|
||||
|
|
@ -61,24 +59,24 @@ class SeriesDtoDao(
|
|||
*s.fields(),
|
||||
*d.fields(),
|
||||
*bma.fields(),
|
||||
*rs.fields(),
|
||||
)
|
||||
|
||||
private val sorts = mapOf(
|
||||
"metadata.titleSort" to DSL.lower(d.TITLE_SORT),
|
||||
"metadata.titleSort" to lower(d.TITLE_SORT),
|
||||
"createdDate" to s.CREATED_DATE,
|
||||
"created" to s.CREATED_DATE,
|
||||
"lastModifiedDate" to s.LAST_MODIFIED_DATE,
|
||||
"lastModified" to s.LAST_MODIFIED_DATE,
|
||||
"collection.number" to cs.NUMBER,
|
||||
"name" to s.NAME,
|
||||
"booksCount" to field(BOOKS_COUNT)
|
||||
"booksCount" to s.BOOK_COUNT,
|
||||
)
|
||||
|
||||
override fun findAll(search: SeriesSearchWithReadProgress, userId: String, pageable: Pageable): Page<SeriesDto> {
|
||||
val conditions = search.toCondition()
|
||||
val having = search.readStatus?.toCondition() ?: DSL.trueCondition()
|
||||
|
||||
return findAll(conditions, having, userId, pageable, search.toJoinConditions())
|
||||
return findAll(conditions, userId, pageable, search.toJoinConditions())
|
||||
}
|
||||
|
||||
override fun findByCollectionId(
|
||||
|
|
@ -88,10 +86,9 @@ class SeriesDtoDao(
|
|||
pageable: Pageable
|
||||
): Page<SeriesDto> {
|
||||
val conditions = search.toCondition().and(cs.COLLECTION_ID.eq(collectionId))
|
||||
val having = search.readStatus?.toCondition() ?: DSL.trueCondition()
|
||||
val joinConditions = search.toJoinConditions().copy(selectCollectionNumber = true, collection = true)
|
||||
|
||||
return findAll(conditions, having, userId, pageable, joinConditions)
|
||||
return findAll(conditions, userId, pageable, joinConditions)
|
||||
}
|
||||
|
||||
override fun findRecentlyUpdated(
|
||||
|
|
@ -102,9 +99,7 @@ class SeriesDtoDao(
|
|||
val conditions = search.toCondition()
|
||||
.and(s.CREATED_DATE.ne(s.LAST_MODIFIED_DATE))
|
||||
|
||||
val having = search.readStatus?.toCondition() ?: DSL.trueCondition()
|
||||
|
||||
return findAll(conditions, having, userId, pageable, search.toJoinConditions())
|
||||
return findAll(conditions, userId, pageable, search.toJoinConditions())
|
||||
}
|
||||
|
||||
override fun findByIdOrNull(seriesId: String, userId: String): SeriesDto? =
|
||||
|
|
@ -119,16 +114,11 @@ class SeriesDtoDao(
|
|||
joinConditions: JoinConditions = JoinConditions()
|
||||
): SelectOnConditionStep<Record> =
|
||||
dsl.selectDistinct(*groupFields)
|
||||
.select(DSL.countDistinct(b.ID).`as`(BOOKS_COUNT))
|
||||
.select(countUnread.`as`(BOOKS_UNREAD_COUNT))
|
||||
.select(countRead.`as`(BOOKS_READ_COUNT))
|
||||
.select(countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT))
|
||||
.apply { if (joinConditions.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(bma).on(s.ID.eq(bma.SERIES_ID))
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
||||
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
||||
.apply { if (joinConditions.tag) leftJoin(st).on(s.ID.eq(st.SERIES_ID)) }
|
||||
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
||||
|
|
@ -136,24 +126,20 @@ class SeriesDtoDao(
|
|||
|
||||
private fun findAll(
|
||||
conditions: Condition,
|
||||
having: Condition,
|
||||
userId: String,
|
||||
pageable: Pageable,
|
||||
joinConditions: JoinConditions = JoinConditions()
|
||||
): Page<SeriesDto> {
|
||||
val count = dsl.selectDistinct(s.ID)
|
||||
val count = dsl.select(s.ID)
|
||||
.from(s)
|
||||
.leftJoin(b).on(s.ID.eq(b.SERIES_ID))
|
||||
.leftJoin(d).on(s.ID.eq(d.SERIES_ID))
|
||||
.leftJoin(bma).on(s.ID.eq(bma.SERIES_ID))
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
||||
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
||||
.apply { if (joinConditions.tag) leftJoin(st).on(s.ID.eq(st.SERIES_ID)) }
|
||||
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
||||
.apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) }
|
||||
.where(conditions)
|
||||
.groupBy(s.ID)
|
||||
.having(having)
|
||||
.fetch()
|
||||
.size
|
||||
|
||||
|
|
@ -161,8 +147,6 @@ class SeriesDtoDao(
|
|||
|
||||
val dtos = selectBase(userId, joinConditions)
|
||||
.where(conditions)
|
||||
.groupBy(*groupFields)
|
||||
.having(having)
|
||||
.orderBy(orderBy)
|
||||
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
|
||||
.fetchAndMap()
|
||||
|
|
@ -176,7 +160,7 @@ class SeriesDtoDao(
|
|||
)
|
||||
}
|
||||
|
||||
private fun readProgressCondition(userId: String): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull)
|
||||
private fun readProgressConditionSeries(userId: String): Condition = rs.USER_ID.eq(userId).or(rs.USER_ID.isNull)
|
||||
|
||||
private fun ResultQuery<Record>.fetchAndMap() =
|
||||
fetch()
|
||||
|
|
@ -184,10 +168,10 @@ class SeriesDtoDao(
|
|||
val sr = rec.into(s)
|
||||
val dr = rec.into(d)
|
||||
val bmar = rec.into(bma)
|
||||
val booksCount = rec.get(BOOKS_COUNT, Int::class.java)
|
||||
val booksUnreadCount = rec.get(BOOKS_UNREAD_COUNT, Int::class.java)
|
||||
val booksReadCount = rec.get(BOOKS_READ_COUNT, Int::class.java)
|
||||
val booksInProgressCount = rec.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java)
|
||||
val rsr = rec.into(rs)
|
||||
val booksReadCount = rsr.readCount ?: 0
|
||||
val booksInProgressCount = rsr.inProgressCount ?: 0
|
||||
val booksUnreadCount = sr.bookCount - booksReadCount - booksInProgressCount
|
||||
|
||||
val genres = dsl.select(g.GENRE)
|
||||
.from(g)
|
||||
|
|
@ -206,7 +190,7 @@ class SeriesDtoDao(
|
|||
.map { AuthorDto(it.name, it.role) }
|
||||
|
||||
sr.toDto(
|
||||
booksCount,
|
||||
sr.bookCount,
|
||||
booksReadCount,
|
||||
booksUnreadCount,
|
||||
booksInProgressCount,
|
||||
|
|
@ -242,6 +226,16 @@ class SeriesDtoDao(
|
|||
}
|
||||
c = c.and(ca)
|
||||
}
|
||||
if (!readStatus.isNullOrEmpty()) {
|
||||
val cr = readStatus.map {
|
||||
when (it) {
|
||||
ReadStatus.UNREAD -> rs.READ_COUNT.isNull
|
||||
ReadStatus.READ -> rs.READ_COUNT.eq(s.BOOK_COUNT)
|
||||
ReadStatus.IN_PROGRESS -> rs.READ_COUNT.ne(s.BOOK_COUNT)
|
||||
}
|
||||
}.reduce { acc, condition -> acc.or(condition) }
|
||||
c = c.and(cr)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
|
@ -262,15 +256,6 @@ class SeriesDtoDao(
|
|||
val aggregationAuthor: Boolean = false,
|
||||
)
|
||||
|
||||
private fun Collection<ReadStatus>.toCondition(): Condition =
|
||||
map {
|
||||
when (it) {
|
||||
ReadStatus.UNREAD -> countUnread.ge(inline(1.toBigDecimal()))
|
||||
ReadStatus.READ -> countRead.ge(inline(1.toBigDecimal()))
|
||||
ReadStatus.IN_PROGRESS -> countInProgress.ge(inline(1.toBigDecimal()))
|
||||
}
|
||||
}.reduce { acc, condition -> acc.or(condition) }
|
||||
|
||||
private fun SeriesRecord.toDto(
|
||||
booksCount: Int,
|
||||
booksReadCount: Int,
|
||||
|
|
|
|||
|
|
@ -363,9 +363,7 @@ class SeriesController(
|
|||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
bookRepository.findAllIdBySeriesId(seriesId).forEach {
|
||||
bookLifecycle.markReadProgressCompleted(it, principal.user)
|
||||
}
|
||||
seriesLifecycle.markReadProgressCompleted(seriesId, principal.user)
|
||||
}
|
||||
|
||||
@DeleteMapping("{seriesId}/read-progress")
|
||||
|
|
@ -378,9 +376,7 @@ class SeriesController(
|
|||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
bookRepository.findAllIdBySeriesId(seriesId).forEach {
|
||||
bookLifecycle.deleteReadProgress(it, principal.user)
|
||||
}
|
||||
seriesLifecycle.deleteReadProgress(seriesId, principal.user)
|
||||
}
|
||||
|
||||
@GetMapping("{seriesId}/read-progress/tachiyomi")
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ class SeriesDtoDaoTest(
|
|||
makeBook("$it", seriesId = created.id, libraryId = library.id)
|
||||
}
|
||||
)
|
||||
seriesLifecycle.sortBooks(created)
|
||||
}
|
||||
|
||||
val series = seriesRepository.findAll().sortedBy { it.name }
|
||||
|
|
@ -107,13 +108,10 @@ class SeriesDtoDaoTest(
|
|||
).sortedBy { it.name }
|
||||
|
||||
// then
|
||||
assertThat(found).hasSize(2)
|
||||
assertThat(found).hasSize(1)
|
||||
|
||||
assertThat(found.first().booksReadCount).isEqualTo(3)
|
||||
assertThat(found.first().name).isEqualTo("2")
|
||||
|
||||
assertThat(found.last().booksReadCount).isEqualTo(1)
|
||||
assertThat(found.last().name).isEqualTo("4")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -129,13 +127,10 @@ class SeriesDtoDaoTest(
|
|||
).sortedBy { it.name }
|
||||
|
||||
// then
|
||||
assertThat(found).hasSize(2)
|
||||
assertThat(found).hasSize(1)
|
||||
|
||||
assertThat(found.first().booksUnreadCount).isEqualTo(3)
|
||||
assertThat(found.first().name).isEqualTo("3")
|
||||
|
||||
assertThat(found.last().booksUnreadCount).isEqualTo(1)
|
||||
assertThat(found.last().name).isEqualTo("4")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -173,8 +168,8 @@ class SeriesDtoDaoTest(
|
|||
).sortedBy { it.name }
|
||||
|
||||
// then
|
||||
assertThat(found).hasSize(3)
|
||||
assertThat(found.map { it.name }).containsExactlyInAnyOrder("2", "3", "4")
|
||||
assertThat(found).hasSize(2)
|
||||
assertThat(found.map { it.name }).containsExactlyInAnyOrder("2", "3")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -607,6 +607,7 @@ class SeriesControllerTest(
|
|||
seriesLifecycle.createSeries(series).also { created ->
|
||||
val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id))
|
||||
seriesLifecycle.addBooks(created, books)
|
||||
seriesLifecycle.sortBooks(created)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -649,6 +650,7 @@ class SeriesControllerTest(
|
|||
seriesLifecycle.createSeries(series).also { created ->
|
||||
val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id))
|
||||
seriesLifecycle.addBooks(created, books)
|
||||
seriesLifecycle.sortBooks(created)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -700,6 +702,7 @@ class SeriesControllerTest(
|
|||
makeBook("3.cbr", libraryId = library.id)
|
||||
)
|
||||
seriesLifecycle.addBooks(created, books)
|
||||
seriesLifecycle.sortBooks(created)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue