rename serie to series and other polish

This commit is contained in:
Gauthier Roebroeck 2019-10-08 15:01:26 +08:00
parent 17ee332ad8
commit 397b7b1952
20 changed files with 303 additions and 286 deletions

View file

@ -37,8 +37,8 @@ class Book(
@NotNull
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "serie_id", nullable = false)
lateinit var serie: Serie
@JoinColumn(name = "series_id", nullable = false)
lateinit var series: Series
@OneToOne(optional = false, orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
@JoinColumn(name = "book_metadata_id", nullable = false)
@ -51,4 +51,4 @@ class Book(
override fun toString(): String = url.toURI().path
}
fun Book.path(): Path = Paths.get(this.url.toURI())
fun Book.path(): Path = Paths.get(this.url.toURI())

View file

@ -21,8 +21,8 @@ import javax.validation.constraints.NotNull
private val natSortComparator: Comparator<String> = CaseInsensitiveSimpleNaturalComparator.getInstance()
@Entity
@Table(name = "serie")
class Serie(
@Table(name = "series")
class Series(
@NotBlank
@Column(name = "name", nullable = false)
var name: String,
@ -46,7 +46,7 @@ class Serie(
@JoinColumn(name = "library_id", nullable = false)
lateinit var library: Library
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "serie")
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "series")
@OrderColumn(name = "index")
private var _books: MutableList<Book> = mutableListOf()
@ -54,7 +54,7 @@ class Serie(
get() = _books.toList()
set(value) {
_books.clear()
value.forEach { it.serie = this }
value.forEach { it.series = this }
_books.addAll(value.sortedWith(compareBy(natSortComparator) { it.name }))
}
@ -63,4 +63,4 @@ class Serie(
}
override fun toString(): String = url.toURI().path
}
}

View file

@ -12,19 +12,19 @@ import java.net.URL
@Repository
interface BookRepository : JpaRepository<Book, Long> {
@Query(
value = "select * from Book b where b.serie_id = ?1 order by b.index",
countQuery = "select count(*) from Book where serie_id = ?1",
value = "select * from Book b where b.series_id = ?1 order by b.index",
countQuery = "select count(*) from Book where series_id = ?1",
nativeQuery = true)
fun findAllBySerieId(serieId: Long, pageable: Pageable): Page<Book>
fun findAllBySeriesId(seriesId: Long, pageable: Pageable): Page<Book>
@Query(
value = "select * from Book b, Book_Metadata m where b.book_metadata_id = m.id " +
"and b.serie_id = ?2 and m.status = ?1 order by b.index",
countQuery = "select count(*) from Book b, Book_Metadata m where b.book_metadata_id = m.id and b.serie_id = ?2 and m.status = ?1",
"and b.series_id = ?2 and m.status = ?1 order by b.index",
countQuery = "select count(*) from Book b, Book_Metadata m where b.book_metadata_id = m.id and b.series_id = ?2 and m.status = ?1",
nativeQuery = true)
fun findAllByMetadataStatusAndSerieId(status: String, serieId: Long, pageable: Pageable): Page<Book>
fun findAllByMetadataStatusAndSeriesId(status: String, seriesId: Long, pageable: Pageable): Page<Book>
fun findByUrl(url: URL): Book?
fun findAllByMetadataStatus(status: Status): List<Book>
fun findAllByMetadataThumbnailIsNull(): List<Book>
}
}

View file

@ -1,14 +1,14 @@
package org.gotson.komga.domain.persistence
import org.gotson.komga.domain.model.Serie
import org.gotson.komga.domain.model.Series
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.JpaSpecificationExecutor
import org.springframework.stereotype.Repository
import java.net.URL
@Repository
interface SerieRepository : JpaRepository<Serie, Long>, JpaSpecificationExecutor<Serie> {
fun findByLibraryIdAndUrlNotIn(libraryId: Long, urls: Iterable<URL>): List<Serie>
fun findByLibraryIdAndUrl(libraryId: Long, url: URL): Serie?
interface SeriesRepository : JpaRepository<Series, Long>, JpaSpecificationExecutor<Series> {
fun findByLibraryIdAndUrlNotIn(libraryId: Long, urls: Iterable<URL>): List<Series>
fun findByLibraryIdAndUrl(libraryId: Long, url: URL): Series?
fun deleteByLibraryId(libraryId: Long)
}
}

View file

@ -16,7 +16,7 @@ class AsyncOrchestrator(
private val libraryScanner: LibraryScanner,
private val libraryRepository: LibraryRepository,
private val bookRepository: BookRepository,
private val bookLifecyle: BookLifecyle
private val bookLifecycle: BookLifecycle
) {
@Async("periodicScanTaskExecutor")
@ -52,7 +52,7 @@ class AsyncOrchestrator(
var sumOfTasksTime = 0L
measureTimeMillis {
sumOfTasksTime = books
.map { bookLifecyle.regenerateThumbnailAndPersist(it) }
.map { bookLifecycle.regenerateThumbnailAndPersist(it) }
.map {
try {
it.get()
@ -65,4 +65,4 @@ class AsyncOrchestrator(
logger.info { "Generated ${books.size} thumbnails in ${DurationFormatUtils.formatDurationHMS(it)} (virtual: ${DurationFormatUtils.formatDurationHMS(sumOfTasksTime)})" }
}
}
}
}

View file

@ -20,7 +20,7 @@ import kotlin.system.measureTimeMillis
private val logger = KotlinLogging.logger {}
@Service
class BookLifecyle(
class BookLifecycle(
private val bookRepository: BookRepository,
private val bookParser: BookParser,
private val imageConverter: ImageConverter
@ -89,4 +89,4 @@ class BookLifecyle(
return BookPageContent(number, pageContent, pageMediaType)
}
}
}

View file

@ -4,7 +4,7 @@ import mu.KotlinLogging
import org.apache.commons.io.FilenameUtils
import org.apache.commons.lang3.time.DurationFormatUtils
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.Serie
import org.gotson.komga.domain.model.Series
import org.springframework.stereotype.Service
import java.nio.file.Files
import java.nio.file.Path
@ -23,11 +23,11 @@ class FileSystemScanner {
val supportedExtensions = listOf("cbz", "zip", "cbr", "rar", "pdf")
fun scanRootFolder(root: Path): List<Serie> {
fun scanRootFolder(root: Path): List<Series> {
logger.info { "Scanning folder: $root" }
logger.info { "Supported extensions: $supportedExtensions" }
lateinit var scannedSeries: List<Serie>
lateinit var scannedSeries: List<Series>
measureTimeMillis {
scannedSeries = Files.walk(root).use { dirsStream ->
@ -47,7 +47,7 @@ class FileSystemScanner {
}.toList()
}
if (books.isNullOrEmpty()) return@mapNotNull null
Serie(
Series(
name = dir.fileName.toString(),
url = dir.toUri().toURL(),
fileLastModified = dir.getUpdatedTime(),
@ -70,4 +70,4 @@ fun Path.getUpdatedTime(): LocalDateTime =
}
fun FileTime.toLocalDateTime(): LocalDateTime =
LocalDateTime.ofInstant(this.toInstant(), ZoneId.systemDefault())
LocalDateTime.ofInstant(this.toInstant(), ZoneId.systemDefault())

View file

@ -7,7 +7,7 @@ import org.gotson.komga.domain.model.Library
import org.gotson.komga.domain.model.PathContainedInPath
import org.gotson.komga.domain.model.path
import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.domain.persistence.SerieRepository
import org.gotson.komga.domain.persistence.SeriesRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.io.FileNotFoundException
@ -19,7 +19,7 @@ private val logger = KotlinLogging.logger {}
@Service
class LibraryLifecycle(
private val libraryRepository: LibraryRepository,
private val serieRepository: SerieRepository,
private val seriesRepository: SeriesRepository,
private val asyncOrchestrator: AsyncOrchestrator
) {
@ -64,7 +64,7 @@ class LibraryLifecycle(
@Transactional
fun deleteLibrary(library: Library) {
logger.info { "Deleting library: ${library.name} with root folder: ${library.root}" }
serieRepository.deleteByLibraryId(library.id)
seriesRepository.deleteByLibraryId(library.id)
libraryRepository.delete(library)
}
}
}

View file

@ -5,7 +5,7 @@ import org.apache.commons.lang3.time.DurationFormatUtils
import org.gotson.komga.domain.model.Library
import org.gotson.komga.domain.model.Status
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.SerieRepository
import org.gotson.komga.domain.persistence.SeriesRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.nio.file.Paths
@ -16,46 +16,46 @@ private val logger = KotlinLogging.logger {}
@Service
class LibraryScanner(
private val fileSystemScanner: FileSystemScanner,
private val serieRepository: SerieRepository,
private val seriesRepository: SeriesRepository,
private val bookRepository: BookRepository,
private val bookLifecyle: BookLifecyle
private val bookLifecycle: BookLifecycle
) {
@Transactional
fun scanRootFolder(library: Library) {
logger.info { "Updating library: ${library.name}, root folder: ${library.root}" }
measureTimeMillis {
val series = fileSystemScanner.scanRootFolder(Paths.get(library.root.toURI()))
val scannedSeries = fileSystemScanner.scanRootFolder(Paths.get(library.root.toURI()))
// delete series that don't exist anymore
if (series.isEmpty()) {
if (scannedSeries.isEmpty()) {
logger.info { "Scan returned no series, deleting all existing series" }
serieRepository.deleteByLibraryId(library.id)
seriesRepository.deleteByLibraryId(library.id)
} else {
series.map { it.url }.let { urls ->
serieRepository.findByLibraryIdAndUrlNotIn(library.id, urls).forEach {
logger.info { "Deleting serie not on disk anymore: $it" }
serieRepository.delete(it)
scannedSeries.map { it.url }.let { urls ->
seriesRepository.findByLibraryIdAndUrlNotIn(library.id, urls).forEach {
logger.info { "Deleting series not on disk anymore: $it" }
seriesRepository.delete(it)
}
}
}
series.forEach { newSerie ->
val existingSerie = serieRepository.findByLibraryIdAndUrl(library.id, newSerie.url)
scannedSeries.forEach { newSeries ->
val existingSeries = seriesRepository.findByLibraryIdAndUrl(library.id, newSeries.url)
// if serie does not exist, save it
if (existingSerie == null) {
logger.info { "Adding new serie: $newSerie" }
serieRepository.save(newSerie.also { it.library = library })
// if series does not exist, save it
if (existingSeries == null) {
logger.info { "Adding new series: $newSeries" }
seriesRepository.save(newSeries.also { it.library = library })
} else {
// if serie already exists, update it
if (newSerie.fileLastModified != existingSerie.fileLastModified) {
logger.info { "Serie changed on disk, updating: $newSerie" }
existingSerie.name = newSerie.name
existingSerie.fileLastModified = newSerie.fileLastModified
// if series already exists, update it
if (newSeries.fileLastModified != existingSeries.fileLastModified) {
logger.info { "Series changed on disk, updating: $newSeries" }
existingSeries.name = newSeries.name
existingSeries.fileLastModified = newSeries.fileLastModified
// update list of books with existing entities if they exist
existingSerie.books = newSerie.books.map { newBook ->
existingSeries.books = newSeries.books.map { newBook ->
val existingBook = bookRepository.findByUrl(newBook.url) ?: newBook
if (newBook.fileLastModified != existingBook.fileLastModified) {
@ -67,7 +67,7 @@ class LibraryScanner(
existingBook
}.toMutableList()
serieRepository.save(existingSerie)
seriesRepository.save(existingSeries)
}
}
}
@ -81,7 +81,7 @@ class LibraryScanner(
var sumOfTasksTime = 0L
measureTimeMillis {
sumOfTasksTime = booksToParse
.map { bookLifecyle.parseAndPersist(it) }
.map { bookLifecycle.parseAndPersist(it) }
.map {
try {
it.get()
@ -96,4 +96,4 @@ class LibraryScanner(
}
}
}

View file

@ -2,8 +2,8 @@ package org.gotson.komga.interfaces.web.opds
import com.github.klinq.jpaspec.likeLower
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.Serie
import org.gotson.komga.domain.persistence.SerieRepository
import org.gotson.komga.domain.model.Series
import org.gotson.komga.domain.persistence.SeriesRepository
import org.gotson.komga.interfaces.web.opds.dto.OpdsAuthor
import org.gotson.komga.interfaces.web.opds.dto.OpdsEntryAcquisition
import org.gotson.komga.interfaces.web.opds.dto.OpdsEntryNavigation
@ -44,7 +44,7 @@ private const val ID_SERIES_LATEST = "latestSeries"
@RestController
@RequestMapping(value = [ROUTE_BASE], produces = [MediaType.APPLICATION_ATOM_XML_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE])
class OpdsController(
private val serieRepository: SerieRepository
private val seriesRepository: SeriesRepository
) {
private val komgaAuthor = OpdsAuthor("Komga", URI("https://github.com/gotson/komga"))
@ -86,10 +86,10 @@ class OpdsController(
): OpdsFeed {
val sort = Sort.by(Sort.Order.asc("name").ignoreCase())
val series = if (!searchTerm.isNullOrEmpty()) {
val spec = Serie::name.likeLower("%$searchTerm%")
serieRepository.findAll(spec, sort)
val spec = Series::name.likeLower("%$searchTerm%")
seriesRepository.findAll(spec, sort)
} else {
serieRepository.findAll(sort)
seriesRepository.findAll(sort)
}
return OpdsFeedNavigation(
@ -107,7 +107,7 @@ class OpdsController(
@GetMapping(ROUTE_SERIES_LATEST)
fun getLatestSeries(): OpdsFeed {
val series = serieRepository.findAll(Sort(Sort.Direction.DESC, "lastModifiedDate"))
val series = seriesRepository.findAll(Sort(Sort.Direction.DESC, "lastModifiedDate"))
return OpdsFeedNavigation(
id = ID_SERIES_LATEST,
title = "Latest series",
@ -122,24 +122,24 @@ class OpdsController(
}
@GetMapping("series/{id}")
fun getOneSerie(
fun getOneSeries(
@PathVariable id: Long
): OpdsFeed =
serieRepository.findByIdOrNull(id)?.let {
seriesRepository.findByIdOrNull(id)?.let { series ->
OpdsFeedAcquisition(
id = it.id.toString(),
title = it.name,
updated = it.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(),
id = series.id.toString(),
title = series.name,
updated = series.lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(),
author = komgaAuthor,
links = listOf(
OpdsLinkFeedNavigation(OpdsLinkRel.SELF, "${ROUTE_BASE}series/$id"),
linkStart
),
entries = it.books.map { it.toOpdsEntry() }
entries = series.books.map { it.toOpdsEntry() }
)
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
private fun Serie.toOpdsEntry() =
private fun Series.toOpdsEntry() =
OpdsEntryNavigation(
title = name,
updated = lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(),
@ -155,11 +155,11 @@ class OpdsController(
id = id.toString(),
content = "",
links = listOf(
OpdsLinkImageThumbnail("image/png", "/api/v1/series/${serie.id}/books/$id/thumbnail"),
OpdsLinkImage(metadata.pages[0].mediaType, "/api/v1/series/${serie.id}/books/$id/pages/1"),
OpdsLinkImageThumbnail("image/png", "/api/v1/series/${series.id}/books/$id/thumbnail"),
OpdsLinkImage(metadata.pages[0].mediaType, "/api/v1/series/${series.id}/books/$id/pages/1"),
OpdsLinkFileAcquisition(metadata.mediaType
?: "application/octet-stream", "/api/v1/series/${serie.id}/books/$id/file"),
OpdsLinkPageStreaming("image/jpeg", "/api/v1/series/${serie.id}/books/$id/pages/{pageNumber}?convert=jpeg&amp;zerobased=true", metadata.pages.size)
?: "application/octet-stream", "/api/v1/series/${series.id}/books/$id/file"),
OpdsLinkPageStreaming("image/jpeg", "/api/v1/series/${series.id}/books/$id/pages/{pageNumber}?convert=jpeg&amp;zerobased=true", metadata.pages.size)
)
)
}
}

View file

@ -70,10 +70,10 @@ class OpdsLinkPageStreaming(
class OpdsLinkRel {
companion object {
val SELF = "self"
val START = "start"
val SUBSECTION = "subsection"
val SORT_NEW = "http://opds-spec.org/sort/new"
val SORT_POPULAR = "http://opds-spec.org/sort/popular"
const val SELF = "self"
const val START = "start"
const val SUBSECTION = "subsection"
const val SORT_NEW = "http://opds-spec.org/sort/new"
const val SORT_POPULAR = "http://opds-spec.org/sort/popular"
}
}
}

View file

@ -6,12 +6,12 @@ import mu.KotlinLogging
import org.apache.commons.io.FilenameUtils
import org.gotson.komga.domain.model.Book
import org.gotson.komga.domain.model.MetadataNotReadyException
import org.gotson.komga.domain.model.Serie
import org.gotson.komga.domain.model.Series
import org.gotson.komga.domain.model.Status
import org.gotson.komga.domain.model.UnsupportedMediaTypeException
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.SerieRepository
import org.gotson.komga.domain.service.BookLifecyle
import org.gotson.komga.domain.persistence.SeriesRepository
import org.gotson.komga.domain.service.BookLifecycle
import org.gotson.komga.infrastructure.image.ImageType
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
@ -40,10 +40,10 @@ private val logger = KotlinLogging.logger {}
@RestController
@RequestMapping("api/v1/series", produces = [MediaType.APPLICATION_JSON_VALUE])
class SerieController(
private val serieRepository: SerieRepository,
class SeriesController(
private val seriesRepository: SeriesRepository,
private val bookRepository: BookRepository,
private val bookLifecyle: BookLifecyle
private val bookLifecycle: BookLifecycle
) {
@GetMapping
@ -52,7 +52,7 @@ class SerieController(
searchTerm: String?,
page: Pageable
): Page<SerieDto> {
): Page<SeriesDto> {
val pageRequest = PageRequest.of(
page.pageNumber,
page.pageSize,
@ -60,81 +60,81 @@ class SerieController(
else Sort.by(Sort.Order.asc("name").ignoreCase())
)
return if (!searchTerm.isNullOrEmpty()) {
val spec = Serie::name.likeLower("%$searchTerm%")
serieRepository.findAll(spec, pageRequest)
val spec = Series::name.likeLower("%$searchTerm%")
seriesRepository.findAll(spec, pageRequest)
} else {
serieRepository.findAll(pageRequest)
seriesRepository.findAll(pageRequest)
}.map { it.toDto() }
}
@GetMapping("/latest")
fun getLatestSeries(
page: Pageable
): Page<SerieDto> {
): Page<SeriesDto> {
val pageRequest = PageRequest.of(
page.pageNumber,
page.pageSize,
Sort(Sort.Direction.DESC, "lastModifiedDate")
)
return serieRepository.findAll(pageRequest).map { it.toDto() }
return seriesRepository.findAll(pageRequest).map { it.toDto() }
}
@GetMapping("{id}")
fun getOneSerie(
fun getOneSeries(
@PathVariable id: Long
): SerieDto =
serieRepository.findByIdOrNull(id)?.toDto() ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
): SeriesDto =
seriesRepository.findByIdOrNull(id)?.toDto() ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
@GetMapping(value = ["{serieId}/thumbnail"], produces = [MediaType.IMAGE_PNG_VALUE])
fun getSerieThumbnail(
@PathVariable serieId: Long
@GetMapping(value = ["{seriesId}/thumbnail"], produces = [MediaType.IMAGE_PNG_VALUE])
fun getSeriesThumbnail(
@PathVariable seriesId: Long
): ByteArray {
return serieRepository.findByIdOrNull(serieId)?.let {
return seriesRepository.findByIdOrNull(seriesId)?.let {
it.books.firstOrNull()?.metadata?.thumbnail ?: throw ResponseStatusException(HttpStatus.NO_CONTENT)
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
@GetMapping("{id}/books")
fun getAllBooksBySerie(
fun getAllBooksBySeries(
@PathVariable id: Long,
@RequestParam(value = "readyonly", defaultValue = "true") readyFilter: Boolean,
page: Pageable
): Page<BookDto> {
if (!serieRepository.existsById(id)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
if (!seriesRepository.existsById(id)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
return if (readyFilter) {
bookRepository.findAllByMetadataStatusAndSerieId(Status.READY.name, id, page)
bookRepository.findAllByMetadataStatusAndSeriesId(Status.READY.name, id, page)
} else {
bookRepository.findAllBySerieId(id, page)
bookRepository.findAllBySeriesId(id, page)
}.map { it.toDto() }
}
@GetMapping("{serieId}/books/{bookId}")
@GetMapping("{seriesId}/books/{bookId}")
fun getOneBook(
@PathVariable serieId: Long,
@PathVariable seriesId: Long,
@PathVariable bookId: Long
): BookDto {
if (!serieRepository.existsById(serieId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
if (!seriesRepository.existsById(seriesId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
return bookRepository.findByIdOrNull(bookId)?.toDto() ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
@GetMapping(value = ["{serieId}/books/{bookId}/thumbnail"], produces = [MediaType.IMAGE_PNG_VALUE])
@GetMapping(value = ["{seriesId}/books/{bookId}/thumbnail"], produces = [MediaType.IMAGE_PNG_VALUE])
fun getBookThumbnail(
@PathVariable serieId: Long,
@PathVariable seriesId: Long,
@PathVariable bookId: Long
): ByteArray {
if (!serieRepository.existsById(serieId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
if (!seriesRepository.existsById(seriesId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
return bookRepository.findByIdOrNull(bookId)?.let {
it.metadata.thumbnail ?: throw ResponseStatusException(HttpStatus.NO_CONTENT)
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
@GetMapping("{serieId}/books/{bookId}/file")
@GetMapping("{seriesId}/books/{bookId}/file")
fun getBookFile(
@PathVariable serieId: Long,
@PathVariable seriesId: Long,
@PathVariable bookId: Long
): ResponseEntity<ByteArray> {
if (!serieRepository.existsById(serieId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
if (!seriesRepository.existsById(seriesId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
return bookRepository.findByIdOrNull(bookId)?.let { book ->
try {
ResponseEntity.ok()
@ -152,12 +152,12 @@ class SerieController(
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
@GetMapping("{serieId}/books/{bookId}/pages")
@GetMapping("{seriesId}/books/{bookId}/pages")
fun getBookPages(
@PathVariable serieId: Long,
@PathVariable seriesId: Long,
@PathVariable bookId: Long
): List<PageDto> {
if (!serieRepository.existsById(serieId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
if (!seriesRepository.existsById(seriesId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
return bookRepository.findByIdOrNull((bookId))?.let {
if (it.metadata.status == Status.UNKNOWN) throw ResponseStatusException(HttpStatus.NO_CONTENT, "Book is not parsed yet")
@ -167,15 +167,15 @@ class SerieController(
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
}
@GetMapping("{serieId}/books/{bookId}/pages/{pageNumber}")
@GetMapping("{seriesId}/books/{bookId}/pages/{pageNumber}")
fun getBookPage(
@PathVariable serieId: Long,
@PathVariable seriesId: Long,
@PathVariable bookId: Long,
@PathVariable pageNumber: Int,
@RequestParam(value = "convert") convertTo: String?,
@RequestParam(value = "zerobased", defaultValue = "false") zeroBasedIndex: Boolean
): ResponseEntity<ByteArray> {
if (!serieRepository.existsById(serieId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
if (!seriesRepository.existsById(seriesId)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
return bookRepository.findByIdOrNull((bookId))?.let { book ->
try {
@ -189,7 +189,7 @@ class SerieController(
val pageNum = if (zeroBasedIndex) pageNumber + 1 else pageNumber
val pageContent = try {
bookLifecyle.getBookPage(book, pageNum, convertFormat)
bookLifecycle.getBookPage(book, pageNum, convertFormat)
} catch (e: UnsupportedMediaTypeException) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, e.message)
} catch (e: Exception) {
@ -221,7 +221,7 @@ class SerieController(
}
}
data class SerieDto(
data class SeriesDto(
val id: Long,
val name: String,
val url: String,
@ -229,7 +229,7 @@ data class SerieDto(
val lastModified: LocalDateTime?
)
fun Serie.toDto() = SerieDto(
fun Series.toDto() = SeriesDto(
id = id,
name = name,
url = url.toString(),
@ -270,4 +270,4 @@ data class PageDto(
)
fun LocalDateTime.toUTC(): LocalDateTime =
atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()
atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime()

View file

@ -0,0 +1,17 @@
alter table serie
rename to series;
alter table series
rename constraint fk_serie_library_library_id to fk_series_library_library_id;
alter index if exists fk_serie_library_library_id_index_4 rename to fk_series_library_library_id_index_4;
alter table book
alter column serie_id
rename to series_id;
alter table book
rename constraint fk_book_serie_serie_id to fk_book_series_series_id;
alter index if exists fk_book_serie_serie_id_index_1 rename to fk_book_series_series_id_index_1;

View file

@ -8,9 +8,9 @@ fun makeBook(name: String, url: String = "file:/$name", fileLastModified: LocalD
return Book(name = name, url = URL(url), fileLastModified = fileLastModified)
}
fun makeSerie(name: String, url: String = "file:/$name", books: List<Book> = listOf()): Serie {
fun makeSeries(name: String, url: String = "file:/$name", books: List<Book> = listOf()): Series {
Thread.sleep(5)
return Serie(name = name, url = URL(url), fileLastModified = LocalDateTime.now(), books = books.toMutableList())
return Series(name = name, url = URL(url), fileLastModified = LocalDateTime.now(), books = books.toMutableList())
}
fun makeLibrary(name: String = "default", url: String = "file:/$name"): Library {
@ -18,4 +18,4 @@ fun makeLibrary(name: String = "default", url: String = "file:/$name"): Library
}
fun makeBookPage(name: String) =
BookPage(name, "image/png")
BookPage(name, "image/png")

View file

@ -3,7 +3,7 @@ package org.gotson.komga.domain.persistence
import org.assertj.core.api.Assertions.assertThat
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.model.makeSerie
import org.gotson.komga.domain.model.makeSeries
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
@ -19,7 +19,7 @@ import java.time.LocalDateTime
@DataJpaTest
@Transactional
class AuditableEntityTest(
@Autowired private val serieRepository: SerieRepository,
@Autowired private val seriesRepository: SeriesRepository,
@Autowired private val libraryRepository: LibraryRepository
) {
@ -37,81 +37,81 @@ class AuditableEntityTest(
@AfterEach
fun `clear repository`() {
serieRepository.deleteAll()
seriesRepository.deleteAll()
}
@Test
fun `given serie with book when saving then created and modified date is also saved`() {
fun `given series with book when saving then created and modified date is also saved`() {
// given
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))).also { it.library = library }
val series = makeSeries(name = "series", books = listOf(makeBook("book1"))).also { it.library = library }
// when
serieRepository.save(serie)
seriesRepository.save(series)
// then
assertThat(serie.createdDate).isBefore(LocalDateTime.now())
assertThat(serie.lastModifiedDate).isBefore(LocalDateTime.now())
assertThat(serie.books.first().createdDate).isBefore(LocalDateTime.now())
assertThat(serie.books.first().lastModifiedDate).isBefore(LocalDateTime.now())
assertThat(series.createdDate).isBefore(LocalDateTime.now())
assertThat(series.lastModifiedDate).isBefore(LocalDateTime.now())
assertThat(series.books.first().createdDate).isBefore(LocalDateTime.now())
assertThat(series.books.first().lastModifiedDate).isBefore(LocalDateTime.now())
}
@Test
fun `given existing serie with book when updating serie only then created date is kept and modified date is changed for serie only`() {
fun `given existing series with book when updating series only then created date is kept and modified date is changed for series only`() {
// given
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))).also { it.library = library }
val series = makeSeries(name = "series", books = listOf(makeBook("book1"))).also { it.library = library }
serieRepository.save(serie)
seriesRepository.save(series)
val creationTimeApprox = LocalDateTime.now()
Thread.sleep(1000)
// when
serie.name = "serieUpdated"
serieRepository.saveAndFlush(serie)
series.name = "seriesUpdated"
seriesRepository.saveAndFlush(series)
val modificationTimeApprox = LocalDateTime.now()
// then
assertThat(serie.createdDate)
assertThat(series.createdDate)
.isBefore(creationTimeApprox)
.isNotEqualTo(serie.lastModifiedDate)
assertThat(serie.lastModifiedDate)
.isNotEqualTo(series.lastModifiedDate)
assertThat(series.lastModifiedDate)
.isAfter(creationTimeApprox)
.isBefore(modificationTimeApprox)
assertThat(serie.books.first().createdDate)
assertThat(series.books.first().createdDate)
.isBefore(creationTimeApprox)
.isEqualTo(serie.books.first().lastModifiedDate)
.isEqualTo(series.books.first().lastModifiedDate)
}
@Test
fun `given existing serie with book when updating book only then created date is kept and modified date is changed for book only`() {
fun `given existing series with book when updating book only then created date is kept and modified date is changed for book only`() {
// given
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))).also { it.library = library }
val series = makeSeries(name = "series", books = listOf(makeBook("book1"))).also { it.library = library }
serieRepository.save(serie)
seriesRepository.save(series)
val creationTimeApprox = LocalDateTime.now()
Thread.sleep(1000)
// when
serie.books.first().name = "bookUpdated"
serieRepository.saveAndFlush(serie)
series.books.first().name = "bookUpdated"
seriesRepository.saveAndFlush(series)
val modificationTimeApprox = LocalDateTime.now()
// then
assertThat(serie.createdDate)
assertThat(series.createdDate)
.isBefore(creationTimeApprox)
.isEqualTo(serie.lastModifiedDate)
.isEqualTo(series.lastModifiedDate)
assertThat(serie.books.first().createdDate)
assertThat(series.books.first().createdDate)
.isBefore(creationTimeApprox)
.isNotEqualTo(serie.books.first().lastModifiedDate)
assertThat(serie.books.first().lastModifiedDate)
.isNotEqualTo(series.books.first().lastModifiedDate)
assertThat(series.books.first().lastModifiedDate)
.isAfter(creationTimeApprox)
.isBefore(modificationTimeApprox)
}
}
}

View file

@ -5,7 +5,7 @@ import org.gotson.komga.domain.model.BookMetadata
import org.gotson.komga.domain.model.Status
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.model.makeSerie
import org.gotson.komga.domain.model.makeSeries
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
@ -21,7 +21,7 @@ import org.springframework.transaction.annotation.Transactional
@DataJpaTest
@Transactional
class BookRepositoryTest(
@Autowired private val serieRepository: SerieRepository,
@Autowired private val seriesRepository: SeriesRepository,
@Autowired private val bookRepository: BookRepository,
@Autowired private val libraryRepository: LibraryRepository
) {
@ -40,22 +40,22 @@ class BookRepositoryTest(
@AfterEach
fun `clear repository`() {
serieRepository.deleteAll()
seriesRepository.deleteAll()
}
@Test
fun `given many books with unordered index when fetching then books are ordered and paged`() {
val serie = makeSerie(
name = "serie",
val series = makeSeries(
name = "series",
books = (1..100 step 2).map { makeBook("$it") }
).also { it.library = library }
serieRepository.save(serie)
seriesRepository.save(series)
serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) }
serieRepository.save(serie)
series.books = series.books.toMutableList().also { it.add(makeBook("2")) }
seriesRepository.save(series)
val pageable = PageRequest.of(0, 20)
val pageOfBooks = bookRepository.findAllBySerieId(serie.id, pageable)
val pageOfBooks = bookRepository.findAllBySeriesId(series.id, pageable)
assertThat(pageOfBooks.isFirst).isTrue()
assertThat(pageOfBooks.isLast).isFalse()
@ -67,18 +67,18 @@ class BookRepositoryTest(
@Test
fun `given many books in ready state with unordered index when fetching then books are ordered and paged`() {
val serie = makeSerie(
name = "serie",
val series = makeSeries(
name = "series",
books = (1..100 step 2).map { makeBook("$it") }
).also { it.library = library }
serieRepository.save(serie)
seriesRepository.save(series)
serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) }
serie.books.forEach { it.metadata = BookMetadata(Status.READY) }
serieRepository.save(serie)
series.books = series.books.toMutableList().also { it.add(makeBook("2")) }
series.books.forEach { it.metadata = BookMetadata(Status.READY) }
seriesRepository.save(series)
val pageable = PageRequest.of(0, 20)
val pageOfBooks = bookRepository.findAllBySerieId(serie.id, pageable)
val pageOfBooks = bookRepository.findAllBySeriesId(series.id, pageable)
assertThat(pageOfBooks.isFirst).isTrue()
assertThat(pageOfBooks.isLast).isFalse()
@ -87,4 +87,4 @@ class BookRepositoryTest(
assertThat(pageOfBooks.content).hasSize(20)
assertThat(pageOfBooks.content.map { it.name }).startsWith("1", "2", "3", "5")
}
}
}

View file

@ -6,7 +6,7 @@ import org.gotson.komga.domain.model.Status
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.model.makeBookPage
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.model.makeSerie
import org.gotson.komga.domain.model.makeSeries
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
@ -21,7 +21,7 @@ import org.springframework.transaction.annotation.Transactional
@DataJpaTest
@Transactional
class PersistenceTest(
@Autowired private val serieRepository: SerieRepository,
@Autowired private val seriesRepository: SeriesRepository,
@Autowired private val bookRepository: BookRepository,
@Autowired private val bookMetadataRepository: BookMetadataRepository,
@Autowired private val libraryRepository: LibraryRepository
@ -41,27 +41,27 @@ class PersistenceTest(
@AfterEach
fun `clear repository`() {
serieRepository.deleteAll()
seriesRepository.deleteAll()
}
@Test
fun `given serie with book when saving then metadata is also saved`() {
fun `given series with book when saving then metadata is also saved`() {
// given
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))).also { it.library = library }
val series = makeSeries(name = "series", books = listOf(makeBook("book1"))).also { it.library = library }
// when
serieRepository.save(serie)
seriesRepository.save(series)
// then
assertThat(serieRepository.count()).isEqualTo(1)
assertThat(seriesRepository.count()).isEqualTo(1)
assertThat(bookRepository.count()).isEqualTo(1)
assertThat(bookMetadataRepository.count()).isEqualTo(1)
}
@Test
fun `given serie with unordered books when saving then books are ordered with natural sort`() {
fun `given series with unordered books when saving then books are ordered with natural sort`() {
// given
val serie = makeSerie(name = "serie", books = listOf(
val series = makeSeries(name = "series", books = listOf(
makeBook("book 1"),
makeBook("book 05"),
makeBook("book 6"),
@ -69,20 +69,20 @@ class PersistenceTest(
)).also { it.library = library }
// when
serieRepository.save(serie)
seriesRepository.save(series)
// then
assertThat(serieRepository.count()).isEqualTo(1)
assertThat(seriesRepository.count()).isEqualTo(1)
assertThat(bookRepository.count()).isEqualTo(4)
assertThat(serieRepository.findAll().first().books.map { it.name })
assertThat(seriesRepository.findAll().first().books.map { it.name })
.containsExactly("book 1", "book 002", "book 05", "book 6")
}
@Test
fun `given existing book when updating metadata then new metadata is saved`() {
// given
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))).also { it.library = library }
serieRepository.save(serie)
val series = makeSeries(name = "series", books = listOf(makeBook("book1"))).also { it.library = library }
seriesRepository.save(series)
// when
val book = bookRepository.findAll().first()
@ -91,7 +91,7 @@ class PersistenceTest(
bookRepository.save(book)
// then
assertThat(serieRepository.count()).isEqualTo(1)
assertThat(seriesRepository.count()).isEqualTo(1)
assertThat(bookRepository.count()).isEqualTo(1)
assertThat(bookMetadataRepository.count()).isEqualTo(1)
bookMetadataRepository.findAll().first().let {
@ -105,8 +105,8 @@ class PersistenceTest(
@Test
fun `given book pages unordered when saving then pages are ordered with natural sort`() {
// given
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"))).also { it.library = library }
serieRepository.save(serie)
val series = makeSeries(name = "series", books = listOf(makeBook("book1"))).also { it.library = library }
seriesRepository.save(series)
// when
val book = bookRepository.findAll().first()
@ -119,14 +119,14 @@ class PersistenceTest(
bookRepository.save(book)
// then
assertThat(serieRepository.count()).isEqualTo(1)
assertThat(seriesRepository.count()).isEqualTo(1)
assertThat(bookRepository.count()).isEqualTo(1)
assertThat(bookMetadataRepository.count()).isEqualTo(1)
bookMetadataRepository.findAll().first().let {
assertThat(it.status == Status.READY)
assertThat(it.mediaType == "test")
assertThat(it.pages).hasSize(3)
assertThat(it.pages.map { it.fileName }).containsExactly("001", "2", "003")
bookMetadataRepository.findAll().first().let { metadata ->
assertThat(metadata.status == Status.READY)
assertThat(metadata.mediaType == "test")
assertThat(metadata.pages).hasSize(3)
assertThat(metadata.pages.map { it.fileName }).containsExactly("001", "2", "003")
}
}
}
}

View file

@ -24,7 +24,7 @@ class FileSystemScannerTest {
}
@Test
fun `given root directory with only files when scanning then return 1 serie containing those files as books`() {
fun `given root directory with only files when scanning then return 1 series containing those files as books`() {
Jimfs.newFileSystem(Configuration.unix()).use { fs ->
val root = fs.getPath("/root")
Files.createDirectory(root)
@ -41,7 +41,7 @@ class FileSystemScannerTest {
}
@Test
fun `given directory with unsupported files when scanning then return a serie excluding those files as books`() {
fun `given directory with unsupported files when scanning then return a series excluding those files as books`() {
Jimfs.newFileSystem(Configuration.unix()).use { fs ->
val root = fs.getPath("/root")
Files.createDirectory(root)
@ -58,14 +58,14 @@ class FileSystemScannerTest {
}
@Test
fun `given directory with sub-directories containing files when scanning then return 1 serie per folder containing direct files as books`() {
fun `given directory with sub-directories containing files when scanning then return 1 series per folder containing direct files as books`() {
Jimfs.newFileSystem(Configuration.unix()).use { fs ->
val root = fs.getPath("/root")
Files.createDirectory(root)
val subDirs = listOf(
"serie1" to listOf("volume1.cbz", "volume2.cbz"),
"serie2" to listOf("book1.cbz", "book2.cbz")
"series1" to listOf("volume1.cbz", "volume2.cbz"),
"series2" to listOf("book1.cbz", "book2.cbz")
).toMap()
subDirs.forEach { (dir, files) ->
@ -73,14 +73,14 @@ class FileSystemScannerTest {
files.forEach { Files.createFile(root.resolve(fs.getPath(dir, it))) }
}
val series = scanner.scanRootFolder(root)
val scannedSeries = scanner.scanRootFolder(root)
assertThat(series).hasSize(2)
assertThat(scannedSeries).hasSize(2)
assertThat(series.map { it.name }).containsExactlyInAnyOrderElementsOf(subDirs.keys)
series.forEach { serie ->
assertThat(serie.books.map { it.name }).containsExactlyInAnyOrderElementsOf(subDirs[serie.name]?.map { FilenameUtils.removeExtension(it) })
assertThat(scannedSeries.map { it.name }).containsExactlyInAnyOrderElementsOf(subDirs.keys)
scannedSeries.forEach { series ->
assertThat(series.books.map { it.name }).containsExactlyInAnyOrderElementsOf(subDirs[series.name]?.map { FilenameUtils.removeExtension(it) })
}
}
}
}
}

View file

@ -9,10 +9,10 @@ import org.gotson.komga.domain.model.Status
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.model.makeBookPage
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.model.makeSerie
import org.gotson.komga.domain.model.makeSeries
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.domain.persistence.SerieRepository
import org.gotson.komga.domain.persistence.SeriesRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ -27,11 +27,11 @@ import java.nio.file.Paths
@SpringBootTest
@AutoConfigureTestDatabase
class LibraryScannerTest(
@Autowired private val serieRepository: SerieRepository,
@Autowired private val seriesRepository: SeriesRepository,
@Autowired private val libraryRepository: LibraryRepository,
@Autowired private val bookRepository: BookRepository,
@Autowired private val libraryScanner: LibraryScanner,
@Autowired private val bookLifecyle: BookLifecyle
@Autowired private val bookLifecycle: BookLifecycle
) {
@MockkBean
@ -42,22 +42,22 @@ class LibraryScannerTest(
@AfterEach
fun `clear repositories`() {
serieRepository.deleteAll()
seriesRepository.deleteAll()
libraryRepository.deleteAll()
}
@Test
@Transactional
fun `given existing Serie when adding files and scanning then only updated Books are persisted`() {
fun `given existing series when adding files and scanning then only updated Books are persisted`() {
// given
val library = libraryRepository.save(makeLibrary())
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1")))
val serieWithMoreBooks = makeSerie(name = "serie", books = listOf(makeBook("book1"), makeBook("book2")))
val series = makeSeries(name = "series", books = listOf(makeBook("book1")))
val seriesWithMoreBooks = makeSeries(name = "series", books = listOf(makeBook("book1"), makeBook("book2")))
every { mockScanner.scanRootFolder(any()) }.returnsMany(
listOf(serie),
listOf(serieWithMoreBooks)
listOf(series),
listOf(seriesWithMoreBooks)
)
libraryScanner.scanRootFolder(library)
@ -65,28 +65,28 @@ class LibraryScannerTest(
libraryScanner.scanRootFolder(library)
// then
val series = serieRepository.findAll()
val allSeries = seriesRepository.findAll()
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
assertThat(series).hasSize(1)
assertThat(series.first().books).hasSize(2)
assertThat(series.first().books.map { it.name }).containsExactly("book1", "book2")
assertThat(allSeries).hasSize(1)
assertThat(allSeries.first().books).hasSize(2)
assertThat(allSeries.first().books.map { it.name }).containsExactly("book1", "book2")
}
@Test
@Transactional
fun `given existing Serie when removing files and scanning then only updated Books are persisted`() {
fun `given existing series when removing files and scanning then only updated Books are persisted`() {
// given
val library = libraryRepository.save(makeLibrary())
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1"), makeBook("book2")))
val serieWithLessBooks = makeSerie(name = "serie", books = listOf(makeBook("book1")))
val series = makeSeries(name = "series", books = listOf(makeBook("book1"), makeBook("book2")))
val seriesWithLessBooks = makeSeries(name = "series", books = listOf(makeBook("book1")))
every { mockScanner.scanRootFolder(any()) }
.returnsMany(
listOf(serie),
listOf(serieWithLessBooks)
listOf(series),
listOf(seriesWithLessBooks)
)
libraryScanner.scanRootFolder(library)
@ -94,29 +94,29 @@ class LibraryScannerTest(
libraryScanner.scanRootFolder(library)
// then
val series = serieRepository.findAll()
val allSeries = seriesRepository.findAll()
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
assertThat(series).hasSize(1)
assertThat(series.first().books).hasSize(1)
assertThat(series.first().books.map { it.name }).containsExactly("book1")
assertThat(allSeries).hasSize(1)
assertThat(allSeries.first().books).hasSize(1)
assertThat(allSeries.first().books.map { it.name }).containsExactly("book1")
assertThat(bookRepository.count()).describedAs("Orphan book has been removed").isEqualTo(1)
}
@Test
@Transactional
fun `given existing Serie when updating files and scanning then Books are updated`() {
fun `given existing series when updating files and scanning then Books are updated`() {
// given
val library = libraryRepository.save(makeLibrary())
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1")))
val serieWithUpdatedBooks = makeSerie(name = "serie", books = listOf(makeBook("book1updated", "file:/book1")))
val series = makeSeries(name = "series", books = listOf(makeBook("book1")))
val seriesWithUpdatedBooks = makeSeries(name = "series", books = listOf(makeBook("book1updated", "file:/book1")))
every { mockScanner.scanRootFolder(any()) }
.returnsMany(
listOf(serie),
listOf(serieWithUpdatedBooks)
listOf(series),
listOf(seriesWithUpdatedBooks)
)
libraryScanner.scanRootFolder(library)
@ -124,25 +124,25 @@ class LibraryScannerTest(
libraryScanner.scanRootFolder(library)
// then
val series = serieRepository.findAll()
val allSeries = seriesRepository.findAll()
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
assertThat(series).hasSize(1)
assertThat(series.first().lastModifiedDate).isNotEqualTo(series.first().createdDate)
assertThat(series.first().books).hasSize(1)
assertThat(series.first().books.map { it.name }).containsExactly("book1updated")
assertThat(series.first().books.first().lastModifiedDate).isNotEqualTo(series.first().books.first().createdDate)
assertThat(allSeries).hasSize(1)
assertThat(allSeries.first().lastModifiedDate).isNotEqualTo(allSeries.first().createdDate)
assertThat(allSeries.first().books).hasSize(1)
assertThat(allSeries.first().books.map { it.name }).containsExactly("book1updated")
assertThat(allSeries.first().books.first().lastModifiedDate).isNotEqualTo(allSeries.first().books.first().createdDate)
}
@Test
fun `given existing Serie when deleting all books and scanning then Series and Books are removed`() {
fun `given existing series when deleting all books and scanning then Series and Books are removed`() {
// given
val library = libraryRepository.save(makeLibrary())
every { mockScanner.scanRootFolder(any()) }
.returnsMany(
listOf(makeSerie(name = "serie", books = listOf(makeBook("book1")))),
listOf(makeSeries(name = "series", books = listOf(makeBook("book1")))),
emptyList()
)
libraryScanner.scanRootFolder(library)
@ -153,19 +153,19 @@ class LibraryScannerTest(
// then
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
assertThat(serieRepository.count()).describedAs("Serie repository should be empty").isEqualTo(0)
assertThat(seriesRepository.count()).describedAs("Series repository should be empty").isEqualTo(0)
assertThat(bookRepository.count()).describedAs("Book repository should be empty").isEqualTo(0)
}
@Test
fun `given existing Series when deleting all books of one serie and scanning then Serie and its Books are removed`() {
fun `given existing Series when deleting all books of one series and scanning then series and its Books are removed`() {
// given
val library = libraryRepository.save(makeLibrary())
every { mockScanner.scanRootFolder(any()) }
.returnsMany(
listOf(makeSerie(name = "serie", books = listOf(makeBook("book1"))), makeSerie(name = "serie2", books = listOf(makeBook("book2")))),
listOf(makeSerie(name = "serie", books = listOf(makeBook("book1"))))
listOf(makeSeries(name = "series", books = listOf(makeBook("book1"))), makeSeries(name = "series2", books = listOf(makeBook("book2")))),
listOf(makeSeries(name = "series", books = listOf(makeBook("book1"))))
)
libraryScanner.scanRootFolder(library)
@ -175,7 +175,7 @@ class LibraryScannerTest(
// then
verify(exactly = 2) { mockScanner.scanRootFolder(any()) }
assertThat(serieRepository.count()).describedAs("Serie repository should be empty").isEqualTo(1)
assertThat(seriesRepository.count()).describedAs("Series repository should be empty").isEqualTo(1)
assertThat(bookRepository.count()).describedAs("Book repository should be empty").isEqualTo(1)
}
@ -187,13 +187,13 @@ class LibraryScannerTest(
val book1 = makeBook("book1")
every { mockScanner.scanRootFolder(any()) }
.returnsMany(
listOf(makeSerie(name = "serie", books = listOf(book1))),
listOf(makeSerie(name = "serie", books = listOf(makeBook(name = "book1", fileLastModified = book1.fileLastModified))))
listOf(makeSeries(name = "series", books = listOf(book1))),
listOf(makeSeries(name = "series", books = listOf(makeBook(name = "book1", fileLastModified = book1.fileLastModified))))
)
libraryScanner.scanRootFolder(library)
every { mockParser.parse(any()) } returns BookMetadata(status = Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")))
bookRepository.findAll().map { bookLifecyle.parseAndPersist(it) }.map { it.get() }
bookRepository.findAll().map { bookLifecycle.parseAndPersist(it) }.map { it.get() }
// when
libraryScanner.scanRootFolder(library)
@ -217,17 +217,17 @@ class LibraryScannerTest(
val library2 = libraryRepository.save(makeLibrary(name = "library2"))
every { mockScanner.scanRootFolder(Paths.get(library1.root.toURI())) } returns
listOf(makeSerie(name = "serie1", books = listOf(makeBook("book1"))))
listOf(makeSeries(name = "series1", books = listOf(makeBook("book1"))))
every { mockScanner.scanRootFolder(Paths.get(library2.root.toURI())) }.returnsMany(
listOf(makeSerie(name = "serie2", books = listOf(makeBook("book2")))),
listOf(makeSeries(name = "series2", books = listOf(makeBook("book2")))),
emptyList()
)
libraryScanner.scanRootFolder(library1)
libraryScanner.scanRootFolder(library2)
assertThat(serieRepository.count()).describedAs("Serie repository should be empty").isEqualTo(2)
assertThat(seriesRepository.count()).describedAs("Series repository should be empty").isEqualTo(2)
assertThat(bookRepository.count()).describedAs("Book repository should be empty").isEqualTo(2)
// when
@ -237,7 +237,7 @@ class LibraryScannerTest(
verify(exactly = 1) { mockScanner.scanRootFolder(Paths.get(library1.root.toURI())) }
verify(exactly = 2) { mockScanner.scanRootFolder(Paths.get(library2.root.toURI())) }
assertThat(serieRepository.count()).describedAs("Serie repository should be empty").isEqualTo(1)
assertThat(seriesRepository.count()).describedAs("Series repository should be empty").isEqualTo(1)
assertThat(bookRepository.count()).describedAs("Book repository should be empty").isEqualTo(1)
}
}
}

View file

@ -4,9 +4,9 @@ import org.gotson.komga.domain.model.BookMetadata
import org.gotson.komga.domain.model.Status
import org.gotson.komga.domain.model.makeBook
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.model.makeSerie
import org.gotson.komga.domain.model.makeSeries
import org.gotson.komga.domain.persistence.LibraryRepository
import org.gotson.komga.domain.persistence.SerieRepository
import org.gotson.komga.domain.persistence.SeriesRepository
import org.hamcrest.CoreMatchers.equalTo
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
@ -27,8 +27,8 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers
@SpringBootTest
@AutoConfigureTestDatabase
@AutoConfigureMockMvc(printOnlyOnFailure = false)
class SerieControllerTest(
@Autowired private val serieRepository: SerieRepository,
class SeriesControllerTest(
@Autowired private val seriesRepository: SeriesRepository,
@Autowired private val libraryRepository: LibraryRepository,
@Autowired private val mockMvc: MockMvc
@ -48,22 +48,22 @@ class SerieControllerTest(
@AfterEach
fun `clear repository`() {
serieRepository.deleteAll()
seriesRepository.deleteAll()
}
@Test
@WithMockUser
fun `given books with unordered index when requesting via api then books are ordered`() {
val serie = makeSerie(
name = "serie",
val series = makeSeries(
name = "series",
books = listOf(makeBook("1"), makeBook("3"))
).also { it.library = library }
serieRepository.save(serie)
seriesRepository.save(series)
serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) }
serieRepository.save(serie)
series.books = series.books.toMutableList().also { it.add(makeBook("2")) }
seriesRepository.save(series)
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${serie.id}/books?readyonly=false"))
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${series.id}/books?readyonly=false"))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].name", equalTo("1")))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[1].name", equalTo("2")))
@ -73,16 +73,16 @@ class SerieControllerTest(
@Test
@WithMockUser
fun `given many books with unordered index when requesting via api then books are ordered and paged`() {
val serie = makeSerie(
name = "serie",
val series = makeSeries(
name = "series",
books = (1..100 step 2).map { makeBook("$it") }
).also { it.library = library }
serieRepository.save(serie)
seriesRepository.save(series)
serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) }
serieRepository.save(serie)
series.books = series.books.toMutableList().also { it.add(makeBook("2")) }
seriesRepository.save(series)
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${serie.id}/books?readyonly=false"))
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${series.id}/books?readyonly=false"))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].name", equalTo("1")))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[1].name", equalTo("2")))
@ -96,17 +96,17 @@ class SerieControllerTest(
@Test
@WithMockUser
fun `given many books in ready state with unordered index when requesting via api then books are ordered and paged`() {
val serie = makeSerie(
name = "serie",
val series = makeSeries(
name = "series",
books = (1..100 step 2).map { makeBook("$it") }
).also { it.library = library }
serieRepository.save(serie)
seriesRepository.save(series)
serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) }
serie.books.forEach { it.metadata = BookMetadata(Status.READY) }
serieRepository.save(serie)
series.books = series.books.toMutableList().also { it.add(makeBook("2")) }
series.books.forEach { it.metadata = BookMetadata(Status.READY) }
seriesRepository.save(series)
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${serie.id}/books?readyonly=true"))
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${series.id}/books?readyonly=true"))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(MockMvcResultMatchers.jsonPath("$.content[0].name", equalTo("1")))
.andExpect(MockMvcResultMatchers.jsonPath("$.content[1].name", equalTo("2")))
@ -116,4 +116,4 @@ class SerieControllerTest(
.andExpect(MockMvcResultMatchers.jsonPath("$.first", equalTo(true)))
.andExpect(MockMvcResultMatchers.jsonPath("$.number", equalTo(0)))
}
}
}