-
-
Notifications
You must be signed in to change notification settings - Fork 235
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
related to #30
- Loading branch information
Showing
14 changed files
with
1,066 additions
and
2 deletions.
There are no files selected for viewing
23 changes: 23 additions & 0 deletions
23
komga/src/flyway/resources/db/migration/V20200610172313__collections.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
create table collection | ||
( | ||
id bigint not null, | ||
name varchar not null, | ||
ordered boolean not null default false, | ||
series_count int not null, | ||
created_date timestamp not null default now(), | ||
last_modified_date timestamp not null default now(), | ||
primary key (id) | ||
); | ||
|
||
create table collection_series | ||
( | ||
collection_id bigint not null, | ||
series_id bigint not null, | ||
number integer not null | ||
); | ||
|
||
alter table collection_series | ||
add constraint fk_collection_series_collection_collection_id foreign key (collection_id) references collection (id); | ||
|
||
alter table collection_series | ||
add constraint fk_collection_series_series_series_id foreign key (series_id) references series (id); |
20 changes: 20 additions & 0 deletions
20
komga/src/main/kotlin/org/gotson/komga/domain/model/SeriesCollection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.gotson.komga.domain.model | ||
|
||
import java.time.LocalDateTime | ||
|
||
data class SeriesCollection( | ||
val name: String, | ||
val ordered: Boolean = false, | ||
|
||
val seriesIds: List<Long> = emptyList(), | ||
|
||
val id: Long = 0, | ||
|
||
override val createdDate: LocalDateTime = LocalDateTime.now(), | ||
override val lastModifiedDate: LocalDateTime = LocalDateTime.now(), | ||
|
||
/** | ||
* Indicates that the seriesIds have been filtered and is not exhaustive. | ||
*/ | ||
val filtered: Boolean = false | ||
) : Auditable() |
30 changes: 30 additions & 0 deletions
30
komga/src/main/kotlin/org/gotson/komga/domain/persistence/SeriesCollectionRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.gotson.komga.domain.persistence | ||
|
||
import org.gotson.komga.domain.model.SeriesCollection | ||
|
||
interface SeriesCollectionRepository { | ||
fun findByIdOrNull(collectionId: Long): SeriesCollection? | ||
fun findAll(): Collection<SeriesCollection> | ||
|
||
/** | ||
* Find one SeriesCollection by collectionId, | ||
* optionally with only seriesId filtered by the provided filterOnLibraryIds. | ||
*/ | ||
fun findByIdOrNull(collectionId: Long, filterOnLibraryIds: Collection<Long>?): SeriesCollection? | ||
|
||
/** | ||
* Find all SeriesCollection with at least one Series belonging to the provided belongsToLibraryIds, | ||
* optionally with only seriesId filtered by the provided filterOnLibraryIds. | ||
*/ | ||
fun findAllByLibraries(belongsToLibraryIds: Collection<Long>, filterOnLibraryIds: Collection<Long>?): Collection<SeriesCollection> | ||
|
||
fun insert(collection: SeriesCollection): SeriesCollection | ||
fun update(collection: SeriesCollection) | ||
|
||
fun removeSeriesFromAll(seriesId: Long) | ||
|
||
fun delete(collectionId: Long) | ||
fun deleteAll() | ||
|
||
fun existsByName(name: String): Boolean | ||
} |
41 changes: 41 additions & 0 deletions
41
komga/src/main/kotlin/org/gotson/komga/domain/service/SeriesCollectionLifecycle.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package org.gotson.komga.domain.service | ||
|
||
import mu.KotlinLogging | ||
import org.gotson.komga.domain.model.DuplicateNameException | ||
import org.gotson.komga.domain.model.SeriesCollection | ||
import org.gotson.komga.domain.persistence.SeriesCollectionRepository | ||
import org.springframework.stereotype.Service | ||
|
||
private val logger = KotlinLogging.logger {} | ||
|
||
@Service | ||
class SeriesCollectionLifecycle( | ||
private val collectionRepository: SeriesCollectionRepository | ||
) { | ||
|
||
@Throws( | ||
DuplicateNameException::class | ||
) | ||
fun addCollection(collection: SeriesCollection): SeriesCollection { | ||
logger.info { "Adding new collection: $collection" } | ||
|
||
if (collectionRepository.existsByName(collection.name)) | ||
throw DuplicateNameException("Collection name already exists") | ||
|
||
return collectionRepository.insert(collection) | ||
} | ||
|
||
fun updateCollection(toUpdate: SeriesCollection) { | ||
val existing = collectionRepository.findByIdOrNull(toUpdate.id) | ||
?: throw IllegalArgumentException("Cannot update collection that does not exist") | ||
|
||
if (existing.name != toUpdate.name && collectionRepository.existsByName(toUpdate.name)) | ||
throw DuplicateNameException("Collection name already exists") | ||
|
||
collectionRepository.update(toUpdate) | ||
} | ||
|
||
fun deleteCollection(collectionId: Long) { | ||
collectionRepository.delete(collectionId) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
171 changes: 171 additions & 0 deletions
171
komga/src/main/kotlin/org/gotson/komga/infrastructure/jooq/SeriesCollectionDao.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package org.gotson.komga.infrastructure.jooq | ||
|
||
import org.gotson.komga.domain.model.SeriesCollection | ||
import org.gotson.komga.domain.persistence.SeriesCollectionRepository | ||
import org.gotson.komga.jooq.Sequences | ||
import org.gotson.komga.jooq.Tables | ||
import org.gotson.komga.jooq.tables.records.CollectionRecord | ||
import org.jooq.DSLContext | ||
import org.jooq.Record | ||
import org.jooq.ResultQuery | ||
import org.springframework.stereotype.Component | ||
import java.time.LocalDateTime | ||
|
||
@Component | ||
class SeriesCollectionDao( | ||
private val dsl: DSLContext | ||
) : SeriesCollectionRepository { | ||
|
||
private val c = Tables.COLLECTION | ||
private val cs = Tables.COLLECTION_SERIES | ||
private val s = Tables.SERIES | ||
|
||
private val groupFields = arrayOf(*c.fields(), *cs.fields()) | ||
|
||
|
||
override fun findByIdOrNull(collectionId: Long): SeriesCollection? = | ||
selectBase() | ||
.where(c.ID.eq(collectionId)) | ||
.groupBy(*groupFields) | ||
.orderBy(cs.NUMBER.asc()) | ||
.fetchAndMap() | ||
.firstOrNull() | ||
|
||
override fun findByIdOrNull(collectionId: Long, filterOnLibraryIds: Collection<Long>?): SeriesCollection? = | ||
selectBase() | ||
.where(c.ID.eq(collectionId)) | ||
.also { step -> | ||
filterOnLibraryIds?.let { step.and(s.LIBRARY_ID.`in`(it)) } | ||
} | ||
.groupBy(*groupFields) | ||
.orderBy(cs.NUMBER.asc()) | ||
.fetchAndMap() | ||
.firstOrNull() | ||
|
||
override fun findAll(): Collection<SeriesCollection> = | ||
selectBase() | ||
.groupBy(*groupFields) | ||
.orderBy(cs.NUMBER.asc()) | ||
.fetchAndMap() | ||
|
||
override fun findAllByLibraries(belongsToLibraryIds: Collection<Long>, filterOnLibraryIds: Collection<Long>?): Collection<SeriesCollection> { | ||
val ids = dsl.select(c.ID) | ||
.from(c) | ||
.leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) | ||
.leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) | ||
.where(s.LIBRARY_ID.`in`(belongsToLibraryIds)) | ||
.fetch(0, Long::class.java) | ||
|
||
return selectBase() | ||
.where(c.ID.`in`(ids)) | ||
.also { step -> | ||
filterOnLibraryIds?.let { step.and(s.LIBRARY_ID.`in`(it)) } | ||
} | ||
.groupBy(*groupFields) | ||
.orderBy(cs.NUMBER.asc()) | ||
.fetchAndMap() | ||
} | ||
|
||
private fun selectBase() = | ||
dsl.select(*groupFields) | ||
.from(c) | ||
.leftJoin(cs).on(c.ID.eq(cs.COLLECTION_ID)) | ||
.leftJoin(s).on(cs.SERIES_ID.eq(s.ID)) | ||
|
||
private fun ResultQuery<Record>.fetchAndMap() = | ||
fetchGroups({ it.into(c) }, { it.into(cs) }) | ||
.map { (cr, csr) -> | ||
val seriesIds = csr.map { it.seriesId } | ||
cr.toDomain(seriesIds) | ||
} | ||
|
||
override fun insert(collection: SeriesCollection): SeriesCollection { | ||
val id = dsl.nextval(Sequences.HIBERNATE_SEQUENCE) | ||
val insert = collection.copy(id = id) | ||
|
||
dsl.insertInto(c) | ||
.set(c.ID, insert.id) | ||
.set(c.NAME, insert.name) | ||
.set(c.ORDERED, insert.ordered) | ||
.set(c.SERIES_COUNT, collection.seriesIds.size) | ||
.execute() | ||
|
||
insertSeries(insert) | ||
|
||
return findByIdOrNull(id)!! | ||
} | ||
|
||
|
||
private fun insertSeries(collection: SeriesCollection) { | ||
collection.seriesIds.forEachIndexed { index, id -> | ||
dsl.insertInto(cs) | ||
.set(cs.COLLECTION_ID, collection.id) | ||
.set(cs.SERIES_ID, id) | ||
.set(cs.NUMBER, index) | ||
.execute() | ||
} | ||
} | ||
|
||
override fun update(collection: SeriesCollection) { | ||
dsl.transaction { config -> | ||
with(config.dsl()) | ||
{ | ||
update(c) | ||
.set(c.NAME, collection.name) | ||
.set(c.ORDERED, collection.ordered) | ||
.set(c.SERIES_COUNT, collection.seriesIds.size) | ||
.set(c.LAST_MODIFIED_DATE, LocalDateTime.now()) | ||
.where(c.ID.eq(collection.id)) | ||
.execute() | ||
|
||
deleteFrom(cs).where(cs.COLLECTION_ID.eq(collection.id)).execute() | ||
|
||
insertSeries(collection) | ||
} | ||
} | ||
} | ||
|
||
override fun removeSeriesFromAll(seriesId: Long) { | ||
dsl.deleteFrom(cs) | ||
.where(cs.SERIES_ID.eq(seriesId)) | ||
.execute() | ||
} | ||
|
||
override fun delete(collectionId: Long) { | ||
dsl.transaction { config -> | ||
with(config.dsl()) | ||
{ | ||
deleteFrom(cs).where(cs.COLLECTION_ID.eq(collectionId)).execute() | ||
deleteFrom(c).where(c.ID.eq(collectionId)).execute() | ||
} | ||
} | ||
} | ||
|
||
override fun deleteAll() { | ||
dsl.transaction { config -> | ||
with(config.dsl()) | ||
{ | ||
deleteFrom(cs).execute() | ||
deleteFrom(c).execute() | ||
} | ||
} | ||
} | ||
|
||
override fun existsByName(name: String): Boolean = | ||
dsl.fetchExists( | ||
dsl.selectFrom(c) | ||
.where(c.NAME.equalIgnoreCase(name)) | ||
) | ||
|
||
|
||
private fun CollectionRecord.toDomain(seriesIds: List<Long>) = | ||
SeriesCollection( | ||
name = name, | ||
ordered = ordered, | ||
seriesIds = seriesIds, | ||
id = id, | ||
createdDate = createdDate, | ||
lastModifiedDate = lastModifiedDate, | ||
filtered = seriesCount != seriesIds.size | ||
) | ||
} |
21 changes: 21 additions & 0 deletions
21
komga/src/main/kotlin/org/gotson/komga/infrastructure/validation/NullOrNotEmpty.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
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.NotEmpty | ||
import javax.validation.constraints.Null | ||
import kotlin.reflect.KClass | ||
|
||
|
||
@ConstraintComposition(CompositionType.OR) | ||
@Constraint(validatedBy = []) | ||
@Null | ||
@NotEmpty | ||
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER) | ||
@Retention(AnnotationRetention.RUNTIME) | ||
annotation class NullOrNotEmpty( | ||
val message: String = "Must be null or not empty", | ||
val groups: Array<KClass<out Any>> = [], | ||
val payload: Array<KClass<out Any>> = [] | ||
) |
Oops, something went wrong.