mirror of
https://github.com/gotson/komga.git
synced 2025-12-21 07:56:57 +01:00
fix(api): better handling of tachiyomi tracking
read progress returns the last book read in a continuous fashion don't mark books as unread when tachiyomi updates progress, only read
This commit is contained in:
parent
571d54a526
commit
a7ab0da025
8 changed files with 158 additions and 101 deletions
|
|
@ -0,0 +1,96 @@
|
|||
package org.gotson.komga.infrastructure.jooq
|
||||
|
||||
import org.gotson.komga.interfaces.rest.dto.TachiyomiReadProgressDto
|
||||
import org.gotson.komga.interfaces.rest.persistence.ReadProgressDtoRepository
|
||||
import org.gotson.komga.jooq.Tables
|
||||
import org.jooq.Condition
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.Record
|
||||
import org.jooq.Record2
|
||||
import org.jooq.impl.DSL.rowNumber
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class ReadProgressDtoDao(
|
||||
private val dsl: DSLContext
|
||||
) : ReadProgressDtoRepository {
|
||||
|
||||
private val rlb = Tables.READLIST_BOOK
|
||||
private val b = Tables.BOOK
|
||||
private val d = Tables.BOOK_METADATA
|
||||
private val r = Tables.READ_PROGRESS
|
||||
|
||||
override fun getProgressBySeries(seriesId: String, userId: String): TachiyomiReadProgressDto {
|
||||
val indexedReadProgress = dsl.select(
|
||||
rowNumber().over().orderBy(d.NUMBER_SORT),
|
||||
r.COMPLETED,
|
||||
)
|
||||
.from(b)
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.leftJoin(d).on(b.ID.eq(d.BOOK_ID))
|
||||
.where(b.SERIES_ID.eq(seriesId))
|
||||
.orderBy(d.NUMBER_SORT)
|
||||
.fetch()
|
||||
.toList()
|
||||
|
||||
val booksCountRecord = dsl
|
||||
.select(SeriesDtoDao.countUnread.`as`(BOOKS_UNREAD_COUNT))
|
||||
.select(SeriesDtoDao.countRead.`as`(BOOKS_READ_COUNT))
|
||||
.select(SeriesDtoDao.countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT))
|
||||
.from(b)
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.where(b.SERIES_ID.eq(seriesId))
|
||||
.fetch()
|
||||
.first()
|
||||
|
||||
return booksCountToDto(booksCountRecord, indexedReadProgress)
|
||||
}
|
||||
|
||||
override fun getProgressByReadList(readListId: String, userId: String): TachiyomiReadProgressDto {
|
||||
val indexedReadProgress = dsl.select(
|
||||
rowNumber().over().orderBy(rlb.NUMBER),
|
||||
r.COMPLETED,
|
||||
)
|
||||
.from(b)
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID))
|
||||
.where(rlb.READLIST_ID.eq(readListId))
|
||||
.orderBy(rlb.NUMBER)
|
||||
.fetch()
|
||||
.toList()
|
||||
|
||||
val booksCountRecord = dsl
|
||||
.select(SeriesDtoDao.countUnread.`as`(BOOKS_UNREAD_COUNT))
|
||||
.select(SeriesDtoDao.countRead.`as`(BOOKS_READ_COUNT))
|
||||
.select(SeriesDtoDao.countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT))
|
||||
.from(b)
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID))
|
||||
.where(rlb.READLIST_ID.eq(readListId))
|
||||
.fetch()
|
||||
.first()
|
||||
|
||||
return booksCountToDto(booksCountRecord, indexedReadProgress)
|
||||
}
|
||||
|
||||
private fun booksCountToDto(booksCountRecord: Record, indexedReadProgress: List<Record2<Int, Boolean>>): TachiyomiReadProgressDto {
|
||||
val booksUnreadCount = booksCountRecord.get(BOOKS_UNREAD_COUNT, Int::class.java)
|
||||
val booksReadCount = booksCountRecord.get(BOOKS_READ_COUNT, Int::class.java)
|
||||
val booksInProgressCount = booksCountRecord.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java)
|
||||
|
||||
val lastReadContinuousIndex = indexedReadProgress
|
||||
.takeWhile { it.component2() == true }
|
||||
.lastOrNull()
|
||||
?.component1() ?: 0
|
||||
|
||||
return TachiyomiReadProgressDto(
|
||||
booksCount = booksUnreadCount + booksReadCount + booksInProgressCount,
|
||||
booksUnreadCount = booksUnreadCount,
|
||||
booksInProgressCount = booksInProgressCount,
|
||||
booksReadCount = booksReadCount,
|
||||
lastReadContinuousIndex = lastReadContinuousIndex,
|
||||
)
|
||||
}
|
||||
|
||||
private fun readProgressCondition(userId: String): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull)
|
||||
}
|
||||
|
|
@ -18,14 +18,14 @@ import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
|||
import org.gotson.komga.interfaces.rest.dto.BookDto
|
||||
import org.gotson.komga.interfaces.rest.dto.ReadListCreationDto
|
||||
import org.gotson.komga.interfaces.rest.dto.ReadListDto
|
||||
import org.gotson.komga.interfaces.rest.dto.ReadListProgressDto
|
||||
import org.gotson.komga.interfaces.rest.dto.ReadListRequestResultDto
|
||||
import org.gotson.komga.interfaces.rest.dto.ReadListUpdateDto
|
||||
import org.gotson.komga.interfaces.rest.dto.TachiyomiReadProgressDto
|
||||
import org.gotson.komga.interfaces.rest.dto.TachiyomiReadProgressUpdateDto
|
||||
import org.gotson.komga.interfaces.rest.dto.restrictUrl
|
||||
import org.gotson.komga.interfaces.rest.dto.toDto
|
||||
import org.gotson.komga.interfaces.rest.persistence.BookDtoRepository
|
||||
import org.gotson.komga.interfaces.rest.persistence.ReadListDtoRepository
|
||||
import org.gotson.komga.interfaces.rest.persistence.ReadProgressDtoRepository
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
|
|
@ -41,6 +41,7 @@ import org.springframework.web.bind.annotation.GetMapping
|
|||
import org.springframework.web.bind.annotation.PatchMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
|
|
@ -59,7 +60,7 @@ class ReadListController(
|
|||
private val readListRepository: ReadListRepository,
|
||||
private val readListLifecycle: ReadListLifecycle,
|
||||
private val bookDtoRepository: BookDtoRepository,
|
||||
private val readListDtoRepository: ReadListDtoRepository,
|
||||
private val readProgressDtoRepository: ReadProgressDtoRepository,
|
||||
private val bookLifecycle: BookLifecycle,
|
||||
) {
|
||||
|
||||
|
|
@ -242,18 +243,18 @@ class ReadListController(
|
|||
?.restrictUrl(!principal.user.roleAdmin)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@GetMapping("{id}/read-progress")
|
||||
@GetMapping("{id}/read-progress/tachiyomi")
|
||||
fun getReadProgress(
|
||||
@PathVariable id: String,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal
|
||||
): ReadListProgressDto =
|
||||
): TachiyomiReadProgressDto =
|
||||
readListRepository.findByIdOrNull(id, principal.user.getAuthorizedLibraryIds(null))?.let { readList ->
|
||||
readListDtoRepository.getProgress(readList.id, principal.user.id)
|
||||
readProgressDtoRepository.getProgressByReadList(readList.id, principal.user.id)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@PatchMapping("{id}/read-progress/tachiyomi")
|
||||
@PutMapping("{id}/read-progress/tachiyomi")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markReadProgress(
|
||||
fun markReadProgressTachiyomi(
|
||||
@PathVariable id: String,
|
||||
@Valid @RequestBody readProgress: TachiyomiReadProgressUpdateDto,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal
|
||||
|
|
@ -264,12 +265,8 @@ class ReadListController(
|
|||
principal.user.id,
|
||||
principal.user.getAuthorizedLibraryIds(null),
|
||||
UnpagedSorted(Sort.by(Sort.Order.asc("readList.number")))
|
||||
).forEachIndexed { index, book ->
|
||||
if (index < readProgress.lastBookRead)
|
||||
bookLifecycle.markReadProgressCompleted(book.id, principal.user)
|
||||
else
|
||||
bookLifecycle.deleteReadProgress(book.id, principal.user)
|
||||
}
|
||||
).filterIndexed { index, _ -> index < readProgress.lastBookRead }
|
||||
.forEach { book -> bookLifecycle.markReadProgressCompleted(book.id, principal.user) }
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,12 @@ import org.gotson.komga.interfaces.rest.dto.BookDto
|
|||
import org.gotson.komga.interfaces.rest.dto.CollectionDto
|
||||
import org.gotson.komga.interfaces.rest.dto.SeriesDto
|
||||
import org.gotson.komga.interfaces.rest.dto.SeriesMetadataUpdateDto
|
||||
import org.gotson.komga.interfaces.rest.dto.TachiyomiReadProgressDto
|
||||
import org.gotson.komga.interfaces.rest.dto.TachiyomiReadProgressUpdateDto
|
||||
import org.gotson.komga.interfaces.rest.dto.restrictUrl
|
||||
import org.gotson.komga.interfaces.rest.dto.toDto
|
||||
import org.gotson.komga.interfaces.rest.persistence.BookDtoRepository
|
||||
import org.gotson.komga.interfaces.rest.persistence.ReadProgressDtoRepository
|
||||
import org.gotson.komga.interfaces.rest.persistence.SeriesDtoRepository
|
||||
import org.springframework.core.io.FileSystemResource
|
||||
import org.springframework.data.domain.Page
|
||||
|
|
@ -57,6 +59,7 @@ import org.springframework.web.bind.annotation.GetMapping
|
|||
import org.springframework.web.bind.annotation.PatchMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
|
|
@ -81,7 +84,8 @@ class SeriesController(
|
|||
private val bookLifecycle: BookLifecycle,
|
||||
private val bookRepository: BookRepository,
|
||||
private val bookDtoRepository: BookDtoRepository,
|
||||
private val collectionRepository: SeriesCollectionRepository
|
||||
private val collectionRepository: SeriesCollectionRepository,
|
||||
private val readProgressDtoRepository: ReadProgressDtoRepository,
|
||||
) {
|
||||
|
||||
@PageableAsQueryParam
|
||||
|
|
@ -364,29 +368,6 @@ class SeriesController(
|
|||
}
|
||||
}
|
||||
|
||||
@PatchMapping("{seriesId}/read-progress/tachiyomi")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markReadProgress(
|
||||
@PathVariable seriesId: String,
|
||||
@Valid @RequestBody readProgress: TachiyomiReadProgressUpdateDto,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal
|
||||
) {
|
||||
seriesRepository.getLibraryId(seriesId)?.let {
|
||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
bookDtoRepository.findAll(
|
||||
BookSearchWithReadProgress(seriesIds = listOf(seriesId)),
|
||||
principal.user.id,
|
||||
UnpagedSorted(Sort.by(Sort.Order.asc("metadata.numberSort"))),
|
||||
).forEachIndexed { index, book ->
|
||||
if (index < readProgress.lastBookRead)
|
||||
bookLifecycle.markReadProgressCompleted(book.id, principal.user)
|
||||
else
|
||||
bookLifecycle.deleteReadProgress(book.id, principal.user)
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("{seriesId}/read-progress")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markAsUnread(
|
||||
|
|
@ -402,6 +383,35 @@ class SeriesController(
|
|||
}
|
||||
}
|
||||
|
||||
@GetMapping("{seriesId}/read-progress/tachiyomi")
|
||||
fun getReadProgressTachiyomi(
|
||||
@PathVariable seriesId: String,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
): TachiyomiReadProgressDto =
|
||||
seriesRepository.getLibraryId(seriesId)?.let {
|
||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||
return readProgressDtoRepository.getProgressBySeries(seriesId, principal.user.id)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@PutMapping("{seriesId}/read-progress/tachiyomi")
|
||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||
fun markReadProgressTachiyomi(
|
||||
@PathVariable seriesId: String,
|
||||
@Valid @RequestBody readProgress: TachiyomiReadProgressUpdateDto,
|
||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
) {
|
||||
seriesRepository.getLibraryId(seriesId)?.let {
|
||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
bookDtoRepository.findAll(
|
||||
BookSearchWithReadProgress(seriesIds = listOf(seriesId)),
|
||||
principal.user.id,
|
||||
UnpagedSorted(Sort.by(Sort.Order.asc("metadata.numberSort"))),
|
||||
).filterIndexed { index, _ -> index < readProgress.lastBookRead }
|
||||
.forEach { book -> bookLifecycle.markReadProgressCompleted(book.id, principal.user) }
|
||||
}
|
||||
|
||||
@GetMapping("{seriesId}/file", produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE])
|
||||
@PreAuthorize("hasRole('$ROLE_FILE_DOWNLOAD')")
|
||||
fun getSeriesFile(
|
||||
|
|
|
|||
|
|
@ -18,13 +18,6 @@ data class ReadListDto(
|
|||
val filtered: Boolean
|
||||
)
|
||||
|
||||
data class ReadListProgressDto(
|
||||
val booksCount: Int,
|
||||
val booksReadCount: Int,
|
||||
val booksUnreadCount: Int,
|
||||
val booksInProgressCount: Int,
|
||||
)
|
||||
|
||||
fun ReadList.toDto() =
|
||||
ReadListDto(
|
||||
id = id,
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
package org.gotson.komga.interfaces.rest.dto
|
||||
|
||||
import org.gotson.komga.infrastructure.jooq.BOOKS_IN_PROGRESS_COUNT
|
||||
import org.gotson.komga.infrastructure.jooq.BOOKS_READ_COUNT
|
||||
import org.gotson.komga.infrastructure.jooq.BOOKS_UNREAD_COUNT
|
||||
import org.gotson.komga.infrastructure.jooq.SeriesDtoDao
|
||||
import org.gotson.komga.interfaces.rest.persistence.ReadListDtoRepository
|
||||
import org.gotson.komga.jooq.Tables
|
||||
import org.jooq.Condition
|
||||
import org.jooq.DSLContext
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class ReadListDtoDao(
|
||||
private val dsl: DSLContext
|
||||
) : ReadListDtoRepository {
|
||||
|
||||
private val rl = Tables.READLIST
|
||||
private val rlb = Tables.READLIST_BOOK
|
||||
private val b = Tables.BOOK
|
||||
private val r = Tables.READ_PROGRESS
|
||||
|
||||
override fun getProgress(readListId: String, userId: String): ReadListProgressDto {
|
||||
|
||||
val booksCountRecord = dsl
|
||||
.select(SeriesDtoDao.countUnread.`as`(BOOKS_UNREAD_COUNT))
|
||||
.select(SeriesDtoDao.countRead.`as`(BOOKS_READ_COUNT))
|
||||
.select(SeriesDtoDao.countInProgress.`as`(BOOKS_IN_PROGRESS_COUNT))
|
||||
.from(b)
|
||||
.leftJoin(r).on(b.ID.eq(r.BOOK_ID)).and(readProgressCondition(userId))
|
||||
.leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID))
|
||||
.where(rlb.READLIST_ID.eq(readListId))
|
||||
.fetch()
|
||||
.first()
|
||||
|
||||
val booksUnreadCount = booksCountRecord.get(BOOKS_UNREAD_COUNT, Int::class.java)
|
||||
val booksReadCount = booksCountRecord.get(BOOKS_READ_COUNT, Int::class.java)
|
||||
val booksInProgressCount = booksCountRecord.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java)
|
||||
|
||||
return ReadListProgressDto(
|
||||
booksCount = booksUnreadCount + booksReadCount + booksInProgressCount,
|
||||
booksUnreadCount = booksUnreadCount,
|
||||
booksInProgressCount = booksInProgressCount,
|
||||
booksReadCount = booksReadCount,
|
||||
)
|
||||
}
|
||||
|
||||
private fun readProgressCondition(userId: String): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package org.gotson.komga.interfaces.rest.dto
|
||||
|
||||
data class TachiyomiReadProgressDto(
|
||||
val booksCount: Int,
|
||||
val booksReadCount: Int,
|
||||
val booksUnreadCount: Int,
|
||||
val booksInProgressCount: Int,
|
||||
val lastReadContinuousIndex: Int,
|
||||
)
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package org.gotson.komga.interfaces.rest.persistence
|
||||
|
||||
import org.gotson.komga.interfaces.rest.dto.ReadListProgressDto
|
||||
|
||||
interface ReadListDtoRepository {
|
||||
fun getProgress(readListId: String, userId: String,): ReadListProgressDto
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package org.gotson.komga.interfaces.rest.persistence
|
||||
|
||||
import org.gotson.komga.interfaces.rest.dto.TachiyomiReadProgressDto
|
||||
|
||||
interface ReadProgressDtoRepository {
|
||||
fun getProgressBySeries(seriesId: String, userId: String,): TachiyomiReadProgressDto
|
||||
fun getProgressByReadList(readListId: String, userId: String,): TachiyomiReadProgressDto
|
||||
}
|
||||
Loading…
Reference in a new issue