mirror of
https://github.com/gotson/komga.git
synced 2026-05-09 05:10:19 +02:00
parent
64acfeff99
commit
0c9a063cc3
12 changed files with 138 additions and 39 deletions
|
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE BOOK_METADATA_AGGREGATION_TAG
|
||||||
|
(
|
||||||
|
TAG varchar NOT NULL,
|
||||||
|
SERIES_ID varchar NOT NULL,
|
||||||
|
FOREIGN KEY (SERIES_ID) REFERENCES SERIES (ID)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- aggregate existing data
|
||||||
|
insert into BOOK_METADATA_AGGREGATION_TAG
|
||||||
|
select distinct bt.TAG, b.SERIES_ID
|
||||||
|
from BOOK_METADATA_TAG bt
|
||||||
|
left join BOOK B on B.ID = bt.BOOK_ID;
|
||||||
|
|
@ -5,6 +5,7 @@ import java.time.LocalDateTime
|
||||||
|
|
||||||
data class BookMetadataAggregation(
|
data class BookMetadataAggregation(
|
||||||
val authors: List<Author> = emptyList(),
|
val authors: List<Author> = emptyList(),
|
||||||
|
val tags: Set<String> = emptySet(),
|
||||||
val releaseDate: LocalDate? = null,
|
val releaseDate: LocalDate? = null,
|
||||||
val summary: String = "",
|
val summary: String = "",
|
||||||
val summaryNumber: String = "",
|
val summaryNumber: String = "",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ interface ReferentialRepository {
|
||||||
fun findAllGenresByCollection(collectionId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
fun findAllGenresByCollection(collectionId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
||||||
|
|
||||||
fun findAllSeriesAndBookTags(filterOnLibraryIds: Collection<String>?): Set<String>
|
fun findAllSeriesAndBookTags(filterOnLibraryIds: Collection<String>?): Set<String>
|
||||||
|
fun findAllSeriesAndBookTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
||||||
|
fun findAllSeriesAndBookTagsByCollection(collectionId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
||||||
fun findAllSeriesTags(filterOnLibraryIds: Collection<String>?): Set<String>
|
fun findAllSeriesTags(filterOnLibraryIds: Collection<String>?): Set<String>
|
||||||
fun findAllSeriesTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
fun findAllSeriesTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
||||||
fun findAllSeriesTagsByCollection(collectionId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
fun findAllSeriesTagsByCollection(collectionId: String, filterOnLibraryIds: Collection<String>?): Set<String>
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,15 @@ class MetadataAggregator {
|
||||||
|
|
||||||
fun aggregate(metadatas: Collection<BookMetadata>): BookMetadataAggregation {
|
fun aggregate(metadatas: Collection<BookMetadata>): BookMetadataAggregation {
|
||||||
val authors = metadatas.flatMap { it.authors }.distinctBy { "${it.role}__${it.name}" }
|
val authors = metadatas.flatMap { it.authors }.distinctBy { "${it.role}__${it.name}" }
|
||||||
val (summary, summaryNumber) = metadatas.sortedBy { it.numberSort }.find { it.summary.isNotBlank() }?.let {
|
val tags = metadatas.flatMap { it.tags }.toSet()
|
||||||
|
val (summary, summaryNumber) = metadatas
|
||||||
|
.sortedBy { it.numberSort }
|
||||||
|
.find { it.summary.isNotBlank() }
|
||||||
|
?.let {
|
||||||
it.summary to it.number
|
it.summary to it.number
|
||||||
} ?: "" to ""
|
} ?: ("" to "")
|
||||||
val releaseDate = metadatas.mapNotNull { it.releaseDate }.minOrNull()
|
val releaseDate = metadatas.mapNotNull { it.releaseDate }.minOrNull()
|
||||||
|
|
||||||
return BookMetadataAggregation(authors = authors, releaseDate = releaseDate, summary = summary, summaryNumber = summaryNumber)
|
return BookMetadataAggregation(authors = authors, tags = tags, releaseDate = releaseDate, summary = summary, summaryNumber = summaryNumber)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class BookMetadataAggregationDao(
|
||||||
|
|
||||||
private val d = Tables.BOOK_METADATA_AGGREGATION
|
private val d = Tables.BOOK_METADATA_AGGREGATION
|
||||||
private val a = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
|
private val a = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
|
||||||
|
private val t = Tables.BOOK_METADATA_AGGREGATION_TAG
|
||||||
|
|
||||||
private val groupFields = arrayOf(*d.fields(), *a.fields())
|
private val groupFields = arrayOf(*d.fields(), *a.fields())
|
||||||
|
|
||||||
|
|
@ -32,14 +33,23 @@ class BookMetadataAggregationDao(
|
||||||
dsl.select(*groupFields)
|
dsl.select(*groupFields)
|
||||||
.from(d)
|
.from(d)
|
||||||
.leftJoin(a).on(d.SERIES_ID.eq(a.SERIES_ID))
|
.leftJoin(a).on(d.SERIES_ID.eq(a.SERIES_ID))
|
||||||
|
.leftJoin(t).on(d.SERIES_ID.eq(t.SERIES_ID))
|
||||||
.where(d.SERIES_ID.`in`(seriesIds))
|
.where(d.SERIES_ID.`in`(seriesIds))
|
||||||
.groupBy(*groupFields)
|
.groupBy(*groupFields)
|
||||||
.fetchGroups(
|
.fetchGroups(
|
||||||
{ it.into(d) }, { it.into(a) }
|
{ it.into(d) }, { it.into(a) }
|
||||||
).map { (dr, ar) ->
|
).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.seriesId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findTags(seriesId: String) =
|
||||||
|
dsl.select(t.TAG)
|
||||||
|
.from(t)
|
||||||
|
.where(t.SERIES_ID.eq(seriesId))
|
||||||
|
.fetchInto(t)
|
||||||
|
.mapNotNull { it.tag }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun insert(metadata: BookMetadataAggregation) {
|
override fun insert(metadata: BookMetadataAggregation) {
|
||||||
dsl.insertInto(d)
|
dsl.insertInto(d)
|
||||||
|
|
@ -50,6 +60,7 @@ class BookMetadataAggregationDao(
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
insertAuthors(metadata)
|
insertAuthors(metadata)
|
||||||
|
insertTags(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|
@ -66,7 +77,12 @@ class BookMetadataAggregationDao(
|
||||||
.where(a.SERIES_ID.eq(metadata.seriesId))
|
.where(a.SERIES_ID.eq(metadata.seriesId))
|
||||||
.execute()
|
.execute()
|
||||||
|
|
||||||
|
dsl.deleteFrom(t)
|
||||||
|
.where(t.SERIES_ID.eq(metadata.seriesId))
|
||||||
|
.execute()
|
||||||
|
|
||||||
insertAuthors(metadata)
|
insertAuthors(metadata)
|
||||||
|
insertTags(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun insertAuthors(metadata: BookMetadataAggregation) {
|
private fun insertAuthors(metadata: BookMetadataAggregation) {
|
||||||
|
|
@ -82,23 +98,39 @@ class BookMetadataAggregationDao(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun insertTags(metadata: BookMetadataAggregation) {
|
||||||
|
if (metadata.tags.isNotEmpty()) {
|
||||||
|
dsl.batch(
|
||||||
|
dsl.insertInto(t, t.SERIES_ID, t.TAG)
|
||||||
|
.values(null as String?, null)
|
||||||
|
).also { step ->
|
||||||
|
metadata.tags.forEach {
|
||||||
|
step.bind(metadata.seriesId, it)
|
||||||
|
}
|
||||||
|
}.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun delete(seriesId: String) {
|
override fun delete(seriesId: String) {
|
||||||
dsl.deleteFrom(a).where(a.SERIES_ID.eq(seriesId)).execute()
|
dsl.deleteFrom(a).where(a.SERIES_ID.eq(seriesId)).execute()
|
||||||
|
dsl.deleteFrom(t).where(t.SERIES_ID.eq(seriesId)).execute()
|
||||||
dsl.deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
|
dsl.deleteFrom(d).where(d.SERIES_ID.eq(seriesId)).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
override fun delete(seriesIds: Collection<String>) {
|
override fun delete(seriesIds: Collection<String>) {
|
||||||
dsl.deleteFrom(a).where(a.SERIES_ID.`in`(seriesIds)).execute()
|
dsl.deleteFrom(a).where(a.SERIES_ID.`in`(seriesIds)).execute()
|
||||||
|
dsl.deleteFrom(t).where(t.SERIES_ID.`in`(seriesIds)).execute()
|
||||||
dsl.deleteFrom(d).where(d.SERIES_ID.`in`(seriesIds)).execute()
|
dsl.deleteFrom(d).where(d.SERIES_ID.`in`(seriesIds)).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun count(): Long = dsl.fetchCount(d).toLong()
|
override fun count(): Long = dsl.fetchCount(d).toLong()
|
||||||
|
|
||||||
private fun BookMetadataAggregationRecord.toDomain(authors: List<Author>) =
|
private fun BookMetadataAggregationRecord.toDomain(authors: List<Author>, tags: Set<String>) =
|
||||||
BookMetadataAggregation(
|
BookMetadataAggregation(
|
||||||
authors = authors,
|
authors = authors,
|
||||||
|
tags = tags,
|
||||||
releaseDate = releaseDate,
|
releaseDate = releaseDate,
|
||||||
summary = summary,
|
summary = summary,
|
||||||
summaryNumber = summaryNumber,
|
summaryNumber = summaryNumber,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ class ReferentialDao(
|
||||||
private val sd = Tables.SERIES_METADATA
|
private val sd = Tables.SERIES_METADATA
|
||||||
private val bma = Tables.BOOK_METADATA_AGGREGATION
|
private val bma = Tables.BOOK_METADATA_AGGREGATION
|
||||||
private val bmaa = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
|
private val bmaa = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
|
||||||
|
private val bmat = Tables.BOOK_METADATA_AGGREGATION_TAG
|
||||||
private val s = Tables.SERIES
|
private val s = Tables.SERIES
|
||||||
private val b = Tables.BOOK
|
private val b = Tables.BOOK
|
||||||
private val g = Tables.SERIES_METADATA_GENRE
|
private val g = Tables.SERIES_METADATA_GENRE
|
||||||
|
|
@ -212,21 +213,47 @@ class ReferentialDao(
|
||||||
override fun findAllSeriesAndBookTags(filterOnLibraryIds: Collection<String>?): Set<String> =
|
override fun findAllSeriesAndBookTags(filterOnLibraryIds: Collection<String>?): Set<String> =
|
||||||
dsl.select(bt.TAG.`as`("tag"))
|
dsl.select(bt.TAG.`as`("tag"))
|
||||||
.from(bt)
|
.from(bt)
|
||||||
.apply {
|
.apply { filterOnLibraryIds?.let { leftJoin(b).on(bt.BOOK_ID.eq(b.ID)).where(b.LIBRARY_ID.`in`(it)) } }
|
||||||
filterOnLibraryIds?.let {
|
|
||||||
leftJoin(b).on(bt.BOOK_ID.eq(b.ID))
|
|
||||||
.where(b.LIBRARY_ID.`in`(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.union(
|
.union(
|
||||||
select(st.TAG.`as`("tag"))
|
select(st.TAG.`as`("tag"))
|
||||||
.from(st)
|
.from(st)
|
||||||
.apply {
|
.apply { filterOnLibraryIds?.let { leftJoin(s).on(st.SERIES_ID.eq(s.ID)).where(s.LIBRARY_ID.`in`(it)) } }
|
||||||
filterOnLibraryIds?.let {
|
)
|
||||||
leftJoin(s).on(st.SERIES_ID.eq(s.ID))
|
.fetchSet(0, String::class.java)
|
||||||
.where(s.LIBRARY_ID.`in`(it))
|
.sortedBy { it.stripAccents().lowercase() }
|
||||||
}
|
.toSet()
|
||||||
}
|
|
||||||
|
override fun findAllSeriesAndBookTagsByLibrary(libraryId: String, filterOnLibraryIds: Collection<String>?): Set<String> =
|
||||||
|
dsl.select(bt.TAG.`as`("tag"))
|
||||||
|
.from(bt)
|
||||||
|
.leftJoin(b).on(bt.BOOK_ID.eq(b.ID))
|
||||||
|
.where(b.LIBRARY_ID.eq(libraryId))
|
||||||
|
.apply { filterOnLibraryIds?.let { and(b.LIBRARY_ID.`in`(it)) } }
|
||||||
|
.union(
|
||||||
|
select(st.TAG.`as`("tag"))
|
||||||
|
.from(st)
|
||||||
|
.leftJoin(s).on(st.SERIES_ID.eq(s.ID))
|
||||||
|
.where(s.LIBRARY_ID.eq(libraryId))
|
||||||
|
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
|
||||||
|
)
|
||||||
|
.fetchSet(0, String::class.java)
|
||||||
|
.sortedBy { it.stripAccents().lowercase() }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
override fun findAllSeriesAndBookTagsByCollection(collectionId: String, filterOnLibraryIds: Collection<String>?): Set<String> =
|
||||||
|
dsl.select(bmat.TAG.`as`("tag"))
|
||||||
|
.from(bmat)
|
||||||
|
.leftJoin(s).on(bmat.SERIES_ID.eq(s.ID))
|
||||||
|
.leftJoin(cs).on(bmat.SERIES_ID.eq(cs.SERIES_ID))
|
||||||
|
.where(cs.COLLECTION_ID.eq(collectionId))
|
||||||
|
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
|
||||||
|
.union(
|
||||||
|
select(st.TAG.`as`("tag"))
|
||||||
|
.from(st)
|
||||||
|
.leftJoin(cs).on(st.SERIES_ID.eq(cs.SERIES_ID))
|
||||||
|
.leftJoin(s).on(st.SERIES_ID.eq(s.ID))
|
||||||
|
.where(cs.COLLECTION_ID.eq(collectionId))
|
||||||
|
.apply { filterOnLibraryIds?.let { and(s.LIBRARY_ID.`in`(it)) } }
|
||||||
)
|
)
|
||||||
.fetchSet(0, String::class.java)
|
.fetchSet(0, String::class.java)
|
||||||
.sortedBy { it.stripAccents().lowercase() }
|
.sortedBy { it.stripAccents().lowercase() }
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ class SeriesDtoDao(
|
||||||
private val st = Tables.SERIES_METADATA_TAG
|
private val st = Tables.SERIES_METADATA_TAG
|
||||||
private val bma = Tables.BOOK_METADATA_AGGREGATION
|
private val bma = Tables.BOOK_METADATA_AGGREGATION
|
||||||
private val bmaa = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
|
private val bmaa = Tables.BOOK_METADATA_AGGREGATION_AUTHOR
|
||||||
|
private val bmat = Tables.BOOK_METADATA_AGGREGATION_TAG
|
||||||
private val fts = Tables.FTS_SERIES_METADATA
|
private val fts = Tables.FTS_SERIES_METADATA
|
||||||
|
|
||||||
val countUnread: AggregateFunction<BigDecimal> = DSL.sum(DSL.`when`(r.COMPLETED.isNull, 1).otherwise(0))
|
val countUnread: AggregateFunction<BigDecimal> = DSL.sum(DSL.`when`(r.COMPLETED.isNull, 1).otherwise(0))
|
||||||
|
|
@ -125,7 +126,11 @@ class SeriesDtoDao(
|
||||||
.leftJoin(bma).on(s.ID.eq(bma.SERIES_ID))
|
.leftJoin(bma).on(s.ID.eq(bma.SERIES_ID))
|
||||||
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
||||||
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
||||||
.apply { if (joinConditions.tag) leftJoin(st).on(s.ID.eq(st.SERIES_ID)) }
|
.apply {
|
||||||
|
if (joinConditions.tag)
|
||||||
|
leftJoin(st).on(s.ID.eq(st.SERIES_ID))
|
||||||
|
.leftJoin(bmat).on(s.ID.eq(bmat.SERIES_ID))
|
||||||
|
}
|
||||||
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
||||||
.apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) }
|
.apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) }
|
||||||
.where(conditions)
|
.where(conditions)
|
||||||
|
|
@ -161,7 +166,11 @@ class SeriesDtoDao(
|
||||||
.leftJoin(bma).on(s.ID.eq(bma.SERIES_ID))
|
.leftJoin(bma).on(s.ID.eq(bma.SERIES_ID))
|
||||||
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
||||||
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
||||||
.apply { if (joinConditions.tag) leftJoin(st).on(s.ID.eq(st.SERIES_ID)) }
|
.apply {
|
||||||
|
if (joinConditions.tag)
|
||||||
|
leftJoin(st).on(s.ID.eq(st.SERIES_ID))
|
||||||
|
.leftJoin(bmat).on(s.ID.eq(bmat.SERIES_ID))
|
||||||
|
}
|
||||||
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
||||||
.apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) }
|
.apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) }
|
||||||
|
|
||||||
|
|
@ -179,7 +188,11 @@ class SeriesDtoDao(
|
||||||
.leftJoin(bma).on(s.ID.eq(bma.SERIES_ID))
|
.leftJoin(bma).on(s.ID.eq(bma.SERIES_ID))
|
||||||
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
.leftJoin(rs).on(s.ID.eq(rs.SERIES_ID)).and(readProgressConditionSeries(userId))
|
||||||
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
.apply { if (joinConditions.genre) leftJoin(g).on(s.ID.eq(g.SERIES_ID)) }
|
||||||
.apply { if (joinConditions.tag) leftJoin(st).on(s.ID.eq(st.SERIES_ID)) }
|
.apply {
|
||||||
|
if (joinConditions.tag)
|
||||||
|
leftJoin(st).on(s.ID.eq(st.SERIES_ID))
|
||||||
|
.leftJoin(bmat).on(s.ID.eq(bmat.SERIES_ID))
|
||||||
|
}
|
||||||
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
.apply { if (joinConditions.collection) leftJoin(cs).on(s.ID.eq(cs.SERIES_ID)) }
|
||||||
.apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) }
|
.apply { if (joinConditions.aggregationAuthor) leftJoin(bmaa).on(s.ID.eq(bmaa.SERIES_ID)) }
|
||||||
.where(conditions)
|
.where(conditions)
|
||||||
|
|
@ -238,13 +251,17 @@ class SeriesDtoDao(
|
||||||
.filter { it.name != null }
|
.filter { it.name != null }
|
||||||
.map { AuthorDto(it.name, it.role) }
|
.map { AuthorDto(it.name, it.role) }
|
||||||
|
|
||||||
|
val aggregatedTags = dsl.selectFrom(bmat)
|
||||||
|
.where(bmat.SERIES_ID.eq(sr.id))
|
||||||
|
.fetchSet(bmat.TAG)
|
||||||
|
|
||||||
sr.toDto(
|
sr.toDto(
|
||||||
sr.bookCount,
|
sr.bookCount,
|
||||||
booksReadCount,
|
booksReadCount,
|
||||||
booksUnreadCount,
|
booksUnreadCount,
|
||||||
booksInProgressCount,
|
booksInProgressCount,
|
||||||
dr.toDto(genres, tags),
|
dr.toDto(genres, tags),
|
||||||
bmar.toDto(aggregatedAuthors)
|
bmar.toDto(aggregatedAuthors, aggregatedTags)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,12 +278,10 @@ class SeriesDtoDao(
|
||||||
if (deleted == false) c = c.and(s.DELETED_DATE.isNull)
|
if (deleted == false) c = c.and(s.DELETED_DATE.isNull)
|
||||||
if (!languages.isNullOrEmpty()) c = c.and(lower(d.LANGUAGE).`in`(languages.map { it.lowercase() }))
|
if (!languages.isNullOrEmpty()) c = c.and(lower(d.LANGUAGE).`in`(languages.map { it.lowercase() }))
|
||||||
if (!genres.isNullOrEmpty()) c = c.and(lower(g.GENRE).`in`(genres.map { it.lowercase() }))
|
if (!genres.isNullOrEmpty()) c = c.and(lower(g.GENRE).`in`(genres.map { it.lowercase() }))
|
||||||
if (!tags.isNullOrEmpty()) c = c.and(lower(st.TAG).`in`(tags.map { it.lowercase() }))
|
if (!tags.isNullOrEmpty()) c = c.and(lower(st.TAG).`in`(tags.map { it.lowercase() }).or(lower(bmat.TAG).`in`(tags.map { it.lowercase() })))
|
||||||
if (!ageRatings.isNullOrEmpty()) {
|
if (!ageRatings.isNullOrEmpty()) {
|
||||||
val c1 = if (ageRatings.contains(null)) d.AGE_RATING.isNull else DSL.falseCondition()
|
val c1 = if (ageRatings.contains(null)) d.AGE_RATING.isNull else DSL.falseCondition()
|
||||||
val c2 = if (ageRatings.filterNotNull()
|
val c2 = if (ageRatings.filterNotNull().isNotEmpty()) d.AGE_RATING.`in`(ageRatings.filterNotNull()) else DSL.falseCondition()
|
||||||
.isNotEmpty()
|
|
||||||
) d.AGE_RATING.`in`(ageRatings.filterNotNull()) else DSL.falseCondition()
|
|
||||||
c = c.and(c1.or(c2))
|
c = c.and(c1.or(c2))
|
||||||
}
|
}
|
||||||
// cast to String is necessary for SQLite, else the years in the IN block are coerced to Int, even though YEAR for SQLite uses strftime (string)
|
// cast to String is necessary for SQLite, else the years in the IN block are coerced to Int, even though YEAR for SQLite uses strftime (string)
|
||||||
|
|
@ -370,9 +385,10 @@ class SeriesDtoDao(
|
||||||
totalBookCountLock = totalBookCountLock,
|
totalBookCountLock = totalBookCountLock,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun BookMetadataAggregationRecord.toDto(authors: List<AuthorDto>) =
|
private fun BookMetadataAggregationRecord.toDto(authors: List<AuthorDto>, tags: Set<String>) =
|
||||||
BookMetadataAggregationDto(
|
BookMetadataAggregationDto(
|
||||||
authors = authors,
|
authors = authors,
|
||||||
|
tags = tags,
|
||||||
releaseDate = releaseDate,
|
releaseDate = releaseDate,
|
||||||
summary = summary,
|
summary = summary,
|
||||||
summaryNumber = summaryNumber,
|
summaryNumber = summaryNumber,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import org.jooq.Condition
|
||||||
import org.jooq.Field
|
import org.jooq.Field
|
||||||
import org.jooq.SortField
|
import org.jooq.SortField
|
||||||
import org.jooq.Table
|
import org.jooq.Table
|
||||||
import org.jooq.TableField
|
|
||||||
import org.jooq.impl.DSL
|
import org.jooq.impl.DSL
|
||||||
import org.springframework.data.domain.Sort
|
import org.springframework.data.domain.Sort
|
||||||
import org.sqlite.SQLiteException
|
import org.sqlite.SQLiteException
|
||||||
|
|
@ -25,7 +24,7 @@ fun Sort.toOrderBy(sorts: Map<String, Field<out Any>>): List<SortField<out Any>>
|
||||||
fun LocalDateTime.toCurrentTimeZone(): LocalDateTime =
|
fun LocalDateTime.toCurrentTimeZone(): LocalDateTime =
|
||||||
this.atZone(ZoneId.of("Z")).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
|
this.atZone(ZoneId.of("Z")).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
|
||||||
|
|
||||||
fun TableField<*, String>.udfStripAccents() =
|
fun Field<String>.udfStripAccents() =
|
||||||
DSL.function(SqliteUdfDataSource.udfStripAccents, String::class.java, this)
|
DSL.function(SqliteUdfDataSource.udfStripAccents, String::class.java, this)
|
||||||
|
|
||||||
fun Table<*>.match(term: String): Condition =
|
fun Table<*>.match(term: String): Condition =
|
||||||
|
|
|
||||||
|
|
@ -93,15 +93,12 @@ class ReferentialController(
|
||||||
@GetMapping("v1/tags")
|
@GetMapping("v1/tags")
|
||||||
fun getTags(
|
fun getTags(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
// TODO: remove those parameters once Tachiyomi Extension is using the new /tags/series endpoint (changed in 0.87.4 - 21 Apr 2021)
|
|
||||||
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
@RequestParam(name = "library_id", required = false) libraryId: String?,
|
||||||
@RequestParam(name = "series_id", required = false) seriesId: String?,
|
|
||||||
@RequestParam(name = "collection_id", required = false) collectionId: String?
|
@RequestParam(name = "collection_id", required = false) collectionId: String?
|
||||||
): Set<String> =
|
): Set<String> =
|
||||||
when {
|
when {
|
||||||
libraryId != null -> referentialRepository.findAllSeriesTagsByLibrary(libraryId, principal.user.getAuthorizedLibraryIds(null))
|
libraryId != null -> referentialRepository.findAllSeriesAndBookTagsByLibrary(libraryId, principal.user.getAuthorizedLibraryIds(null))
|
||||||
seriesId != null -> referentialRepository.findAllBookTagsBySeries(seriesId, principal.user.getAuthorizedLibraryIds(null))
|
collectionId != null -> referentialRepository.findAllSeriesAndBookTagsByCollection(collectionId, principal.user.getAuthorizedLibraryIds(null))
|
||||||
collectionId != null -> referentialRepository.findAllSeriesTagsByCollection(collectionId, principal.user.getAuthorizedLibraryIds(null))
|
|
||||||
else -> referentialRepository.findAllSeriesAndBookTags(principal.user.getAuthorizedLibraryIds(null))
|
else -> referentialRepository.findAllSeriesAndBookTags(principal.user.getAuthorizedLibraryIds(null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ data class SeriesMetadataDto(
|
||||||
|
|
||||||
data class BookMetadataAggregationDto(
|
data class BookMetadataAggregationDto(
|
||||||
val authors: List<AuthorDto> = emptyList(),
|
val authors: List<AuthorDto> = emptyList(),
|
||||||
|
val tags: Set<String> = emptySet(),
|
||||||
@JsonFormat(pattern = "yyyy-MM-dd")
|
@JsonFormat(pattern = "yyyy-MM-dd")
|
||||||
val releaseDate: LocalDate?,
|
val releaseDate: LocalDate?,
|
||||||
val summary: String,
|
val summary: String,
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,14 @@ class MetadataAggregatorTest {
|
||||||
@Test
|
@Test
|
||||||
fun `given metadatas when aggregating then aggregation is relevant`() {
|
fun `given metadatas when aggregating then aggregation is relevant`() {
|
||||||
val metadatas = listOf(
|
val metadatas = listOf(
|
||||||
BookMetadata(title = "ignored", summary = "summary 1", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), releaseDate = LocalDate.of(2020, 1, 1)),
|
BookMetadata(title = "ignored", summary = "summary 1", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), releaseDate = LocalDate.of(2020, 1, 1), tags = setOf("tag1")),
|
||||||
BookMetadata(title = "ignored", summary = "summary 2", number = "2", numberSort = 2F, authors = listOf(Author("author3", "role3"), Author("author2", "role3")), releaseDate = LocalDate.of(2021, 1, 1)),
|
BookMetadata(title = "ignored", summary = "summary 2", number = "2", numberSort = 2F, authors = listOf(Author("author3", "role3"), Author("author2", "role3")), releaseDate = LocalDate.of(2021, 1, 1), tags = setOf("tag2")),
|
||||||
)
|
)
|
||||||
|
|
||||||
val aggregation = aggregator.aggregate(metadatas)
|
val aggregation = aggregator.aggregate(metadatas)
|
||||||
|
|
||||||
assertThat(aggregation.authors).hasSize(4)
|
assertThat(aggregation.authors).hasSize(4)
|
||||||
|
assertThat(aggregation.tags).hasSize(2)
|
||||||
assertThat(aggregation.releaseDate?.year).isEqualTo(2020)
|
assertThat(aggregation.releaseDate?.year).isEqualTo(2020)
|
||||||
assertThat(aggregation.summary).isEqualTo("summary 1")
|
assertThat(aggregation.summary).isEqualTo("summary 1")
|
||||||
assertThat(aggregation.summaryNumber).isEqualTo("1")
|
assertThat(aggregation.summaryNumber).isEqualTo("1")
|
||||||
|
|
@ -50,14 +51,15 @@ class MetadataAggregatorTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given metadatas with duplicate authors when aggregating then aggregation has no duplicate authors`() {
|
fun `given metadatas with duplicate authors or tags when aggregating then aggregation has no duplicates`() {
|
||||||
val metadatas = listOf(
|
val metadatas = listOf(
|
||||||
BookMetadata(title = "ignored", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2"))),
|
BookMetadata(title = "ignored", number = "1", numberSort = 1F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), tags = setOf("tag1", "tag2")),
|
||||||
BookMetadata(title = "ignored", number = "2", numberSort = 2F, authors = listOf(Author("author1", "role1"), Author("author2", "role2"))),
|
BookMetadata(title = "ignored", number = "2", numberSort = 2F, authors = listOf(Author("author1", "role1"), Author("author2", "role2")), tags = setOf("tag1")),
|
||||||
)
|
)
|
||||||
|
|
||||||
val aggregation = aggregator.aggregate(metadatas)
|
val aggregation = aggregator.aggregate(metadatas)
|
||||||
|
|
||||||
assertThat(aggregation.authors).hasSize(2)
|
assertThat(aggregation.authors).hasSize(2)
|
||||||
|
assertThat(aggregation.tags).hasSize(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ class BookMetadataAggregationDaoTest(
|
||||||
val now = LocalDateTime.now()
|
val now = LocalDateTime.now()
|
||||||
val metadata = BookMetadataAggregation(
|
val metadata = BookMetadataAggregation(
|
||||||
authors = listOf(Author("author", "role")),
|
authors = listOf(Author("author", "role")),
|
||||||
|
tags = setOf("tag1", "tag2"),
|
||||||
releaseDate = LocalDate.now(),
|
releaseDate = LocalDate.now(),
|
||||||
summary = "Summary",
|
summary = "Summary",
|
||||||
summaryNumber = "1",
|
summaryNumber = "1",
|
||||||
|
|
@ -74,6 +75,7 @@ class BookMetadataAggregationDaoTest(
|
||||||
assertThat(name).isEqualTo(metadata.authors.first().name)
|
assertThat(name).isEqualTo(metadata.authors.first().name)
|
||||||
assertThat(role).isEqualTo(metadata.authors.first().role)
|
assertThat(role).isEqualTo(metadata.authors.first().role)
|
||||||
}
|
}
|
||||||
|
assertThat(created.tags).containsExactlyInAnyOrderElementsOf(metadata.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -104,6 +106,7 @@ class BookMetadataAggregationDaoTest(
|
||||||
|
|
||||||
val metadata = BookMetadataAggregation(
|
val metadata = BookMetadataAggregation(
|
||||||
authors = listOf(Author("author", "role")),
|
authors = listOf(Author("author", "role")),
|
||||||
|
tags = setOf("tag1", "tag2"),
|
||||||
releaseDate = LocalDate.now(),
|
releaseDate = LocalDate.now(),
|
||||||
summary = "Summary",
|
summary = "Summary",
|
||||||
seriesId = series.id
|
seriesId = series.id
|
||||||
|
|
@ -137,6 +140,7 @@ class BookMetadataAggregationDaoTest(
|
||||||
|
|
||||||
val metadata = BookMetadataAggregation(
|
val metadata = BookMetadataAggregation(
|
||||||
authors = listOf(Author("author", "role")),
|
authors = listOf(Author("author", "role")),
|
||||||
|
tags = setOf("tag1", "tag2"),
|
||||||
releaseDate = LocalDate.now(),
|
releaseDate = LocalDate.now(),
|
||||||
summary = "Summary",
|
summary = "Summary",
|
||||||
summaryNumber = "1",
|
summaryNumber = "1",
|
||||||
|
|
@ -153,6 +157,7 @@ class BookMetadataAggregationDaoTest(
|
||||||
summary = "SummaryUpdated",
|
summary = "SummaryUpdated",
|
||||||
summaryNumber = "2",
|
summaryNumber = "2",
|
||||||
authors = listOf(Author("authorUpdated", "roleUpdated"), Author("author2", "role2")),
|
authors = listOf(Author("authorUpdated", "roleUpdated"), Author("author2", "role2")),
|
||||||
|
tags = setOf("tag1", "tag2updated"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,5 +175,6 @@ class BookMetadataAggregationDaoTest(
|
||||||
assertThat(modified.authors).hasSize(2)
|
assertThat(modified.authors).hasSize(2)
|
||||||
assertThat(modified.authors.map { it.name }).containsExactlyInAnyOrderElementsOf(updated.authors.map { it.name })
|
assertThat(modified.authors.map { it.name }).containsExactlyInAnyOrderElementsOf(updated.authors.map { it.name })
|
||||||
assertThat(modified.authors.map { it.role }).containsExactlyInAnyOrderElementsOf(updated.authors.map { it.role })
|
assertThat(modified.authors.map { it.role }).containsExactlyInAnyOrderElementsOf(updated.authors.map { it.role })
|
||||||
|
assertThat(modified.tags).containsExactlyInAnyOrderElementsOf(updated.tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue