feat(api): wip version of the page-hashes endpoints

This commit is contained in:
Gauthier Roebroeck 2022-01-26 18:18:23 +08:00
parent fc33ad7ed3
commit 5777952c05
10 changed files with 324 additions and 0 deletions

View File

@ -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,
}
}

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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?
}

View File

@ -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)
}
}

View File

@ -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")
}
}

View File

@ -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()
}
}

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)