mirror of
https://github.com/gotson/komga.git
synced 2025-01-09 04:08:00 +08:00
feat(api): wip version of the page-hashes endpoints
This commit is contained in:
parent
fc33ad7ed3
commit
5777952c05
@ -0,0 +1,20 @@
|
||||
package org.gotson.komga.domain.model
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class PageHash(
|
||||
val hash: String,
|
||||
val mediaType: String,
|
||||
val size: Long? = null,
|
||||
val action: Action,
|
||||
val deleteCount: Int = 0,
|
||||
|
||||
override val createdDate: LocalDateTime = LocalDateTime.now(),
|
||||
override val lastModifiedDate: LocalDateTime = createdDate,
|
||||
) : Auditable() {
|
||||
enum class Action {
|
||||
DELETE_AUTO,
|
||||
DELETE_MANUAL,
|
||||
IGNORE,
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package org.gotson.komga.domain.model
|
||||
|
||||
import java.net.URL
|
||||
|
||||
data class PageHashMatch(
|
||||
val bookId: String,
|
||||
val url: URL,
|
||||
val pageNumber: Int,
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package org.gotson.komga.domain.model
|
||||
|
||||
data class PageHashUnknown(
|
||||
val hash: String,
|
||||
val mediaType: String,
|
||||
val size: Long? = null,
|
||||
val matchCount: Int,
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package org.gotson.komga.domain.persistence
|
||||
|
||||
import org.gotson.komga.domain.model.PageHash
|
||||
import org.gotson.komga.domain.model.PageHashMatch
|
||||
import org.gotson.komga.domain.model.PageHashUnknown
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
|
||||
interface PageHashRepository {
|
||||
fun findAllKnown(actions: List<PageHash.Action>?, pageable: Pageable): Page<PageHash>
|
||||
fun findAllUnknown(pageable: Pageable): Page<PageHashUnknown>
|
||||
|
||||
fun findMatchesByHash(hash: String, pageable: Pageable): Page<PageHashMatch>
|
||||
|
||||
fun getKnownThumbnail(hash: String): ByteArray?
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.gotson.komga.domain.service
|
||||
|
||||
import org.gotson.komga.domain.model.BookPageContent
|
||||
import org.gotson.komga.domain.persistence.BookRepository
|
||||
import org.gotson.komga.domain.persistence.PageHashRepository
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class PageHashLifecycle(
|
||||
private val pageHashRepository: PageHashRepository,
|
||||
private val bookLifecycle: BookLifecycle,
|
||||
private val bookRepository: BookRepository,
|
||||
) {
|
||||
|
||||
fun getPage(hash: String, resizeTo: Int? = null): BookPageContent? {
|
||||
val match = pageHashRepository.findMatchesByHash(hash, Pageable.ofSize(1)).firstOrNull() ?: return null
|
||||
val book = bookRepository.findByIdOrNull(match.bookId) ?: return null
|
||||
|
||||
return bookLifecycle.getBookPage(book, match.pageNumber, resizeTo = resizeTo)
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package org.gotson.komga.infrastructure.jooq
|
||||
|
||||
import org.gotson.komga.domain.model.PageHash
|
||||
import org.gotson.komga.domain.model.PageHashMatch
|
||||
import org.gotson.komga.domain.model.PageHashUnknown
|
||||
import org.gotson.komga.domain.persistence.PageHashRepository
|
||||
import org.gotson.komga.jooq.Tables
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.impl.DSL
|
||||
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.net.URL
|
||||
|
||||
@Component
|
||||
class PageHashDao(
|
||||
private val dsl: DSLContext,
|
||||
) : PageHashRepository {
|
||||
|
||||
private val p = Tables.MEDIA_PAGE
|
||||
private val b = Tables.BOOK
|
||||
|
||||
private val sorts = mapOf(
|
||||
"hash" to p.FILE_HASH,
|
||||
"mediatype" to p.MEDIA_TYPE,
|
||||
"size" to DSL.field("size"),
|
||||
"matchCount" to DSL.field("count"),
|
||||
"url" to b.URL,
|
||||
"bookId" to b.ID,
|
||||
"pageNumber" to p.NUMBER,
|
||||
)
|
||||
|
||||
override fun findAllKnown(actions: List<PageHash.Action>?, pageable: Pageable): Page<PageHash> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun findAllUnknown(pageable: Pageable): Page<PageHashUnknown> {
|
||||
val query = dsl.select(
|
||||
p.FILE_HASH,
|
||||
p.MEDIA_TYPE,
|
||||
p.FILE_SIZE,
|
||||
DSL.count(p.BOOK_ID).`as`("count"),
|
||||
)
|
||||
.from(p)
|
||||
.where(p.FILE_HASH.ne(""))
|
||||
.groupBy(p.FILE_HASH, p.MEDIA_TYPE, p.FILE_SIZE)
|
||||
.having(DSL.count(p.BOOK_ID).gt(1))
|
||||
|
||||
val count = dsl.fetchCount(query)
|
||||
|
||||
val orderBy = pageable.sort.toOrderBy(sorts)
|
||||
val items = query
|
||||
.orderBy(orderBy)
|
||||
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
|
||||
.fetch {
|
||||
PageHashUnknown(it.value1(), it.value2(), it.value3(), it.value4())
|
||||
}
|
||||
|
||||
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
|
||||
return PageImpl(
|
||||
items,
|
||||
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort)
|
||||
else PageRequest.of(0, maxOf(count, 20), pageSort),
|
||||
count.toLong(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun findMatchesByHash(hash: String, pageable: Pageable): Page<PageHashMatch> {
|
||||
val query = dsl.select(p.BOOK_ID, b.URL, p.NUMBER)
|
||||
.from(p)
|
||||
.leftJoin(b).on(p.BOOK_ID.eq(b.ID))
|
||||
.where(p.FILE_HASH.eq(hash))
|
||||
|
||||
val count = dsl.fetchCount(query)
|
||||
|
||||
val orderBy = pageable.sort.toOrderBy(sorts)
|
||||
val items = query
|
||||
.orderBy(orderBy)
|
||||
.apply { if (pageable.isPaged) limit(pageable.pageSize).offset(pageable.offset) }
|
||||
.fetch {
|
||||
PageHashMatch(
|
||||
bookId = it.value1(),
|
||||
url = URL(it.value2()),
|
||||
pageNumber = it.value3() + 1,
|
||||
)
|
||||
}
|
||||
|
||||
val pageSort = if (orderBy.isNotEmpty()) pageable.sort else Sort.unsorted()
|
||||
return PageImpl(
|
||||
items,
|
||||
if (pageable.isPaged) PageRequest.of(pageable.pageNumber, pageable.pageSize, pageSort)
|
||||
else PageRequest.of(0, maxOf(count, 20), pageSort),
|
||||
count.toLong(),
|
||||
)
|
||||
}
|
||||
|
||||
override fun getKnownThumbnail(hash: String): ByteArray? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package org.gotson.komga.interfaces.api.rest
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter
|
||||
import io.swagger.v3.oas.annotations.media.Content
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||
import org.gotson.komga.domain.model.PageHash
|
||||
import org.gotson.komga.domain.model.ROLE_ADMIN
|
||||
import org.gotson.komga.domain.persistence.PageHashRepository
|
||||
import org.gotson.komga.domain.service.PageHashLifecycle
|
||||
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
||||
import org.gotson.komga.infrastructure.web.getMediaTypeOrDefault
|
||||
import org.gotson.komga.interfaces.api.rest.dto.PageHashDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.PageHashMatchDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.PageHashUnknownDto
|
||||
import org.gotson.komga.interfaces.api.rest.dto.toDto
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.PutMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
|
||||
@RestController
|
||||
@RequestMapping("api/v1/page-hashes", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
@PreAuthorize("hasRole('$ROLE_ADMIN')")
|
||||
class PageHashController(
|
||||
private val pageHashRepository: PageHashRepository,
|
||||
private val pageHashLifecycle: PageHashLifecycle,
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
@PageableAsQueryParam
|
||||
fun getPageHashes(
|
||||
@RequestParam(name = "action", required = false) actions: List<PageHash.Action>?,
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<PageHashDto> =
|
||||
pageHashRepository.findAllKnown(actions, page).map { it.toDto() }
|
||||
|
||||
@GetMapping("/{hash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE],)
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
fun getPageHashThumbnail(@PathVariable hash: String): ByteArray =
|
||||
pageHashRepository.getKnownThumbnail(hash) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@GetMapping("/unknown")
|
||||
@PageableAsQueryParam
|
||||
fun getUnknownPageHashes(
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<PageHashUnknownDto> =
|
||||
pageHashRepository.findAllUnknown(page).map { it.toDto() }
|
||||
|
||||
@GetMapping("unknown/{hash}")
|
||||
@PageableAsQueryParam
|
||||
fun getUnknownPageHashMatches(
|
||||
@PathVariable hash: String,
|
||||
@Parameter(hidden = true) page: Pageable,
|
||||
): Page<PageHashMatchDto> =
|
||||
pageHashRepository.findMatchesByHash(hash, page).map { it.toDto() }
|
||||
|
||||
@GetMapping("unknown/{hash}/thumbnail", produces = [MediaType.IMAGE_JPEG_VALUE],)
|
||||
@ApiResponse(content = [Content(schema = Schema(type = "string", format = "binary"))])
|
||||
fun getUnknownPageHashThumbnail(
|
||||
@PathVariable hash: String,
|
||||
@RequestParam("resize") resize: Int? = null,
|
||||
): ResponseEntity<ByteArray> =
|
||||
pageHashLifecycle.getPage(hash, resize)?.let {
|
||||
ResponseEntity.ok()
|
||||
.contentType(getMediaTypeOrDefault(it.mediaType))
|
||||
.body(it.content)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
@PutMapping
|
||||
@ResponseStatus(HttpStatus.ACCEPTED)
|
||||
fun updatePageHash() {
|
||||
TODO()
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package org.gotson.komga.interfaces.api.rest.dto
|
||||
|
||||
import org.gotson.komga.domain.model.PageHash
|
||||
import java.time.LocalDateTime
|
||||
|
||||
data class PageHashDto(
|
||||
val hash: String,
|
||||
val mediaType: String,
|
||||
val size: Long?,
|
||||
val action: PageHash.Action,
|
||||
val deleteCount: Int,
|
||||
|
||||
val created: LocalDateTime,
|
||||
val lastModified: LocalDateTime,
|
||||
)
|
||||
|
||||
fun PageHash.toDto() = PageHashDto(
|
||||
hash = hash,
|
||||
mediaType = mediaType,
|
||||
size = size,
|
||||
action = action,
|
||||
deleteCount = deleteCount,
|
||||
created = createdDate,
|
||||
lastModified = lastModifiedDate,
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package org.gotson.komga.interfaces.api.rest.dto
|
||||
|
||||
import org.gotson.komga.domain.model.PageHashMatch
|
||||
import org.gotson.komga.infrastructure.web.toFilePath
|
||||
|
||||
data class PageHashMatchDto(
|
||||
val bookId: String,
|
||||
val url: String,
|
||||
val pageNumber: Int,
|
||||
)
|
||||
|
||||
fun PageHashMatch.toDto() =
|
||||
PageHashMatchDto(
|
||||
bookId = bookId,
|
||||
url = url.toFilePath(),
|
||||
pageNumber = pageNumber,
|
||||
)
|
@ -0,0 +1,19 @@
|
||||
package org.gotson.komga.interfaces.api.rest.dto
|
||||
|
||||
import com.jakewharton.byteunits.BinaryByteUnit
|
||||
import org.gotson.komga.domain.model.PageHashUnknown
|
||||
|
||||
data class PageHashUnknownDto(
|
||||
val hash: String,
|
||||
val mediaType: String,
|
||||
val sizeBytes: Long?,
|
||||
val size: String? = sizeBytes?.let { BinaryByteUnit.format(it) },
|
||||
val matchCount: Int,
|
||||
)
|
||||
|
||||
fun PageHashUnknown.toDto() = PageHashUnknownDto(
|
||||
hash = hash,
|
||||
mediaType = mediaType,
|
||||
sizeBytes = size,
|
||||
matchCount = matchCount,
|
||||
)
|
Loading…
Reference in New Issue
Block a user