mirror of
https://github.com/gotson/komga.git
synced 2026-02-15 03:43:05 +01:00
parent
2f7d2a447f
commit
3ca50d7b34
5 changed files with 49 additions and 19 deletions
|
|
@ -28,6 +28,7 @@ class SeriesDtoDao(
|
||||||
private val s = Tables.SERIES
|
private val s = Tables.SERIES
|
||||||
private val b = Tables.BOOK
|
private val b = Tables.BOOK
|
||||||
private val d = Tables.SERIES_METADATA
|
private val d = Tables.SERIES_METADATA
|
||||||
|
private val r = Tables.READ_PROGRESS
|
||||||
|
|
||||||
private val groupFields = arrayOf(
|
private val groupFields = arrayOf(
|
||||||
*s.fields(),
|
*s.fields(),
|
||||||
|
|
@ -40,28 +41,29 @@ class SeriesDtoDao(
|
||||||
"lastModifiedDate" to s.LAST_MODIFIED_DATE
|
"lastModifiedDate" to s.LAST_MODIFIED_DATE
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun findAll(search: SeriesSearch, pageable: Pageable): Page<SeriesDto> {
|
override fun findAll(search: SeriesSearch, userId: Long, pageable: Pageable): Page<SeriesDto> {
|
||||||
val conditions = search.toCondition()
|
val conditions = search.toCondition()
|
||||||
|
|
||||||
return findAll(conditions, pageable)
|
return findAll(conditions, userId, pageable)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findRecentlyUpdated(search: SeriesSearch, pageable: Pageable): Page<SeriesDto> {
|
override fun findRecentlyUpdated(search: SeriesSearch, userId: Long, pageable: Pageable): Page<SeriesDto> {
|
||||||
val conditions = search.toCondition()
|
val conditions = search.toCondition()
|
||||||
.and(s.CREATED_DATE.ne(s.LAST_MODIFIED_DATE))
|
.and(s.CREATED_DATE.ne(s.LAST_MODIFIED_DATE))
|
||||||
|
|
||||||
return findAll(conditions, pageable)
|
return findAll(conditions, userId, pageable)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findByIdOrNull(seriesId: Long): SeriesDto? =
|
override fun findByIdOrNull(seriesId: Long, userId: Long): SeriesDto? =
|
||||||
selectBase()
|
selectBase()
|
||||||
.where(s.ID.eq(seriesId))
|
.where(s.ID.eq(seriesId))
|
||||||
|
.and(readProgressCondition(userId))
|
||||||
.groupBy(*groupFields)
|
.groupBy(*groupFields)
|
||||||
.fetchAndMap()
|
.fetchAndMap()
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
|
|
||||||
private fun findAll(conditions: Condition, pageable: Pageable): Page<SeriesDto> {
|
private fun findAll(conditions: Condition, userId: Long, pageable: Pageable): Page<SeriesDto> {
|
||||||
val count = dsl.selectCount()
|
val count = dsl.selectCount()
|
||||||
.from(s)
|
.from(s)
|
||||||
.leftJoin(d).on(s.ID.eq(d.SERIES_ID))
|
.leftJoin(d).on(s.ID.eq(d.SERIES_ID))
|
||||||
|
|
@ -72,6 +74,7 @@ class SeriesDtoDao(
|
||||||
|
|
||||||
val dtos = selectBase()
|
val dtos = selectBase()
|
||||||
.where(conditions)
|
.where(conditions)
|
||||||
|
.and(readProgressCondition(userId))
|
||||||
.groupBy(*groupFields)
|
.groupBy(*groupFields)
|
||||||
.orderBy(orderBy)
|
.orderBy(orderBy)
|
||||||
.limit(pageable.pageSize)
|
.limit(pageable.pageSize)
|
||||||
|
|
@ -87,18 +90,23 @@ class SeriesDtoDao(
|
||||||
|
|
||||||
private fun selectBase() =
|
private fun selectBase() =
|
||||||
dsl.select(*groupFields)
|
dsl.select(*groupFields)
|
||||||
.select(DSL.count(b.ID).`as`("bookCount"))
|
.select(DSL.count(b.ID).`as`("booksCount"))
|
||||||
|
.select(DSL.count(r.COMPLETED).`as`("booksReadCount"))
|
||||||
.from(s)
|
.from(s)
|
||||||
.leftJoin(b).on(s.ID.eq(b.SERIES_ID))
|
.leftJoin(b).on(s.ID.eq(b.SERIES_ID))
|
||||||
.leftJoin(d).on(s.ID.eq(d.SERIES_ID))
|
.leftJoin(d).on(s.ID.eq(d.SERIES_ID))
|
||||||
|
.leftJoin(r).on(b.ID.eq(r.BOOK_ID))
|
||||||
|
|
||||||
|
private fun readProgressCondition(userId: Long): Condition = r.USER_ID.eq(userId).or(r.USER_ID.isNull)
|
||||||
|
|
||||||
private fun ResultQuery<Record>.fetchAndMap() =
|
private fun ResultQuery<Record>.fetchAndMap() =
|
||||||
fetch()
|
fetch()
|
||||||
.map { r ->
|
.map { r ->
|
||||||
val sr = r.into(s)
|
val sr = r.into(s)
|
||||||
val dr = r.into(d)
|
val dr = r.into(d)
|
||||||
val bookCount = r["bookCount"] as Int
|
val booksCount = r["booksCount"] as Int
|
||||||
sr.toDto(bookCount, dr.toDto())
|
val booksReadCount = r["booksReadCount"] as Int
|
||||||
|
sr.toDto(booksCount, booksReadCount, dr.toDto())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SeriesSearch.toCondition(): Condition {
|
private fun SeriesSearch.toCondition(): Condition {
|
||||||
|
|
@ -111,7 +119,7 @@ class SeriesDtoDao(
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun SeriesRecord.toDto(bookCount: Int, metadata: SeriesMetadataDto) =
|
private fun SeriesRecord.toDto(booksCount: Int, booksReadCount: Int, metadata: SeriesMetadataDto) =
|
||||||
SeriesDto(
|
SeriesDto(
|
||||||
id = id,
|
id = id,
|
||||||
libraryId = libraryId,
|
libraryId = libraryId,
|
||||||
|
|
@ -120,7 +128,8 @@ class SeriesDtoDao(
|
||||||
created = createdDate.toUTC(),
|
created = createdDate.toUTC(),
|
||||||
lastModified = lastModifiedDate.toUTC(),
|
lastModified = lastModifiedDate.toUTC(),
|
||||||
fileLastModified = fileLastModified.toUTC(),
|
fileLastModified = fileLastModified.toUTC(),
|
||||||
booksCount = bookCount,
|
booksCount = booksCount,
|
||||||
|
booksReadCount = booksReadCount,
|
||||||
metadata = metadata
|
metadata = metadata
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class SeriesController(
|
||||||
metadataStatus = metadataStatus ?: emptyList()
|
metadataStatus = metadataStatus ?: emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
return seriesDtoRepository.findAll(seriesSearch, pageRequest)
|
return seriesDtoRepository.findAll(seriesSearch, principal.user.id, pageRequest)
|
||||||
.map { it.restrictUrl(!principal.user.roleAdmin) }
|
.map { it.restrictUrl(!principal.user.roleAdmin) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +104,7 @@ class SeriesController(
|
||||||
|
|
||||||
return seriesDtoRepository.findAll(
|
return seriesDtoRepository.findAll(
|
||||||
SeriesSearch(libraryIds = libraryIds),
|
SeriesSearch(libraryIds = libraryIds),
|
||||||
|
principal.user.id,
|
||||||
pageRequest
|
pageRequest
|
||||||
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +126,7 @@ class SeriesController(
|
||||||
|
|
||||||
return seriesDtoRepository.findAll(
|
return seriesDtoRepository.findAll(
|
||||||
SeriesSearch(libraryIds = libraryIds),
|
SeriesSearch(libraryIds = libraryIds),
|
||||||
|
principal.user.id,
|
||||||
pageRequest
|
pageRequest
|
||||||
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
||||||
}
|
}
|
||||||
|
|
@ -146,6 +148,7 @@ class SeriesController(
|
||||||
|
|
||||||
return seriesDtoRepository.findRecentlyUpdated(
|
return seriesDtoRepository.findRecentlyUpdated(
|
||||||
SeriesSearch(libraryIds = libraryIds),
|
SeriesSearch(libraryIds = libraryIds),
|
||||||
|
principal.user.id,
|
||||||
pageRequest
|
pageRequest
|
||||||
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +158,7 @@ class SeriesController(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable(name = "seriesId") id: Long
|
@PathVariable(name = "seriesId") id: Long
|
||||||
): SeriesDto =
|
): SeriesDto =
|
||||||
seriesDtoRepository.findByIdOrNull(id)?.let {
|
seriesDtoRepository.findByIdOrNull(id, principal.user.id)?.let {
|
||||||
if (!principal.user.canAccessLibrary(it.libraryId)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
if (!principal.user.canAccessLibrary(it.libraryId)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||||
it.restrictUrl(!principal.user.roleAdmin)
|
it.restrictUrl(!principal.user.roleAdmin)
|
||||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
@ -227,7 +230,8 @@ class SeriesController(
|
||||||
fun updateMetadata(
|
fun updateMetadata(
|
||||||
@PathVariable seriesId: Long,
|
@PathVariable seriesId: Long,
|
||||||
@Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.")
|
@Parameter(description = "Metadata fields to update. Set a field to null to unset the metadata. You can omit fields you don't want to update.")
|
||||||
@Valid @RequestBody newMetadata: SeriesMetadataUpdateDto
|
@Valid @RequestBody newMetadata: SeriesMetadataUpdateDto,
|
||||||
|
@AuthenticationPrincipal principal: KomgaPrincipal
|
||||||
): SeriesDto =
|
): SeriesDto =
|
||||||
seriesMetadataRepository.findByIdOrNull(seriesId)?.let { existing ->
|
seriesMetadataRepository.findByIdOrNull(seriesId)?.let { existing ->
|
||||||
val updated = with(newMetadata) {
|
val updated = with(newMetadata) {
|
||||||
|
|
@ -241,7 +245,7 @@ class SeriesController(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
seriesMetadataRepository.update(updated)
|
seriesMetadataRepository.update(updated)
|
||||||
seriesDtoRepository.findByIdOrNull(seriesId)!!
|
seriesDtoRepository.findByIdOrNull(seriesId, principal.user.id)!!
|
||||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
||||||
@PostMapping("{seriesId}/read-progress")
|
@PostMapping("{seriesId}/read-progress")
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,11 @@ data class SeriesDto(
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||||
val fileLastModified: LocalDateTime,
|
val fileLastModified: LocalDateTime,
|
||||||
val booksCount: Int,
|
val booksCount: Int,
|
||||||
|
val booksReadCount: Int,
|
||||||
val metadata: SeriesMetadataDto
|
val metadata: SeriesMetadataDto
|
||||||
)
|
) {
|
||||||
|
val booksUnreadCount: Int = booksCount - booksReadCount
|
||||||
|
}
|
||||||
|
|
||||||
fun SeriesDto.restrictUrl(restrict: Boolean) =
|
fun SeriesDto.restrictUrl(restrict: Boolean) =
|
||||||
if (restrict) copy(url = "") else this
|
if (restrict) copy(url = "") else this
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import org.springframework.data.domain.Page
|
||||||
import org.springframework.data.domain.Pageable
|
import org.springframework.data.domain.Pageable
|
||||||
|
|
||||||
interface SeriesDtoRepository {
|
interface SeriesDtoRepository {
|
||||||
fun findAll(search: SeriesSearch, pageable: Pageable): Page<SeriesDto>
|
fun findAll(search: SeriesSearch, userId: Long, pageable: Pageable): Page<SeriesDto>
|
||||||
fun findRecentlyUpdated(search: SeriesSearch, pageable: Pageable): Page<SeriesDto>
|
fun findRecentlyUpdated(search: SeriesSearch, userId: Long, pageable: Pageable): Page<SeriesDto>
|
||||||
fun findByIdOrNull(seriesId: Long): SeriesDto?
|
fun findByIdOrNull(seriesId: Long, userId: Long): SeriesDto?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -510,6 +510,13 @@ class SeriesControllerTest(
|
||||||
status { isNoContent }
|
status { isNoContent }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mockMvc.get("/api/v1/series/${series.id}")
|
||||||
|
.andExpect {
|
||||||
|
status { isOk }
|
||||||
|
jsonPath("$.booksUnreadCount") { value(0) }
|
||||||
|
jsonPath("$.booksReadCount") { value(2) }
|
||||||
|
}
|
||||||
|
|
||||||
mockMvc.get("/api/v1/series/${series.id}/books")
|
mockMvc.get("/api/v1/series/${series.id}/books")
|
||||||
.andExpect {
|
.andExpect {
|
||||||
status { isOk }
|
status { isOk }
|
||||||
|
|
@ -548,6 +555,13 @@ class SeriesControllerTest(
|
||||||
status { isNoContent }
|
status { isNoContent }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mockMvc.get("/api/v1/series/${series.id}")
|
||||||
|
.andExpect {
|
||||||
|
status { isOk }
|
||||||
|
jsonPath("$.booksUnreadCount") { value(2) }
|
||||||
|
jsonPath("$.booksReadCount") { value(0) }
|
||||||
|
}
|
||||||
|
|
||||||
mockMvc.get("/api/v1/series/${series.id}/books")
|
mockMvc.get("/api/v1/series/${series.id}/books")
|
||||||
.andExpect {
|
.andExpect {
|
||||||
status { isOk }
|
status { isOk }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue