mirror of
https://github.com/gotson/komga.git
synced 2026-05-08 12:35:30 +02:00
feat(api): get duplicate books by filehash
This commit is contained in:
parent
ba5072a731
commit
3c97c20481
4 changed files with 105 additions and 48 deletions
|
|
@ -64,6 +64,7 @@ class BookDtoDao(
|
||||||
"lastModifiedDate" to b.LAST_MODIFIED_DATE,
|
"lastModifiedDate" to b.LAST_MODIFIED_DATE,
|
||||||
"fileSize" to b.FILE_SIZE,
|
"fileSize" to b.FILE_SIZE,
|
||||||
"size" to b.FILE_SIZE,
|
"size" to b.FILE_SIZE,
|
||||||
|
"fileHash" to b.FILE_HASH,
|
||||||
"url" to b.URL.noCase(),
|
"url" to b.URL.noCase(),
|
||||||
"media.status" to m.STATUS.noCase(),
|
"media.status" to m.STATUS.noCase(),
|
||||||
"media.comment" to m.COMMENT.noCase(),
|
"media.comment" to m.COMMENT.noCase(),
|
||||||
|
|
@ -87,7 +88,7 @@ class BookDtoDao(
|
||||||
userId: String,
|
userId: String,
|
||||||
filterOnLibraryIds: Collection<String>?,
|
filterOnLibraryIds: Collection<String>?,
|
||||||
search: BookSearchWithReadProgress,
|
search: BookSearchWithReadProgress,
|
||||||
pageable: Pageable
|
pageable: Pageable,
|
||||||
): Page<BookDto> {
|
): Page<BookDto> {
|
||||||
val conditions = rlb.READLIST_ID.eq(readListId).and(search.toCondition())
|
val conditions = rlb.READLIST_ID.eq(readListId).and(search.toCondition())
|
||||||
|
|
||||||
|
|
@ -139,7 +140,7 @@ class BookDtoDao(
|
||||||
dtos,
|
dtos,
|
||||||
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort)
|
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort)
|
||||||
else PageRequest.of(0, maxOf(count, 20), pageSort),
|
else PageRequest.of(0, maxOf(count, 20), pageSort),
|
||||||
count.toLong()
|
count.toLong(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,7 +160,7 @@ class BookDtoDao(
|
||||||
readListId: String,
|
readListId: String,
|
||||||
bookId: String,
|
bookId: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
filterOnLibraryIds: Collection<String>?
|
filterOnLibraryIds: Collection<String>?,
|
||||||
): BookDto? =
|
): BookDto? =
|
||||||
findSiblingReadList(readListId, bookId, userId, filterOnLibraryIds, next = false)
|
findSiblingReadList(readListId, bookId, userId, filterOnLibraryIds, next = false)
|
||||||
|
|
||||||
|
|
@ -167,7 +168,7 @@ class BookDtoDao(
|
||||||
readListId: String,
|
readListId: String,
|
||||||
bookId: String,
|
bookId: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
filterOnLibraryIds: Collection<String>?
|
filterOnLibraryIds: Collection<String>?,
|
||||||
): BookDto? =
|
): BookDto? =
|
||||||
findSiblingReadList(readListId, bookId, userId, filterOnLibraryIds, next = true)
|
findSiblingReadList(readListId, bookId, userId, filterOnLibraryIds, next = true)
|
||||||
|
|
||||||
|
|
@ -200,7 +201,34 @@ class BookDtoDao(
|
||||||
return PageImpl(
|
return PageImpl(
|
||||||
dtos,
|
dtos,
|
||||||
PageRequest.of(pageable.pageNumber, pageable.pageSize, pageable.sort),
|
PageRequest.of(pageable.pageNumber, pageable.pageSize, pageable.sort),
|
||||||
seriesIds.size.toLong()
|
seriesIds.size.toLong(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findAllDuplicates(userId: String, pageable: Pageable): Page<BookDto> {
|
||||||
|
val hashes = dsl.select(b.FILE_HASH, DSL.count(b.FILE_HASH))
|
||||||
|
.from(b)
|
||||||
|
.where(b.FILE_HASH.ne(""))
|
||||||
|
.groupBy(b.FILE_HASH)
|
||||||
|
.having(DSL.count(b.FILE_HASH).gt(1))
|
||||||
|
.fetch()
|
||||||
|
.associate { it.value1() to it.value2() }
|
||||||
|
|
||||||
|
val count = hashes.values.sum()
|
||||||
|
|
||||||
|
val orderBy = pageable.sort.toOrderBy(sorts)
|
||||||
|
val dtos = selectBase(userId)
|
||||||
|
.where(b.FILE_HASH.`in`(hashes.keys))
|
||||||
|
.orderBy(orderBy)
|
||||||
|
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
|
||||||
|
.fetchAndMap()
|
||||||
|
|
||||||
|
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
|
||||||
|
return PageImpl(
|
||||||
|
dtos,
|
||||||
|
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort)
|
||||||
|
else PageRequest.of(0, maxOf(count, 20), pageSort),
|
||||||
|
count.toLong(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +257,7 @@ class BookDtoDao(
|
||||||
bookId: String,
|
bookId: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
filterOnLibraryIds: Collection<String>?,
|
filterOnLibraryIds: Collection<String>?,
|
||||||
next: Boolean
|
next: Boolean,
|
||||||
): BookDto? {
|
): BookDto? {
|
||||||
val numberSort = dsl.select(rlb.NUMBER)
|
val numberSort = dsl.select(rlb.NUMBER)
|
||||||
.from(b)
|
.from(b)
|
||||||
|
|
@ -254,7 +282,7 @@ class BookDtoDao(
|
||||||
*b.fields(),
|
*b.fields(),
|
||||||
*m.fields(),
|
*m.fields(),
|
||||||
*d.fields(),
|
*d.fields(),
|
||||||
*r.fields()
|
*r.fields(),
|
||||||
).apply { if (joinConditions.selectReadListNumber) select(rlb.NUMBER) }
|
).apply { if (joinConditions.selectReadListNumber) select(rlb.NUMBER) }
|
||||||
.from(b)
|
.from(b)
|
||||||
.leftJoin(m).on(b.ID.eq(m.BOOK_ID))
|
.leftJoin(m).on(b.ID.eq(m.BOOK_ID))
|
||||||
|
|
@ -354,6 +382,7 @@ class BookDtoDao(
|
||||||
metadata = metadata,
|
metadata = metadata,
|
||||||
readProgress = readProgress,
|
readProgress = readProgress,
|
||||||
deleted = deletedDate != null,
|
deleted = deletedDate != null,
|
||||||
|
fileHash = fileHash,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun MediaRecord.toDto() =
|
private fun MediaRecord.toDto() =
|
||||||
|
|
@ -361,7 +390,7 @@ class BookDtoDao(
|
||||||
status = status,
|
status = status,
|
||||||
mediaType = mediaType ?: "",
|
mediaType = mediaType ?: "",
|
||||||
pagesCount = pageCount.toInt(),
|
pagesCount = pageCount.toInt(),
|
||||||
comment = comment ?: ""
|
comment = comment ?: "",
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun BookMetadataRecord.toDto(authors: List<AuthorDto>, tags: Set<String>, links: List<WebLinkDto>) =
|
private fun BookMetadataRecord.toDto(authors: List<AuthorDto>, tags: Set<String>, links: List<WebLinkDto>) =
|
||||||
|
|
@ -385,7 +414,7 @@ class BookDtoDao(
|
||||||
links = links,
|
links = links,
|
||||||
linksLock = linksLock,
|
linksLock = linksLock,
|
||||||
created = createdDate,
|
created = createdDate,
|
||||||
lastModified = lastModifiedDate
|
lastModified = lastModifiedDate,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun ReadProgressRecord.toDto() =
|
private fun ReadProgressRecord.toDto() =
|
||||||
|
|
@ -394,6 +423,6 @@ class BookDtoDao(
|
||||||
completed = completed,
|
completed = completed,
|
||||||
readDate = readDate,
|
readDate = readDate,
|
||||||
created = createdDate,
|
created = createdDate,
|
||||||
lastModified = lastModifiedDate
|
lastModified = lastModifiedDate,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,6 @@ interface BookDtoRepository {
|
||||||
): BookDto?
|
): BookDto?
|
||||||
|
|
||||||
fun findAllOnDeck(userId: String, filterOnLibraryIds: Collection<String>?, pageable: Pageable): Page<BookDto>
|
fun findAllOnDeck(userId: String, filterOnLibraryIds: Collection<String>?, pageable: Pageable): Page<BookDto>
|
||||||
|
|
||||||
|
fun findAllDuplicates(userId: String, pageable: Pageable): Page<BookDto>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class BookController(
|
||||||
private val readListRepository: ReadListRepository,
|
private val readListRepository: ReadListRepository,
|
||||||
private val contentDetector: ContentDetector,
|
private val contentDetector: ContentDetector,
|
||||||
private val eventPublisher: EventPublisher,
|
private val eventPublisher: EventPublisher,
|
||||||
private val thumbnailBookRepository: ThumbnailBookRepository
|
private val thumbnailBookRepository: ThumbnailBookRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@PageableAsQueryParam
|
@PageableAsQueryParam
|
||||||
|
|
@ -112,7 +112,7 @@ class BookController(
|
||||||
@RequestParam(name = "released_after", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) releasedAfter: LocalDate?,
|
@RequestParam(name = "released_after", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) releasedAfter: LocalDate?,
|
||||||
@RequestParam(name = "tag", required = false) tags: List<String>?,
|
@RequestParam(name = "tag", required = false) tags: List<String>?,
|
||||||
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||||
@Parameter(hidden = true) page: Pageable
|
@Parameter(hidden = true) page: Pageable,
|
||||||
): Page<BookDto> {
|
): Page<BookDto> {
|
||||||
val sort =
|
val sort =
|
||||||
when {
|
when {
|
||||||
|
|
@ -126,7 +126,7 @@ class BookController(
|
||||||
else PageRequest.of(
|
else PageRequest.of(
|
||||||
page.pageNumber,
|
page.pageNumber,
|
||||||
page.pageSize,
|
page.pageSize,
|
||||||
sort
|
sort,
|
||||||
)
|
)
|
||||||
|
|
||||||
val bookSearch = BookSearchWithReadProgress(
|
val bookSearch = BookSearchWithReadProgress(
|
||||||
|
|
@ -135,7 +135,7 @@ class BookController(
|
||||||
mediaStatus = mediaStatus,
|
mediaStatus = mediaStatus,
|
||||||
readStatus = readStatus,
|
readStatus = readStatus,
|
||||||
releasedAfter = releasedAfter,
|
releasedAfter = releasedAfter,
|
||||||
tags = tags
|
tags = tags,
|
||||||
)
|
)
|
||||||
|
|
||||||
return bookDtoRepository.findAll(bookSearch, principal.user.id, pageRequest)
|
return bookDtoRepository.findAll(bookSearch, principal.user.id, pageRequest)
|
||||||
|
|
@ -148,7 +148,7 @@ class BookController(
|
||||||
fun getLatestBooks(
|
fun getLatestBooks(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||||
@Parameter(hidden = true) page: Pageable
|
@Parameter(hidden = true) page: Pageable,
|
||||||
): Page<BookDto> {
|
): Page<BookDto> {
|
||||||
val sort = Sort.by(Sort.Order.desc("lastModifiedDate"))
|
val sort = Sort.by(Sort.Order.desc("lastModifiedDate"))
|
||||||
|
|
||||||
|
|
@ -157,15 +157,15 @@ class BookController(
|
||||||
else PageRequest.of(
|
else PageRequest.of(
|
||||||
page.pageNumber,
|
page.pageNumber,
|
||||||
page.pageSize,
|
page.pageSize,
|
||||||
sort
|
sort,
|
||||||
)
|
)
|
||||||
|
|
||||||
return bookDtoRepository.findAll(
|
return bookDtoRepository.findAll(
|
||||||
BookSearchWithReadProgress(
|
BookSearchWithReadProgress(
|
||||||
libraryIds = principal.user.getAuthorizedLibraryIds(null)
|
libraryIds = principal.user.getAuthorizedLibraryIds(null),
|
||||||
),
|
),
|
||||||
principal.user.id,
|
principal.user.id,
|
||||||
pageRequest
|
pageRequest,
|
||||||
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,18 +175,43 @@ class BookController(
|
||||||
fun getBooksOnDeck(
|
fun getBooksOnDeck(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@RequestParam(name = "library_id", required = false) libraryIds: List<String>?,
|
@RequestParam(name = "library_id", required = false) libraryIds: List<String>?,
|
||||||
@Parameter(hidden = true) page: Pageable
|
@Parameter(hidden = true) page: Pageable,
|
||||||
): Page<BookDto> =
|
): Page<BookDto> =
|
||||||
bookDtoRepository.findAllOnDeck(
|
bookDtoRepository.findAllOnDeck(
|
||||||
principal.user.id,
|
principal.user.id,
|
||||||
principal.user.getAuthorizedLibraryIds(libraryIds),
|
principal.user.getAuthorizedLibraryIds(libraryIds),
|
||||||
page
|
page,
|
||||||
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
).map { it.restrictUrl(!principal.user.roleAdmin) }
|
||||||
|
|
||||||
|
@PageableWithoutSortAsQueryParam
|
||||||
|
@GetMapping("api/v1/books/duplicates")
|
||||||
|
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||||
|
fun getDuplicateBooks(
|
||||||
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
|
@RequestParam(name = "unpaged", required = false) unpaged: Boolean = false,
|
||||||
|
@Parameter(hidden = true) page: Pageable,
|
||||||
|
): Page<BookDto> {
|
||||||
|
val sort =
|
||||||
|
when {
|
||||||
|
page.sort.isSorted -> page.sort
|
||||||
|
else -> Sort.by(Sort.Order.asc("fileHash"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val pageRequest =
|
||||||
|
if (unpaged) Pageable.unpaged()
|
||||||
|
else PageRequest.of(
|
||||||
|
page.pageNumber,
|
||||||
|
page.pageSize,
|
||||||
|
sort,
|
||||||
|
)
|
||||||
|
|
||||||
|
return bookDtoRepository.findAllDuplicates(principal.user.id, pageRequest)
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("api/v1/books/{bookId}")
|
@GetMapping("api/v1/books/{bookId}")
|
||||||
fun getOneBook(
|
fun getOneBook(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable bookId: String
|
@PathVariable bookId: String,
|
||||||
): BookDto =
|
): BookDto =
|
||||||
bookDtoRepository.findByIdOrNull(bookId, principal.user.id)?.let {
|
bookDtoRepository.findByIdOrNull(bookId, principal.user.id)?.let {
|
||||||
if (!principal.user.canAccessLibrary(it.libraryId)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessLibrary(it.libraryId)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -196,7 +221,7 @@ class BookController(
|
||||||
@GetMapping("api/v1/books/{bookId}/previous")
|
@GetMapping("api/v1/books/{bookId}/previous")
|
||||||
fun getBookSiblingPrevious(
|
fun getBookSiblingPrevious(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable bookId: String
|
@PathVariable bookId: String,
|
||||||
): BookDto {
|
): BookDto {
|
||||||
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
||||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -210,7 +235,7 @@ class BookController(
|
||||||
@GetMapping("api/v1/books/{bookId}/next")
|
@GetMapping("api/v1/books/{bookId}/next")
|
||||||
fun getBookSiblingNext(
|
fun getBookSiblingNext(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable bookId: String
|
@PathVariable bookId: String,
|
||||||
): BookDto {
|
): BookDto {
|
||||||
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
||||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -224,7 +249,7 @@ class BookController(
|
||||||
@GetMapping("api/v1/books/{bookId}/readlists")
|
@GetMapping("api/v1/books/{bookId}/readlists")
|
||||||
fun getAllReadListsByBook(
|
fun getAllReadListsByBook(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable(name = "bookId") bookId: String
|
@PathVariable(name = "bookId") bookId: String,
|
||||||
): List<ReadListDto> {
|
): List<ReadListDto> {
|
||||||
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
||||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -238,13 +263,13 @@ class BookController(
|
||||||
@GetMapping(
|
@GetMapping(
|
||||||
value = [
|
value = [
|
||||||
"api/v1/books/{bookId}/thumbnail",
|
"api/v1/books/{bookId}/thumbnail",
|
||||||
"opds/v1.2/books/{bookId}/thumbnail"
|
"opds/v1.2/books/{bookId}/thumbnail",
|
||||||
],
|
],
|
||||||
produces = [MediaType.IMAGE_JPEG_VALUE]
|
produces = [MediaType.IMAGE_JPEG_VALUE],
|
||||||
)
|
)
|
||||||
fun getBookThumbnail(
|
fun getBookThumbnail(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable bookId: String
|
@PathVariable bookId: String,
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
||||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -258,7 +283,7 @@ class BookController(
|
||||||
fun getBookThumbnailById(
|
fun getBookThumbnailById(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable(name = "bookId") bookId: String,
|
@PathVariable(name = "bookId") bookId: String,
|
||||||
@PathVariable(name = "thumbnailId") thumbnailId: String
|
@PathVariable(name = "thumbnailId") thumbnailId: String,
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
bookRepository.getLibraryIdOrNull(bookId)?.let {
|
||||||
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -301,9 +326,9 @@ class BookController(
|
||||||
bookId = book.id,
|
bookId = book.id,
|
||||||
thumbnail = file.bytes,
|
thumbnail = file.bytes,
|
||||||
type = ThumbnailBook.Type.USER_UPLOADED,
|
type = ThumbnailBook.Type.USER_UPLOADED,
|
||||||
selected = selected
|
selected = selected,
|
||||||
),
|
),
|
||||||
if (selected) MarkSelectedPreference.YES else MarkSelectedPreference.NO
|
if (selected) MarkSelectedPreference.YES else MarkSelectedPreference.NO,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,14 +376,14 @@ class BookController(
|
||||||
value = [
|
value = [
|
||||||
"api/v1/books/{bookId}/file",
|
"api/v1/books/{bookId}/file",
|
||||||
"api/v1/books/{bookId}/file/*",
|
"api/v1/books/{bookId}/file/*",
|
||||||
"opds/v1.2/books/{bookId}/file/*"
|
"opds/v1.2/books/{bookId}/file/*",
|
||||||
],
|
],
|
||||||
produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE]
|
produces = [MediaType.APPLICATION_OCTET_STREAM_VALUE],
|
||||||
)
|
)
|
||||||
@PreAuthorize("hasRole('$ROLE_FILE_DOWNLOAD')")
|
@PreAuthorize("hasRole('$ROLE_FILE_DOWNLOAD')")
|
||||||
fun getBookFile(
|
fun getBookFile(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable bookId: String
|
@PathVariable bookId: String,
|
||||||
): ResponseEntity<StreamingResponseBody> =
|
): ResponseEntity<StreamingResponseBody> =
|
||||||
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
||||||
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -378,7 +403,7 @@ class BookController(
|
||||||
contentDisposition = ContentDisposition.builder("attachment")
|
contentDisposition = ContentDisposition.builder("attachment")
|
||||||
.filename(book.path.name)
|
.filename(book.path.name)
|
||||||
.build()
|
.build()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.contentType(getMediaTypeOrDefault(media.mediaType))
|
.contentType(getMediaTypeOrDefault(media.mediaType))
|
||||||
.contentLength(this.contentLength())
|
.contentLength(this.contentLength())
|
||||||
|
|
@ -393,7 +418,7 @@ class BookController(
|
||||||
@GetMapping("api/v1/books/{bookId}/pages")
|
@GetMapping("api/v1/books/{bookId}/pages")
|
||||||
fun getBookPages(
|
fun getBookPages(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@PathVariable bookId: String
|
@PathVariable bookId: String,
|
||||||
): List<PageDto> =
|
): List<PageDto> =
|
||||||
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
||||||
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -403,7 +428,7 @@ class BookController(
|
||||||
Media.Status.UNKNOWN -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book has not been analyzed yet")
|
Media.Status.UNKNOWN -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book has not been analyzed yet")
|
||||||
Media.Status.OUTDATED -> throw ResponseStatusException(
|
Media.Status.OUTDATED -> throw ResponseStatusException(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
"Book is outdated and must be re-analyzed"
|
"Book is outdated and must be re-analyzed",
|
||||||
)
|
)
|
||||||
Media.Status.ERROR -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed")
|
Media.Status.ERROR -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book analysis failed")
|
||||||
Media.Status.UNSUPPORTED -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book format is not supported")
|
Media.Status.UNSUPPORTED -> throw ResponseStatusException(HttpStatus.NOT_FOUND, "Book format is not supported")
|
||||||
|
|
@ -417,16 +442,16 @@ class BookController(
|
||||||
content = [
|
content = [
|
||||||
Content(
|
Content(
|
||||||
mediaType = "image/*",
|
mediaType = "image/*",
|
||||||
schema = Schema(type = "string", format = "binary")
|
schema = Schema(type = "string", format = "binary"),
|
||||||
)
|
),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
@GetMapping(
|
@GetMapping(
|
||||||
value = [
|
value = [
|
||||||
"api/v1/books/{bookId}/pages/{pageNumber}",
|
"api/v1/books/{bookId}/pages/{pageNumber}",
|
||||||
"opds/v1.2/books/{bookId}/pages/{pageNumber}"
|
"opds/v1.2/books/{bookId}/pages/{pageNumber}",
|
||||||
],
|
],
|
||||||
produces = [MediaType.ALL_VALUE]
|
produces = [MediaType.ALL_VALUE],
|
||||||
)
|
)
|
||||||
@PreAuthorize("hasRole('$ROLE_PAGE_STREAMING')")
|
@PreAuthorize("hasRole('$ROLE_PAGE_STREAMING')")
|
||||||
fun getBookPage(
|
fun getBookPage(
|
||||||
|
|
@ -436,7 +461,7 @@ class BookController(
|
||||||
@PathVariable pageNumber: Int,
|
@PathVariable pageNumber: Int,
|
||||||
@Parameter(
|
@Parameter(
|
||||||
description = "Convert the image to the provided format.",
|
description = "Convert the image to the provided format.",
|
||||||
schema = Schema(allowableValues = ["jpeg", "png"])
|
schema = Schema(allowableValues = ["jpeg", "png"]),
|
||||||
)
|
)
|
||||||
@RequestParam(value = "convert", required = false) convertTo: String?,
|
@RequestParam(value = "convert", required = false) convertTo: String?,
|
||||||
@Parameter(description = "If set to true, pages will start at index 0. If set to false, pages will start at index 1.")
|
@Parameter(description = "If set to true, pages will start at index 0. If set to false, pages will start at index 1.")
|
||||||
|
|
@ -471,7 +496,7 @@ class BookController(
|
||||||
contentDisposition = ContentDisposition.builder("inline")
|
contentDisposition = ContentDisposition.builder("inline")
|
||||||
.filename(imageFileName)
|
.filename(imageFileName)
|
||||||
.build()
|
.build()
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.contentType(getMediaTypeOrDefault(pageContent.mediaType))
|
.contentType(getMediaTypeOrDefault(pageContent.mediaType))
|
||||||
.setNotModified(media)
|
.setNotModified(media)
|
||||||
|
|
@ -491,13 +516,13 @@ class BookController(
|
||||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||||
@GetMapping(
|
@GetMapping(
|
||||||
value = ["api/v1/books/{bookId}/pages/{pageNumber}/thumbnail"],
|
value = ["api/v1/books/{bookId}/pages/{pageNumber}/thumbnail"],
|
||||||
produces = [MediaType.IMAGE_JPEG_VALUE]
|
produces = [MediaType.IMAGE_JPEG_VALUE],
|
||||||
)
|
)
|
||||||
fun getBookPageThumbnail(
|
fun getBookPageThumbnail(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
request: WebRequest,
|
request: WebRequest,
|
||||||
@PathVariable bookId: String,
|
@PathVariable bookId: String,
|
||||||
@PathVariable pageNumber: Int
|
@PathVariable pageNumber: Int,
|
||||||
): ResponseEntity<ByteArray> =
|
): ResponseEntity<ByteArray> =
|
||||||
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
||||||
val media = mediaRepository.findById(bookId)
|
val media = mediaRepository.findById(bookId)
|
||||||
|
|
@ -591,7 +616,7 @@ class BookController(
|
||||||
@PathVariable bookId: String,
|
@PathVariable bookId: String,
|
||||||
@Parameter(description = "page can be omitted if completed is set to true. completed can be omitted, and will be set accordingly depending on the page passed and the total number of pages in the book.")
|
@Parameter(description = "page can be omitted if completed is set to true. completed can be omitted, and will be set accordingly depending on the page passed and the total number of pages in the book.")
|
||||||
@Valid @RequestBody readProgress: ReadProgressUpdateDto,
|
@Valid @RequestBody readProgress: ReadProgressUpdateDto,
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
) {
|
) {
|
||||||
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
||||||
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -612,7 +637,7 @@ class BookController(
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
fun deleteReadProgress(
|
fun deleteReadProgress(
|
||||||
@PathVariable bookId: String,
|
@PathVariable bookId: String,
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
) {
|
) {
|
||||||
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
||||||
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.FORBIDDEN)
|
||||||
|
|
@ -647,7 +672,7 @@ class BookController(
|
||||||
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||||
fun deleteBook(
|
fun deleteBook(
|
||||||
@PathVariable bookId: String
|
@PathVariable bookId: String,
|
||||||
) {
|
) {
|
||||||
taskReceiver.deleteBook(
|
taskReceiver.deleteBook(
|
||||||
bookId = bookId,
|
bookId = bookId,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ data class BookDto(
|
||||||
val metadata: BookMetadataDto,
|
val metadata: BookMetadataDto,
|
||||||
val readProgress: ReadProgressDto? = null,
|
val readProgress: ReadProgressDto? = null,
|
||||||
val deleted: Boolean,
|
val deleted: Boolean,
|
||||||
|
val fileHash: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun BookDto.restrictUrl(restrict: Boolean) =
|
fun BookDto.restrictUrl(restrict: Boolean) =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue