mirror of
https://github.com/gotson/komga.git
synced 2025-12-20 23:45:11 +01:00
feat: add more series metadata fields
title, sort title, lock for: status, title and sort title
This commit is contained in:
parent
f1952eee4a
commit
8f08ce82e1
15 changed files with 269 additions and 123 deletions
|
|
@ -17,9 +17,9 @@
|
|||
<v-card-subtitle class="pa-2 pb-1 text--primary"
|
||||
v-line-clamp="2"
|
||||
style="word-break: normal !important; height: 4em"
|
||||
:title="series.name"
|
||||
:title="series.metadata.title"
|
||||
>
|
||||
{{ series.name }}
|
||||
{{ series.metadata.title }}
|
||||
</v-card-subtitle>
|
||||
|
||||
<v-card-text class="px-2"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<v-btn icon @click="dialogCancel">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>Edit {{ series.name }}</v-toolbar-title>
|
||||
<v-toolbar-title>Edit {{ $_.get(series, 'metadata.title') }}</v-toolbar-title>
|
||||
<v-spacer/>
|
||||
<v-toolbar-items>
|
||||
<v-btn text color="primary" @click="dialogConfirm">Save changes</v-btn>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<v-card-title class="hidden-xs-only">
|
||||
<v-icon class="mr-4">mdi-pencil</v-icon>
|
||||
Edit {{ series.name }}
|
||||
Edit {{ $_.get(series, 'metadata.title') }}
|
||||
</v-card-title>
|
||||
|
||||
<v-tabs :vertical="$vuetify.breakpoint.smAndUp">
|
||||
|
|
@ -32,13 +32,59 @@
|
|||
<v-card flat>
|
||||
<form novalidate>
|
||||
<v-container fluid>
|
||||
|
||||
<!-- Title -->
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field v-model="form.title"
|
||||
label="Title"
|
||||
@change="form.titleLock = true"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon :color="form.titleLock ? 'secondary' : ''"
|
||||
@click="form.titleLock = !form.titleLock"
|
||||
>
|
||||
{{ form.titleLock ? 'mdi-lock' : 'mdi-lock-open' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Sort Title -->
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-text-field v-model="form.titleSort"
|
||||
label="Sort Title"
|
||||
@change="form.titleSortLock = true"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon :color="form.titleSortLock ? 'secondary' : ''"
|
||||
@click="form.titleSortLock = !form.titleSortLock"
|
||||
>
|
||||
{{ form.titleSortLock ? 'mdi-lock' : 'mdi-lock-open' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Status -->
|
||||
<v-row>
|
||||
<v-col cols="auto">
|
||||
<v-select
|
||||
:items="seriesStatus"
|
||||
v-model="form.status"
|
||||
label="Status"
|
||||
/>
|
||||
<v-select :items="seriesStatus"
|
||||
v-model="form.status"
|
||||
label="Status"
|
||||
@change="form.statusLock = true"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-icon :color="form.statusLock ? 'secondary' : ''"
|
||||
@click="form.statusLock = !form.statusLock"
|
||||
>
|
||||
{{ form.statusLock ? 'mdi-lock' : 'mdi-lock-open' }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
|
|
@ -86,7 +132,12 @@ export default Vue.extend({
|
|||
snackText: '',
|
||||
seriesStatus: Object.keys(SeriesStatus).map(x => capitalize(x)),
|
||||
form: {
|
||||
status: ''
|
||||
status: '',
|
||||
statusLock: false,
|
||||
title: '',
|
||||
titleLock: false,
|
||||
titleSort: '',
|
||||
titleSortLock: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -116,15 +167,19 @@ export default Vue.extend({
|
|||
methods: {
|
||||
dialogReset (series: SeriesDto) {
|
||||
this.form.status = capitalize(series.metadata.status)
|
||||
this.form.statusLock = series.metadata.statusLock
|
||||
this.form.title = series.metadata.title
|
||||
this.form.titleLock = series.metadata.titleLock
|
||||
this.form.titleSort = series.metadata.titleSort
|
||||
this.form.titleSortLock = series.metadata.titleSortLock
|
||||
},
|
||||
dialogCancel () {
|
||||
this.$emit('input', false)
|
||||
this.dialogReset(this.series)
|
||||
},
|
||||
dialogConfirm () {
|
||||
this.editSeries()
|
||||
async dialogConfirm () {
|
||||
await this.editSeries()
|
||||
this.$emit('input', false)
|
||||
this.dialogReset(this.series)
|
||||
},
|
||||
showSnack (message: string) {
|
||||
this.snackText = message
|
||||
|
|
@ -133,10 +188,16 @@ export default Vue.extend({
|
|||
async editSeries () {
|
||||
try {
|
||||
const metadata = {
|
||||
status: this.form.status.toUpperCase()
|
||||
status: this.form.status.toUpperCase(),
|
||||
statusLock: this.form.statusLock,
|
||||
title: this.form.title,
|
||||
titleLock: this.form.titleLock,
|
||||
titleSort: this.form.titleSort,
|
||||
titleSortLock: this.form.titleSortLock
|
||||
} as SeriesMetadataUpdateDto
|
||||
|
||||
await this.$komgaSeries.updateMetadata(this.series.id, metadata)
|
||||
const updatedSeries = await this.$komgaSeries.updateMetadata(this.series.id, metadata)
|
||||
this.$emit('update:series', updatedSeries)
|
||||
} catch (e) {
|
||||
this.showSnack(e.message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
class="ma-1 mr-3"
|
||||
/>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.name"/>
|
||||
<v-list-item-title v-text="item.metadata.title"/>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -105,9 +105,9 @@ export default class KomgaSeriesService {
|
|||
}
|
||||
}
|
||||
|
||||
async updateMetadata (seriesId: number, metadata: SeriesMetadataUpdateDto) {
|
||||
async updateMetadata (seriesId: number, metadata: SeriesMetadataUpdateDto): Promise<SeriesDto> {
|
||||
try {
|
||||
await this.http.patch(`${API_SERIES}/${seriesId}/metadata`, metadata)
|
||||
return (await this.http.patch(`${API_SERIES}/${seriesId}/metadata`, metadata)).data
|
||||
} catch (e) {
|
||||
let msg = `An error occurred while trying to update series metadata`
|
||||
if (e.response.data.message) {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,20 @@ interface SeriesDto {
|
|||
|
||||
interface SeriesMetadata {
|
||||
status: string,
|
||||
statusLock: boolean,
|
||||
created: string,
|
||||
lastModified: string
|
||||
lastModified: string,
|
||||
title: string,
|
||||
titleLock: boolean,
|
||||
titleSort: string,
|
||||
titleSortLock: boolean
|
||||
}
|
||||
|
||||
interface SeriesMetadataUpdateDto {
|
||||
status?: string
|
||||
status?: string,
|
||||
statusLock?: boolean,
|
||||
title?: string,
|
||||
titleLock?: boolean,
|
||||
titleSort?: string,
|
||||
titleSortLock?: boolean
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,12 +100,13 @@ export default mixins(VisibleElements).extend({
|
|||
pagesState: [] as LoadState[],
|
||||
pageSize: 20,
|
||||
totalElements: null as number | null,
|
||||
sortOptions: [{ name: 'Name', key: 'name' }, { name: 'Date added', key: 'createdDate' }, {
|
||||
name: 'Date updated',
|
||||
key: 'lastModifiedDate'
|
||||
}] as SortOption[],
|
||||
sortOptions: [
|
||||
{ name: 'Name', key: 'metadata.titleSort' },
|
||||
{ name: 'Date added', key: 'createdDate' },
|
||||
{ name: 'Date updated', key: 'lastModifiedDate' }
|
||||
] as SortOption[],
|
||||
sortActive: {} as SortActive,
|
||||
sortDefault: { key: 'name', order: 'asc' } as SortActive,
|
||||
sortDefault: { key: 'metadata.titleSort', order: 'asc' } as SortActive,
|
||||
filterStatus: [] as string[],
|
||||
SeriesStatus,
|
||||
cardWidth: 150,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
</v-menu>
|
||||
|
||||
<v-toolbar-title>
|
||||
<span v-if="series.name">{{ series.name }}</span>
|
||||
<span v-if="$_.get(series, 'metadata.title')">{{ series.metadata.title }}</span>
|
||||
<badge class="ml-4" v-if="totalElements" v-model="totalElements"/>
|
||||
</v-toolbar-title>
|
||||
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
<v-col cols="8">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<div class="headline" v-if="series.name">{{ series.name }}</div>
|
||||
<div class="headline" v-if="$_.get(series, 'metadata.title')">{{ series.metadata.title }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
</v-container>
|
||||
|
||||
<edit-series-dialog v-model="dialogEdit"
|
||||
:series="series"/>
|
||||
:series.sync="series"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -159,11 +159,6 @@ export default mixins(VisibleElements).extend({
|
|||
if (this.$route.params.index !== index) {
|
||||
this.updateRoute(index)
|
||||
}
|
||||
},
|
||||
dialogEdit (val) {
|
||||
if (!val) {
|
||||
this.loadSeries()
|
||||
}
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class Series(
|
|||
|
||||
@OneToOne(optional = false, orphanRemoval = true, cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "metadata_id", nullable = false)
|
||||
var metadata: SeriesMetadata = SeriesMetadata()
|
||||
var metadata: SeriesMetadata = SeriesMetadata(title = name, titleSort = name)
|
||||
|
||||
init {
|
||||
this.books = books.toList()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,13 @@ import javax.persistence.Table
|
|||
class SeriesMetadata(
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "status", nullable = false)
|
||||
var status: Status = Status.ONGOING
|
||||
var status: Status = Status.ONGOING,
|
||||
|
||||
@Column(name = "title", nullable = false)
|
||||
var title: String,
|
||||
|
||||
@Column(name = "title_sort", nullable = false)
|
||||
var titleSort: String
|
||||
|
||||
) : AuditableEntity() {
|
||||
@Id
|
||||
|
|
@ -26,6 +32,15 @@ class SeriesMetadata(
|
|||
@Column(name = "id", nullable = false, unique = true)
|
||||
val id: Long = 0
|
||||
|
||||
@Column(name = "status_lock", nullable = false)
|
||||
var statusLock: Boolean = false
|
||||
|
||||
@Column(name = "title_lock", nullable = false)
|
||||
var titleLock: Boolean = false
|
||||
|
||||
@Column(name = "title_sort_lock", nullable = false)
|
||||
var titleSortLock: Boolean = false
|
||||
|
||||
enum class Status {
|
||||
ENDED, ONGOING, ABANDONED, HIATUS
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,16 +113,16 @@ class OpdsController(
|
|||
@AuthenticationPrincipal principal: KomgaPrincipal,
|
||||
@RequestParam("search") searchTerm: String?
|
||||
): OpdsFeed {
|
||||
val sort = Sort.by(Sort.Order.asc("name").ignoreCase())
|
||||
val sort = Sort.by(Sort.Order.asc("metadata.titleSort").ignoreCase())
|
||||
val series =
|
||||
mutableListOf<Specification<Series>>().let { specs ->
|
||||
if (!principal.user.sharedAllLibraries) {
|
||||
specs.add(Series::library.`in`(principal.user.sharedLibraries))
|
||||
}
|
||||
mutableListOf<Specification<Series>>().let { specs ->
|
||||
if (!principal.user.sharedAllLibraries) {
|
||||
specs.add(Series::library.`in`(principal.user.sharedLibraries))
|
||||
}
|
||||
|
||||
if (!searchTerm.isNullOrEmpty()) {
|
||||
specs.add(Series::name.likeLower("%$searchTerm%"))
|
||||
}
|
||||
if (!searchTerm.isNullOrEmpty()) {
|
||||
specs.add(Series::name.likeLower("%$searchTerm%"))
|
||||
}
|
||||
|
||||
if (specs.isNotEmpty()) {
|
||||
seriesRepository.findAll(specs.reduce { acc, spec -> acc.and(spec)!! }, sort)
|
||||
|
|
@ -230,14 +230,14 @@ class OpdsController(
|
|||
OpdsLinkFeedNavigation(OpdsLinkRel.SELF, "${ROUTE_BASE}libraries/$id"),
|
||||
linkStart
|
||||
),
|
||||
entries = seriesRepository.findByLibraryId(library.id, Sort.by(Sort.Order.asc("name").ignoreCase())).map { it.toOpdsEntry() }
|
||||
entries = seriesRepository.findByLibraryId(library.id, Sort.by(Sort.Order.asc("metadata.titleSort").ignoreCase())).map { it.toOpdsEntry() }
|
||||
)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
|
||||
private fun Series.toOpdsEntry() =
|
||||
OpdsEntryNavigation(
|
||||
title = name,
|
||||
title = metadata.title,
|
||||
updated = lastModifiedDate?.atZone(ZoneId.systemDefault()) ?: ZonedDateTime.now(),
|
||||
id = id.toString(),
|
||||
content = "",
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class SeriesController(
|
|||
page.pageNumber,
|
||||
page.pageSize,
|
||||
if (page.sort.isSorted) page.sort
|
||||
else Sort.by(Sort.Order.asc("name").ignoreCase())
|
||||
else Sort.by(Sort.Order.asc("metadata.titleSort").ignoreCase())
|
||||
)
|
||||
|
||||
return mutableListOf<Specification<Series>>().let { specs ->
|
||||
|
|
@ -226,6 +226,15 @@ class SeriesController(
|
|||
): SeriesDto =
|
||||
seriesRepository.findByIdOrNull(seriesId)?.let { series ->
|
||||
newMetadata.status?.let { series.metadata.status = newMetadata.status }
|
||||
newMetadata.statusLock?.let { series.metadata.statusLock = newMetadata.statusLock }
|
||||
if (!newMetadata.title.isNullOrBlank()) {
|
||||
series.metadata.title = newMetadata.title
|
||||
}
|
||||
newMetadata.titleLock?.let { series.metadata.titleLock = newMetadata.titleLock }
|
||||
if (!newMetadata.titleSort.isNullOrBlank()) {
|
||||
series.metadata.titleSort = newMetadata.titleSort
|
||||
}
|
||||
newMetadata.titleSortLock?.let { series.metadata.titleSortLock = newMetadata.titleSortLock }
|
||||
seriesRepository.save(series).toDto(includeUrl = true)
|
||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,15 @@ data class SeriesDto(
|
|||
|
||||
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 lastModified: LocalDateTime?,
|
||||
val title: String,
|
||||
val titleLock: Boolean,
|
||||
val titleSort: String,
|
||||
val titleSortLock: Boolean
|
||||
)
|
||||
|
||||
fun Series.toDto(includeUrl: Boolean) = SeriesDto(
|
||||
|
|
@ -38,7 +43,12 @@ fun Series.toDto(includeUrl: Boolean) = SeriesDto(
|
|||
booksCount = books.size,
|
||||
metadata = SeriesMetadataDto(
|
||||
status = metadata.status.name,
|
||||
statusLock = metadata.statusLock,
|
||||
created = metadata.createdDate?.toUTC(),
|
||||
lastModified = metadata.lastModifiedDate?.toUTC()
|
||||
lastModified = metadata.lastModifiedDate?.toUTC(),
|
||||
title = metadata.title,
|
||||
titleLock = metadata.titleLock,
|
||||
titleSort = metadata.titleSort,
|
||||
titleSortLock = metadata.titleSortLock
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,5 +3,10 @@ package org.gotson.komga.interfaces.rest.dto
|
|||
import org.gotson.komga.domain.model.SeriesMetadata
|
||||
|
||||
data class SeriesMetadataUpdateDto(
|
||||
val status: SeriesMetadata.Status?
|
||||
val status: SeriesMetadata.Status?,
|
||||
val statusLock: Boolean?,
|
||||
val title: String?,
|
||||
val titleLock: Boolean?,
|
||||
val titleSort: String?,
|
||||
val titleSortLock: Boolean?
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
alter table series_metadata
|
||||
add (
|
||||
status_lock boolean default false,
|
||||
title varchar,
|
||||
title_lock boolean default false,
|
||||
title_sort varchar,
|
||||
title_sort_lock boolean default false
|
||||
);
|
||||
|
||||
update series_metadata m
|
||||
set m.title = (select name from series where metadata_id = m.id),
|
||||
m.title_sort = (select name from series where metadata_id = m.id);
|
||||
|
||||
alter table series_metadata
|
||||
alter column title set not null;
|
||||
|
||||
alter table series_metadata
|
||||
alter column title_sort set not null;
|
||||
|
|
@ -29,9 +29,9 @@ import javax.sql.DataSource
|
|||
@AutoConfigureTestDatabase
|
||||
@AutoConfigureMockMvc(printOnlyOnFailure = false)
|
||||
class SeriesControllerTest(
|
||||
@Autowired private val seriesRepository: SeriesRepository,
|
||||
@Autowired private val libraryRepository: LibraryRepository,
|
||||
@Autowired private val mockMvc: MockMvc
|
||||
@Autowired private val seriesRepository: SeriesRepository,
|
||||
@Autowired private val libraryRepository: LibraryRepository,
|
||||
@Autowired private val mockMvc: MockMvc
|
||||
) {
|
||||
|
||||
lateinit var jdbcTemplate: JdbcTemplate
|
||||
|
|
@ -60,14 +60,36 @@ class SeriesControllerTest(
|
|||
seriesRepository.deleteAll()
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class SeriesSort {
|
||||
@Test
|
||||
@WithMockCustomUser
|
||||
fun `given series with titleSort when requesting via api then series are sorted by titleSort`() {
|
||||
val alpha = makeSeries("The Alpha").also {
|
||||
it.metadata.titleSort = "Alpha, The"
|
||||
it.library = library
|
||||
}
|
||||
seriesRepository.save(alpha)
|
||||
val beta = makeSeries("Beta").also { it.library = library }
|
||||
seriesRepository.save(beta)
|
||||
|
||||
mockMvc.get("/api/v1/series")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].metadata.title") { value("The Alpha") }
|
||||
jsonPath("$.content[1].metadata.title") { value("Beta") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class BookOrdering {
|
||||
@Test
|
||||
@WithMockCustomUser
|
||||
fun `given books with unordered index when requesting via api then books are ordered`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"), makeBook("3"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"), makeBook("3"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
|
|
@ -75,20 +97,20 @@ class SeriesControllerTest(
|
|||
seriesRepository.save(series)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}/books")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].name") { value("1") }
|
||||
jsonPath("$.content[1].name") { value("2") }
|
||||
jsonPath("$.content[2].name") { value("3") }
|
||||
}
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].name") { value("1") }
|
||||
jsonPath("$.content[1].name") { value("2") }
|
||||
jsonPath("$.content[2].name") { value("3") }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockCustomUser
|
||||
fun `given many books with unordered index when requesting via api then books are ordered and paged`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = (1..100 step 2).map { makeBook("$it") }
|
||||
name = "series",
|
||||
books = (1..100 step 2).map { makeBook("$it") }
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
|
|
@ -96,24 +118,24 @@ class SeriesControllerTest(
|
|||
seriesRepository.save(series)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}/books")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].name") { value("1") }
|
||||
jsonPath("$.content[1].name") { value("2") }
|
||||
jsonPath("$.content[2].name") { value("3") }
|
||||
jsonPath("$.content[3].name") { value("5") }
|
||||
jsonPath("$.size") { value(20) }
|
||||
jsonPath("$.first") { value(true) }
|
||||
jsonPath("$.number") { value(0) }
|
||||
}
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].name") { value("1") }
|
||||
jsonPath("$.content[1].name") { value("2") }
|
||||
jsonPath("$.content[2].name") { value("3") }
|
||||
jsonPath("$.content[3].name") { value("5") }
|
||||
jsonPath("$.size") { value(20) }
|
||||
jsonPath("$.first") { value(true) }
|
||||
jsonPath("$.number") { value(0) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockCustomUser
|
||||
fun `given many books in ready state with unordered index when requesting via api then books are ordered and paged`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = (1..100 step 2).map { makeBook("$it") }
|
||||
name = "series",
|
||||
books = (1..100 step 2).map { makeBook("$it") }
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
|
|
@ -122,16 +144,16 @@ class SeriesControllerTest(
|
|||
seriesRepository.save(series)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}/books?mediaStatus=READY")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].name") { value("1") }
|
||||
jsonPath("$.content[1].name") { value("2") }
|
||||
jsonPath("$.content[2].name") { value("3") }
|
||||
jsonPath("$.content[3].name") { value("5") }
|
||||
jsonPath("$.size") { value(20) }
|
||||
jsonPath("$.first") { value(true) }
|
||||
jsonPath("$.number") { value(0) }
|
||||
}
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content[0].name") { value("1") }
|
||||
jsonPath("$.content[1].name") { value("2") }
|
||||
jsonPath("$.content[2].name") { value("3") }
|
||||
jsonPath("$.content[3].name") { value("5") }
|
||||
jsonPath("$.size") { value(20) }
|
||||
jsonPath("$.first") { value(true) }
|
||||
jsonPath("$.number") { value(0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,8 +163,8 @@ class SeriesControllerTest(
|
|||
@WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [1])
|
||||
fun `given user with access to a single library when getting series then only gets series from this library`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
|
|
@ -150,17 +172,17 @@ class SeriesControllerTest(
|
|||
libraryRepository.save(otherLibrary)
|
||||
|
||||
val otherSeries = makeSeries(
|
||||
name = "otherSeries",
|
||||
books = listOf(makeBook("2"))
|
||||
name = "otherSeries",
|
||||
books = listOf(makeBook("2"))
|
||||
).also { it.library = otherLibrary }
|
||||
seriesRepository.save(otherSeries)
|
||||
|
||||
mockMvc.get("/api/v1/series")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content.length()") { value(1) }
|
||||
jsonPath("$.content[0].name") { value("series") }
|
||||
}
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.content.length()") { value(1) }
|
||||
jsonPath("$.content[0].name") { value("series") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -170,39 +192,39 @@ class SeriesControllerTest(
|
|||
@WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [])
|
||||
fun `given user with no access to any library when getting specific series then returns unauthorized`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}")
|
||||
.andExpect { status { isUnauthorized } }
|
||||
.andExpect { status { isUnauthorized } }
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [])
|
||||
fun `given user with no access to any library when getting specific series thumbnail then returns unauthorized`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}/thumbnail")
|
||||
.andExpect { status { isUnauthorized } }
|
||||
.andExpect { status { isUnauthorized } }
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockCustomUser(sharedAllLibraries = false, sharedLibraries = [])
|
||||
fun `given user with no access to any library when getting specific series books then returns unauthorized`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}/books")
|
||||
.andExpect { status { isUnauthorized } }
|
||||
.andExpect { status { isUnauthorized } }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -212,13 +234,13 @@ class SeriesControllerTest(
|
|||
@WithMockCustomUser
|
||||
fun `given book without thumbnail when getting series thumbnail then returns not found`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}/thumbnail")
|
||||
.andExpect { status { isNotFound } }
|
||||
.andExpect { status { isNotFound } }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -228,8 +250,8 @@ class SeriesControllerTest(
|
|||
@WithMockCustomUser
|
||||
fun `given regular user when getting series then url is hidden`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1.cbr"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1.cbr"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
|
|
@ -239,27 +261,27 @@ class SeriesControllerTest(
|
|||
}
|
||||
|
||||
mockMvc.get("/api/v1/series")
|
||||
.andExpect(validation)
|
||||
.andExpect(validation)
|
||||
|
||||
mockMvc.get("/api/v1/series/latest")
|
||||
.andExpect(validation)
|
||||
.andExpect(validation)
|
||||
|
||||
mockMvc.get("/api/v1/series/new")
|
||||
.andExpect(validation)
|
||||
.andExpect(validation)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.url") { value("") }
|
||||
}
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.url") { value("") }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockCustomUser(roles = [UserRoles.ADMIN])
|
||||
fun `given admin user when getting series then url is available`() {
|
||||
val series = makeSeries(
|
||||
name = "series",
|
||||
books = listOf(makeBook("1.cbr"))
|
||||
name = "series",
|
||||
books = listOf(makeBook("1.cbr"))
|
||||
).also { it.library = library }
|
||||
seriesRepository.save(series)
|
||||
|
||||
|
|
@ -270,19 +292,19 @@ class SeriesControllerTest(
|
|||
}
|
||||
|
||||
mockMvc.get("/api/v1/series")
|
||||
.andExpect(validation)
|
||||
.andExpect(validation)
|
||||
|
||||
mockMvc.get("/api/v1/series/latest")
|
||||
.andExpect(validation)
|
||||
.andExpect(validation)
|
||||
|
||||
mockMvc.get("/api/v1/series/new")
|
||||
.andExpect(validation)
|
||||
.andExpect(validation)
|
||||
|
||||
mockMvc.get("/api/v1/series/${series.id}")
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.url") { value(url) }
|
||||
}
|
||||
.andExpect {
|
||||
status { isOk }
|
||||
jsonPath("$.url") { value(url) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue