Skip to content

Commit

Permalink
feat(api): mark all books in series as read or unread
Browse files Browse the repository at this point in the history
related to gotson#25
  • Loading branch information
gotson committed Jun 3, 2020
1 parent 1aab9b0 commit 75b7216
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ class BookLifecycle(
readProgressRepository.save(ReadProgress(book.id, user.id, page, page == media.pages.size))
}

fun markReadProgressCompleted(book: Book, user: KomgaUser) {
val media = mediaRepository.findById(book.id)
fun markReadProgressCompleted(bookId: Long, user: KomgaUser) {
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) {
readProgressRepository.delete(book.id, user.id)
fun deleteReadProgress(bookId: Long, user: KomgaUser) {
readProgressRepository.delete(bookId, user.id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ class BookController(

try {
if (readProgress.completed != null && readProgress.completed)
bookLifecycle.markReadProgressCompleted(book, principal.user)
bookLifecycle.markReadProgressCompleted(book.id, principal.user)
else
bookLifecycle.markReadProgress(book, principal.user, readProgress.page!!)
} catch (e: IllegalArgumentException) {
Expand All @@ -408,7 +408,7 @@ class BookController(
bookRepository.findByIdOrNull(bookId)?.let { book ->
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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.gotson.komga.domain.model.SeriesSearch
import org.gotson.komga.domain.persistence.BookRepository
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
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.swagger.PageableAsQueryParam
import org.gotson.komga.infrastructure.swagger.PageableWithoutSortAsQueryParam
Expand All @@ -32,6 +33,7 @@ import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
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.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
Expand All @@ -53,6 +55,7 @@ class SeriesController(
private val seriesRepository: SeriesRepository,
private val seriesMetadataRepository: SeriesMetadataRepository,
private val seriesDtoRepository: SeriesDtoRepository,
private val bookLifecycle: BookLifecycle,
private val bookRepository: BookRepository,
private val bookDtoRepository: BookDtoRepository,
private val bookController: BookController
Expand Down Expand Up @@ -241,4 +244,33 @@ class SeriesController(
seriesDtoRepository.findByIdOrNull(seriesId)!!
} ?: 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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class BookControllerTest(
}

@AfterAll
fun `teardown library`() {
fun `teardown`() {
userRepository.findAll().forEach {
userLifecycle.deleteUser(it)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package org.gotson.komga.interfaces.rest

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.makeBook
import org.gotson.komga.domain.model.makeLibrary
import org.gotson.komga.domain.model.makeSeries
import org.gotson.komga.domain.persistence.BookMetadataRepository
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.MediaRepository
import org.gotson.komga.domain.persistence.SeriesMetadataRepository
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.SeriesLifecycle
import org.hamcrest.Matchers
import org.hamcrest.core.IsNull
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
Expand All @@ -32,8 +38,10 @@ import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.servlet.MockMvc
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.patch
import org.springframework.test.web.servlet.post
import javax.sql.DataSource
import kotlin.random.Random

Expand All @@ -50,6 +58,8 @@ class SeriesControllerTest(
@Autowired private val bookRepository: BookRepository,
@Autowired private val mediaRepository: MediaRepository,
@Autowired private val bookMetadataRepository: BookMetadataRepository,
@Autowired private val userRepository: KomgaUserRepository,
@Autowired private val userLifecycle: KomgaUserLifecycle,
@Autowired private val mockMvc: MockMvc
) {

Expand All @@ -66,11 +76,15 @@ class SeriesControllerTest(
fun `setup library`() {
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
fun `teardown library`() {
fun `teardown`() {
userRepository.findAll().forEach {
userLifecycle.deleteUser(it)
}
libraryRepository.findAll().forEach {
libraryLifecycle.deleteLibrary(it)
}
Expand Down Expand Up @@ -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) }
}
}
}
}

0 comments on commit 75b7216

Please sign in to comment.