mirror of
https://github.com/gotson/komga.git
synced 2026-05-09 05:10:19 +02:00
feat(api): search series by regex
This commit is contained in:
parent
c4a439276a
commit
1fe55809a1
14 changed files with 183 additions and 12 deletions
|
|
@ -87,7 +87,7 @@ dependencies {
|
||||||
// While waiting for https://github.com/xerial/sqlite-jdbc/pull/491 and https://github.com/xerial/sqlite-jdbc/pull/494
|
// While waiting for https://github.com/xerial/sqlite-jdbc/pull/491 and https://github.com/xerial/sqlite-jdbc/pull/494
|
||||||
// runtimeOnly("org.xerial:sqlite-jdbc:3.32.3.2")
|
// runtimeOnly("org.xerial:sqlite-jdbc:3.32.3.2")
|
||||||
// jooqGenerator("org.xerial:sqlite-jdbc:3.32.3.2")
|
// jooqGenerator("org.xerial:sqlite-jdbc:3.32.3.2")
|
||||||
runtimeOnly("com.github.gotson:sqlite-jdbc:3.32.3.6")
|
implementation("com.github.gotson:sqlite-jdbc:3.32.3.6")
|
||||||
jooqGenerator("com.github.gotson:sqlite-jdbc:3.32.3.6")
|
jooqGenerator("com.github.gotson:sqlite-jdbc:3.32.3.6")
|
||||||
|
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test") {
|
testImplementation("org.springframework.boot:spring-boot-starter-test") {
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,21 @@ open class SeriesSearch(
|
||||||
val libraryIds: Collection<String>? = null,
|
val libraryIds: Collection<String>? = null,
|
||||||
val collectionIds: Collection<String>? = null,
|
val collectionIds: Collection<String>? = null,
|
||||||
val searchTerm: String? = null,
|
val searchTerm: String? = null,
|
||||||
|
val searchRegex: Pair<String, SearchField>? = null,
|
||||||
val metadataStatus: Collection<SeriesMetadata.Status>? = null,
|
val metadataStatus: Collection<SeriesMetadata.Status>? = null,
|
||||||
val publishers: Collection<String>? = null,
|
val publishers: Collection<String>? = null,
|
||||||
val deleted: Boolean? = null,
|
val deleted: Boolean? = null,
|
||||||
)
|
) {
|
||||||
|
enum class SearchField {
|
||||||
|
NAME, TITLE, TITLE_SORT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SeriesSearchWithReadProgress(
|
class SeriesSearchWithReadProgress(
|
||||||
libraryIds: Collection<String>? = null,
|
libraryIds: Collection<String>? = null,
|
||||||
collectionIds: Collection<String>? = null,
|
collectionIds: Collection<String>? = null,
|
||||||
searchTerm: String? = null,
|
searchTerm: String? = null,
|
||||||
|
searchRegex: Pair<String, SearchField>? = null,
|
||||||
metadataStatus: Collection<SeriesMetadata.Status>? = null,
|
metadataStatus: Collection<SeriesMetadata.Status>? = null,
|
||||||
publishers: Collection<String>? = null,
|
publishers: Collection<String>? = null,
|
||||||
deleted: Boolean? = null,
|
deleted: Boolean? = null,
|
||||||
|
|
@ -27,6 +33,7 @@ class SeriesSearchWithReadProgress(
|
||||||
libraryIds = libraryIds,
|
libraryIds = libraryIds,
|
||||||
collectionIds = collectionIds,
|
collectionIds = collectionIds,
|
||||||
searchTerm = searchTerm,
|
searchTerm = searchTerm,
|
||||||
|
searchRegex = searchRegex,
|
||||||
metadataStatus = metadataStatus,
|
metadataStatus = metadataStatus,
|
||||||
publishers = publishers,
|
publishers = publishers,
|
||||||
deleted = deleted,
|
deleted = deleted,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package org.gotson.komga.infrastructure.datasource
|
package org.gotson.komga.infrastructure.datasource
|
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig
|
||||||
import com.zaxxer.hikari.HikariDataSource
|
import com.zaxxer.hikari.HikariDataSource
|
||||||
import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
||||||
import org.springframework.boot.jdbc.DataSourceBuilder
|
import org.springframework.boot.jdbc.DataSourceBuilder
|
||||||
|
|
@ -15,12 +16,20 @@ class DataSourcesConfiguration(
|
||||||
|
|
||||||
@Bean("sqliteDataSource")
|
@Bean("sqliteDataSource")
|
||||||
@Primary
|
@Primary
|
||||||
fun sqliteDataSource(): DataSource = (
|
fun sqliteDataSource(): DataSource {
|
||||||
DataSourceBuilder.create()
|
|
||||||
|
val sqliteUdfDataSource = DataSourceBuilder.create()
|
||||||
.driverClassName("org.sqlite.JDBC")
|
.driverClassName("org.sqlite.JDBC")
|
||||||
.url("jdbc:sqlite:${komgaProperties.database.file}?foreign_keys=on;")
|
.url("jdbc:sqlite:${komgaProperties.database.file}?foreign_keys=on;")
|
||||||
.type(HikariDataSource::class.java)
|
.type(SqliteUdfDataSource::class.java)
|
||||||
.build() as HikariDataSource
|
.build()
|
||||||
|
|
||||||
|
return HikariDataSource(
|
||||||
|
HikariConfig().apply {
|
||||||
|
dataSource = sqliteUdfDataSource
|
||||||
|
poolName = "SqliteUdfPool"
|
||||||
|
maximumPoolSize = 1
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.apply { maximumPoolSize = 1 }
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.gotson.komga.infrastructure.datasource
|
||||||
|
|
||||||
|
import mu.KotlinLogging
|
||||||
|
import org.springframework.jdbc.datasource.SimpleDriverDataSource
|
||||||
|
import org.sqlite.Function
|
||||||
|
import org.sqlite.SQLiteConnection
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
class SqliteUdfDataSource : SimpleDriverDataSource() {
|
||||||
|
|
||||||
|
override fun getConnection(): Connection =
|
||||||
|
super.getConnection().also { createUdfRegexp(it as SQLiteConnection) }
|
||||||
|
|
||||||
|
override fun getConnection(username: String, password: String): Connection =
|
||||||
|
super.getConnection(username, password).also { createUdfRegexp(it as SQLiteConnection) }
|
||||||
|
|
||||||
|
private fun createUdfRegexp(connection: SQLiteConnection) {
|
||||||
|
log.debug { "Adding custom REGEXP function" }
|
||||||
|
Function.create(
|
||||||
|
connection, "REGEXP",
|
||||||
|
object : Function() {
|
||||||
|
override fun xFunc() {
|
||||||
|
val regexp = (value_text(0) ?: "").toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
val text = value_text(1) ?: ""
|
||||||
|
|
||||||
|
result(if (regexp.containsMatchIn(text)) 1 else 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -127,6 +127,7 @@ class SeriesDao(
|
||||||
if (!libraryIds.isNullOrEmpty()) c = c.and(s.LIBRARY_ID.`in`(libraryIds))
|
if (!libraryIds.isNullOrEmpty()) c = c.and(s.LIBRARY_ID.`in`(libraryIds))
|
||||||
if (!collectionIds.isNullOrEmpty()) c = c.and(cs.COLLECTION_ID.`in`(collectionIds))
|
if (!collectionIds.isNullOrEmpty()) c = c.and(cs.COLLECTION_ID.`in`(collectionIds))
|
||||||
searchTerm?.let { c = c.and(d.TITLE.containsIgnoreCase(it)) }
|
searchTerm?.let { c = c.and(d.TITLE.containsIgnoreCase(it)) }
|
||||||
|
searchRegex?.let { c = c.and((it.second.toColumn()).likeRegex(it.first)) }
|
||||||
if (!metadataStatus.isNullOrEmpty()) c = c.and(d.STATUS.`in`(metadataStatus))
|
if (!metadataStatus.isNullOrEmpty()) c = c.and(d.STATUS.`in`(metadataStatus))
|
||||||
if (!publishers.isNullOrEmpty()) c = c.and(DSL.lower(d.PUBLISHER).`in`(publishers.map { it.lowercase() }))
|
if (!publishers.isNullOrEmpty()) c = c.and(DSL.lower(d.PUBLISHER).`in`(publishers.map { it.lowercase() }))
|
||||||
if (deleted == true) c = c.and(s.DELETED_DATE.isNotNull)
|
if (deleted == true) c = c.and(s.DELETED_DATE.isNotNull)
|
||||||
|
|
@ -135,6 +136,13 @@ class SeriesDao(
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SeriesSearch.SearchField.toColumn() =
|
||||||
|
when (this) {
|
||||||
|
SeriesSearch.SearchField.NAME -> s.NAME
|
||||||
|
SeriesSearch.SearchField.TITLE -> d.TITLE
|
||||||
|
SeriesSearch.SearchField.TITLE_SORT -> d.TITLE_SORT
|
||||||
|
}
|
||||||
|
|
||||||
private fun SeriesRecord.toDomain() =
|
private fun SeriesRecord.toDomain() =
|
||||||
Series(
|
Series(
|
||||||
name = name,
|
name = name,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.gotson.komga.infrastructure.jooq
|
package org.gotson.komga.infrastructure.jooq
|
||||||
|
|
||||||
import org.gotson.komga.domain.model.ReadStatus
|
import org.gotson.komga.domain.model.ReadStatus
|
||||||
|
import org.gotson.komga.domain.model.SeriesSearch
|
||||||
import org.gotson.komga.domain.model.SeriesSearchWithReadProgress
|
import org.gotson.komga.domain.model.SeriesSearchWithReadProgress
|
||||||
import org.gotson.komga.infrastructure.web.toFilePath
|
import org.gotson.komga.infrastructure.web.toFilePath
|
||||||
import org.gotson.komga.interfaces.rest.dto.AuthorDto
|
import org.gotson.komga.interfaces.rest.dto.AuthorDto
|
||||||
|
|
@ -205,6 +206,7 @@ class SeriesDtoDao(
|
||||||
if (!libraryIds.isNullOrEmpty()) c = c.and(s.LIBRARY_ID.`in`(libraryIds))
|
if (!libraryIds.isNullOrEmpty()) c = c.and(s.LIBRARY_ID.`in`(libraryIds))
|
||||||
if (!collectionIds.isNullOrEmpty()) c = c.and(cs.COLLECTION_ID.`in`(collectionIds))
|
if (!collectionIds.isNullOrEmpty()) c = c.and(cs.COLLECTION_ID.`in`(collectionIds))
|
||||||
searchTerm?.let { c = c.and(d.TITLE.containsIgnoreCase(it)) }
|
searchTerm?.let { c = c.and(d.TITLE.containsIgnoreCase(it)) }
|
||||||
|
searchRegex?.let { c = c.and((it.second.toColumn()).likeRegex(it.first)) }
|
||||||
if (!metadataStatus.isNullOrEmpty()) c = c.and(d.STATUS.`in`(metadataStatus))
|
if (!metadataStatus.isNullOrEmpty()) c = c.and(d.STATUS.`in`(metadataStatus))
|
||||||
if (!publishers.isNullOrEmpty()) c = c.and(lower(d.PUBLISHER).`in`(publishers.map { it.lowercase() }))
|
if (!publishers.isNullOrEmpty()) c = c.and(lower(d.PUBLISHER).`in`(publishers.map { it.lowercase() }))
|
||||||
if (deleted == true) c = c.and(s.DELETED_DATE.isNotNull)
|
if (deleted == true) c = c.and(s.DELETED_DATE.isNotNull)
|
||||||
|
|
@ -242,6 +244,13 @@ class SeriesDtoDao(
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SeriesSearch.SearchField.toColumn() =
|
||||||
|
when (this) {
|
||||||
|
SeriesSearch.SearchField.NAME -> s.NAME
|
||||||
|
SeriesSearch.SearchField.TITLE -> d.TITLE
|
||||||
|
SeriesSearch.SearchField.TITLE_SORT -> d.TITLE_SORT
|
||||||
|
}
|
||||||
|
|
||||||
private fun SeriesSearchWithReadProgress.toJoinConditions() =
|
private fun SeriesSearchWithReadProgress.toJoinConditions() =
|
||||||
JoinConditions(
|
JoinConditions(
|
||||||
genre = !genres.isNullOrEmpty(),
|
genre = !genres.isNullOrEmpty(),
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@ package org.gotson.komga.infrastructure.web
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||||
annotation class Authors()
|
annotation class Authors
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.gotson.komga.infrastructure.web
|
||||||
|
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
annotation class DelimitedPair(
|
||||||
|
val parameterName: String
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.gotson.komga.infrastructure.web
|
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter
|
||||||
|
import org.springframework.web.bind.support.WebDataBinderFactory
|
||||||
|
import org.springframework.web.context.request.NativeWebRequest
|
||||||
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver
|
||||||
|
import org.springframework.web.method.support.ModelAndViewContainer
|
||||||
|
|
||||||
|
class DelimitedPairHandlerMethodArgumentResolver : HandlerMethodArgumentResolver {
|
||||||
|
override fun supportsParameter(parameter: MethodParameter): Boolean =
|
||||||
|
parameter.getParameterAnnotation(DelimitedPair::class.java) != null
|
||||||
|
|
||||||
|
override fun resolveArgument(
|
||||||
|
parameter: MethodParameter,
|
||||||
|
mavContainer: ModelAndViewContainer?,
|
||||||
|
webRequest: NativeWebRequest,
|
||||||
|
binderFactory: WebDataBinderFactory?
|
||||||
|
): Pair<String, String>? {
|
||||||
|
val paramName = parameter.getParameterAnnotation(DelimitedPair::class.java)?.parameterName ?: return null
|
||||||
|
val param = webRequest.getParameterValues(paramName) ?: return null
|
||||||
|
|
||||||
|
// Single empty parameter, e.g "search="
|
||||||
|
if (param.size == 1 && param[0].isNullOrBlank()) return null
|
||||||
|
|
||||||
|
return parseParameterIntoPairs(param.first())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseParameterIntoPairs(source: String, delimiter: String = ","): Pair<String, String>? =
|
||||||
|
if (!source.contains(delimiter)) null
|
||||||
|
else Pair(source.substringBeforeLast(delimiter), source.substringAfterLast(delimiter))
|
||||||
|
}
|
||||||
|
|
@ -81,6 +81,7 @@ class WebMvcConfiguration : WebMvcConfigurer {
|
||||||
|
|
||||||
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
|
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
|
||||||
resolvers.add(AuthorsHandlerMethodArgumentResolver())
|
resolvers.add(AuthorsHandlerMethodArgumentResolver())
|
||||||
|
resolvers.add(DelimitedPairHandlerMethodArgumentResolver())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package org.gotson.komga.interfaces.rest
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.Parameter
|
import io.swagger.v3.oas.annotations.Parameter
|
||||||
|
import io.swagger.v3.oas.annotations.Parameters
|
||||||
|
import io.swagger.v3.oas.annotations.enums.ParameterIn
|
||||||
import io.swagger.v3.oas.annotations.media.Content
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
|
|
@ -20,6 +22,7 @@ import org.gotson.komga.domain.model.ROLE_ADMIN
|
||||||
import org.gotson.komga.domain.model.ROLE_FILE_DOWNLOAD
|
import org.gotson.komga.domain.model.ROLE_FILE_DOWNLOAD
|
||||||
import org.gotson.komga.domain.model.ReadStatus
|
import org.gotson.komga.domain.model.ReadStatus
|
||||||
import org.gotson.komga.domain.model.SeriesMetadata
|
import org.gotson.komga.domain.model.SeriesMetadata
|
||||||
|
import org.gotson.komga.domain.model.SeriesSearch
|
||||||
import org.gotson.komga.domain.model.SeriesSearchWithReadProgress
|
import org.gotson.komga.domain.model.SeriesSearchWithReadProgress
|
||||||
import org.gotson.komga.domain.persistence.BookRepository
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
import org.gotson.komga.domain.persistence.SeriesCollectionRepository
|
import org.gotson.komga.domain.persistence.SeriesCollectionRepository
|
||||||
|
|
@ -33,6 +36,7 @@ import org.gotson.komga.infrastructure.swagger.AuthorsAsQueryParam
|
||||||
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
import org.gotson.komga.infrastructure.swagger.PageableAsQueryParam
|
||||||
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
|
||||||
import org.gotson.komga.infrastructure.web.Authors
|
import org.gotson.komga.infrastructure.web.Authors
|
||||||
|
import org.gotson.komga.infrastructure.web.DelimitedPair
|
||||||
import org.gotson.komga.interfaces.rest.dto.BookDto
|
import org.gotson.komga.interfaces.rest.dto.BookDto
|
||||||
import org.gotson.komga.interfaces.rest.dto.CollectionDto
|
import org.gotson.komga.interfaces.rest.dto.CollectionDto
|
||||||
import org.gotson.komga.interfaces.rest.dto.SeriesDto
|
import org.gotson.komga.interfaces.rest.dto.SeriesDto
|
||||||
|
|
@ -93,10 +97,17 @@ class SeriesController(
|
||||||
|
|
||||||
@PageableAsQueryParam
|
@PageableAsQueryParam
|
||||||
@AuthorsAsQueryParam
|
@AuthorsAsQueryParam
|
||||||
|
@Parameters(
|
||||||
|
Parameter(
|
||||||
|
description = "Search by regex criteria, in the form: regex,field. Supported fields are TITLE and TITLE_SORT.",
|
||||||
|
`in` = ParameterIn.QUERY, name = "search_regex", schema = Schema(type = "string")
|
||||||
|
)
|
||||||
|
)
|
||||||
@GetMapping
|
@GetMapping
|
||||||
fun getAllSeries(
|
fun getAllSeries(
|
||||||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||||
@RequestParam(name = "search", required = false) searchTerm: String?,
|
@RequestParam(name = "search", required = false) searchTerm: String?,
|
||||||
|
@Parameter(hidden = true) @DelimitedPair("search_regex") searchRegex: Pair<String, String>?,
|
||||||
@RequestParam(name = "library_id", required = false) libraryIds: List<String>?,
|
@RequestParam(name = "library_id", required = false) libraryIds: List<String>?,
|
||||||
@RequestParam(name = "collection_id", required = false) collectionIds: List<String>?,
|
@RequestParam(name = "collection_id", required = false) collectionIds: List<String>?,
|
||||||
@RequestParam(name = "status", required = false) metadataStatus: List<SeriesMetadata.Status>?,
|
@RequestParam(name = "status", required = false) metadataStatus: List<SeriesMetadata.Status>?,
|
||||||
|
|
@ -128,6 +139,13 @@ class SeriesController(
|
||||||
libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds),
|
libraryIds = principal.user.getAuthorizedLibraryIds(libraryIds),
|
||||||
collectionIds = collectionIds,
|
collectionIds = collectionIds,
|
||||||
searchTerm = searchTerm,
|
searchTerm = searchTerm,
|
||||||
|
searchRegex = searchRegex?.let {
|
||||||
|
when (it.second.lowercase()) {
|
||||||
|
"title" -> Pair(it.first, SeriesSearch.SearchField.TITLE)
|
||||||
|
"title_sort" -> Pair(it.first, SeriesSearch.SearchField.TITLE_SORT)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
},
|
||||||
metadataStatus = metadataStatus,
|
metadataStatus = metadataStatus,
|
||||||
readStatus = readStatus,
|
readStatus = readStatus,
|
||||||
publishers = publishers,
|
publishers = publishers,
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,15 @@ logging:
|
||||||
name: komga-dev.log
|
name: komga-dev.log
|
||||||
level:
|
level:
|
||||||
org.apache.activemq.audit.message: WARN
|
org.apache.activemq.audit.message: WARN
|
||||||
|
org.gotson.komga: DEBUG
|
||||||
# org.jooq: DEBUG
|
# org.jooq: DEBUG
|
||||||
# web: DEBUG
|
# web: DEBUG
|
||||||
org.gotson.komga: DEBUG
|
# com.zaxxer.hikari: DEBUG
|
||||||
|
# org.springframework.jms: DEBUG
|
||||||
|
# org.springframework.security.web.FilterChainProxy: DEBUG
|
||||||
logback:
|
logback:
|
||||||
rollingpolicy:
|
rollingpolicy:
|
||||||
max-history: 1
|
max-history: 1
|
||||||
# org.springframework.jms: DEBUG
|
|
||||||
# org.springframework.security.web.FilterChainProxy: DEBUG
|
|
||||||
|
|
||||||
management.metrics.export.influx:
|
management.metrics.export.influx:
|
||||||
# enabled: true
|
# enabled: true
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ class SeriesDaoTest(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given existing series when searching then results is returned`() {
|
fun `given existing series when searching then result is returned`() {
|
||||||
val series = Series(
|
val series = Series(
|
||||||
name = "Series",
|
name = "Series",
|
||||||
url = URL("file://series"),
|
url = URL("file://series"),
|
||||||
|
|
@ -213,6 +213,21 @@ class SeriesDaoTest(
|
||||||
assertThat(found).hasSize(1)
|
assertThat(found).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given existing series when searching by regex then result is returned`() {
|
||||||
|
val series = Series(
|
||||||
|
name = "my Series",
|
||||||
|
url = URL("file://series"),
|
||||||
|
fileLastModified = LocalDateTime.now(),
|
||||||
|
libraryId = library.id
|
||||||
|
)
|
||||||
|
seriesDao.insert(series)
|
||||||
|
|
||||||
|
assertThat(seriesDao.findAll(SeriesSearch(searchRegex = Pair("^my", SeriesSearch.SearchField.NAME)))).hasSize(1)
|
||||||
|
assertThat(seriesDao.findAll(SeriesSearch(searchRegex = Pair("ries$", SeriesSearch.SearchField.NAME)))).hasSize(1)
|
||||||
|
assertThat(seriesDao.findAll(SeriesSearch(searchRegex = Pair("series", SeriesSearch.SearchField.NAME)))).hasSize(1)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given existing series when finding by libraryId then series are returned`() {
|
fun `given existing series when finding by libraryId then series are returned`() {
|
||||||
val series = Series(
|
val series = Series(
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,38 @@ class SeriesControllerTest(
|
||||||
seriesLifecycle.deleteMany(seriesRepository.findAll())
|
seriesLifecycle.deleteMany(seriesRepository.findAll())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class Search {
|
||||||
|
@Test
|
||||||
|
@WithMockCustomUser
|
||||||
|
fun `given series when searching by regex then series are found`() {
|
||||||
|
val alphaC = seriesLifecycle.createSeries(makeSeries("TheAlpha", libraryId = library.id))
|
||||||
|
seriesMetadataRepository.findById(alphaC.id).let {
|
||||||
|
seriesMetadataRepository.update(it.copy(titleSort = "Alpha, The"))
|
||||||
|
}
|
||||||
|
seriesLifecycle.createSeries(makeSeries("TheBeta", libraryId = library.id))
|
||||||
|
|
||||||
|
mockMvc.get("/api/v1/series") {
|
||||||
|
param("search_regex", "a$,title_sort")
|
||||||
|
}
|
||||||
|
.andExpect {
|
||||||
|
status { isOk() }
|
||||||
|
jsonPath("$.content.length()") { value(1) }
|
||||||
|
jsonPath("$.content[0].metadata.title") { value("TheBeta") }
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.get("/api/v1/series") {
|
||||||
|
param("search_regex", "^the,title")
|
||||||
|
}
|
||||||
|
.andExpect {
|
||||||
|
status { isOk() }
|
||||||
|
jsonPath("$.content.length()") { value(2) }
|
||||||
|
jsonPath("$.content[0].metadata.title") { value("TheAlpha") }
|
||||||
|
jsonPath("$.content[1].metadata.title") { value("TheBeta") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class SeriesSort {
|
inner class SeriesSort {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue