mirror of
https://github.com/gotson/komga.git
synced 2025-12-22 00:13:30 +01:00
feat: add/rearrange metadata fields
existing fields moved from book to series: publisher, age rating, reading direction new book fields: tags new series fields: tags, genres, language, summary closes #276
This commit is contained in:
parent
d8db46c589
commit
9e406e3316
37 changed files with 1084 additions and 325 deletions
|
|
@ -70,6 +70,8 @@ dependencies {
|
|||
implementation("commons-io:commons-io:2.7")
|
||||
implementation("org.apache.commons:commons-lang3:3.11")
|
||||
|
||||
implementation("com.ibm.icu:icu4j:67.1")
|
||||
|
||||
implementation("org.apache.tika:tika-core:1.24.1")
|
||||
implementation("org.apache.commons:commons-compress:1.20")
|
||||
implementation("com.github.junrar:junrar:7.2.0")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package db.migration.sqlite
|
||||
|
||||
import org.flywaydb.core.api.migration.BaseJavaMigration
|
||||
import org.flywaydb.core.api.migration.Context
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
import org.springframework.jdbc.datasource.SingleConnectionDataSource
|
||||
|
||||
class V20200820150923__metadata_fields_part_2 : BaseJavaMigration() {
|
||||
override fun migrate(context: Context) {
|
||||
val jdbcTemplate = JdbcTemplate(SingleConnectionDataSource(context.connection, true))
|
||||
|
||||
val bookMetadata = jdbcTemplate.queryForList(
|
||||
"""select m.AGE_RATING, m.AGE_RATING_LOCK, m.PUBLISHER, m.PUBLISHER_LOCK, m.READING_DIRECTION, m.READING_DIRECTION_LOCK, b.SERIES_ID, m.NUMBER_SORT
|
||||
from BOOK_METADATA m
|
||||
left join BOOK B on B.ID = m.BOOK_ID"""
|
||||
)
|
||||
|
||||
if (bookMetadata.isNotEmpty()) {
|
||||
val parameters = bookMetadata
|
||||
.groupBy { it["SERIES_ID"] }
|
||||
.map { (seriesId, v) ->
|
||||
val ageRating = v.mapNotNull { it["AGE_RATING"] as Int? }.max()
|
||||
val ageRatingLock = v.mapNotNull { it["AGE_RATING_LOCK"] as Int? }.max()
|
||||
|
||||
val publisher =
|
||||
v.filter { (it["PUBLISHER"] as String).isNotEmpty() }
|
||||
.sortedByDescending { it["NUMBER_SORT"] as Double? }
|
||||
.map { it["PUBLISHER"] as String }
|
||||
.firstOrNull()
|
||||
val publisherLock = v.mapNotNull { it["PUBLISHER_LOCK"] as Int? }.max()
|
||||
|
||||
val readingDir =
|
||||
v.mapNotNull { it["READING_DIRECTION"] as String? }
|
||||
.groupingBy { it }
|
||||
.eachCount()
|
||||
.maxBy { it.value }?.key
|
||||
val readingDirLock = v.mapNotNull { it["READING_DIRECTION_LOCK"] as Int? }.max()
|
||||
|
||||
arrayOf(ageRating, ageRatingLock, publisher, publisherLock, readingDir, readingDirLock, seriesId)
|
||||
}
|
||||
|
||||
jdbcTemplate.batchUpdate(
|
||||
"UPDATE SERIES_METADATA SET AGE_RATING = ?, AGE_RATING_LOCK = ?, PUBLISHER = ?, PUBLISHER_LOCK = ?, READING_DIRECTION = ?, READING_DIRECTION_LOCK = ? WHERE SERIES_ID = ?",
|
||||
parameters
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
alter table series_metadata
|
||||
add column PUBLISHER varchar NOT NULL DEFAULT '';
|
||||
alter table series_metadata
|
||||
add column PUBLISHER_LOCK boolean NOT NULL DEFAULT 0;
|
||||
|
||||
alter table series_metadata
|
||||
add column READING_DIRECTION varchar NULL;
|
||||
alter table series_metadata
|
||||
add column READING_DIRECTION_LOCK boolean NOT NULL DEFAULT 0;
|
||||
|
||||
alter table series_metadata
|
||||
add column AGE_RATING int NULL;
|
||||
alter table series_metadata
|
||||
add column AGE_RATING_LOCK boolean NOT NULL DEFAULT 0;
|
||||
|
||||
alter table SERIES_METADATA
|
||||
add column SUMMARY varchar NOT NULL DEFAULT '';
|
||||
alter table SERIES_METADATA
|
||||
add column SUMMARY_LOCK boolean NOT NULL DEFAULT 0;
|
||||
|
||||
alter table SERIES_METADATA
|
||||
add column LANGUAGE varchar NOT NULL DEFAULT '';
|
||||
alter table SERIES_METADATA
|
||||
add column LANGUAGE_LOCK boolean NOT NULL DEFAULT 0;
|
||||
|
||||
alter table SERIES_METADATA
|
||||
add column GENRES_LOCK boolean NOT NULL DEFAULT 0;
|
||||
|
||||
alter table SERIES_METADATA
|
||||
add column TAGS_LOCK boolean NOT NULL DEFAULT 0;
|
||||
|
||||
|
||||
CREATE TABLE SERIES_METADATA_GENRE
|
||||
(
|
||||
GENRE varchar NOT NULL,
|
||||
SERIES_ID varchar NOT NULL,
|
||||
FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID)
|
||||
);
|
||||
|
||||
CREATE TABLE SERIES_METADATA_TAG
|
||||
(
|
||||
TAG varchar NOT NULL,
|
||||
SERIES_ID varchar NOT NULL,
|
||||
FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID)
|
||||
);
|
||||
|
||||
CREATE TABLE BOOK_METADATA_TAG
|
||||
(
|
||||
TAG varchar NOT NULL,
|
||||
BOOK_ID varchar NOT NULL,
|
||||
FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID)
|
||||
);
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
PRAGMA foreign_keys= OFF;
|
||||
|
||||
ALTER TABLE BOOK_METADATA
|
||||
RENAME TO _BOOK_METADATA_OLD;
|
||||
|
||||
CREATE TABLE BOOK_METADATA
|
||||
(
|
||||
CREATED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
LAST_MODIFIED_DATE datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
NUMBER varchar NOT NULL,
|
||||
NUMBER_LOCK boolean NOT NULL DEFAULT 0,
|
||||
NUMBER_SORT real NOT NULL,
|
||||
NUMBER_SORT_LOCK boolean NOT NULL DEFAULT 0,
|
||||
RELEASE_DATE date NULL,
|
||||
RELEASE_DATE_LOCK boolean NOT NULL DEFAULT 0,
|
||||
SUMMARY varchar NOT NULL DEFAULT '',
|
||||
SUMMARY_LOCK boolean NOT NULL DEFAULT 0,
|
||||
TITLE varchar NOT NULL,
|
||||
TITLE_LOCK boolean NOT NULL DEFAULT 0,
|
||||
AUTHORS_LOCK boolean NOT NULL DEFAULT 0,
|
||||
TAGS_LOCK boolean NOT NULL DEFAULT 0,
|
||||
BOOK_ID varchar NOT NULL PRIMARY KEY,
|
||||
FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID)
|
||||
);
|
||||
|
||||
INSERT INTO BOOK_METADATA (CREATED_DATE, LAST_MODIFIED_DATE, NUMBER, NUMBER_LOCK, NUMBER_SORT, NUMBER_SORT_LOCK,
|
||||
RELEASE_DATE, RELEASE_DATE_LOCK, SUMMARY, SUMMARY_LOCK, TITLE, TITLE_LOCK, AUTHORS_LOCK,
|
||||
BOOK_ID)
|
||||
SELECT CREATED_DATE,
|
||||
LAST_MODIFIED_DATE,
|
||||
NUMBER,
|
||||
NUMBER_LOCK,
|
||||
NUMBER_SORT,
|
||||
NUMBER_SORT_LOCK,
|
||||
RELEASE_DATE,
|
||||
RELEASE_DATE_LOCK,
|
||||
SUMMARY,
|
||||
SUMMARY_LOCK,
|
||||
TITLE,
|
||||
TITLE_LOCK,
|
||||
AUTHORS_LOCK,
|
||||
BOOK_ID
|
||||
FROM _BOOK_METADATA_OLD;
|
||||
|
||||
DROP TABLE _BOOK_METADATA_OLD;
|
||||
|
||||
PRAGMA foreign_keys= ON;
|
||||
|
|
@ -8,21 +8,17 @@ class BookMetadata(
|
|||
summary: String = "",
|
||||
number: String,
|
||||
val numberSort: Float,
|
||||
val readingDirection: ReadingDirection? = null,
|
||||
publisher: String = "",
|
||||
val ageRating: Int? = null,
|
||||
val releaseDate: LocalDate? = null,
|
||||
val authors: List<Author> = emptyList(),
|
||||
val tags: Set<String> = emptySet(),
|
||||
|
||||
val titleLock: Boolean = false,
|
||||
val summaryLock: Boolean = false,
|
||||
val numberLock: Boolean = false,
|
||||
val numberSortLock: Boolean = false,
|
||||
val readingDirectionLock: Boolean = false,
|
||||
val publisherLock: Boolean = false,
|
||||
val ageRatingLock: Boolean = false,
|
||||
val releaseDateLock: Boolean = false,
|
||||
val authorsLock: Boolean = false,
|
||||
val tagsLock: Boolean = false,
|
||||
|
||||
val bookId: String = "",
|
||||
|
||||
|
|
@ -33,27 +29,22 @@ class BookMetadata(
|
|||
val title = title.trim()
|
||||
val summary = summary.trim()
|
||||
val number = number.trim()
|
||||
val publisher = publisher.trim()
|
||||
|
||||
fun copy(
|
||||
title: String = this.title,
|
||||
summary: String = this.summary,
|
||||
number: String = this.number,
|
||||
numberSort: Float = this.numberSort,
|
||||
readingDirection: ReadingDirection? = this.readingDirection,
|
||||
publisher: String = this.publisher,
|
||||
ageRating: Int? = this.ageRating,
|
||||
releaseDate: LocalDate? = this.releaseDate,
|
||||
authors: List<Author> = this.authors.toList(),
|
||||
tags: Set<String> = this.tags,
|
||||
titleLock: Boolean = this.titleLock,
|
||||
summaryLock: Boolean = this.summaryLock,
|
||||
numberLock: Boolean = this.numberLock,
|
||||
numberSortLock: Boolean = this.numberSortLock,
|
||||
readingDirectionLock: Boolean = this.readingDirectionLock,
|
||||
publisherLock: Boolean = this.publisherLock,
|
||||
ageRatingLock: Boolean = this.ageRatingLock,
|
||||
releaseDateLock: Boolean = this.releaseDateLock,
|
||||
authorsLock: Boolean = this.authorsLock,
|
||||
tagsLock: Boolean = this.tagsLock,
|
||||
bookId: String = this.bookId,
|
||||
createdDate: LocalDateTime = this.createdDate,
|
||||
lastModifiedDate: LocalDateTime = this.lastModifiedDate
|
||||
|
|
@ -63,32 +54,21 @@ class BookMetadata(
|
|||
summary = summary,
|
||||
number = number,
|
||||
numberSort = numberSort,
|
||||
readingDirection = readingDirection,
|
||||
publisher = publisher,
|
||||
ageRating = ageRating,
|
||||
releaseDate = releaseDate,
|
||||
authors = authors,
|
||||
tags = tags,
|
||||
titleLock = titleLock,
|
||||
summaryLock = summaryLock,
|
||||
numberLock = numberLock,
|
||||
numberSortLock = numberSortLock,
|
||||
readingDirectionLock = readingDirectionLock,
|
||||
publisherLock = publisherLock,
|
||||
ageRatingLock = ageRatingLock,
|
||||
releaseDateLock = releaseDateLock,
|
||||
authorsLock = authorsLock,
|
||||
tagsLock = tagsLock,
|
||||
bookId = bookId,
|
||||
createdDate = createdDate,
|
||||
lastModifiedDate = lastModifiedDate
|
||||
)
|
||||
|
||||
enum class ReadingDirection {
|
||||
LEFT_TO_RIGHT,
|
||||
RIGHT_TO_LEFT,
|
||||
VERTICAL,
|
||||
WEBTOON
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
"BookMetadata(numberSort=$numberSort, readingDirection=$readingDirection, ageRating=$ageRating, releaseDate=$releaseDate, authors=$authors, titleLock=$titleLock, summaryLock=$summaryLock, numberLock=$numberLock, numberSortLock=$numberSortLock, readingDirectionLock=$readingDirectionLock, publisherLock=$publisherLock, ageRatingLock=$ageRatingLock, releaseDateLock=$releaseDateLock, authorsLock=$authorsLock, bookId=$bookId, createdDate=$createdDate, lastModifiedDate=$lastModifiedDate, title='$title', summary='$summary', number='$number', publisher='$publisher')"
|
||||
"BookMetadata(numberSort=$numberSort, releaseDate=$releaseDate, authors=$authors, titleLock=$titleLock, summaryLock=$summaryLock, numberLock=$numberLock, numberSortLock=$numberSortLock, releaseDateLock=$releaseDateLock, authorsLock=$authorsLock, bookId=$bookId, createdDate=$createdDate, lastModifiedDate=$lastModifiedDate, title='$title', summary='$summary', number='$number')"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ data class BookMetadataPatch(
|
|||
val summary: String?,
|
||||
val number: String?,
|
||||
val numberSort: Float?,
|
||||
val readingDirection: BookMetadata.ReadingDirection?,
|
||||
val publisher: String?,
|
||||
val ageRating: Int?,
|
||||
val releaseDate: LocalDate?,
|
||||
val authors: List<Author>?,
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,24 @@ class SeriesMetadata(
|
|||
val status: Status = Status.ONGOING,
|
||||
title: String,
|
||||
titleSort: String = title,
|
||||
summary: String = "",
|
||||
val readingDirection: ReadingDirection? = null,
|
||||
publisher: String = "",
|
||||
val ageRating: Int? = null,
|
||||
val language: String = "",
|
||||
val genres: Set<String> = emptySet(),
|
||||
val tags: Set<String> = emptySet(),
|
||||
|
||||
val statusLock: Boolean = false,
|
||||
val titleLock: Boolean = false,
|
||||
val titleSortLock: Boolean = false,
|
||||
val summaryLock: Boolean = false,
|
||||
val readingDirectionLock: Boolean = false,
|
||||
val publisherLock: Boolean = false,
|
||||
val ageRatingLock: Boolean = false,
|
||||
val languageLock: Boolean = false,
|
||||
val genresLock: Boolean = false,
|
||||
val tagsLock: Boolean = false,
|
||||
|
||||
val seriesId: String = "",
|
||||
|
||||
|
|
@ -18,14 +32,30 @@ class SeriesMetadata(
|
|||
) : Auditable() {
|
||||
val title = title.trim()
|
||||
val titleSort = titleSort.trim()
|
||||
val summary = summary.trim()
|
||||
val publisher = publisher.trim()
|
||||
|
||||
fun copy(
|
||||
status: Status = this.status,
|
||||
title: String = this.title,
|
||||
titleSort: String = this.titleSort,
|
||||
summary: String = this.summary,
|
||||
readingDirection: ReadingDirection? = this.readingDirection,
|
||||
publisher: String = this.publisher,
|
||||
ageRating: Int? = this.ageRating,
|
||||
language: String = this.language,
|
||||
genres: Set<String> = this.genres,
|
||||
tags: Set<String> = this.tags,
|
||||
statusLock: Boolean = this.statusLock,
|
||||
titleLock: Boolean = this.titleLock,
|
||||
titleSortLock: Boolean = this.titleSortLock,
|
||||
summaryLock: Boolean = this.summaryLock,
|
||||
readingDirectionLock: Boolean = this.readingDirectionLock,
|
||||
publisherLock: Boolean = this.publisherLock,
|
||||
ageRatingLock: Boolean = this.ageRatingLock,
|
||||
languageLock: Boolean = this.languageLock,
|
||||
genresLock: Boolean = this.genresLock,
|
||||
tagsLock: Boolean = this.tagsLock,
|
||||
seriesId: String = this.seriesId,
|
||||
createdDate: LocalDateTime = this.createdDate,
|
||||
lastModifiedDate: LocalDateTime = this.lastModifiedDate
|
||||
|
|
@ -34,9 +64,23 @@ class SeriesMetadata(
|
|||
status = status,
|
||||
title = title,
|
||||
titleSort = titleSort,
|
||||
summary = summary,
|
||||
readingDirection = readingDirection,
|
||||
publisher = publisher,
|
||||
ageRating = ageRating,
|
||||
language = language,
|
||||
genres = genres,
|
||||
tags = tags,
|
||||
statusLock = statusLock,
|
||||
titleLock = titleLock,
|
||||
titleSortLock = titleSortLock,
|
||||
summaryLock = summaryLock,
|
||||
readingDirectionLock = readingDirectionLock,
|
||||
publisherLock = publisherLock,
|
||||
ageRatingLock = ageRatingLock,
|
||||
languageLock = languageLock,
|
||||
genresLock = genresLock,
|
||||
tagsLock = tagsLock,
|
||||
seriesId = seriesId,
|
||||
createdDate = createdDate,
|
||||
lastModifiedDate = lastModifiedDate
|
||||
|
|
@ -46,6 +90,14 @@ class SeriesMetadata(
|
|||
ENDED, ONGOING, ABANDONED, HIATUS
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
"SeriesMetadata(status=$status, statusLock=$statusLock, titleLock=$titleLock, titleSortLock=$titleSortLock, seriesId=$seriesId, createdDate=$createdDate, lastModifiedDate=$lastModifiedDate, title='$title', titleSort='$titleSort')"
|
||||
enum class ReadingDirection {
|
||||
LEFT_TO_RIGHT,
|
||||
RIGHT_TO_LEFT,
|
||||
VERTICAL,
|
||||
WEBTOON
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SeriesMetadata(status=$status, readingDirection=$readingDirection, ageRating=$ageRating, language=$language, genres=$genres, statusLock=$statusLock, titleLock=$titleLock, titleSortLock=$titleSortLock, readingDirectionLock=$readingDirectionLock, publisherLock=$publisherLock, ageRatingLock=$ageRatingLock, languageLock=$languageLock, genresLock=$genresLock, seriesId='$seriesId', createdDate=$createdDate, lastModifiedDate=$lastModifiedDate, title='$title', titleSort='$titleSort', summary='$summary', publisher='$publisher')"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ data class SeriesMetadataPatch(
|
|||
val title: String?,
|
||||
val titleSort: String?,
|
||||
val status: SeriesMetadata.Status?,
|
||||
val summary: String?,
|
||||
val readingDirection: SeriesMetadata.ReadingDirection?,
|
||||
val publisher: String?,
|
||||
val ageRating: Int?,
|
||||
val language: String?,
|
||||
val genres: Set<String>,
|
||||
|
||||
val collections: List<String> = emptyList()
|
||||
val collections: List<String>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ interface BookMetadataRepository {
|
|||
fun findByIdOrNull(bookId: String): BookMetadata?
|
||||
fun findByIds(bookIds: Collection<String>): Collection<BookMetadata>
|
||||
|
||||
fun findAuthorsByName(search: String): List<String>
|
||||
|
||||
fun insert(metadata: BookMetadata)
|
||||
fun insertMany(metadatas: Collection<BookMetadata>)
|
||||
fun update(metadata: BookMetadata)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package org.gotson.komga.domain.persistence
|
||||
|
||||
interface ReferentialRepository {
|
||||
fun findAuthorsByName(search: String): List<String>
|
||||
fun findAllGenres(): Set<String>
|
||||
fun findAllTags(): Set<String>
|
||||
}
|
||||
|
|
@ -6,10 +6,11 @@ interface SeriesMetadataRepository {
|
|||
fun findById(seriesId: String): SeriesMetadata
|
||||
fun findByIdOrNull(seriesId: String): SeriesMetadata?
|
||||
|
||||
fun insert(metadata: SeriesMetadata): SeriesMetadata
|
||||
fun insert(metadata: SeriesMetadata)
|
||||
fun update(metadata: SeriesMetadata)
|
||||
|
||||
fun delete(seriesId: String)
|
||||
fun delete(seriesIds: Collection<String>)
|
||||
|
||||
fun count(): Long
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ class MetadataApplier {
|
|||
summary = getIfNotLocked(summary, patch.summary, summaryLock),
|
||||
number = getIfNotLocked(number, patch.number, numberLock),
|
||||
numberSort = getIfNotLocked(numberSort, patch.numberSort, numberSortLock),
|
||||
readingDirection = getIfNotLocked(readingDirection, patch.readingDirection, readingDirectionLock),
|
||||
releaseDate = getIfNotLocked(releaseDate, patch.releaseDate, releaseDateLock),
|
||||
ageRating = getIfNotLocked(ageRating, patch.ageRating, ageRatingLock),
|
||||
publisher = getIfNotLocked(publisher, patch.publisher, publisherLock),
|
||||
authors = getIfNotLocked(authors, patch.authors, authorsLock)
|
||||
)
|
||||
}
|
||||
|
|
@ -36,7 +33,13 @@ class MetadataApplier {
|
|||
copy(
|
||||
status = getIfNotLocked(status, patch.status, statusLock),
|
||||
title = getIfNotLocked(title, patch.title, titleLock),
|
||||
titleSort = getIfNotLocked(titleSort, patch.titleSort, titleSortLock)
|
||||
titleSort = getIfNotLocked(titleSort, patch.titleSort, titleSortLock),
|
||||
summary = getIfNotLocked(summary, patch.summary, summaryLock),
|
||||
readingDirection = getIfNotLocked(readingDirection, patch.readingDirection, readingDirectionLock),
|
||||
ageRating = getIfNotLocked(ageRating, patch.ageRating, ageRatingLock),
|
||||
publisher = getIfNotLocked(publisher, patch.publisher, publisherLock),
|
||||
language = getIfNotLocked(language, patch.language, languageLock),
|
||||
genres = getIfNotLocked(genres, patch.genres, genresLock)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -134,15 +134,19 @@ class MetadataLifecycle(
|
|||
// handle series metadata
|
||||
if ((provider is ComicInfoProvider && library.importComicInfoSeries) ||
|
||||
(provider is EpubMetadataProvider && library.importEpubSeries)) {
|
||||
val title = patches.uniqueOrNull { it.title }
|
||||
val titleSort = patches.uniqueOrNull { it.titleSort }
|
||||
val status = patches.uniqueOrNull { it.status }
|
||||
|
||||
if (title == null) logger.debug { "Ignoring title, values are not unique within series books" }
|
||||
if (titleSort == null) logger.debug { "Ignoring sort title, values are not unique within series books" }
|
||||
if (status == null) logger.debug { "Ignoring status, values are not unique within series books" }
|
||||
|
||||
val aggregatedPatch = SeriesMetadataPatch(title, titleSort, status)
|
||||
val aggregatedPatch = SeriesMetadataPatch(
|
||||
title = patches.mostFrequent { it.title },
|
||||
titleSort = patches.mostFrequent { it.titleSort },
|
||||
status = patches.mostFrequent { it.status },
|
||||
genres = patches.flatMap { it.genres }.toSet(),
|
||||
language = patches.mostFrequent { it.language },
|
||||
summary = null,
|
||||
readingDirection = patches.mostFrequent { it.readingDirection },
|
||||
ageRating = patches.mapNotNull { it.ageRating }.max(),
|
||||
publisher = patches.mostFrequent { it.publisher },
|
||||
collections = emptyList()
|
||||
)
|
||||
|
||||
seriesMetadataRepository.findById(series.id).let {
|
||||
logger.debug { "Apply metadata for series: $series" }
|
||||
|
|
@ -188,13 +192,12 @@ class MetadataLifecycle(
|
|||
}
|
||||
}
|
||||
|
||||
private fun <T, R : Any> Iterable<T>.uniqueOrNull(transform: (T) -> R?): R? {
|
||||
private fun <T, R : Any> Iterable<T>.mostFrequent(transform: (T) -> R?): R? {
|
||||
return this
|
||||
.mapNotNull(transform)
|
||||
.distinct()
|
||||
.let {
|
||||
if (it.size == 1) it.first() else null
|
||||
}
|
||||
.groupingBy { it }
|
||||
.eachCount()
|
||||
.maxBy { it.value }?.key
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ class SeriesLifecycle(
|
|||
|
||||
collectionRepository.removeSeriesFromAll(seriesId)
|
||||
thumbnailsSeriesRepository.deleteBySeriesId(seriesId)
|
||||
seriesMetadataRepository.delete(seriesId)
|
||||
|
||||
seriesRepository.delete(seriesId)
|
||||
}
|
||||
|
|
@ -130,6 +131,7 @@ class SeriesLifecycle(
|
|||
|
||||
collectionRepository.removeSeriesFromAll(seriesIds)
|
||||
thumbnailsSeriesRepository.deleteBySeriesIds(seriesIds)
|
||||
seriesMetadataRepository.delete(seriesIds)
|
||||
|
||||
seriesRepository.deleteAll(seriesIds)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class BookDtoDao(
|
|||
private val a = Tables.BOOK_METADATA_AUTHOR
|
||||
private val s = Tables.SERIES
|
||||
private val rlb = Tables.READLIST_BOOK
|
||||
private val bt = Tables.BOOK_METADATA_TAG
|
||||
|
||||
private val sorts = mapOf(
|
||||
"name" to DSL.lower(b.NAME),
|
||||
|
|
@ -194,7 +195,12 @@ class BookDtoDao(
|
|||
.filter { it.name != null }
|
||||
.map { AuthorDto(it.name, it.role) }
|
||||
|
||||
br.toDto(mr.toDto(), dr.toDto(authors), if (rr.userId != null) rr.toDto() else null)
|
||||
val tags = dsl.select(bt.TAG)
|
||||
.from(bt)
|
||||
.where(bt.BOOK_ID.eq(br.id))
|
||||
.fetchSet(bt.TAG)
|
||||
|
||||
br.toDto(mr.toDto(), dr.toDto(authors, tags), if (rr.userId != null) rr.toDto() else null)
|
||||
}
|
||||
|
||||
private fun BookSearchWithReadProgress.toCondition(): Condition {
|
||||
|
|
@ -245,7 +251,7 @@ class BookDtoDao(
|
|||
comment = comment ?: ""
|
||||
)
|
||||
|
||||
private fun BookMetadataRecord.toDto(authors: List<AuthorDto>) =
|
||||
private fun BookMetadataRecord.toDto(authors: List<AuthorDto>, tags: Set<String>) =
|
||||
BookMetadataDto(
|
||||
title = title,
|
||||
titleLock = titleLock,
|
||||
|
|
@ -255,16 +261,14 @@ class BookDtoDao(
|
|||
numberLock = numberLock,
|
||||
numberSort = numberSort,
|
||||
numberSortLock = numberSortLock,
|
||||
readingDirection = readingDirection ?: "",
|
||||
readingDirectionLock = readingDirectionLock,
|
||||
publisher = publisher,
|
||||
publisherLock = publisherLock,
|
||||
ageRating = ageRating,
|
||||
ageRatingLock = ageRatingLock,
|
||||
releaseDate = releaseDate,
|
||||
releaseDateLock = releaseDateLock,
|
||||
authors = authors,
|
||||
authorsLock = authorsLock
|
||||
authorsLock = authorsLock,
|
||||
tags = tags,
|
||||
tagsLock = tagsLock,
|
||||
created = createdDate,
|
||||
lastModified = lastModifiedDate
|
||||
)
|
||||
|
||||
private fun ReadProgressRecord.toDto() =
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import org.gotson.komga.jooq.Tables
|
|||
import org.gotson.komga.jooq.tables.records.BookMetadataAuthorRecord
|
||||
import org.gotson.komga.jooq.tables.records.BookMetadataRecord
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.impl.DSL
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
|
@ -18,6 +19,7 @@ class BookMetadataDao(
|
|||
|
||||
private val d = Tables.BOOK_METADATA
|
||||
private val a = Tables.BOOK_METADATA_AUTHOR
|
||||
private val bt = Tables.BOOK_METADATA_TAG
|
||||
|
||||
private val groupFields = arrayOf(*d.fields(), *a.fields())
|
||||
|
||||
|
|
@ -39,16 +41,16 @@ class BookMetadataDao(
|
|||
.fetchGroups(
|
||||
{ it.into(d) }, { it.into(a) }
|
||||
).map { (dr, ar) ->
|
||||
dr.toDomain(ar.filterNot { it.name == null }.map { it.toDomain() })
|
||||
dr.toDomain(ar.filterNot { it.name == null }.map { it.toDomain() }, findTags(dr.bookId))
|
||||
}
|
||||
|
||||
override fun findAuthorsByName(search: String): List<String> {
|
||||
return dsl.selectDistinct(a.NAME)
|
||||
.from(a)
|
||||
.where(a.NAME.containsIgnoreCase(search))
|
||||
.orderBy(a.NAME)
|
||||
.fetch(a.NAME)
|
||||
}
|
||||
private fun findTags(bookId: String) =
|
||||
dsl.select(bt.TAG)
|
||||
.from(bt)
|
||||
.where(bt.BOOK_ID.eq(bookId))
|
||||
.fetchInto(bt)
|
||||
.mapNotNull { it.tag }
|
||||
.toSet()
|
||||
|
||||
override fun insert(metadata: BookMetadata) {
|
||||
insertMany(listOf(metadata))
|
||||
|
|
@ -69,16 +71,11 @@ class BookMetadataDao(
|
|||
d.NUMBER_LOCK,
|
||||
d.NUMBER_SORT,
|
||||
d.NUMBER_SORT_LOCK,
|
||||
d.READING_DIRECTION,
|
||||
d.READING_DIRECTION_LOCK,
|
||||
d.PUBLISHER,
|
||||
d.PUBLISHER_LOCK,
|
||||
d.AGE_RATING,
|
||||
d.AGE_RATING_LOCK,
|
||||
d.RELEASE_DATE,
|
||||
d.RELEASE_DATE_LOCK,
|
||||
d.AUTHORS_LOCK
|
||||
).values(null as String?, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null)
|
||||
d.AUTHORS_LOCK,
|
||||
d.TAGS_LOCK
|
||||
).values(null as String?, null, null, null, null, null, null, null, null, null, null, null, null)
|
||||
).also { step ->
|
||||
metadatas.forEach {
|
||||
step.bind(
|
||||
|
|
@ -91,20 +88,16 @@ class BookMetadataDao(
|
|||
it.numberLock,
|
||||
it.numberSort,
|
||||
it.numberSortLock,
|
||||
it.readingDirection?.toString(),
|
||||
it.readingDirectionLock,
|
||||
it.publisher,
|
||||
it.publisherLock,
|
||||
it.ageRating,
|
||||
it.ageRatingLock,
|
||||
it.releaseDate,
|
||||
it.releaseDateLock,
|
||||
it.authorsLock
|
||||
it.authorsLock,
|
||||
it.tagsLock
|
||||
)
|
||||
}
|
||||
}.execute()
|
||||
|
||||
insertAuthors(config.dsl(), metadatas)
|
||||
insertTags(config.dsl(), metadatas)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -122,33 +115,36 @@ class BookMetadataDao(
|
|||
}
|
||||
|
||||
private fun updateMetadata(dsl: DSLContext, metadata: BookMetadata) {
|
||||
dsl.update(d)
|
||||
.set(d.TITLE, metadata.title)
|
||||
.set(d.TITLE_LOCK, metadata.titleLock)
|
||||
.set(d.SUMMARY, metadata.summary)
|
||||
.set(d.SUMMARY_LOCK, metadata.summaryLock)
|
||||
.set(d.NUMBER, metadata.number)
|
||||
.set(d.NUMBER_LOCK, metadata.numberLock)
|
||||
.set(d.NUMBER_SORT, metadata.numberSort)
|
||||
.set(d.NUMBER_SORT_LOCK, metadata.numberSortLock)
|
||||
.set(d.READING_DIRECTION, metadata.readingDirection?.toString())
|
||||
.set(d.READING_DIRECTION_LOCK, metadata.readingDirectionLock)
|
||||
.set(d.PUBLISHER, metadata.publisher)
|
||||
.set(d.PUBLISHER_LOCK, metadata.publisherLock)
|
||||
.set(d.AGE_RATING, metadata.ageRating)
|
||||
.set(d.AGE_RATING_LOCK, metadata.ageRatingLock)
|
||||
.set(d.RELEASE_DATE, metadata.releaseDate)
|
||||
.set(d.RELEASE_DATE_LOCK, metadata.releaseDateLock)
|
||||
.set(d.AUTHORS_LOCK, metadata.authorsLock)
|
||||
.set(d.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
|
||||
.where(d.BOOK_ID.eq(metadata.bookId))
|
||||
.execute()
|
||||
dsl.transaction { config ->
|
||||
with(config.dsl()) {
|
||||
update(d)
|
||||
.set(d.TITLE, metadata.title)
|
||||
.set(d.TITLE_LOCK, metadata.titleLock)
|
||||
.set(d.SUMMARY, metadata.summary)
|
||||
.set(d.SUMMARY_LOCK, metadata.summaryLock)
|
||||
.set(d.NUMBER, metadata.number)
|
||||
.set(d.NUMBER_LOCK, metadata.numberLock)
|
||||
.set(d.NUMBER_SORT, metadata.numberSort)
|
||||
.set(d.NUMBER_SORT_LOCK, metadata.numberSortLock)
|
||||
.set(d.RELEASE_DATE, metadata.releaseDate)
|
||||
.set(d.RELEASE_DATE_LOCK, metadata.releaseDateLock)
|
||||
.set(d.AUTHORS_LOCK, metadata.authorsLock)
|
||||
.set(d.TAGS_LOCK, metadata.tagsLock)
|
||||
.set(d.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
|
||||
.where(d.BOOK_ID.eq(metadata.bookId))
|
||||
.execute()
|
||||
|
||||
dsl.deleteFrom(a)
|
||||
.where(a.BOOK_ID.eq(metadata.bookId))
|
||||
.execute()
|
||||
deleteFrom(a)
|
||||
.where(a.BOOK_ID.eq(metadata.bookId))
|
||||
.execute()
|
||||
deleteFrom(bt)
|
||||
.where(bt.BOOK_ID.eq(metadata.bookId))
|
||||
.execute()
|
||||
|
||||
insertAuthors(dsl, listOf(metadata))
|
||||
insertAuthors(this, listOf(metadata))
|
||||
insertTags(config.dsl(), listOf(metadata))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertAuthors(dsl: DSLContext, metadatas: Collection<BookMetadata>) {
|
||||
|
|
@ -166,10 +162,26 @@ class BookMetadataDao(
|
|||
}
|
||||
}
|
||||
|
||||
private fun insertTags(dsl: DSLContext, metadatas: Collection<BookMetadata>) {
|
||||
if (metadatas.any { it.tags.isNotEmpty() }) {
|
||||
dsl.batch(
|
||||
dsl.insertInto(bt, bt.BOOK_ID, bt.TAG)
|
||||
.values(null as String?, null)
|
||||
).also { step ->
|
||||
metadatas.forEach { metadata ->
|
||||
metadata.tags.forEach {
|
||||
step.bind(metadata.bookId, it)
|
||||
}
|
||||
}
|
||||
}.execute()
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(bookId: String) {
|
||||
dsl.transaction { config ->
|
||||
with(config.dsl()) {
|
||||
deleteFrom(a).where(a.BOOK_ID.eq(bookId)).execute()
|
||||
deleteFrom(bt).where(bt.BOOK_ID.eq(bookId)).execute()
|
||||
deleteFrom(d).where(d.BOOK_ID.eq(bookId)).execute()
|
||||
}
|
||||
}
|
||||
|
|
@ -179,24 +191,21 @@ class BookMetadataDao(
|
|||
dsl.transaction { config ->
|
||||
with(config.dsl()) {
|
||||
deleteFrom(a).where(a.BOOK_ID.`in`(bookIds)).execute()
|
||||
deleteFrom(bt).where(bt.BOOK_ID.`in`(bookIds)).execute()
|
||||
deleteFrom(d).where(d.BOOK_ID.`in`(bookIds)).execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun BookMetadataRecord.toDomain(authors: Collection<Author>) =
|
||||
private fun BookMetadataRecord.toDomain(authors: List<Author>, tags: Set<String>) =
|
||||
BookMetadata(
|
||||
title = title,
|
||||
summary = summary,
|
||||
number = number,
|
||||
numberSort = numberSort,
|
||||
readingDirection = readingDirection?.let {
|
||||
BookMetadata.ReadingDirection.valueOf(readingDirection)
|
||||
},
|
||||
publisher = publisher,
|
||||
ageRating = ageRating,
|
||||
releaseDate = releaseDate,
|
||||
authors = authors.toMutableList(),
|
||||
authors = authors,
|
||||
tags = tags,
|
||||
|
||||
bookId = bookId,
|
||||
|
||||
|
|
@ -207,11 +216,9 @@ class BookMetadataDao(
|
|||
summaryLock = summaryLock,
|
||||
numberLock = numberLock,
|
||||
numberSortLock = numberSortLock,
|
||||
readingDirectionLock = readingDirectionLock,
|
||||
publisherLock = publisherLock,
|
||||
ageRatingLock = ageRatingLock,
|
||||
releaseDateLock = releaseDateLock,
|
||||
authorsLock = authorsLock
|
||||
authorsLock = authorsLock,
|
||||
tagsLock = tagsLock
|
||||
)
|
||||
|
||||
private fun BookMetadataAuthorRecord.toDomain() =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package org.gotson.komga.infrastructure.jooq
|
||||
|
||||
import org.gotson.komga.domain.persistence.ReferentialRepository
|
||||
import org.gotson.komga.jooq.Tables
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.impl.DSL
|
||||
import org.jooq.impl.DSL.field
|
||||
import org.jooq.impl.DSL.lower
|
||||
import org.jooq.impl.DSL.one
|
||||
import org.jooq.impl.DSL.select
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@Component
|
||||
class ReferentialDao(
|
||||
private val dsl: DSLContext
|
||||
) : ReferentialRepository {
|
||||
|
||||
private val a = Tables.BOOK_METADATA_AUTHOR
|
||||
private val g = Tables.SERIES_METADATA_GENRE
|
||||
private val bt = Tables.BOOK_METADATA_TAG
|
||||
private val st = Tables.SERIES_METADATA_TAG
|
||||
|
||||
override fun findAuthorsByName(search: String): List<String> =
|
||||
dsl.selectDistinct(a.NAME)
|
||||
.from(a)
|
||||
.where(a.NAME.containsIgnoreCase(search))
|
||||
.orderBy(a.NAME)
|
||||
.fetch(a.NAME)
|
||||
|
||||
override fun findAllGenres(): Set<String> =
|
||||
dsl.selectDistinct(g.GENRE)
|
||||
.from(g)
|
||||
.orderBy(lower(g.GENRE))
|
||||
.fetchSet(g.GENRE)
|
||||
|
||||
override fun findAllTags(): Set<String> =
|
||||
dsl.select(bt.TAG.`as`("tag"))
|
||||
.from(bt)
|
||||
.union(
|
||||
select(st.TAG.`as`("tag")).from(st)
|
||||
)
|
||||
.fetchSet(0, String::class.java)
|
||||
.sortedBy { it.toLowerCase() }
|
||||
.toSet()
|
||||
}
|
||||
|
|
@ -98,7 +98,6 @@ class SeriesDao(
|
|||
dsl.transaction { config ->
|
||||
with(config.dsl())
|
||||
{
|
||||
deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
|
||||
deleteFrom(s).where(s.ID.eq(seriesId)).execute()
|
||||
}
|
||||
}
|
||||
|
|
@ -108,7 +107,6 @@ class SeriesDao(
|
|||
dsl.transaction { config ->
|
||||
with(config.dsl())
|
||||
{
|
||||
deleteFrom(d).execute()
|
||||
deleteFrom(s).execute()
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +116,6 @@ class SeriesDao(
|
|||
dsl.transaction { config ->
|
||||
with(config.dsl())
|
||||
{
|
||||
deleteFrom(d).where(d.SERIES_ID.`in`(seriesIds)).execute()
|
||||
deleteFrom(s).where(s.ID.`in`(seriesIds)).execute()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class SeriesDtoDao(
|
|||
private val d = Tables.SERIES_METADATA
|
||||
private val r = Tables.READ_PROGRESS
|
||||
private val cs = Tables.COLLECTION_SERIES
|
||||
private val g = Tables.SERIES_METADATA_GENRE
|
||||
private val st = Tables.SERIES_METADATA_TAG
|
||||
|
||||
val countUnread: AggregateFunction<BigDecimal> = DSL.sum(DSL.`when`(r.COMPLETED.isNull, 1).otherwise(0))
|
||||
val countRead: AggregateFunction<BigDecimal> = DSL.sum(DSL.`when`(r.COMPLETED.isTrue, 1).otherwise(0))
|
||||
|
|
@ -161,7 +163,18 @@ class SeriesDtoDao(
|
|||
val booksUnreadCount = booksCountRecord.get(BOOKS_UNREAD_COUNT, Int::class.java)
|
||||
val booksReadCount = booksCountRecord.get(BOOKS_READ_COUNT, Int::class.java)
|
||||
val booksInProgressCount = booksCountRecord.get(BOOKS_IN_PROGRESS_COUNT, Int::class.java)
|
||||
sr.toDto(booksCount, booksReadCount, booksUnreadCount, booksInProgressCount, dr.toDto())
|
||||
|
||||
val genres = dsl.select(g.GENRE)
|
||||
.from(g)
|
||||
.where(g.SERIES_ID.eq(sr.id))
|
||||
.fetchSet(g.GENRE)
|
||||
|
||||
val tags = dsl.select(st.TAG)
|
||||
.from(st)
|
||||
.where(st.SERIES_ID.eq(sr.id))
|
||||
.fetchSet(st.TAG)
|
||||
|
||||
sr.toDto(booksCount, booksReadCount, booksUnreadCount, booksInProgressCount, dr.toDto(genres, tags))
|
||||
}
|
||||
|
||||
private fun SeriesSearchWithReadProgress.toCondition(): Condition {
|
||||
|
|
@ -200,7 +213,7 @@ class SeriesDtoDao(
|
|||
metadata = metadata
|
||||
)
|
||||
|
||||
private fun SeriesMetadataRecord.toDto() =
|
||||
private fun SeriesMetadataRecord.toDto(genres: Set<String>, tags: Set<String>) =
|
||||
SeriesMetadataDto(
|
||||
status = status,
|
||||
statusLock = statusLock,
|
||||
|
|
@ -209,7 +222,21 @@ class SeriesDtoDao(
|
|||
title = title,
|
||||
titleLock = titleLock,
|
||||
titleSort = titleSort,
|
||||
titleSortLock = titleSortLock
|
||||
titleSortLock = titleSortLock,
|
||||
summary = summary,
|
||||
summaryLock = summaryLock,
|
||||
readingDirection = readingDirection ?: "",
|
||||
readingDirectionLock = readingDirectionLock,
|
||||
publisher = publisher,
|
||||
publisherLock = publisherLock,
|
||||
ageRating = ageRating,
|
||||
ageRatingLock = ageRatingLock,
|
||||
language = language,
|
||||
languageLock = languageLock,
|
||||
genres = genres,
|
||||
genresLock = genresLock,
|
||||
tags = tags,
|
||||
tagsLock = tagsLock
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
|||
import org.gotson.komga.jooq.Tables
|
||||
import org.gotson.komga.jooq.tables.records.SeriesMetadataRecord
|
||||
import org.jooq.DSLContext
|
||||
import org.jooq.impl.DSL.lower
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
|
@ -15,63 +16,180 @@ class SeriesMetadataDao(
|
|||
) : SeriesMetadataRepository {
|
||||
|
||||
private val d = Tables.SERIES_METADATA
|
||||
private val g = Tables.SERIES_METADATA_GENRE
|
||||
private val st = Tables.SERIES_METADATA_TAG
|
||||
|
||||
override fun findById(seriesId: String): SeriesMetadata =
|
||||
findOne(seriesId).toDomain()
|
||||
findOne(seriesId).toDomain(findGenres(seriesId), findTags(seriesId))
|
||||
|
||||
override fun findByIdOrNull(seriesId: String): SeriesMetadata? =
|
||||
findOne(seriesId)?.toDomain()
|
||||
findOne(seriesId)?.toDomain(findGenres(seriesId), findTags(seriesId))
|
||||
|
||||
private fun findOne(seriesId: String) =
|
||||
dsl.selectFrom(d)
|
||||
.where(d.SERIES_ID.eq(seriesId))
|
||||
.fetchOneInto(d)
|
||||
|
||||
override fun insert(metadata: SeriesMetadata): SeriesMetadata {
|
||||
dsl.insertInto(d)
|
||||
.set(d.SERIES_ID, metadata.seriesId)
|
||||
.set(d.STATUS, metadata.status.toString())
|
||||
.set(d.TITLE, metadata.title)
|
||||
.set(d.TITLE_SORT, metadata.titleSort)
|
||||
.set(d.STATUS_LOCK, metadata.statusLock)
|
||||
.set(d.TITLE_LOCK, metadata.titleLock)
|
||||
.set(d.TITLE_SORT_LOCK, metadata.titleSortLock)
|
||||
.execute()
|
||||
private fun findGenres(seriesId: String) =
|
||||
dsl.select(g.GENRE)
|
||||
.from(g)
|
||||
.where(g.SERIES_ID.eq(seriesId))
|
||||
.fetchInto(g)
|
||||
.mapNotNull { it.genre }
|
||||
.toSet()
|
||||
|
||||
return findById(metadata.seriesId)
|
||||
private fun findTags(seriesId: String) =
|
||||
dsl.select(st.TAG)
|
||||
.from(st)
|
||||
.where(st.SERIES_ID.eq(seriesId))
|
||||
.fetchInto(st)
|
||||
.mapNotNull { it.tag }
|
||||
.toSet()
|
||||
|
||||
override fun insert(metadata: SeriesMetadata) {
|
||||
dsl.transaction { config ->
|
||||
config.dsl().insertInto(d)
|
||||
.set(d.SERIES_ID, metadata.seriesId)
|
||||
.set(d.STATUS, metadata.status.toString())
|
||||
.set(d.TITLE, metadata.title)
|
||||
.set(d.TITLE_SORT, metadata.titleSort)
|
||||
.set(d.SUMMARY, metadata.summary)
|
||||
.set(d.READING_DIRECTION, metadata.readingDirection?.toString())
|
||||
.set(d.PUBLISHER, metadata.publisher)
|
||||
.set(d.AGE_RATING, metadata.ageRating)
|
||||
.set(d.LANGUAGE, metadata.language)
|
||||
.set(d.STATUS_LOCK, metadata.statusLock)
|
||||
.set(d.TITLE_LOCK, metadata.titleLock)
|
||||
.set(d.TITLE_SORT_LOCK, metadata.titleSortLock)
|
||||
.set(d.SUMMARY_LOCK, metadata.summaryLock)
|
||||
.set(d.READING_DIRECTION_LOCK, metadata.readingDirectionLock)
|
||||
.set(d.PUBLISHER_LOCK, metadata.publisherLock)
|
||||
.set(d.AGE_RATING_LOCK, metadata.ageRatingLock)
|
||||
.set(d.LANGUAGE_LOCK, metadata.languageLock)
|
||||
.set(d.GENRES_LOCK, metadata.genresLock)
|
||||
.set(d.TAGS_LOCK, metadata.tagsLock)
|
||||
.execute()
|
||||
|
||||
insertGenres(config.dsl(), metadata)
|
||||
insertTags(config.dsl(), metadata)
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(metadata: SeriesMetadata) {
|
||||
dsl.update(d)
|
||||
.set(d.STATUS, metadata.status.toString())
|
||||
.set(d.TITLE, metadata.title)
|
||||
.set(d.TITLE_SORT, metadata.titleSort)
|
||||
.set(d.STATUS_LOCK, metadata.statusLock)
|
||||
.set(d.TITLE_LOCK, metadata.titleLock)
|
||||
.set(d.TITLE_SORT_LOCK, metadata.titleSortLock)
|
||||
.set(d.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
|
||||
.where(d.SERIES_ID.eq(metadata.seriesId))
|
||||
.execute()
|
||||
dsl.transaction { config ->
|
||||
config.dsl().update(d)
|
||||
.set(d.STATUS, metadata.status.toString())
|
||||
.set(d.TITLE, metadata.title)
|
||||
.set(d.TITLE_SORT, metadata.titleSort)
|
||||
.set(d.SUMMARY, metadata.summary)
|
||||
.set(d.READING_DIRECTION, metadata.readingDirection?.toString())
|
||||
.set(d.PUBLISHER, metadata.publisher)
|
||||
.set(d.AGE_RATING, metadata.ageRating)
|
||||
.set(d.LANGUAGE, metadata.language)
|
||||
.set(d.STATUS_LOCK, metadata.statusLock)
|
||||
.set(d.TITLE_LOCK, metadata.titleLock)
|
||||
.set(d.TITLE_SORT_LOCK, metadata.titleSortLock)
|
||||
.set(d.SUMMARY_LOCK, metadata.summaryLock)
|
||||
.set(d.READING_DIRECTION_LOCK, metadata.readingDirectionLock)
|
||||
.set(d.PUBLISHER_LOCK, metadata.publisherLock)
|
||||
.set(d.AGE_RATING_LOCK, metadata.ageRatingLock)
|
||||
.set(d.LANGUAGE_LOCK, metadata.languageLock)
|
||||
.set(d.GENRES_LOCK, metadata.genresLock)
|
||||
.set(d.TAGS_LOCK, metadata.tagsLock)
|
||||
.set(d.LAST_MODIFIED_DATE, LocalDateTime.now(ZoneId.of("Z")))
|
||||
.where(d.SERIES_ID.eq(metadata.seriesId))
|
||||
.execute()
|
||||
|
||||
config.dsl().deleteFrom(g)
|
||||
.where(g.SERIES_ID.eq(metadata.seriesId))
|
||||
.execute()
|
||||
|
||||
config.dsl().deleteFrom(st)
|
||||
.where(st.SERIES_ID.eq(metadata.seriesId))
|
||||
.execute()
|
||||
|
||||
insertGenres(config.dsl(), metadata)
|
||||
insertTags(config.dsl(), metadata)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertGenres(dsl: DSLContext, metadata: SeriesMetadata) {
|
||||
if (metadata.genres.isNotEmpty()) {
|
||||
dsl.batch(
|
||||
dsl.insertInto(g, g.SERIES_ID, g.GENRE)
|
||||
.values(null as String?, null)
|
||||
).also { step ->
|
||||
metadata.genres.forEach {
|
||||
step.bind(metadata.seriesId, it)
|
||||
}
|
||||
}.execute()
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertTags(dsl: DSLContext, metadata: SeriesMetadata) {
|
||||
if (metadata.tags.isNotEmpty()) {
|
||||
dsl.batch(
|
||||
dsl.insertInto(st, st.SERIES_ID, st.TAG)
|
||||
.values(null as String?, null)
|
||||
).also { step ->
|
||||
metadata.tags.forEach {
|
||||
step.bind(metadata.seriesId, it)
|
||||
}
|
||||
}.execute()
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(seriesId: String) {
|
||||
dsl.deleteFrom(d)
|
||||
.where(d.SERIES_ID.eq(seriesId))
|
||||
.execute()
|
||||
dsl.transaction { config ->
|
||||
with(config.dsl()) {
|
||||
deleteFrom(g).where(g.SERIES_ID.eq(seriesId)).execute()
|
||||
deleteFrom(st).where(st.SERIES_ID.eq(seriesId)).execute()
|
||||
deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(seriesIds: Collection<String>) {
|
||||
dsl.transaction { config ->
|
||||
with(config.dsl()) {
|
||||
deleteFrom(g).where(g.SERIES_ID.`in`(seriesIds)).execute()
|
||||
deleteFrom(st).where(st.SERIES_ID.`in`(seriesIds)).execute()
|
||||
deleteFrom(d).where(d.SERIES_ID.`in`(seriesIds)).execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun count(): Long = dsl.fetchCount(d).toLong()
|
||||
|
||||
|
||||
private fun SeriesMetadataRecord.toDomain() =
|
||||
private fun SeriesMetadataRecord.toDomain(genres: Set<String>, tags: Set<String>) =
|
||||
SeriesMetadata(
|
||||
status = SeriesMetadata.Status.valueOf(status),
|
||||
title = title,
|
||||
titleSort = titleSort,
|
||||
seriesId = seriesId,
|
||||
summary = summary,
|
||||
readingDirection = readingDirection?.let {
|
||||
SeriesMetadata.ReadingDirection.valueOf(readingDirection)
|
||||
},
|
||||
publisher = publisher,
|
||||
ageRating = ageRating,
|
||||
language = language,
|
||||
genres = genres,
|
||||
tags = tags,
|
||||
|
||||
statusLock = statusLock,
|
||||
titleLock = titleLock,
|
||||
titleSortLock = titleSortLock,
|
||||
summaryLock = summaryLock,
|
||||
readingDirectionLock = readingDirectionLock,
|
||||
publisherLock = publisherLock,
|
||||
ageRatingLock = ageRatingLock,
|
||||
languageLock = languageLock,
|
||||
genresLock = genresLock,
|
||||
tagsLock = tagsLock,
|
||||
|
||||
seriesId = seriesId,
|
||||
|
||||
createdDate = createdDate.toCurrentTimeZone(),
|
||||
lastModifiedDate = lastModifiedDate.toCurrentTimeZone()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,15 +4,16 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper
|
|||
import mu.KotlinLogging
|
||||
import org.gotson.komga.domain.model.Author
|
||||
import org.gotson.komga.domain.model.Book
|
||||
import org.gotson.komga.domain.model.BookMetadata
|
||||
import org.gotson.komga.domain.model.BookMetadataPatch
|
||||
import org.gotson.komga.domain.model.Media
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.domain.model.SeriesMetadataPatch
|
||||
import org.gotson.komga.domain.service.BookAnalyzer
|
||||
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.ComicInfo
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.Manga
|
||||
import org.gotson.komga.infrastructure.validation.BCP47TagValidator
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDate
|
||||
|
|
@ -42,20 +43,11 @@ class ComicInfoProvider(
|
|||
comicInfo.coverArtist?.let { authors += it.splitWithRole("cover") }
|
||||
comicInfo.editor?.let { authors += it.splitWithRole("editor") }
|
||||
|
||||
val readingDirection = when (comicInfo.manga) {
|
||||
Manga.NO -> BookMetadata.ReadingDirection.LEFT_TO_RIGHT
|
||||
Manga.YES_AND_RIGHT_TO_LEFT -> BookMetadata.ReadingDirection.RIGHT_TO_LEFT
|
||||
else -> null
|
||||
}
|
||||
|
||||
return BookMetadataPatch(
|
||||
title = comicInfo.title,
|
||||
summary = comicInfo.summary,
|
||||
number = comicInfo.number,
|
||||
numberSort = comicInfo.number?.toFloatOrNull(),
|
||||
readingDirection = readingDirection,
|
||||
publisher = comicInfo.publisher,
|
||||
ageRating = comicInfo.ageRating?.ageRating,
|
||||
releaseDate = releaseDate,
|
||||
authors = authors.ifEmpty { null },
|
||||
readList = comicInfo.alternateSeries ?: comicInfo.storyArc,
|
||||
|
|
@ -67,11 +59,25 @@ class ComicInfoProvider(
|
|||
|
||||
override fun getSeriesMetadataFromBook(book: Book, media: Media): SeriesMetadataPatch? {
|
||||
getComicInfo(book, media)?.let { comicInfo ->
|
||||
val readingDirection = when (comicInfo.manga) {
|
||||
Manga.NO -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT
|
||||
Manga.YES_AND_RIGHT_TO_LEFT -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT
|
||||
else -> null
|
||||
}
|
||||
|
||||
val genres = comicInfo.genre?.split(',')?.map { it.trim() }?.toSet() ?: emptySet()
|
||||
|
||||
return SeriesMetadataPatch(
|
||||
comicInfo.series,
|
||||
comicInfo.series,
|
||||
null,
|
||||
listOfNotNull(comicInfo.seriesGroup)
|
||||
title = comicInfo.series,
|
||||
titleSort = comicInfo.series,
|
||||
status = null,
|
||||
summary = null,
|
||||
readingDirection = readingDirection,
|
||||
publisher = comicInfo.publisher,
|
||||
ageRating = comicInfo.ageRating?.ageRating,
|
||||
language = if(comicInfo.languageISO != null && BCP47TagValidator.isValid(comicInfo.languageISO!!)) comicInfo.languageISO else null,
|
||||
genres = genres,
|
||||
collections = listOfNotNull(comicInfo.seriesGroup)
|
||||
)
|
||||
}
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ package org.gotson.komga.infrastructure.metadata.epub
|
|||
|
||||
import org.gotson.komga.domain.model.Author
|
||||
import org.gotson.komga.domain.model.Book
|
||||
import org.gotson.komga.domain.model.BookMetadata
|
||||
import org.gotson.komga.domain.model.BookMetadataPatch
|
||||
import org.gotson.komga.domain.model.Media
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.domain.model.SeriesMetadataPatch
|
||||
import org.gotson.komga.infrastructure.mediacontainer.EpubExtractor
|
||||
import org.gotson.komga.infrastructure.metadata.BookMetadataProvider
|
||||
import org.gotson.komga.infrastructure.metadata.SeriesMetadataProvider
|
||||
import org.gotson.komga.infrastructure.validation.BCP47TagValidator
|
||||
import org.jsoup.Jsoup
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.LocalDate
|
||||
|
|
@ -34,18 +35,9 @@ class EpubMetadataProvider(
|
|||
val opf = Jsoup.parse(packageFile)
|
||||
|
||||
val title = opf.selectFirst("metadata > dc|title")?.text()
|
||||
val publisher = opf.selectFirst("metadata > dc|publisher")?.text()
|
||||
val description = opf.selectFirst("metadata > dc|description")?.text()
|
||||
val date = opf.selectFirst("metadata > dc|date")?.text()?.let { parseDate(it) }
|
||||
|
||||
val direction = opf.getElementsByTag("spine").first().attr("page-progression-direction")?.let {
|
||||
when (it) {
|
||||
"rtl" -> BookMetadata.ReadingDirection.RIGHT_TO_LEFT
|
||||
"ltr" -> BookMetadata.ReadingDirection.LEFT_TO_RIGHT
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val creatorRefines = opf.select("metadata > meta[property=role][scheme=marc:relators]")
|
||||
.associate { it.attr("refines").removePrefix("#") to it.text() }
|
||||
val authors = opf.select("metadata > dc|creator")
|
||||
|
|
@ -63,9 +55,6 @@ class EpubMetadataProvider(
|
|||
summary = description,
|
||||
number = null,
|
||||
numberSort = null,
|
||||
readingDirection = direction,
|
||||
publisher = publisher,
|
||||
ageRating = null,
|
||||
releaseDate = date,
|
||||
authors = authors,
|
||||
readList = null,
|
||||
|
|
@ -81,8 +70,30 @@ class EpubMetadataProvider(
|
|||
val opf = Jsoup.parse(packageFile)
|
||||
|
||||
val series = opf.selectFirst("metadata > meta[property=belongs-to-collection]")?.text()
|
||||
val publisher = opf.selectFirst("metadata > dc|publisher")?.text()
|
||||
val language = opf.selectFirst("metadata > dc|language")?.text()
|
||||
val genre = opf.selectFirst("metadata > dc|subject")?.text()
|
||||
|
||||
return SeriesMetadataPatch(series, series, null)
|
||||
val direction = opf.getElementsByTag("spine").first().attr("page-progression-direction")?.let {
|
||||
when (it) {
|
||||
"rtl" -> SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT
|
||||
"ltr" -> SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
return SeriesMetadataPatch(
|
||||
title = series,
|
||||
titleSort = series,
|
||||
status = null,
|
||||
readingDirection = direction,
|
||||
publisher = publisher,
|
||||
ageRating = null,
|
||||
summary = null,
|
||||
language = if(language != null && BCP47TagValidator.isValid(language)) language else null,
|
||||
genres = if(genre != null) setOf(genre) else emptySet(),
|
||||
collections = emptyList()
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package org.gotson.komga.infrastructure.validation
|
||||
|
||||
import com.ibm.icu.util.ULocale
|
||||
import javax.validation.Constraint
|
||||
import javax.validation.ConstraintValidator
|
||||
import javax.validation.ConstraintValidatorContext
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
@Constraint(validatedBy = [BCP47Validator::class])
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.ANNOTATION_CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class BCP47(
|
||||
val message: String = "Must be a valid BCP 47 language tag",
|
||||
val groups: Array<KClass<out Any>> = [],
|
||||
val payload: Array<KClass<out Any>> = []
|
||||
)
|
||||
|
||||
class BCP47Validator : ConstraintValidator<BCP47, String> {
|
||||
|
||||
override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {
|
||||
if (value == null) return false
|
||||
return BCP47TagValidator.isValid(value)
|
||||
}
|
||||
}
|
||||
|
||||
object BCP47TagValidator {
|
||||
val languages by lazy { ULocale.getISOLanguages().toSet() }
|
||||
|
||||
fun isValid(value: String): Boolean {
|
||||
return ULocale.forLanguageTag(value).let {
|
||||
it.language.isNotBlank() && languages.contains(it.language)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package org.gotson.komga.infrastructure.validation
|
||||
|
||||
import com.ibm.icu.util.ULocale
|
||||
import javax.validation.Constraint
|
||||
import javax.validation.ConstraintValidator
|
||||
import javax.validation.ConstraintValidatorContext
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
@Constraint(validatedBy = [BlankValidator::class])
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.ANNOTATION_CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class Blank(
|
||||
val message: String = "Must be blank",
|
||||
val groups: Array<KClass<out Any>> = [],
|
||||
val payload: Array<KClass<out Any>> = []
|
||||
)
|
||||
|
||||
class BlankValidator : ConstraintValidator<Blank, String> {
|
||||
|
||||
override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean {
|
||||
if (value == null) return false
|
||||
return value.isBlank()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package org.gotson.komga.infrastructure.validation
|
||||
|
||||
import org.hibernate.validator.constraints.CompositionType
|
||||
import org.hibernate.validator.constraints.ConstraintComposition
|
||||
import javax.validation.Constraint
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.Null
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
@ConstraintComposition(CompositionType.OR)
|
||||
@Constraint(validatedBy = [])
|
||||
@Null
|
||||
@Blank
|
||||
@BCP47
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class NullOrBlankOrBCP47(
|
||||
val message: String = "Must be null or blank or valid BCP 47 language tag",
|
||||
val groups: Array<KClass<out Any>> = [],
|
||||
val payload: Array<KClass<out Any>> = []
|
||||
)
|
||||
|
|
@ -422,18 +422,16 @@ class BookController(
|
|||
numberLock = numberLock ?: existing.numberLock,
|
||||
numberSort = numberSort ?: existing.numberSort,
|
||||
numberSortLock = numberSortLock ?: existing.numberSortLock,
|
||||
readingDirection = if (isSet("readingDirection")) readingDirection else existing.readingDirection,
|
||||
readingDirectionLock = readingDirectionLock ?: existing.readingDirectionLock,
|
||||
publisher = publisher ?: existing.publisher,
|
||||
publisherLock = publisherLock ?: existing.publisherLock,
|
||||
ageRating = if (isSet("ageRating")) ageRating else existing.ageRating,
|
||||
ageRatingLock = ageRatingLock ?: existing.ageRatingLock,
|
||||
releaseDate = if (isSet("releaseDate")) releaseDate else existing.releaseDate,
|
||||
releaseDateLock = releaseDateLock ?: existing.releaseDateLock,
|
||||
authors = if (isSet("authors")) {
|
||||
if (authors != null) authors!!.map { Author(it.name ?: "", it.role ?: "") } else emptyList()
|
||||
} else existing.authors,
|
||||
authorsLock = authorsLock ?: existing.authorsLock
|
||||
authorsLock = authorsLock ?: existing.authorsLock,
|
||||
tags = if(isSet("tags")) {
|
||||
if(tags != null) tags!! else emptySet()
|
||||
} else existing.tags,
|
||||
tagsLock = tagsLock ?: existing.tagsLock
|
||||
)
|
||||
}
|
||||
bookMetadataRepository.update(updated)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package org.gotson.komga.interfaces.rest
|
||||
|
||||
import org.gotson.komga.domain.persistence.BookMetadataRepository
|
||||
import org.gotson.komga.domain.persistence.ReferentialRepository
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
|
|
@ -11,12 +11,20 @@ import org.springframework.web.bind.annotation.RestController
|
|||
@RestController
|
||||
@RequestMapping("api/v1", produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||
class ReferentialController(
|
||||
private val bookMetadataRepository: BookMetadataRepository
|
||||
private val referentialRepository: ReferentialRepository
|
||||
) {
|
||||
|
||||
@GetMapping("/authors")
|
||||
fun getAuthors(
|
||||
@RequestParam(name = "search", defaultValue = "") search: String
|
||||
): List<String> =
|
||||
bookMetadataRepository.findAuthorsByName(search)
|
||||
referentialRepository.findAuthorsByName(search)
|
||||
|
||||
@GetMapping("/genres")
|
||||
fun getGenres(): Set<String> =
|
||||
referentialRepository.findAllGenres()
|
||||
|
||||
@GetMapping("/tags")
|
||||
fun getTags(): Set<String> =
|
||||
referentialRepository.findAllTags()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema
|
|||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||
import mu.KotlinLogging
|
||||
import org.gotson.komga.application.tasks.TaskReceiver
|
||||
import org.gotson.komga.domain.model.Author
|
||||
import org.gotson.komga.domain.model.BookSearchWithReadProgress
|
||||
import org.gotson.komga.domain.model.Media
|
||||
import org.gotson.komga.domain.model.ROLE_ADMIN
|
||||
|
|
@ -286,7 +287,25 @@ class SeriesController(
|
|||
title = title ?: existing.title,
|
||||
titleLock = titleLock ?: existing.titleLock,
|
||||
titleSort = titleSort ?: existing.titleSort,
|
||||
titleSortLock = titleSortLock ?: existing.titleSortLock
|
||||
titleSortLock = titleSortLock ?: existing.titleSortLock,
|
||||
summary = summary ?: existing.summary,
|
||||
summaryLock = summaryLock ?: existing.summaryLock,
|
||||
language = language ?: existing.language,
|
||||
languageLock = languageLock ?: existing.languageLock,
|
||||
readingDirection = if (isSet("readingDirection")) readingDirection else existing.readingDirection,
|
||||
readingDirectionLock = readingDirectionLock ?: existing.readingDirectionLock,
|
||||
publisher = publisher ?: existing.publisher,
|
||||
publisherLock = publisherLock ?: existing.publisherLock,
|
||||
ageRating = if (isSet("ageRating")) ageRating else existing.ageRating,
|
||||
ageRatingLock = ageRatingLock ?: existing.ageRatingLock,
|
||||
genres = if (isSet("genres")) {
|
||||
if (genres != null) genres!! else emptySet()
|
||||
} else existing.genres,
|
||||
genresLock = genresLock ?: existing.genresLock,
|
||||
tags = if(isSet("tags")) {
|
||||
if(tags != null) tags!! else emptySet()
|
||||
} else existing.tags,
|
||||
tagsLock = tagsLock ?: existing.tagsLock
|
||||
)
|
||||
}
|
||||
seriesMetadataRepository.update(updated)
|
||||
|
|
|
|||
|
|
@ -45,17 +45,18 @@ data class BookMetadataDto(
|
|||
val numberLock: Boolean,
|
||||
val numberSort: Float,
|
||||
val numberSortLock: Boolean,
|
||||
val readingDirection: String,
|
||||
val readingDirectionLock: Boolean,
|
||||
val publisher: String,
|
||||
val publisherLock: Boolean,
|
||||
val ageRating: Int?,
|
||||
val ageRatingLock: Boolean,
|
||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||
val releaseDate: LocalDate?,
|
||||
val releaseDateLock: Boolean,
|
||||
val authors: List<AuthorDto>,
|
||||
val authorsLock: Boolean
|
||||
val authorsLock: Boolean,
|
||||
val tags: Set<String>,
|
||||
val tagsLock: Boolean,
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val created: LocalDateTime,
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val lastModified: LocalDateTime
|
||||
)
|
||||
|
||||
data class AuthorDto(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package org.gotson.komga.interfaces.rest.dto
|
||||
|
||||
import org.gotson.komga.domain.model.BookMetadata
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.infrastructure.validation.NullOrNotBlank
|
||||
import java.time.LocalDate
|
||||
import javax.validation.Valid
|
||||
|
|
@ -30,25 +30,6 @@ class BookMetadataUpdateDto {
|
|||
|
||||
var numberSortLock: Boolean? = null
|
||||
|
||||
var readingDirection: BookMetadata.ReadingDirection?
|
||||
by Delegates.observable<BookMetadata.ReadingDirection?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
}
|
||||
|
||||
var readingDirectionLock: Boolean? = null
|
||||
|
||||
var publisher: String? = null
|
||||
|
||||
var publisherLock: Boolean? = null
|
||||
|
||||
@get:PositiveOrZero
|
||||
var ageRating: Int?
|
||||
by Delegates.observable<Int?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
}
|
||||
|
||||
var ageRatingLock: Boolean? = null
|
||||
|
||||
var releaseDate: LocalDate?
|
||||
by Delegates.observable<LocalDate?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
|
|
@ -63,6 +44,13 @@ class BookMetadataUpdateDto {
|
|||
}
|
||||
|
||||
var authorsLock: Boolean? = null
|
||||
|
||||
var tags: Set<String>?
|
||||
by Delegates.observable<Set<String>?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
}
|
||||
|
||||
var tagsLock: Boolean? = null
|
||||
}
|
||||
|
||||
class AuthorUpdateDto {
|
||||
|
|
|
|||
|
|
@ -27,12 +27,27 @@ fun SeriesDto.restrictUrl(restrict: Boolean) =
|
|||
data class SeriesMetadataDto(
|
||||
val status: String,
|
||||
val statusLock: Boolean,
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val created: LocalDateTime,
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val lastModified: LocalDateTime,
|
||||
val title: String,
|
||||
val titleLock: Boolean,
|
||||
val titleSort: String,
|
||||
val titleSortLock: Boolean
|
||||
val titleSortLock: Boolean,
|
||||
val summary: String,
|
||||
val summaryLock: Boolean,
|
||||
val readingDirection: String,
|
||||
val readingDirectionLock: Boolean,
|
||||
val publisher: String,
|
||||
val publisherLock: Boolean,
|
||||
val ageRating: Int?,
|
||||
val ageRatingLock: Boolean,
|
||||
val language: String,
|
||||
val languageLock: Boolean,
|
||||
val genres: Set<String>,
|
||||
val genresLock: Boolean,
|
||||
val tags: Set<String>,
|
||||
val tagsLock: Boolean,
|
||||
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val created: LocalDateTime,
|
||||
@JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
|
||||
val lastModified: LocalDateTime
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,69 @@
|
|||
package org.gotson.komga.interfaces.rest.dto
|
||||
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.infrastructure.validation.NullOrBlankOrBCP47
|
||||
import org.gotson.komga.infrastructure.validation.NullOrNotBlank
|
||||
import javax.validation.Valid
|
||||
import javax.validation.constraints.PositiveOrZero
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class SeriesMetadataUpdateDto {
|
||||
private val isSet = mutableMapOf<String, Boolean>()
|
||||
fun isSet(prop: String) = isSet.getOrDefault(prop, false)
|
||||
|
||||
val status: SeriesMetadata.Status? = null
|
||||
|
||||
val statusLock: Boolean? = null
|
||||
|
||||
data class SeriesMetadataUpdateDto(
|
||||
val status: SeriesMetadata.Status?,
|
||||
val statusLock: Boolean?,
|
||||
@get:NullOrNotBlank
|
||||
val title: String?,
|
||||
val titleLock: Boolean?,
|
||||
val title: String? = null
|
||||
|
||||
val titleLock: Boolean? = null
|
||||
|
||||
@get:NullOrNotBlank
|
||||
val titleSort: String?,
|
||||
val titleSortLock: Boolean?
|
||||
)
|
||||
val titleSort: String? = null
|
||||
|
||||
val titleSortLock: Boolean? = null
|
||||
|
||||
var summary: String? = null
|
||||
|
||||
var summaryLock: Boolean? = null
|
||||
|
||||
var publisher: String? = null
|
||||
|
||||
var publisherLock: Boolean? = null
|
||||
|
||||
var readingDirection: SeriesMetadata.ReadingDirection?
|
||||
by Delegates.observable<SeriesMetadata.ReadingDirection?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
}
|
||||
|
||||
var readingDirectionLock: Boolean? = null
|
||||
|
||||
@get:PositiveOrZero
|
||||
var ageRating: Int?
|
||||
by Delegates.observable<Int?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
}
|
||||
|
||||
var ageRatingLock: Boolean? = null
|
||||
|
||||
@get:NullOrBlankOrBCP47
|
||||
var language: String? = null
|
||||
|
||||
var languageLock: Boolean? = null
|
||||
|
||||
var genres: Set<String>?
|
||||
by Delegates.observable<Set<String>?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
}
|
||||
|
||||
var genresLock: Boolean? = null
|
||||
|
||||
var tags: Set<String>?
|
||||
by Delegates.observable<Set<String>?>(null) { prop, _, _ ->
|
||||
isSet[prop.name] = true
|
||||
}
|
||||
|
||||
var tagsLock: Boolean? = null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import org.assertj.core.api.Assertions.assertThat
|
|||
import org.assertj.core.api.Assertions.catchThrowable
|
||||
import org.gotson.komga.domain.model.Author
|
||||
import org.gotson.komga.domain.model.BookMetadata
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.domain.model.makeBook
|
||||
import org.gotson.komga.domain.model.makeLibrary
|
||||
import org.gotson.komga.domain.model.makeSeries
|
||||
|
|
@ -64,21 +65,17 @@ class BookMetadataDaoTest(
|
|||
summary = "Summary",
|
||||
number = "1",
|
||||
numberSort = 1F,
|
||||
readingDirection = BookMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
publisher = "publisher",
|
||||
ageRating = 18,
|
||||
releaseDate = LocalDate.now(),
|
||||
authors = mutableListOf(Author("author", "role")),
|
||||
authors = listOf(Author("author", "role")),
|
||||
tags = setOf("tag", "another"),
|
||||
bookId = book.id,
|
||||
titleLock = true,
|
||||
summaryLock = true,
|
||||
numberLock = true,
|
||||
numberSortLock = true,
|
||||
readingDirectionLock = true,
|
||||
publisherLock = true,
|
||||
ageRatingLock = true,
|
||||
releaseDateLock = true,
|
||||
authorsLock = true
|
||||
authorsLock = true,
|
||||
tagsLock = true
|
||||
)
|
||||
|
||||
bookMetadataDao.insert(metadata)
|
||||
|
|
@ -92,25 +89,21 @@ class BookMetadataDaoTest(
|
|||
assertThat(created.summary).isEqualTo(metadata.summary)
|
||||
assertThat(created.number).isEqualTo(metadata.number)
|
||||
assertThat(created.numberSort).isEqualTo(metadata.numberSort)
|
||||
assertThat(created.readingDirection).isEqualTo(metadata.readingDirection)
|
||||
assertThat(created.publisher).isEqualTo(metadata.publisher)
|
||||
assertThat(created.ageRating).isEqualTo(metadata.ageRating)
|
||||
assertThat(created.releaseDate).isEqualTo(metadata.releaseDate)
|
||||
assertThat(created.authors).hasSize(1)
|
||||
with(created.authors.first()) {
|
||||
assertThat(name).isEqualTo(metadata.authors.first().name)
|
||||
assertThat(role).isEqualTo(metadata.authors.first().role)
|
||||
}
|
||||
assertThat(created.tags).containsAll(metadata.tags)
|
||||
|
||||
assertThat(created.titleLock).isEqualTo(metadata.titleLock)
|
||||
assertThat(created.summaryLock).isEqualTo(metadata.summaryLock)
|
||||
assertThat(created.numberLock).isEqualTo(metadata.numberLock)
|
||||
assertThat(created.numberSortLock).isEqualTo(metadata.numberSortLock)
|
||||
assertThat(created.readingDirectionLock).isEqualTo(metadata.readingDirectionLock)
|
||||
assertThat(created.publisherLock).isEqualTo(metadata.publisherLock)
|
||||
assertThat(created.ageRatingLock).isEqualTo(metadata.ageRatingLock)
|
||||
assertThat(created.releaseDateLock).isEqualTo(metadata.releaseDateLock)
|
||||
assertThat(created.authorsLock).isEqualTo(metadata.authorsLock)
|
||||
assertThat(created.tagsLock).isEqualTo(metadata.tagsLock)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -131,21 +124,17 @@ class BookMetadataDaoTest(
|
|||
assertThat(created.summary).isBlank()
|
||||
assertThat(created.number).isEqualTo(metadata.number)
|
||||
assertThat(created.numberSort).isEqualTo(metadata.numberSort)
|
||||
assertThat(created.readingDirection).isNull()
|
||||
assertThat(created.publisher).isBlank()
|
||||
assertThat(created.ageRating).isNull()
|
||||
assertThat(created.releaseDate).isNull()
|
||||
assertThat(created.authors).isEmpty()
|
||||
assertThat(created.tags).isEmpty()
|
||||
|
||||
assertThat(created.titleLock).isFalse()
|
||||
assertThat(created.summaryLock).isFalse()
|
||||
assertThat(created.numberLock).isFalse()
|
||||
assertThat(created.numberSortLock).isFalse()
|
||||
assertThat(created.readingDirectionLock).isFalse()
|
||||
assertThat(created.publisherLock).isFalse()
|
||||
assertThat(created.ageRatingLock).isFalse()
|
||||
assertThat(created.releaseDateLock).isFalse()
|
||||
assertThat(created.authorsLock).isFalse()
|
||||
assertThat(created.tagsLock).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -155,11 +144,9 @@ class BookMetadataDaoTest(
|
|||
summary = "Summary",
|
||||
number = "1",
|
||||
numberSort = 1F,
|
||||
readingDirection = BookMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
publisher = "publisher",
|
||||
ageRating = 18,
|
||||
releaseDate = LocalDate.now(),
|
||||
authors = mutableListOf(Author("author", "role")),
|
||||
authors = listOf(Author("author", "role")),
|
||||
tags = setOf("tag"),
|
||||
bookId = book.id
|
||||
)
|
||||
bookMetadataDao.insert(metadata)
|
||||
|
|
@ -171,20 +158,16 @@ class BookMetadataDaoTest(
|
|||
summary = "SummaryUpdated",
|
||||
number = "2",
|
||||
numberSort = 2F,
|
||||
readingDirection = BookMetadata.ReadingDirection.RIGHT_TO_LEFT,
|
||||
publisher = "publisher2",
|
||||
ageRating = 15,
|
||||
releaseDate = LocalDate.now(),
|
||||
authors = mutableListOf(Author("author2", "role2")),
|
||||
authors = listOf(Author("author2", "role2")),
|
||||
tags = setOf("another"),
|
||||
titleLock = true,
|
||||
summaryLock = true,
|
||||
numberLock = true,
|
||||
numberSortLock = true,
|
||||
readingDirectionLock = true,
|
||||
publisherLock = true,
|
||||
ageRatingLock = true,
|
||||
releaseDateLock = true,
|
||||
authorsLock = true
|
||||
authorsLock = true,
|
||||
tagsLock = true
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -201,20 +184,16 @@ class BookMetadataDaoTest(
|
|||
assertThat(modified.summary).isEqualTo(updated.summary)
|
||||
assertThat(modified.number).isEqualTo(updated.number)
|
||||
assertThat(modified.numberSort).isEqualTo(updated.numberSort)
|
||||
assertThat(modified.readingDirection).isEqualTo(updated.readingDirection)
|
||||
assertThat(modified.publisher).isEqualTo(updated.publisher)
|
||||
assertThat(modified.ageRating).isEqualTo(updated.ageRating)
|
||||
|
||||
assertThat(modified.titleLock).isEqualTo(updated.titleLock)
|
||||
assertThat(modified.summaryLock).isEqualTo(updated.summaryLock)
|
||||
assertThat(modified.numberLock).isEqualTo(updated.numberLock)
|
||||
assertThat(modified.numberSortLock).isEqualTo(updated.numberSortLock)
|
||||
assertThat(modified.readingDirectionLock).isEqualTo(updated.readingDirectionLock)
|
||||
assertThat(modified.publisherLock).isEqualTo(updated.publisherLock)
|
||||
assertThat(modified.ageRatingLock).isEqualTo(updated.ageRatingLock)
|
||||
assertThat(modified.releaseDateLock).isEqualTo(updated.releaseDateLock)
|
||||
assertThat(modified.authorsLock).isEqualTo(updated.authorsLock)
|
||||
assertThat(modified.tagsLock).isEqualTo(updated.tagsLock)
|
||||
|
||||
assertThat(modified.tags).containsAll(updated.tags)
|
||||
assertThat(modified.authors.first().name).isEqualTo(updated.authors.first().name)
|
||||
assertThat(modified.authors.first().role).isEqualTo(updated.authors.first().role)
|
||||
}
|
||||
|
|
@ -226,11 +205,8 @@ class BookMetadataDaoTest(
|
|||
summary = "Summary",
|
||||
number = "1",
|
||||
numberSort = 1F,
|
||||
readingDirection = BookMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
publisher = "publisher",
|
||||
ageRating = 18,
|
||||
releaseDate = LocalDate.now(),
|
||||
authors = mutableListOf(Author("author", "role")),
|
||||
authors = listOf(Author("author", "role")),
|
||||
bookId = book.id
|
||||
)
|
||||
bookMetadataDao.insert(metadata)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ class SeriesMetadataDaoTest(
|
|||
|
||||
@AfterEach
|
||||
fun deleteSeries() {
|
||||
seriesRepository.findAll().forEach {
|
||||
seriesMetadataDao.delete(it.id)
|
||||
}
|
||||
seriesRepository.deleteAll()
|
||||
}
|
||||
|
||||
|
|
@ -52,20 +55,93 @@ class SeriesMetadataDaoTest(
|
|||
status = SeriesMetadata.Status.ENDED,
|
||||
title = "Series",
|
||||
titleSort = "Series, The",
|
||||
summary = "Summary",
|
||||
readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
publisher = "publisher",
|
||||
ageRating = 18,
|
||||
genres = setOf("Action", "Adventure"),
|
||||
tags = setOf("tag","another"),
|
||||
language = "en",
|
||||
titleLock = true,
|
||||
titleSortLock = true,
|
||||
summaryLock = true,
|
||||
readingDirectionLock = true,
|
||||
publisherLock = true,
|
||||
ageRatingLock = true,
|
||||
genresLock = true,
|
||||
languageLock = true,
|
||||
tagsLock = true,
|
||||
seriesId = series.id
|
||||
)
|
||||
|
||||
val created = seriesMetadataDao.insert(metadata)
|
||||
seriesMetadataDao.insert(metadata)
|
||||
val created = seriesMetadataDao.findById(metadata.seriesId)
|
||||
|
||||
assertThat(created.seriesId).isEqualTo(series.id)
|
||||
assertThat(created.createdDate).isCloseTo(now, offset)
|
||||
assertThat(created.lastModifiedDate).isCloseTo(now, offset)
|
||||
assertThat(created.title).isEqualTo("Series")
|
||||
assertThat(created.titleSort).isEqualTo("Series, The")
|
||||
|
||||
assertThat(created.title).isEqualTo(metadata.title)
|
||||
assertThat(created.titleSort).isEqualTo(metadata.titleSort)
|
||||
assertThat(created.summary).isEqualTo(metadata.summary)
|
||||
assertThat(created.status).isEqualTo(SeriesMetadata.Status.ENDED)
|
||||
assertThat(created.readingDirection).isEqualTo(metadata.readingDirection)
|
||||
assertThat(created.publisher).isEqualTo(metadata.publisher)
|
||||
assertThat(created.ageRating).isEqualTo(metadata.ageRating)
|
||||
assertThat(created.language).isEqualTo(metadata.language)
|
||||
assertThat(created.genres).containsAll(metadata.genres)
|
||||
assertThat(created.tags).containsAll(metadata.tags)
|
||||
|
||||
assertThat(created.titleLock).isEqualTo(metadata.titleLock)
|
||||
assertThat(created.titleSortLock).isEqualTo(metadata.titleSortLock)
|
||||
assertThat(created.statusLock).isEqualTo(metadata.statusLock)
|
||||
assertThat(created.summaryLock).isEqualTo(metadata.summaryLock)
|
||||
assertThat(created.readingDirectionLock).isEqualTo(metadata.readingDirectionLock)
|
||||
assertThat(created.publisherLock).isEqualTo(metadata.publisherLock)
|
||||
assertThat(created.ageRatingLock).isEqualTo(metadata.ageRatingLock)
|
||||
assertThat(created.genresLock).isEqualTo(metadata.genresLock)
|
||||
assertThat(created.languageLock).isEqualTo(metadata.languageLock)
|
||||
assertThat(created.tagsLock).isEqualTo(metadata.tagsLock)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a minimum seriesMetadata when inserting then it is persisted`() {
|
||||
val series = makeSeries("Series", libraryId = library.id).also { seriesRepository.insert(it) }
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
val metadata = SeriesMetadata(
|
||||
title = "Series",
|
||||
seriesId = series.id
|
||||
)
|
||||
|
||||
seriesMetadataDao.insert(metadata)
|
||||
val created = seriesMetadataDao.findById(metadata.seriesId)
|
||||
|
||||
assertThat(created.seriesId).isEqualTo(series.id)
|
||||
assertThat(created.createdDate).isCloseTo(now, offset)
|
||||
assertThat(created.lastModifiedDate).isCloseTo(now, offset)
|
||||
|
||||
assertThat(created.title).isEqualTo(metadata.title)
|
||||
assertThat(created.titleSort).isEqualTo(metadata.title)
|
||||
assertThat(created.summary).isBlank()
|
||||
assertThat(created.status).isEqualTo(SeriesMetadata.Status.ONGOING)
|
||||
assertThat(created.readingDirection).isNull()
|
||||
assertThat(created.publisher).isBlank()
|
||||
assertThat(created.language).isBlank()
|
||||
assertThat(created.ageRating).isNull()
|
||||
assertThat(created.genres).isEmpty()
|
||||
assertThat(created.tags).isEmpty()
|
||||
|
||||
assertThat(created.titleLock).isFalse()
|
||||
assertThat(created.titleSortLock).isFalse()
|
||||
assertThat(created.statusLock).isFalse()
|
||||
assertThat(created.summaryLock).isFalse()
|
||||
assertThat(created.readingDirectionLock).isFalse()
|
||||
assertThat(created.publisherLock).isFalse()
|
||||
assertThat(created.ageRatingLock).isFalse()
|
||||
assertThat(created.genresLock).isFalse()
|
||||
assertThat(created.languageLock).isFalse()
|
||||
assertThat(created.tagsLock).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -109,9 +185,17 @@ class SeriesMetadataDaoTest(
|
|||
status = SeriesMetadata.Status.ENDED,
|
||||
title = "Series",
|
||||
titleSort = "Series, The",
|
||||
summary = "Summary",
|
||||
readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
publisher = "publisher",
|
||||
ageRating = 18,
|
||||
language = "en",
|
||||
genres = setOf("Action"),
|
||||
tags = setOf("tag"),
|
||||
seriesId = series.id
|
||||
)
|
||||
val created = seriesMetadataDao.insert(metadata)
|
||||
seriesMetadataDao.insert(metadata)
|
||||
val created = seriesMetadataDao.findById(metadata.seriesId)
|
||||
|
||||
|
||||
val modificationDate = LocalDateTime.now()
|
||||
|
|
@ -121,9 +205,23 @@ class SeriesMetadataDaoTest(
|
|||
status = SeriesMetadata.Status.HIATUS,
|
||||
title = "Changed",
|
||||
titleSort = "Changed, The",
|
||||
summary = "SummaryUpdated",
|
||||
readingDirection = SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT,
|
||||
publisher = "publisher2",
|
||||
ageRating = 15,
|
||||
language = "jp",
|
||||
genres = setOf("Adventure"),
|
||||
tags = setOf("Another"),
|
||||
statusLock = true,
|
||||
titleLock = true,
|
||||
titleSortLock = true
|
||||
titleSortLock = true,
|
||||
summaryLock = true,
|
||||
readingDirectionLock = true,
|
||||
publisherLock = true,
|
||||
ageRatingLock = true,
|
||||
languageLock = true,
|
||||
genresLock = true,
|
||||
tagsLock = true
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -135,11 +233,26 @@ class SeriesMetadataDaoTest(
|
|||
assertThat(modified.lastModifiedDate)
|
||||
.isCloseTo(modificationDate, offset)
|
||||
.isNotEqualTo(modified.createdDate)
|
||||
assertThat(modified.title).isEqualTo("Changed")
|
||||
assertThat(modified.titleSort).isEqualTo("Changed, The")
|
||||
assertThat(modified.status).isEqualTo(SeriesMetadata.Status.HIATUS)
|
||||
assertThat(modified.title).isEqualTo(updated.title)
|
||||
assertThat(modified.titleSort).isEqualTo(updated.titleSort)
|
||||
assertThat(modified.summary).isEqualTo(updated.summary)
|
||||
assertThat(modified.status).isEqualTo(updated.status)
|
||||
assertThat(modified.readingDirection).isEqualTo(updated.readingDirection)
|
||||
assertThat(modified.publisher).isEqualTo(updated.publisher)
|
||||
assertThat(modified.ageRating).isEqualTo(updated.ageRating)
|
||||
assertThat(modified.language).isEqualTo(updated.language)
|
||||
assertThat(modified.genres).containsAll(updated.genres)
|
||||
assertThat(modified.tags).containsAll(updated.tags)
|
||||
|
||||
assertThat(modified.titleLock).isTrue()
|
||||
assertThat(modified.titleSortLock).isTrue()
|
||||
assertThat(modified.statusLock).isTrue()
|
||||
assertThat(modified.summaryLock).isTrue()
|
||||
assertThat(modified.readingDirectionLock).isTrue()
|
||||
assertThat(modified.ageRatingLock).isTrue()
|
||||
assertThat(modified.languageLock).isTrue()
|
||||
assertThat(modified.genresLock).isTrue()
|
||||
assertThat(modified.publisherLock).isTrue()
|
||||
assertThat(modified.tagsLock).isTrue()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ import io.mockk.every
|
|||
import io.mockk.mockk
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.gotson.komga.domain.model.Media
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.domain.model.makeBook
|
||||
import org.gotson.komga.domain.service.BookAnalyzer
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.AgeRating
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.ComicInfo
|
||||
import org.gotson.komga.infrastructure.metadata.comicinfo.dto.Manga
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.LocalDate
|
||||
|
|
@ -38,10 +40,10 @@ class ComicInfoProviderTest {
|
|||
title = "title"
|
||||
summary = "summary"
|
||||
number = "010"
|
||||
publisher = "publisher"
|
||||
ageRating = AgeRating.MA_15
|
||||
year = 2020
|
||||
month = 2
|
||||
alternateSeries = "story arc"
|
||||
alternateNumber = "5"
|
||||
}
|
||||
|
||||
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
|
||||
|
|
@ -53,10 +55,9 @@ class ComicInfoProviderTest {
|
|||
assertThat(summary).isEqualTo("summary")
|
||||
assertThat(number).isEqualTo("010")
|
||||
assertThat(numberSort).isEqualTo(10F)
|
||||
assertThat(publisher).isEqualTo("publisher")
|
||||
assertThat(ageRating).isEqualTo(15)
|
||||
assertThat(readList).isEqualTo("story arc")
|
||||
assertThat(readListNumber).isEqualTo(5)
|
||||
assertThat(releaseDate).isEqualTo(LocalDate.of(2020, 2, 1))
|
||||
assertThat(readingDirection).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +155,11 @@ class ComicInfoProviderTest {
|
|||
val comicInfo = ComicInfo().apply {
|
||||
series = "series"
|
||||
seriesGroup = "collection"
|
||||
publisher = "publisher"
|
||||
ageRating = AgeRating.MA_15
|
||||
manga = Manga.YES_AND_RIGHT_TO_LEFT
|
||||
languageISO = "en"
|
||||
genre = "Action, Adventure"
|
||||
}
|
||||
|
||||
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
|
||||
|
|
@ -165,6 +171,27 @@ class ComicInfoProviderTest {
|
|||
assertThat(titleSort).isEqualTo("series")
|
||||
assertThat(status).isNull()
|
||||
assertThat(collections).containsExactly("collection")
|
||||
assertThat(publisher).isEqualTo("publisher")
|
||||
assertThat(ageRating).isEqualTo(15)
|
||||
assertThat(readingDirection).isEqualTo(SeriesMetadata.ReadingDirection.RIGHT_TO_LEFT)
|
||||
assertThat(language).isEqualTo("en")
|
||||
assertThat(summary).isBlank()
|
||||
assertThat(genres).containsExactlyInAnyOrder("Action", "Adventure")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given comicInfo with incorrect values when getting series metadata then metadata patch is valid`() {
|
||||
val comicInfo = ComicInfo().apply {
|
||||
languageISO = "japanese"
|
||||
}
|
||||
|
||||
every { mockMapper.readValue(any<ByteArray>(), ComicInfo::class.java) } returns comicInfo
|
||||
|
||||
val patch = comicInfoProvider.getSeriesMetadataFromBook(book, media)!!
|
||||
|
||||
with(patch) {
|
||||
assertThat(language).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ package org.gotson.komga.interfaces.rest
|
|||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.groups.Tuple.tuple
|
||||
import org.gotson.komga.domain.model.Author
|
||||
import org.gotson.komga.domain.model.BookMetadata
|
||||
import org.gotson.komga.domain.model.BookPage
|
||||
import org.gotson.komga.domain.model.BookSearch
|
||||
import org.gotson.komga.domain.model.KomgaUser
|
||||
import org.gotson.komga.domain.model.Media
|
||||
import org.gotson.komga.domain.model.ROLE_ADMIN
|
||||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
import org.gotson.komga.domain.model.ThumbnailBook
|
||||
import org.gotson.komga.domain.model.makeBook
|
||||
import org.gotson.komga.domain.model.makeLibrary
|
||||
|
|
@ -577,8 +577,7 @@ class BookControllerTest(
|
|||
@ValueSource(strings = [
|
||||
"""{"title":""}""",
|
||||
"""{"number":""}""",
|
||||
"""{"authors":"[{"name":""}]"}""",
|
||||
"""{"ageRating":-1}"""
|
||||
"""{"authors":"[{"name":""}]"}"""
|
||||
])
|
||||
@WithMockCustomUser(roles = [ROLE_ADMIN])
|
||||
fun `given invalid json when updating metadata then raise validation error`(jsonString: String) {
|
||||
|
|
@ -612,12 +611,6 @@ class BookControllerTest(
|
|||
"numberLock":true,
|
||||
"numberSort": 1.0,
|
||||
"numberSortLock":true,
|
||||
"readingDirection":"LEFT_TO_RIGHT",
|
||||
"readingDirectionLock":true,
|
||||
"publisher":"newPublisher",
|
||||
"publisherLock":true,
|
||||
"ageRating":12,
|
||||
"ageRatingLock":true,
|
||||
"releaseDate":"2020-01-01",
|
||||
"releaseDateLock":true,
|
||||
"authors":[
|
||||
|
|
@ -630,7 +623,9 @@ class BookControllerTest(
|
|||
"role":"newAuthorRole2"
|
||||
}
|
||||
],
|
||||
"authorsLock":true
|
||||
"authorsLock":true,
|
||||
"tags":["tag"],
|
||||
"tagsLock":true
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
|
|
@ -647,9 +642,6 @@ class BookControllerTest(
|
|||
assertThat(summary).isEqualTo("newSummary")
|
||||
assertThat(number).isEqualTo("newNumber")
|
||||
assertThat(numberSort).isEqualTo(1F)
|
||||
assertThat(readingDirection).isEqualTo(BookMetadata.ReadingDirection.LEFT_TO_RIGHT)
|
||||
assertThat(publisher).isEqualTo("newPublisher")
|
||||
assertThat(ageRating).isEqualTo(12)
|
||||
assertThat(releaseDate).isEqualTo(LocalDate.of(2020, 1, 1))
|
||||
assertThat(authors)
|
||||
.hasSize(2)
|
||||
|
|
@ -658,16 +650,15 @@ class BookControllerTest(
|
|||
tuple("newAuthor", "newauthorrole"),
|
||||
tuple("newAuthor2", "newauthorrole2")
|
||||
)
|
||||
assertThat(tags).containsExactly("tag")
|
||||
|
||||
assertThat(titleLock).isEqualTo(true)
|
||||
assertThat(summaryLock).isEqualTo(true)
|
||||
assertThat(numberLock).isEqualTo(true)
|
||||
assertThat(numberSortLock).isEqualTo(true)
|
||||
assertThat(readingDirectionLock).isEqualTo(true)
|
||||
assertThat(publisherLock).isEqualTo(true)
|
||||
assertThat(ageRatingLock).isEqualTo(true)
|
||||
assertThat(releaseDateLock).isEqualTo(true)
|
||||
assertThat(authorsLock).isEqualTo(true)
|
||||
assertThat(tagsLock).isEqualTo(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -686,10 +677,9 @@ class BookControllerTest(
|
|||
val bookId = bookRepository.findAll().first().id
|
||||
bookMetadataRepository.findById(bookId).let { metadata ->
|
||||
val updated = metadata.copy(
|
||||
ageRating = 12,
|
||||
readingDirection = BookMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
authors = metadata.authors.toMutableList().also { it.add(Author("Author", "role")) },
|
||||
releaseDate = testDate
|
||||
releaseDate = testDate,
|
||||
tags = setOf("tag")
|
||||
)
|
||||
|
||||
bookMetadataRepository.update(updated)
|
||||
|
|
@ -697,18 +687,15 @@ class BookControllerTest(
|
|||
|
||||
val metadata = bookMetadataRepository.findById(bookId)
|
||||
with(metadata) {
|
||||
assertThat(readingDirection).isEqualTo(BookMetadata.ReadingDirection.LEFT_TO_RIGHT)
|
||||
assertThat(ageRating).isEqualTo(12)
|
||||
assertThat(authors).hasSize(1)
|
||||
assertThat(releaseDate).isEqualTo(testDate)
|
||||
}
|
||||
|
||||
val jsonString = """
|
||||
{
|
||||
"readingDirection":null,
|
||||
"ageRating":null,
|
||||
"authors":null,
|
||||
"releaseDate":null
|
||||
"releaseDate":null,
|
||||
"tags":null
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
|
|
@ -721,10 +708,9 @@ class BookControllerTest(
|
|||
|
||||
val updatedMetadata = bookMetadataRepository.findById(bookId)
|
||||
with(updatedMetadata) {
|
||||
assertThat(readingDirection).isNull()
|
||||
assertThat(ageRating).isNull()
|
||||
assertThat(authors).isEmpty()
|
||||
assertThat(releaseDate).isNull()
|
||||
assertThat(tags).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -743,8 +729,6 @@ class BookControllerTest(
|
|||
val bookId = bookRepository.findAll().first().id
|
||||
bookMetadataRepository.findById(bookId).let { metadata ->
|
||||
val updated = metadata.copy(
|
||||
ageRating = 12,
|
||||
readingDirection = BookMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
authors = metadata.authors.toMutableList().also { it.add(Author("Author", "role")) },
|
||||
releaseDate = testDate,
|
||||
summary = "summary",
|
||||
|
|
@ -752,7 +736,6 @@ class BookControllerTest(
|
|||
numberLock = true,
|
||||
numberSort = 2F,
|
||||
numberSortLock = true,
|
||||
publisher = "publisher",
|
||||
title = "title"
|
||||
)
|
||||
|
||||
|
|
@ -773,14 +756,11 @@ class BookControllerTest(
|
|||
|
||||
val metadata = bookMetadataRepository.findById(bookId)
|
||||
with(metadata) {
|
||||
assertThat(readingDirection).isEqualTo(BookMetadata.ReadingDirection.LEFT_TO_RIGHT)
|
||||
assertThat(ageRating).isEqualTo(12)
|
||||
assertThat(authors).hasSize(1)
|
||||
assertThat(releaseDate).isEqualTo(testDate)
|
||||
assertThat(summary).isEqualTo("summary")
|
||||
assertThat(number).isEqualTo("number")
|
||||
assertThat(numberSort).isEqualTo(2F)
|
||||
assertThat(publisher).isEqualTo("publisher")
|
||||
assertThat(title).isEqualTo("title")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import org.springframework.test.web.servlet.delete
|
|||
import org.springframework.test.web.servlet.get
|
||||
import org.springframework.test.web.servlet.patch
|
||||
import org.springframework.test.web.servlet.post
|
||||
import java.time.LocalDate
|
||||
import kotlin.random.Random
|
||||
|
||||
@ExtendWith(SpringExtension::class)
|
||||
|
|
@ -353,7 +354,9 @@ class SeriesControllerTest(
|
|||
@ParameterizedTest
|
||||
@ValueSource(strings = [
|
||||
"""{"title":""}""",
|
||||
"""{"titleSort":""}"""
|
||||
"""{"titleSort":""}""",
|
||||
"""{"ageRating":-1}""",
|
||||
"""{"language":"japanese"}"""
|
||||
])
|
||||
@WithMockCustomUser(roles = [ROLE_ADMIN])
|
||||
fun `given invalid json when updating metadata then raise validation error`(jsonString: String) {
|
||||
|
|
@ -378,11 +381,25 @@ class SeriesControllerTest(
|
|||
val jsonString = """
|
||||
{
|
||||
"title":"newTitle",
|
||||
"titleSort":"newTitleSort",
|
||||
"status":"HIATUS",
|
||||
"titleLock":true,
|
||||
"titleSort":"newTitleSort",
|
||||
"titleSortLock":true,
|
||||
"statusLock":true
|
||||
"status":"HIATUS",
|
||||
"statusLock":true,
|
||||
"summary":"newSummary",
|
||||
"summaryLock":true,
|
||||
"readingDirection":"LEFT_TO_RIGHT",
|
||||
"readingDirectionLock":true,
|
||||
"ageRating":12,
|
||||
"ageRatingLock":true,
|
||||
"publisher":"newPublisher",
|
||||
"publisherLock":true,
|
||||
"language":"ja",
|
||||
"languageLock":true,
|
||||
"genres":["Action"],
|
||||
"genresLock":true,
|
||||
"tags":["tag"],
|
||||
"tagsLock":true
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
|
|
@ -398,9 +415,77 @@ class SeriesControllerTest(
|
|||
assertThat(title).isEqualTo("newTitle")
|
||||
assertThat(titleSort).isEqualTo("newTitleSort")
|
||||
assertThat(status).isEqualTo(SeriesMetadata.Status.HIATUS)
|
||||
assertThat(readingDirection).isEqualTo(SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT)
|
||||
assertThat(publisher).isEqualTo("newPublisher")
|
||||
assertThat(summary).isEqualTo("newSummary")
|
||||
assertThat(language).isEqualTo("ja")
|
||||
assertThat(ageRating).isEqualTo(12)
|
||||
assertThat(genres).containsExactly("Action")
|
||||
assertThat(tags).containsExactly("tag")
|
||||
|
||||
assertThat(titleLock).isEqualTo(true)
|
||||
assertThat(titleSortLock).isEqualTo(true)
|
||||
assertThat(statusLock).isEqualTo(true)
|
||||
assertThat(readingDirectionLock).isEqualTo(true)
|
||||
assertThat(publisherLock).isEqualTo(true)
|
||||
assertThat(ageRatingLock).isEqualTo(true)
|
||||
assertThat(languageLock).isEqualTo(true)
|
||||
assertThat(summaryLock).isEqualTo(true)
|
||||
assertThat(genresLock).isEqualTo(true)
|
||||
assertThat(tagsLock).isEqualTo(true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockCustomUser(roles = [ROLE_ADMIN])
|
||||
fun `given json with null fields when updating metadata then fields with null are unset`() {
|
||||
val createdSeries = makeSeries(name = "series", libraryId = library.id).let { series ->
|
||||
seriesLifecycle.createSeries(series).also { created ->
|
||||
val books = listOf(makeBook("1", libraryId = library.id))
|
||||
seriesLifecycle.addBooks(created, books)
|
||||
}
|
||||
}
|
||||
|
||||
seriesMetadataRepository.findById(createdSeries.id).let { metadata ->
|
||||
val updated = metadata.copy(
|
||||
ageRating = 12,
|
||||
readingDirection = SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT,
|
||||
genres = setOf("Action"),
|
||||
tags = setOf("tag")
|
||||
)
|
||||
|
||||
seriesMetadataRepository.update(updated)
|
||||
}
|
||||
|
||||
val metadata = seriesMetadataRepository.findById(createdSeries.id)
|
||||
with(metadata) {
|
||||
assertThat(readingDirection).isEqualTo(SeriesMetadata.ReadingDirection.LEFT_TO_RIGHT)
|
||||
assertThat(ageRating).isEqualTo(12)
|
||||
assertThat(genres).hasSize(1)
|
||||
}
|
||||
|
||||
val jsonString = """
|
||||
{
|
||||
"readingDirection":null,
|
||||
"ageRating":null,
|
||||
"genres":null,
|
||||
"tags":null
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
mockMvc.patch("/api/v1/series/${createdSeries.id}/metadata") {
|
||||
contentType = MediaType.APPLICATION_JSON
|
||||
content = jsonString
|
||||
}.andExpect {
|
||||
status { isNoContent }
|
||||
}
|
||||
|
||||
val updatedMetadata = seriesMetadataRepository.findById(createdSeries.id)
|
||||
with(updatedMetadata) {
|
||||
assertThat(readingDirection).isNull()
|
||||
assertThat(ageRating).isNull()
|
||||
assertThat(genres).isEmpty()
|
||||
assertThat(tags).isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue