mirror of
https://github.com/gotson/komga.git
synced 2025-12-22 00:13:30 +01:00
parent
1aab9b0714
commit
75b72164fe
5 changed files with 130 additions and 10 deletions
|
|
@ -114,13 +114,13 @@ class BookLifecycle(
|
||||||
readProgressRepository.save(ReadProgress(book.id, user.id, page, page == media.pages.size))
|
readProgressRepository.save(ReadProgress(book.id, user.id, page, page == media.pages.size))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markReadProgressCompleted(book: Book, user: KomgaUser) {
|
fun markReadProgressCompleted(bookId: Long, user: KomgaUser) {
|
||||||
val media = mediaRepository.findById(book.id)
|
val media = mediaRepository.findById(bookId)
|
||||||
|
|
||||||
readProgressRepository.save(ReadProgress(book.id, user.id, media.pages.size, true))
|
readProgressRepository.save(ReadProgress(bookId, user.id, media.pages.size, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteReadProgress(book: Book, user: KomgaUser) {
|
fun deleteReadProgress(bookId: Long, user: KomgaUser) {
|
||||||
readProgressRepository.delete(book.id, user.id)
|
readProgressRepository.delete(bookId, user.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,7 @@ class BookController(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (readProgress.completed != null && readProgress.completed)
|
if (readProgress.completed != null && readProgress.completed)
|
||||||
bookLifecycle.markReadProgressCompleted(book, principal.user)
|
bookLifecycle.markReadProgressCompleted(book.id, principal.user)
|
||||||
else
|
else
|
||||||
bookLifecycle.markReadProgress(book, principal.user, readProgress.page!!)
|
bookLifecycle.markReadProgress(book, principal.user, readProgress.page!!)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
|
|
@ -408,7 +408,7 @@ class BookController(
|
||||||
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
bookRepository.findByIdOrNull(bookId)?.let { book ->
|
||||||
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
if (!principal.user.canAccessBook(book)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||||
|
|
||||||
bookLifecycle.deleteReadProgress(book, principal.user)
|
bookLifecycle.deleteReadProgress(book.id, principal.user)
|
||||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import org.gotson.komga.domain.model.SeriesSearch
|
||||||
import org.gotson.komga.domain.persistence.BookRepository
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
||||||
import org.gotson.komga.domain.persistence.SeriesRepository
|
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||||
|
import org.gotson.komga.domain.service.BookLifecycle
|
||||||
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
import org.gotson.komga.infrastructure.security.KomgaPrincipal
|
||||||
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
|
||||||
|
|
@ -32,6 +33,7 @@ import org.springframework.http.MediaType
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.security.access.prepost.PreAuthorize
|
import org.springframework.security.access.prepost.PreAuthorize
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PatchMapping
|
import org.springframework.web.bind.annotation.PatchMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
|
@ -53,6 +55,7 @@ class SeriesController(
|
||||||
private val seriesRepository: SeriesRepository,
|
private val seriesRepository: SeriesRepository,
|
||||||
private val seriesMetadataRepository: SeriesMetadataRepository,
|
private val seriesMetadataRepository: SeriesMetadataRepository,
|
||||||
private val seriesDtoRepository: SeriesDtoRepository,
|
private val seriesDtoRepository: SeriesDtoRepository,
|
||||||
|
private val bookLifecycle: BookLifecycle,
|
||||||
private val bookRepository: BookRepository,
|
private val bookRepository: BookRepository,
|
||||||
private val bookDtoRepository: BookDtoRepository,
|
private val bookDtoRepository: BookDtoRepository,
|
||||||
private val bookController: BookController
|
private val bookController: BookController
|
||||||
|
|
@ -241,4 +244,33 @@ class SeriesController(
|
||||||
seriesDtoRepository.findByIdOrNull(seriesId)!!
|
seriesDtoRepository.findByIdOrNull(seriesId)!!
|
||||||
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
@PostMapping("{seriesId}/read-progress")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
fun markAsRead(
|
||||||
|
@PathVariable seriesId: Long,
|
||||||
|
@AuthenticationPrincipal principal: KomgaPrincipal
|
||||||
|
) {
|
||||||
|
seriesRepository.getLibraryId(seriesId)?.let {
|
||||||
|
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||||
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
bookRepository.findAllIdBySeriesId(seriesId).forEach {
|
||||||
|
bookLifecycle.markReadProgressCompleted(it, principal.user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("{seriesId}/read-progress")
|
||||||
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
fun markAsUnread(
|
||||||
|
@PathVariable seriesId: Long,
|
||||||
|
@AuthenticationPrincipal principal: KomgaPrincipal
|
||||||
|
) {
|
||||||
|
seriesRepository.getLibraryId(seriesId)?.let {
|
||||||
|
if (!principal.user.canAccessLibrary(it)) throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
|
||||||
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
|
||||||
|
bookRepository.findAllIdBySeriesId(seriesId).forEach {
|
||||||
|
bookLifecycle.deleteReadProgress(it, principal.user)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ class BookControllerTest(
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
fun `teardown library`() {
|
fun `teardown`() {
|
||||||
userRepository.findAll().forEach {
|
userRepository.findAll().forEach {
|
||||||
userLifecycle.deleteUser(it)
|
userLifecycle.deleteUser(it)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,25 @@
|
||||||
package org.gotson.komga.interfaces.rest
|
package org.gotson.komga.interfaces.rest
|
||||||
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.gotson.komga.domain.model.BookPage
|
||||||
|
import org.gotson.komga.domain.model.KomgaUser
|
||||||
|
import org.gotson.komga.domain.model.Media
|
||||||
import org.gotson.komga.domain.model.SeriesMetadata
|
import org.gotson.komga.domain.model.SeriesMetadata
|
||||||
import org.gotson.komga.domain.model.makeBook
|
import org.gotson.komga.domain.model.makeBook
|
||||||
import org.gotson.komga.domain.model.makeLibrary
|
import org.gotson.komga.domain.model.makeLibrary
|
||||||
import org.gotson.komga.domain.model.makeSeries
|
import org.gotson.komga.domain.model.makeSeries
|
||||||
import org.gotson.komga.domain.persistence.BookMetadataRepository
|
import org.gotson.komga.domain.persistence.BookMetadataRepository
|
||||||
import org.gotson.komga.domain.persistence.BookRepository
|
import org.gotson.komga.domain.persistence.BookRepository
|
||||||
|
import org.gotson.komga.domain.persistence.KomgaUserRepository
|
||||||
import org.gotson.komga.domain.persistence.LibraryRepository
|
import org.gotson.komga.domain.persistence.LibraryRepository
|
||||||
import org.gotson.komga.domain.persistence.MediaRepository
|
import org.gotson.komga.domain.persistence.MediaRepository
|
||||||
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
|
||||||
import org.gotson.komga.domain.persistence.SeriesRepository
|
import org.gotson.komga.domain.persistence.SeriesRepository
|
||||||
|
import org.gotson.komga.domain.service.KomgaUserLifecycle
|
||||||
import org.gotson.komga.domain.service.LibraryLifecycle
|
import org.gotson.komga.domain.service.LibraryLifecycle
|
||||||
import org.gotson.komga.domain.service.SeriesLifecycle
|
import org.gotson.komga.domain.service.SeriesLifecycle
|
||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
|
import org.hamcrest.core.IsNull
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
|
|
@ -32,8 +38,10 @@ import org.springframework.jdbc.core.JdbcTemplate
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension
|
import org.springframework.test.context.junit.jupiter.SpringExtension
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import org.springframework.test.web.servlet.MockMvcResultMatchersDsl
|
import org.springframework.test.web.servlet.MockMvcResultMatchersDsl
|
||||||
|
import org.springframework.test.web.servlet.delete
|
||||||
import org.springframework.test.web.servlet.get
|
import org.springframework.test.web.servlet.get
|
||||||
import org.springframework.test.web.servlet.patch
|
import org.springframework.test.web.servlet.patch
|
||||||
|
import org.springframework.test.web.servlet.post
|
||||||
import javax.sql.DataSource
|
import javax.sql.DataSource
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
@ -50,6 +58,8 @@ class SeriesControllerTest(
|
||||||
@Autowired private val bookRepository: BookRepository,
|
@Autowired private val bookRepository: BookRepository,
|
||||||
@Autowired private val mediaRepository: MediaRepository,
|
@Autowired private val mediaRepository: MediaRepository,
|
||||||
@Autowired private val bookMetadataRepository: BookMetadataRepository,
|
@Autowired private val bookMetadataRepository: BookMetadataRepository,
|
||||||
|
@Autowired private val userRepository: KomgaUserRepository,
|
||||||
|
@Autowired private val userLifecycle: KomgaUserLifecycle,
|
||||||
@Autowired private val mockMvc: MockMvc
|
@Autowired private val mockMvc: MockMvc
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
@ -66,11 +76,15 @@ class SeriesControllerTest(
|
||||||
fun `setup library`() {
|
fun `setup library`() {
|
||||||
jdbcTemplate.execute("ALTER SEQUENCE hibernate_sequence RESTART WITH 1")
|
jdbcTemplate.execute("ALTER SEQUENCE hibernate_sequence RESTART WITH 1")
|
||||||
|
|
||||||
library = libraryRepository.insert(library)
|
library = libraryRepository.insert(library) // id = 1
|
||||||
|
userRepository.save(KomgaUser("user@example.org", "", false)) // id = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
fun `teardown library`() {
|
fun `teardown`() {
|
||||||
|
userRepository.findAll().forEach {
|
||||||
|
userLifecycle.deleteUser(it)
|
||||||
|
}
|
||||||
libraryRepository.findAll().forEach {
|
libraryRepository.findAll().forEach {
|
||||||
libraryLifecycle.deleteLibrary(it)
|
libraryLifecycle.deleteLibrary(it)
|
||||||
}
|
}
|
||||||
|
|
@ -469,4 +483,78 @@ class SeriesControllerTest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class ReadProgress {
|
||||||
|
@Test
|
||||||
|
@WithMockCustomUser(id = 2)
|
||||||
|
fun `given user when marking series as read then progress is marked for all books`() {
|
||||||
|
val series = makeSeries(name = "series", libraryId = library.id).let { series ->
|
||||||
|
seriesLifecycle.createSeries(series).also { created ->
|
||||||
|
val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id))
|
||||||
|
seriesLifecycle.addBooks(created, books)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookRepository.findAll().forEach { book ->
|
||||||
|
mediaRepository.findById(book.id).let {
|
||||||
|
mediaRepository.update(it.copy(
|
||||||
|
status = Media.Status.READY,
|
||||||
|
pages = (1..10).map { BookPage("$it", "image/jpeg") }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.post("/api/v1/series/${series.id}/read-progress")
|
||||||
|
.andExpect {
|
||||||
|
status { isNoContent }
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.get("/api/v1/series/${series.id}/books")
|
||||||
|
.andExpect {
|
||||||
|
status { isOk }
|
||||||
|
jsonPath("$.content[0].readProgress.completed") { value(true) }
|
||||||
|
jsonPath("$.content[1].readProgress.completed") { value(true) }
|
||||||
|
jsonPath("$.numberOfElements") { value(2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockCustomUser(id = 2)
|
||||||
|
fun `given user when marking series as unread then progress is removed for all books`() {
|
||||||
|
val series = makeSeries(name = "series", libraryId = library.id).let { series ->
|
||||||
|
seriesLifecycle.createSeries(series).also { created ->
|
||||||
|
val books = listOf(makeBook("1.cbr", libraryId = library.id), makeBook("2.cbr", libraryId = library.id))
|
||||||
|
seriesLifecycle.addBooks(created, books)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookRepository.findAll().forEach { book ->
|
||||||
|
mediaRepository.findById(book.id).let {
|
||||||
|
mediaRepository.update(it.copy(
|
||||||
|
status = Media.Status.READY,
|
||||||
|
pages = (1..10).map { BookPage("$it", "image/jpeg") }
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.post("/api/v1/series/${series.id}/read-progress")
|
||||||
|
.andExpect {
|
||||||
|
status { isNoContent }
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.delete("/api/v1/series/${series.id}/read-progress")
|
||||||
|
.andExpect {
|
||||||
|
status { isNoContent }
|
||||||
|
}
|
||||||
|
|
||||||
|
mockMvc.get("/api/v1/series/${series.id}/books")
|
||||||
|
.andExpect {
|
||||||
|
status { isOk }
|
||||||
|
jsonPath("$.content[0].readProgress") { value(IsNull.nullValue()) }
|
||||||
|
jsonPath("$.content[1].readProgress") { value(IsNull.nullValue()) }
|
||||||
|
jsonPath("$.numberOfElements") { value(2) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue