mirror of
https://github.com/gotson/komga.git
synced 2025-12-06 08:32:25 +01:00
use mutable entities
add audiability for Book and Serie changed Serie to Book relationship to unidirectional for now
This commit is contained in:
parent
1c674916ce
commit
f3cbb5a960
19 changed files with 290 additions and 106 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -10,7 +10,8 @@
|
|||
.springBeans
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
!.idea/
|
||||
.idea/*
|
||||
!/.idea/runConfigurations/
|
||||
*.iws
|
||||
*.iml
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
|||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing
|
||||
import org.springframework.scheduling.annotation.EnableScheduling
|
||||
|
||||
@SpringBootApplication
|
||||
|
|
@ -11,6 +12,7 @@ import org.springframework.scheduling.annotation.EnableScheduling
|
|||
KomgaProperties::class
|
||||
)
|
||||
@EnableScheduling
|
||||
@EnableJpaAuditing
|
||||
class Application
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package org.gotson.komga.domain.model
|
||||
|
||||
import org.springframework.data.annotation.CreatedDate
|
||||
import org.springframework.data.annotation.LastModifiedDate
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.Column
|
||||
import javax.persistence.EntityListeners
|
||||
import javax.persistence.MappedSuperclass
|
||||
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener::class)
|
||||
abstract class AuditableEntity {
|
||||
@CreatedDate
|
||||
@Column(name = "created_date", updatable = false, nullable = false)
|
||||
var createdDate: LocalDateTime? = null
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "last_modified_date", nullable = false)
|
||||
var lastModifiedDate: LocalDateTime? = null
|
||||
}
|
||||
|
|
@ -11,37 +11,30 @@ import javax.persistence.FetchType
|
|||
import javax.persistence.GeneratedValue
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.ManyToOne
|
||||
import javax.persistence.OneToOne
|
||||
import javax.persistence.PrimaryKeyJoinColumn
|
||||
import javax.persistence.Table
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotNull
|
||||
|
||||
@Entity
|
||||
@Table(name = "book")
|
||||
class Book(
|
||||
@NotBlank
|
||||
@Column(name = "name", nullable = false)
|
||||
val name: String,
|
||||
var name: String,
|
||||
|
||||
@Column(name = "url", nullable = false)
|
||||
val url: URL,
|
||||
var url: URL,
|
||||
|
||||
@Column(name = "updated", nullable = false)
|
||||
val updated: LocalDateTime
|
||||
) {
|
||||
@Column(name = "file_last_modified", nullable = false)
|
||||
var fileLastModified: LocalDateTime
|
||||
) : AuditableEntity() {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@Column(name = "id", nullable = false)
|
||||
@PrimaryKeyJoinColumn
|
||||
var id: Long = 0
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "serie_id", nullable = false)
|
||||
lateinit var serie: Serie
|
||||
|
||||
@OneToOne(optional = false, orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "book_metadata_id", nullable = false)
|
||||
var metadata: BookMetadata = BookMetadata().also { it.book = this }
|
||||
|
|
|
|||
|
|
@ -19,16 +19,18 @@ import javax.persistence.Table
|
|||
class BookMetadata(
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false)
|
||||
val status: Status = Status.UNKNOWN,
|
||||
var status: Status = Status.UNKNOWN,
|
||||
|
||||
@Column(name = "media_type")
|
||||
val mediaType: String? = null,
|
||||
var mediaType: String? = null,
|
||||
|
||||
@Column(name = "thumbnail")
|
||||
@Lob
|
||||
val thumbnail: ByteArray? = null,
|
||||
var thumbnail: ByteArray? = null,
|
||||
|
||||
pages: List<BookPage> = emptyList()
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "book_metadata_page", joinColumns = [JoinColumn(name = "book_metadata_id")])
|
||||
var pages: MutableList<BookPage> = mutableListOf()
|
||||
) {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
|
|
@ -38,15 +40,11 @@ class BookMetadata(
|
|||
@OneToOne(optional = false, fetch = FetchType.LAZY, mappedBy = "metadata")
|
||||
lateinit var book: Book
|
||||
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "book_metadata_page", joinColumns = [JoinColumn(name = "book_metadata_id")])
|
||||
private val _pages: MutableList<BookPage> = mutableListOf()
|
||||
|
||||
val pages: List<BookPage>
|
||||
get() = _pages.toList()
|
||||
|
||||
init {
|
||||
_pages.addAll(pages)
|
||||
fun reset() {
|
||||
status = Status.UNKNOWN
|
||||
mediaType = null
|
||||
thumbnail = null
|
||||
pages = mutableListOf()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import javax.persistence.Entity
|
|||
import javax.persistence.FetchType
|
||||
import javax.persistence.GeneratedValue
|
||||
import javax.persistence.Id
|
||||
import javax.persistence.JoinColumn
|
||||
import javax.persistence.JoinTable
|
||||
import javax.persistence.OneToMany
|
||||
import javax.persistence.Table
|
||||
import javax.validation.constraints.NotBlank
|
||||
|
|
@ -17,30 +19,31 @@ import javax.validation.constraints.NotBlank
|
|||
class Serie(
|
||||
@NotBlank
|
||||
@Column(name = "name", nullable = false)
|
||||
val name: String,
|
||||
var name: String,
|
||||
|
||||
@Column(name = "url", nullable = false)
|
||||
val url: URL,
|
||||
var url: URL,
|
||||
|
||||
@Column(name = "updated", nullable = false)
|
||||
val updated: LocalDateTime
|
||||
) {
|
||||
@Column(name = "file_last_modified", nullable = false)
|
||||
var fileLastModified: LocalDateTime,
|
||||
|
||||
books: MutableList<Book>
|
||||
|
||||
) : AuditableEntity() {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@Column(name = "id", nullable = false, unique = true)
|
||||
var id: Long = 0
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, mappedBy = "serie", orphanRemoval = true)
|
||||
private var _books: MutableList<Book> = mutableListOf()
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
@JoinTable(name = "serie_books_mapping", joinColumns = [JoinColumn(name = "serie_id")], inverseJoinColumns = [JoinColumn(name = "book_id")])
|
||||
var books: MutableList<Book> = mutableListOf()
|
||||
set(value) {
|
||||
value.forEach { it.serie = this }
|
||||
field = value
|
||||
books.clear()
|
||||
books.addAll(value)
|
||||
}
|
||||
|
||||
val books: List<Book>
|
||||
get() = _books.toList()
|
||||
|
||||
fun setBooks(books: List<Book>) {
|
||||
_books = books.toMutableList()
|
||||
init {
|
||||
this.books = books
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,16 @@ package org.gotson.komga.domain.persistence
|
|||
|
||||
import org.gotson.komga.domain.model.Book
|
||||
import org.gotson.komga.domain.model.Status
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.net.URL
|
||||
|
||||
@Repository
|
||||
interface BookRepository : JpaRepository<Book, Long> {
|
||||
fun findAllBySerieId(serieId: Long, pageable: Pageable): Page<Book>
|
||||
// fun findAllBySerieId(serieId: Long, pageable: Pageable): Page<Book>
|
||||
// fun findAllBySerieIdAndUrlNotIn(serieId: Long, urls: Iterable<URL>): List<Book>
|
||||
fun findByUrl(url: URL): Book?
|
||||
fun findAllByMetadataStatus(status: Status): List<Book>
|
||||
fun findAllByMetadataStatusAndSerieId(status: Status, serieId: Long, pageable: Pageable): Page<Book>
|
||||
// fun findAllByMetadataStatusAndSerieId(status: Status, serieId: Long, pageable: Pageable): Page<Book>
|
||||
// fun deleteAllBySerieId(serieId: Long)
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import java.net.URL
|
|||
|
||||
@Repository
|
||||
interface SerieRepository : JpaRepository<Serie, Long>, JpaSpecificationExecutor<Serie> {
|
||||
fun deleteAllByUrlNotIn(urls: Iterable<URL>)
|
||||
fun countByUrlNotIn(urls: Iterable<URL>): Long
|
||||
// fun deleteAllByUrlNotIn(urls: Iterable<URL>)
|
||||
fun findByUrlNotIn(urls: Iterable<URL>): List<Serie>
|
||||
fun findByUrl(url: URL): Serie?
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ class BookParser(
|
|||
null
|
||||
}
|
||||
|
||||
return BookMetadata(mediaType = mediaType, status = Status.READY, pages = pages, thumbnail = thumbnail)
|
||||
return BookMetadata(mediaType = mediaType, status = Status.READY, pages = pages.toMutableList(), thumbnail = thumbnail)
|
||||
}
|
||||
|
||||
fun getPageStream(book: Book, number: Int): InputStream {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class FileSystemScanner {
|
|||
Book(
|
||||
name = FilenameUtils.getBaseName(it.fileName.toString()),
|
||||
url = it.toUri().toURL(),
|
||||
updated = it.getUpdatedTime()
|
||||
fileLastModified = it.getUpdatedTime()
|
||||
)
|
||||
}.toList()
|
||||
.sortedWith(
|
||||
|
|
@ -54,8 +54,9 @@ class FileSystemScanner {
|
|||
Serie(
|
||||
name = dir.fileName.toString(),
|
||||
url = dir.toUri().toURL(),
|
||||
updated = dir.getUpdatedTime()
|
||||
).also { it.setBooks(books) }
|
||||
fileLastModified = dir.getUpdatedTime(),
|
||||
books = books.toMutableList()
|
||||
)
|
||||
}.toList()
|
||||
}.also {
|
||||
val countOfBooks = scannedSeries.sumBy { it.books.size }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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.springframework.data.auditing.AuditingHandler
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
|
@ -16,7 +17,8 @@ class LibraryManager(
|
|||
private val fileSystemScanner: FileSystemScanner,
|
||||
private val serieRepository: SerieRepository,
|
||||
private val bookRepository: BookRepository,
|
||||
private val bookManager: BookManager
|
||||
private val bookManager: BookManager,
|
||||
private val auditingHandler: AuditingHandler
|
||||
) {
|
||||
|
||||
@Transactional
|
||||
|
|
@ -30,33 +32,49 @@ class LibraryManager(
|
|||
logger.info { "Scan returned no series, deleting all existing series" }
|
||||
serieRepository.deleteAll()
|
||||
} else {
|
||||
val urls = series.map { it.url }
|
||||
val countOfSeriesToDelete = serieRepository.countByUrlNotIn(urls)
|
||||
if (countOfSeriesToDelete > 0) {
|
||||
logger.info { "Deleting $countOfSeriesToDelete series not on disk anymore" }
|
||||
serieRepository.deleteAllByUrlNotIn(urls)
|
||||
series.map { it.url }.let { urls ->
|
||||
serieRepository.findByUrlNotIn(urls).forEach {
|
||||
urls.forEach { logger.info { "Deleting serie not on disk anymore: $it" } }
|
||||
serieRepository.delete(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// match IDs for existing entities
|
||||
series.forEach { newSerie ->
|
||||
serieRepository.findByUrl(newSerie.url)?.let { existingSerie ->
|
||||
newSerie.id = existingSerie.id
|
||||
newSerie.books.forEach { newBook ->
|
||||
bookRepository.findByUrl(newBook.url)?.let { existingBook ->
|
||||
newBook.id = existingBook.id
|
||||
// conserve metadata if book has not changed
|
||||
if (newBook.updated == existingBook.updated)
|
||||
newBook.metadata = existingBook.metadata
|
||||
else
|
||||
logger.info { "Book changed on disk, reset metadata status: ${newBook.url}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val existingSerie = serieRepository.findByUrl(newSerie.url)
|
||||
|
||||
serieRepository.saveAll(series)
|
||||
// if serie does not exist, save it
|
||||
if (existingSerie == null) {
|
||||
serieRepository.save(newSerie)
|
||||
} else {
|
||||
// if serie already exists, update it
|
||||
if (newSerie.fileLastModified != existingSerie.fileLastModified) {
|
||||
logger.info { "Serie changed on disk, updating: ${newSerie.url}" }
|
||||
existingSerie.name = newSerie.name
|
||||
|
||||
var anyBookchanged = false
|
||||
// update list of books with existing entities if they exist
|
||||
existingSerie.books = newSerie.books.map { newBook ->
|
||||
val existingBook = bookRepository.findByUrl(newBook.url) ?: newBook
|
||||
|
||||
if (newBook.fileLastModified != existingBook.fileLastModified) {
|
||||
logger.info { "Book changed on disk, update and reset metadata status: ${newBook.url}" }
|
||||
existingBook.fileLastModified = newBook.fileLastModified
|
||||
existingBook.name = newBook.name
|
||||
existingBook.metadata.reset()
|
||||
anyBookchanged = true
|
||||
}
|
||||
existingBook
|
||||
}.toMutableList()
|
||||
|
||||
// propagate modification of any of the books to the serie, so that LastModifiedDate is updated
|
||||
if (anyBookchanged)
|
||||
auditingHandler.markModified(existingSerie)
|
||||
|
||||
serieRepository.save(existingSerie)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.also { logger.info { "Library update finished in $it ms" } }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,14 +53,16 @@ class SerieController(
|
|||
@GetMapping("{id}/books")
|
||||
fun getAllBooksBySerie(
|
||||
@PathVariable id: Long,
|
||||
@RequestParam(value = "readyonly", defaultValue = "true") readyFilter: Boolean,
|
||||
page: Pageable
|
||||
): Page<BookDto> {
|
||||
if (!serieRepository.existsById(id)) throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
@RequestParam(value = "readyonly", defaultValue = "true") readyFilter: Boolean
|
||||
// page: Pageable
|
||||
): List<BookDto> {
|
||||
val serie = serieRepository.findByIdOrNull(id) ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
return if (readyFilter) {
|
||||
bookRepository.findAllByMetadataStatusAndSerieId(Status.READY, id, page)
|
||||
// bookRepository.findAllByMetadataStatusAndSerieId(Status.READY, id, page)
|
||||
serie.books.filter { it.metadata.status == Status.READY }
|
||||
} else {
|
||||
bookRepository.findAllBySerieId(id, page)
|
||||
// bookRepository.findAllBySerieId(id, page)
|
||||
serie.books
|
||||
}.map { it.toDto() }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,19 @@ create sequence hibernate_sequence start with 1 increment by 1;
|
|||
|
||||
create table book
|
||||
(
|
||||
id bigint generated by default as identity,
|
||||
id bigint not null,
|
||||
created_date timestamp not null,
|
||||
last_modified_date timestamp not null,
|
||||
file_last_modified timestamp not null,
|
||||
name varchar not null,
|
||||
updated timestamp not null,
|
||||
url varchar not null,
|
||||
book_metadata_id bigint not null,
|
||||
serie_id bigint not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table book_metadata
|
||||
(
|
||||
id bigint generated by default as identity,
|
||||
id bigint not null,
|
||||
media_type varchar,
|
||||
status varchar not null,
|
||||
thumbnail blob,
|
||||
|
|
@ -29,21 +30,35 @@ create table book_metadata_page
|
|||
|
||||
create table serie
|
||||
(
|
||||
id bigint generated by default as identity,
|
||||
id bigint not null,
|
||||
created_date timestamp not null,
|
||||
last_modified_date timestamp not null,
|
||||
file_last_modified timestamp not null,
|
||||
name varchar not null,
|
||||
updated timestamp not null,
|
||||
url varchar not null,
|
||||
primary key (id)
|
||||
);
|
||||
|
||||
create table serie_books_mapping
|
||||
(
|
||||
serie_id bigint not null,
|
||||
book_id bigint not null
|
||||
);
|
||||
|
||||
alter table book
|
||||
add constraint uk_book_book_metadata_id unique (book_metadata_id);
|
||||
|
||||
alter table serie_books_mapping
|
||||
add constraint UK_ya6l4fjcs4rlhwx1fwqwsmm unique (book_id);
|
||||
|
||||
alter table book
|
||||
add constraint fk_book_book_metadata_book_metadata_id foreign key (book_metadata_id) references book_metadata (id);
|
||||
|
||||
alter table book
|
||||
add constraint fk_book_serie_serie_id foreign key (serie_id) references serie (id);
|
||||
|
||||
alter table book_metadata_page
|
||||
add constraint fk_book_metadata_page_book_metadata_book_metadata_id foreign key (book_metadata_id) references book_metadata (id);
|
||||
|
||||
alter table serie_books_mapping
|
||||
add constraint fk_serie_books_mapping_book_book_id foreign key (book_id) references book (id);
|
||||
|
||||
alter table serie_books_mapping
|
||||
add constraint fk_serie_books_mapping_serie_serie_id foreign key (serie_id) references serie (id);
|
||||
|
|
|
|||
|
|
@ -3,11 +3,15 @@ package org.gotson.komga.domain.model
|
|||
import java.net.URL
|
||||
import java.time.LocalDateTime
|
||||
|
||||
fun makeBook(name: String, url: String = "file:/$name") =
|
||||
Book(name = name, url = URL(url), updated = LocalDateTime.now())
|
||||
fun makeBook(name: String, url: String = "file:/$name", fileLastModified: LocalDateTime = LocalDateTime.now()): Book {
|
||||
Thread.sleep(5)
|
||||
return Book(name = name, url = URL(url), fileLastModified = fileLastModified)
|
||||
}
|
||||
|
||||
fun makeSerie(name: String, url: String = "file:/$name", books: List<Book> = listOf()) =
|
||||
Serie(name = name, url = URL(url), updated = LocalDateTime.now()).also { it.setBooks(books) }
|
||||
fun makeSerie(name: String, url: String = "file:/$name", books: List<Book> = listOf()): Serie {
|
||||
Thread.sleep(5)
|
||||
return Serie(name = name, url = URL(url), fileLastModified = LocalDateTime.now(), books = books.toMutableList())
|
||||
}
|
||||
|
||||
fun makeBookPage(name: String) =
|
||||
BookPage(name, "image/png")
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
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.makeSerie
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
|
||||
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@DataJpaTest
|
||||
@Transactional
|
||||
class AuditableEntityTest(
|
||||
@Autowired private val serieRepository: SerieRepository,
|
||||
@Autowired private val entityManager: TestEntityManager
|
||||
) {
|
||||
|
||||
@AfterEach
|
||||
fun `clear repository`() {
|
||||
entityManager.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given serie with book when saving then created and modified date is also saved`() {
|
||||
// given
|
||||
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1")))
|
||||
|
||||
// when
|
||||
serieRepository.save(serie)
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given existing serie with book when updating serie only then created date is kept and modified date is changed for serie only`() {
|
||||
// given
|
||||
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1")))
|
||||
|
||||
serieRepository.save(serie)
|
||||
|
||||
val creationTimeApprox = LocalDateTime.now()
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
// when
|
||||
serie.name = "serieUpdated"
|
||||
serieRepository.saveAndFlush(serie)
|
||||
|
||||
val modificationTimeApprox = LocalDateTime.now()
|
||||
|
||||
// then
|
||||
assertThat(serie.createdDate)
|
||||
.isBefore(creationTimeApprox)
|
||||
.isNotEqualTo(serie.lastModifiedDate)
|
||||
assertThat(serie.lastModifiedDate)
|
||||
.isAfter(creationTimeApprox)
|
||||
.isBefore(modificationTimeApprox)
|
||||
|
||||
assertThat(serie.books.first().createdDate)
|
||||
.isBefore(creationTimeApprox)
|
||||
.isEqualTo(serie.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`() {
|
||||
// given
|
||||
val serie = makeSerie(name = "serie", books = listOf(makeBook("book1")))
|
||||
|
||||
serieRepository.save(serie)
|
||||
|
||||
val creationTimeApprox = LocalDateTime.now()
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
// when
|
||||
serie.books.first().name = "bookUpdated"
|
||||
serieRepository.saveAndFlush(serie)
|
||||
|
||||
val modificationTimeApprox = LocalDateTime.now()
|
||||
|
||||
// then
|
||||
assertThat(serie.createdDate)
|
||||
.isBefore(creationTimeApprox)
|
||||
.isEqualTo(serie.lastModifiedDate)
|
||||
|
||||
assertThat(serie.books.first().createdDate)
|
||||
.isBefore(creationTimeApprox)
|
||||
.isNotEqualTo(serie.books.first().lastModifiedDate)
|
||||
assertThat(serie.books.first().lastModifiedDate)
|
||||
.isAfter(creationTimeApprox)
|
||||
.isBefore(modificationTimeApprox)
|
||||
}
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ class PersistenceTest(
|
|||
|
||||
// when
|
||||
val book = bookRepository.findAll().first()
|
||||
book.metadata = BookMetadata(status = Status.READY, mediaType = "test", pages = listOf(makeBookPage("page1")))
|
||||
book.metadata = BookMetadata(status = Status.READY, mediaType = "test", pages = mutableListOf(makeBookPage("page1")))
|
||||
|
||||
bookRepository.save(book)
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class LibraryManagerTest(
|
|||
|
||||
assertThat(series).hasSize(1)
|
||||
assertThat(series.first().books).hasSize(2)
|
||||
assertThat(series.first().books.map { it.name }).containsExactlyInAnyOrder("book1", "book2")
|
||||
assertThat(series.first().books.map { it.name }).containsExactly("book1", "book2")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -95,7 +95,7 @@ class LibraryManagerTest(
|
|||
|
||||
assertThat(series).hasSize(1)
|
||||
assertThat(series.first().books).hasSize(1)
|
||||
assertThat(series.first().books.map { it.name }).containsExactlyInAnyOrder("book1")
|
||||
assertThat(series.first().books.map { it.name }).containsExactly("book1")
|
||||
assertThat(bookRepository.count()).describedAs("Orphan book has been removed").isEqualTo(1)
|
||||
}
|
||||
|
||||
|
|
@ -121,8 +121,10 @@ class LibraryManagerTest(
|
|||
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 }).containsExactlyInAnyOrder("book1updated")
|
||||
assertThat(series.first().books.map { it.name }).containsExactly("book1updated")
|
||||
assertThat(series.first().books.first().lastModifiedDate).isNotEqualTo(series.first().books.first().createdDate)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -150,14 +152,15 @@ class LibraryManagerTest(
|
|||
@Test
|
||||
fun `given existing Book with metadata when rescanning then metadata is kept intact`() {
|
||||
//given
|
||||
val book1 = makeBook("book1")
|
||||
every { mockScanner.scanRootFolder(any()) }
|
||||
.returnsMany(
|
||||
listOf(makeSerie(name = "serie", books = listOf(makeBook("book1")))),
|
||||
listOf(makeSerie(name = "serie", books = listOf(makeBook("book1"))))
|
||||
listOf(makeSerie(name = "serie", books = listOf(book1))),
|
||||
listOf(makeSerie(name = "serie", books = listOf(makeBook(name = "book1", fileLastModified = book1.fileLastModified))))
|
||||
)
|
||||
libraryManager.scanRootFolder(library)
|
||||
|
||||
every { mockParser.parse(any()) } returns BookMetadata(status = Status.READY, mediaType = "application/zip", pages = listOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")))
|
||||
every { mockParser.parse(any()) } returns BookMetadata(status = Status.READY, mediaType = "application/zip", pages = mutableListOf(makeBookPage("1.jpg"), makeBookPage("2.jpg")))
|
||||
bookRepository.findAll().forEach { bookManager.parseAndPersist(it) }
|
||||
|
||||
// when
|
||||
|
|
@ -172,5 +175,6 @@ class LibraryManagerTest(
|
|||
assertThat(book.metadata.mediaType).isEqualTo("application/zip")
|
||||
assertThat(book.metadata.pages).hasSize(2)
|
||||
assertThat(book.metadata.pages.map { it.fileName }).containsExactly("1.jpg", "2.jpg")
|
||||
assertThat(book.lastModifiedDate).isNotEqualTo(book.createdDate)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package org.gotson.komga.infrastructure
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.ActiveProfiles
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
@SpringBootTest
|
||||
@AutoConfigureTestDatabase
|
||||
@ActiveProfiles("flyway")
|
||||
class FlywayTest {
|
||||
|
||||
@Test
|
||||
fun `Application loads properly with flyway profile`() {
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ application.version: TESTING
|
|||
|
||||
spring:
|
||||
flyway:
|
||||
enabled: true
|
||||
enabled: false
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
ddl-auto: none
|
||||
jpa:
|
||||
properties:
|
||||
hibernate:
|
||||
|
|
|
|||
Loading…
Reference in a new issue