diff --git a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt index 645d3f535..7f02bc5ee 100644 --- a/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt +++ b/komga/src/main/kotlin/org/gotson/komga/domain/persistence/BookRepository.kt @@ -5,14 +5,26 @@ import org.gotson.komga.domain.model.Status import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.jpa.repository.Query import org.springframework.stereotype.Repository import java.net.URL @Repository interface BookRepository : JpaRepository { + @Query( + value = "select * from Book b where b.serie_id = ?1 order by b.index", + countQuery = "select count(*) from Book where serie_id = ?1", + nativeQuery = true) fun findAllBySerieId(serieId: Long, pageable: Pageable): Page + + @Query( + value = "select * from Book b, Book_Metadata m where b.book_metadata_id = m.id " + + "and b.serie_id = ?2 and m.status = ?1 order by b.index", + countQuery = "select count(*) from Book b, Book_Metadata m where b.book_metadata_id = m.id and b.serie_id = ?2 and m.status = ?1", + nativeQuery = true) + fun findAllByMetadataStatusAndSerieId(status: String, serieId: Long, pageable: Pageable): Page + fun findByUrl(url: URL): Book? fun findAllByMetadataStatus(status: Status): List - fun findAllByMetadataStatusAndSerieId(status: Status, serieId: Long, pageable: Pageable): Page fun findAllByMetadataThumbnailIsNull(): List } \ No newline at end of file diff --git a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt index 6962e2cc5..6529ed531 100644 --- a/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt +++ b/komga/src/main/kotlin/org/gotson/komga/interfaces/web/SerieController.kt @@ -99,7 +99,7 @@ class SerieController( ): Page { if (!serieRepository.existsById(id)) throw ResponseStatusException(HttpStatus.NOT_FOUND) return if (readyFilter) { - bookRepository.findAllByMetadataStatusAndSerieId(Status.READY, id, page) + bookRepository.findAllByMetadataStatusAndSerieId(Status.READY.name, id, page) } else { bookRepository.findAllBySerieId(id, page) }.map { it.toDto() } diff --git a/komga/src/test/kotlin/org/gotson/komga/interfaces/web/SerieControllerTest.kt b/komga/src/test/kotlin/org/gotson/komga/interfaces/web/SerieControllerTest.kt new file mode 100644 index 000000000..93a9f2722 --- /dev/null +++ b/komga/src/test/kotlin/org/gotson/komga/interfaces/web/SerieControllerTest.kt @@ -0,0 +1,110 @@ +package org.gotson.komga.interfaces.web + +import org.gotson.komga.domain.model.BookMetadata +import org.gotson.komga.domain.model.Status +import org.gotson.komga.domain.model.makeBook +import org.gotson.komga.domain.model.makeSerie +import org.gotson.komga.domain.persistence.BookRepository +import org.gotson.komga.domain.persistence.SerieRepository +import org.gotson.komga.domain.service.BookManager +import org.gotson.komga.domain.service.LibraryManager +import org.hamcrest.CoreMatchers.equalTo +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders +import org.springframework.test.web.servlet.result.MockMvcResultMatchers +import javax.persistence.EntityManager + +@ExtendWith(SpringExtension::class) +@SpringBootTest +@AutoConfigureTestDatabase +@AutoConfigureMockMvc(printOnlyOnFailure = false) +class SerieControllerTest( + @Autowired private val serieRepository: SerieRepository, + @Autowired private val bookRepository: BookRepository, + @Autowired private val libraryManager: LibraryManager, + @Autowired private val bookManager: BookManager, + @Autowired private val entityManager: EntityManager, + @Autowired private val mockMvc: MockMvc + +) { + + @AfterEach + fun `clear repositories`() { + entityManager.clear() + } + + @Test + @WithMockUser + fun `given books with unordered index when requesting via api then books are ordered`() { + val serie = makeSerie( + name = "serie", + books = listOf(makeBook("1"), makeBook("3")) + ) + serieRepository.save(serie) + + serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) } + serieRepository.save(serie) + + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${serie.id}/books?readyonly=false")) + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[0].name", equalTo("1"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[1].name", equalTo("2"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[2].name", equalTo("3"))) + } + + @Test + @WithMockUser + fun `given many books with unordered index when requesting via api then books are ordered and paged`() { + val serie = makeSerie( + name = "serie", + books = (1..100 step 2).map { makeBook("$it") } + ) + serieRepository.save(serie) + + serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) } + serieRepository.save(serie) + + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${serie.id}/books?readyonly=false")) + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[0].name", equalTo("1"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[1].name", equalTo("2"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[2].name", equalTo("3"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[3].name", equalTo("5"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.size", equalTo(20))) + .andExpect(MockMvcResultMatchers.jsonPath("$.first", equalTo(true))) + .andExpect(MockMvcResultMatchers.jsonPath("$.number", equalTo(0))) + } + + @Test + @WithMockUser + fun `given many books in ready state with unordered index when requesting via api then books are ordered and paged`() { + val serie = makeSerie( + name = "serie", + books = (1..100 step 2).map { makeBook("$it") } + ) + serieRepository.save(serie) + + serie.books = serie.books.toMutableList().also { it.add(makeBook("2")) } + serie.books.forEach { it.metadata = BookMetadata(Status.READY) } + serieRepository.save(serie) + + mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/series/${serie.id}/books?readyonly=true")) + .andExpect(MockMvcResultMatchers.status().isOk) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[0].name", equalTo("1"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[1].name", equalTo("2"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[2].name", equalTo("3"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.content[3].name", equalTo("5"))) + .andExpect(MockMvcResultMatchers.jsonPath("$.size", equalTo(20))) + .andExpect(MockMvcResultMatchers.jsonPath("$.first", equalTo(true))) + .andExpect(MockMvcResultMatchers.jsonPath("$.number", equalTo(0))) + } +} \ No newline at end of file