diff --git a/src/jelu-ui/src/components/BookDetail.vue b/src/jelu-ui/src/components/BookDetail.vue index f944e96d..fac76c6b 100644 --- a/src/jelu-ui/src/components/BookDetail.vue +++ b/src/jelu-ui/src/components/BookDetail.vue @@ -25,6 +25,7 @@ import ReviewModalVue from './ReviewModal.vue' import BookQuoteModalVue from './BookQuoteModal.vue' import { BookQuote } from "../model/BookQuote" import BookQuoteCard from "./BookQuoteCard.vue" +import { Series } from '../model/Series' const { t, d } = useI18n({ inheritLocale: true, @@ -68,12 +69,19 @@ const getBook = async () => { useTitle('Jelu | ' + book.value.book.title) getUserReviewsForBook() getBookQuotesForBook() + getAllSeriesInfo() } catch (error) { console.log("failed get book : " + error); getBookIsLoading.value = false } }; +const getAllSeriesInfo = async () => { + book.value?.book.series?.forEach(s => { + fetchSeries(s.seriesId as string) + }) +} + const getUserReviewsForBook = async() => { await until(user.value).not.toBeNull() dataService.findReviews(user.value.id, book.value?.book.id, null, null, null, 0, 20) @@ -456,6 +464,54 @@ const deleteBookQuote = async (bookQuoteId: string) => { }) } +const seriesmap: Map = new Map() + +const fetchSeries = async (seriesId: string) => { + dataService.getSeriesById(seriesId) + .then(data => { + seriesmap.set(seriesId, data) + }) + .catch(e => { + console.log("fetching series error") + }) +} + +const getSeriesInfo = async (seriesId: string) => { + if (seriesmap.get(seriesId) != null) { + const s = seriesmap.get(seriesId) + return await formatSeries(s as Series) + } + dataService.getSeriesById(seriesId) + .then(data => { + seriesmap.set(seriesId, data) + return formatSeries(data) + }) + .catch(e => { + return "error" + }) +} + +const formatSeries = async (series: Series) => { + let txt = "" + if (series.description != null && series.description.length > 0) { + txt += series.description.substring(0, 40) + txt += " | " + } + if (series.avgRating != null) { + txt += "avg : " + txt += series.avgRating + txt += " " + } + if (series.userRating != null) { + txt += "me : " + txt += series.userRating + } + if (txt.trim().length < 1) { + return 'no data' + } + return txt +} + getBook() @@ -710,6 +766,9 @@ getBook()
  • { console.log("visibilty " + newVal + " " + oldVal) }) -// https://stackoverflow.com/questions/39924644/es6-generate-an-array-of-numbers -const range = (start: number, end: number, step: number) => { - return Array.from(Array.from(Array(Math.ceil((end - start) / step)).keys()), x => start + x * step); -} - const classFor = (n: number) => { if (n === 0) { return "rating-hidden" @@ -158,7 +154,7 @@ const editReview = () => {
    - {{ series.name }} : + {{ series.name }} : +
    +
    +
    + +
    +
    + {{ t('labels.avg_rating', {rating : series.avgRating}) }} / {{ t('labels.user_rating', {rating : series.userRating}) }} +
    +
    +import { Ref, ref, watch } from "vue"; +import { useI18n } from 'vue-i18n'; +import { Series } from "../model/Series"; +import dataService from "../services/DataService"; +import { ObjectUtils } from "../utils/ObjectUtils"; + +const { t } = useI18n({ + inheritLocale: true, + useScope: 'global' +}) + +const props = defineProps<{ + series: Series, + edit: boolean, +}>() + +const emit = defineEmits<{ + (e: 'close'): void +}>() + +const progress: Ref = ref(false) + +const seriesName = ref(props.edit != null && props.edit === true && props.series.name != null ? props.series.name : "") +const description = ref(props.edit != null && props.edit === true && props.series.description != null ? props.series.description : "") +const rating = ref(props.edit != null && props.edit === true && props.series.userRating != null ? props.series.userRating : undefined) + +watch(rating, (newval, oldval) => { + console.log("rating changed " + newval) + if (newval != null && newval > 10) { + rating.value = 10 + } + if (newval != null && newval < 0) { + rating.value = 0 + } +}) + +const editSeries = () => { + console.log("submit ") + if (props.series.id != null) { + progress.value = true + dataService.updateSeries(props.series.id, { + description: description.value, + name: seriesName.value, + rating: rating.value + }) + .then(res => { + progress.value = false + emit('close') + }) + .catch(err => { + progress.value = false + console.log(err) + }) + } +} + +const classFor = (n: number, rating: number | undefined) => { + const color = rating == undefined ? 'bg-danger' : 'bg-accent' + if (n === 0) { + return "rating-hidden" + } else if (Number.isInteger(n)) { + return `${color} mask mask-star-2 mask-half-2` + } else { + return `${color} mask mask-star-2 mask-half-1` + } +} + +const submit = () => { + console.log("not implemented yet") +} + + + + + + diff --git a/src/jelu-ui/src/locales/en.json b/src/jelu-ui/src/locales/en.json index 0f1a20f0..b6616edf 100644 --- a/src/jelu-ui/src/locales/en.json +++ b/src/jelu-ui/src/locales/en.json @@ -110,7 +110,9 @@ "set_progress" : "set your current progress", "user_avg_rating" : "my average rating : {rating}", "avg_rating" : "average rating : {rating}", - "add_quote" : "add book quote" + "add_quote" : "add book quote", + "description" : "description", + "user_rating" : "my rating : {rating}" }, "settings" : { "pick_language" : "Pick your language", @@ -344,5 +346,10 @@ "quote" : "quote | quotes", "position" : "position", "delete_quote" : "delete this quote ?" + }, + "series" : { + "edit_series" : "edit series", + "create_series" : "create series", + "name" : "name" } } diff --git a/src/jelu-ui/src/model/Series.ts b/src/jelu-ui/src/model/Series.ts index a835e47b..48e9fadb 100644 --- a/src/jelu-ui/src/model/Series.ts +++ b/src/jelu-ui/src/model/Series.ts @@ -8,4 +8,12 @@ export interface Series { creationDate?: string, name: string, modificationDate?: string, + avgRating?: number, + userRating?: number, + description?: string, +} +export interface SeriesUpdate { + name?: string, + description?: string, + rating?: number, } diff --git a/src/jelu-ui/src/services/DataService.ts b/src/jelu-ui/src/services/DataService.ts index 9926fbb3..5dcbc13a 100644 --- a/src/jelu-ui/src/services/DataService.ts +++ b/src/jelu-ui/src/services/DataService.ts @@ -22,7 +22,7 @@ import { CreateReviewDto, Review, UpdateReviewDto, Visibility } from "../model/R import { Role } from "../model/Role"; import { StringUtils } from "../utils/StringUtils"; import { MetadataRequest } from "../model/MetadataRequest"; -import { Series } from "../model/Series"; +import { Series, SeriesUpdate } from "../model/Series"; import { DirectoryListing } from "../model/DirectoryListing"; import { BookQuote, CreateBookQuoteDto, UpdateBookQuoteDto } from "../model/BookQuote"; @@ -1546,6 +1546,22 @@ class DataService { throw new Error("error update review " + error) } } + + updateSeries = async (seriesId: string, updateDto: SeriesUpdate) => { + try { + const response = await this.apiClient.put(`${this.API_SERIES}/${seriesId}`, updateDto); + console.log("called update series") + console.log(response) + return response.data; + } + catch (error) { + if (axios.isAxiosError(error) && error.response) { + console.log("error axios " + error.response.status + " " + error.response.data.error) + } + console.log("error update series " + (error as AxiosError).code) + throw new Error("error update series " + error) + } + } updateBook = async (bookId: string, bookUpdateDto: Book) => { try { diff --git a/src/jelu-ui/src/utils/ObjectUtils.ts b/src/jelu-ui/src/utils/ObjectUtils.ts index 6ea66bdc..15036a82 100644 --- a/src/jelu-ui/src/utils/ObjectUtils.ts +++ b/src/jelu-ui/src/utils/ObjectUtils.ts @@ -102,5 +102,9 @@ export class ObjectUtils { } } } - + +// https://stackoverflow.com/questions/39924644/es6-generate-an-array-of-numbers +public static range = (start: number, end: number, step: number) => { + return Array.from(Array.from(Array(Math.ceil((end - start) / step)).keys()), x => start + x * step); +} } diff --git a/src/main/kotlin/io/github/bayang/jelu/controllers/BooksController.kt b/src/main/kotlin/io/github/bayang/jelu/controllers/BooksController.kt index b82fc1da..d755429e 100644 --- a/src/main/kotlin/io/github/bayang/jelu/controllers/BooksController.kt +++ b/src/main/kotlin/io/github/bayang/jelu/controllers/BooksController.kt @@ -12,6 +12,7 @@ import io.github.bayang.jelu.dto.JeluUser import io.github.bayang.jelu.dto.LibraryFilter import io.github.bayang.jelu.dto.Role import io.github.bayang.jelu.dto.SeriesDto +import io.github.bayang.jelu.dto.SeriesUpdateDto import io.github.bayang.jelu.dto.TagDto import io.github.bayang.jelu.dto.UserBookBulkUpdateDto import io.github.bayang.jelu.dto.UserBookLightDto @@ -201,7 +202,11 @@ class BooksController( fun series( @RequestParam(name = "name", required = false) name: String?, @PageableDefault(page = 0, size = 20, direction = Sort.Direction.ASC, sort = ["name"]) @ParameterObject pageable: Pageable, - ): Page = repository.findAllSeries(name, pageable) + principal: Authentication, + ): Page { + assertIsJeluUser(principal.principal) + return repository.findAllSeries(name, (principal.principal as JeluUser).user.id.value, pageable) + } @GetMapping(path = ["/series/{id}"]) fun seriesById( @@ -209,7 +214,7 @@ class BooksController( principal: Authentication, ): SeriesDto { assertIsJeluUser(principal.principal) - return repository.findSeriesById(seriesId) + return repository.findSeriesById(seriesId, (principal.principal as JeluUser).user.id.value) } @GetMapping(path = ["/series/{id}/books"]) @@ -347,4 +352,17 @@ class BooksController( ): Int { return repository.bulkEditUserbooks(bulkUpdateDto) } + + @PutMapping(path = ["/series/{id}"], consumes = [MediaType.APPLICATION_JSON_VALUE]) + fun updateSeries( + @PathVariable("id") + seriesId: UUID, + @RequestBody + @Valid + seriesUpdate: SeriesUpdateDto, + principal: Authentication, + ): SeriesDto { + assertIsJeluUser(principal.principal) + return repository.updateSeries(seriesId, seriesUpdate, (principal.principal as JeluUser).user) + } } diff --git a/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt b/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt index 8b2e9dd9..61b1803f 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt @@ -5,9 +5,11 @@ import io.github.bayang.jelu.dto.AuthorUpdateDto import io.github.bayang.jelu.dto.BookCreateDto import io.github.bayang.jelu.dto.BookUpdateDto import io.github.bayang.jelu.dto.CreateReadingEventDto +import io.github.bayang.jelu.dto.CreateSeriesRatingDto import io.github.bayang.jelu.dto.CreateUserBookDto import io.github.bayang.jelu.dto.LibraryFilter import io.github.bayang.jelu.dto.Role +import io.github.bayang.jelu.dto.SeriesCreateDto import io.github.bayang.jelu.dto.SeriesOrderDto import io.github.bayang.jelu.dto.SeriesUpdateDto import io.github.bayang.jelu.dto.TagDto @@ -162,7 +164,6 @@ class BookRepository( .join(BookSeries, JoinType.LEFT, onColumn = BookTable.id, otherColumn = BookSeries.book) .join(SeriesTable, JoinType.LEFT, onColumn = SeriesTable.id, otherColumn = BookSeries.series) .select(BookTable.columns) - // .selectAll() .withDistinct() if (!title?.trim().isNullOrBlank()) { @@ -238,7 +239,6 @@ class BookRepository( .join(BookSeries, JoinType.LEFT, onColumn = BookTable.id, otherColumn = BookSeries.book) .join(SeriesTable, JoinType.LEFT, onColumn = SeriesTable.id, otherColumn = BookSeries.series) .select(BookTable.columns) - // .selectAll() .withDistinct() if (!title?.trim().isNullOrBlank()) { @@ -337,8 +337,18 @@ class BookRepository( ) } - fun findAllSeries(name: String?, pageable: Pageable): Page { - val query: Query = SeriesTable.selectAll() + fun findAllSeries(name: String?, userId: UUID?, pageable: Pageable): Page { + val cols = mutableListOf>() + cols.addAll(SeriesTable.columns) + cols.addAll(SeriesRatingTable.columns) + val ratingTableAlias = SeriesRatingTable.alias("srt") + val ratingAlias = ratingTableAlias[SeriesRatingTable.rating].avg().alias("avgRating") + cols.add(ratingAlias) + val query: Query = SeriesTable.join(SeriesRatingTable, JoinType.LEFT, additionalConstraint = { SeriesRatingTable.series eq SeriesTable.id and (SeriesRatingTable.user eq userId) }) + .join(ratingTableAlias, JoinType.LEFT, onColumn = ratingTableAlias[SeriesRatingTable.series], otherColumn = SeriesTable.id) + .select(cols) + .groupBy(SeriesTable.id) + .withDistinct(true) name?.let { query.andWhere { SeriesTable.name like "%$name%" } } @@ -347,12 +357,23 @@ class BookRepository( val orders: Array, SortOrder>> = parseSorts(pageable.sort, Pair(SeriesTable.name, SortOrder.ASC_NULLS_LAST), SeriesTable) query.orderBy(*orders) return PageImpl( - query.map { resultRow -> Series.wrapRow(resultRow) }, + query.map { resultRow -> wrapSeriesRow(resultRow, ratingAlias) }, pageable, total, ) } + private fun wrapSeriesRow( + resultRow: ResultRow, + ratingAlias: ExpressionAlias, + ): Series { + val s = Series.wrapRow(resultRow) + val rating = resultRow[ratingAlias] + s.avgRating = rating?.toDouble() + s.userRating = resultRow[SeriesRatingTable.rating] + return s + } + fun findAuthorsByName(name: String): List { return Author.find { AuthorTable.name like "%$name%" }.toList() } @@ -373,6 +394,23 @@ class BookRepository( fun findSeriesById(seriesId: UUID): Series = Series[seriesId] + fun findSeriesById(seriesId: UUID, userId: UUID): Series { + val cols = mutableListOf>() + cols.addAll(SeriesTable.columns) + cols.addAll(SeriesRatingTable.columns) + val ratingTableAlias = SeriesRatingTable.alias("srt") + val ratingAlias = ratingTableAlias[SeriesRatingTable.rating].avg().alias("avgRating") + cols.add(ratingAlias) + val query: Query = SeriesTable.join(SeriesRatingTable, JoinType.LEFT, additionalConstraint = { SeriesRatingTable.series eq SeriesTable.id and (SeriesRatingTable.user eq userId) }) + .join(ratingTableAlias, JoinType.LEFT, onColumn = ratingTableAlias[SeriesRatingTable.series], otherColumn = SeriesTable.id) + .select(cols) + .andWhere { SeriesTable.id eq seriesId } + .groupBy(SeriesTable.id) + .withDistinct(true) + val res = query.map { resultRow -> wrapSeriesRow(resultRow, ratingAlias) } + return res.first() + } + fun findTagBooksById( tagId: UUID, user: User, @@ -808,15 +846,30 @@ class BookRepository( return found } - fun updateSeries(seriesId: UUID, series: SeriesUpdateDto): Series { + fun updateSeries(seriesId: UUID, series: SeriesUpdateDto, user: User): Series { val found: Series = Series[seriesId] - if (series.name.isNotBlank()) { + if (!series.name.isNullOrBlank()) { found.name = series.name.trim() } + found.description = series.description + if (series.rating != null) { + val findSeriesRating = findSeriesRating(seriesId, user.id.value) + if (findSeriesRating != null) { + findSeriesRating.rating = series.rating + } else { + save(CreateSeriesRatingDto(seriesId, series.rating), user) + } + } else { + SeriesRatingTable.deleteWhere { SeriesRatingTable.series eq seriesId and(SeriesRatingTable.user eq user.id) } + } found.modificationDate = nowInstant() return found } + fun findSeriesRating(seriesId: UUID, userId: UUID): SeriesRating? { + return SeriesRating.find { SeriesRatingTable.series eq seriesId and(SeriesRatingTable.user eq userId) }.firstOrNull() + } + fun save(book: BookCreateDto): Book { val authorsList = mutableListOf() book.authors?.forEach { authorDto -> @@ -934,13 +987,17 @@ class BookRepository( return created } - fun saveSeries(series: SeriesUpdateDto): Series { + fun saveSeries(series: SeriesCreateDto, user: User): Series { val created = Series.new(UUID.randomUUID()) { this.name = series.name.trim() + this.description = series.description val instant: Instant = nowInstant() creationDate = instant modificationDate = instant } + if (series.rating != null) { + save(CreateSeriesRatingDto(created.id.value, series.rating), user) + } return created } @@ -973,6 +1030,18 @@ class BookRepository( return null } + fun save(seriesRatingDto: CreateSeriesRatingDto, user: User): SeriesRating { + val created = SeriesRating.new(UUID.randomUUID()) { + this.rating = seriesRatingDto.rating + val instant: Instant = nowInstant() + this.creationDate = instant + this.modificationDate = instant + this.series = Series[seriesRatingDto.seriesId] + this.user = user + } + return created + } + fun update(series: SeriesOrderDto, seriesEntity: Series, book: Book): BookSeriesItem? { val existing = BookSeriesItem.find { BookSeries.book eq book.id and (BookSeries.series eq seriesEntity.id) } diff --git a/src/main/kotlin/io/github/bayang/jelu/dao/SeriesRatingTable.kt b/src/main/kotlin/io/github/bayang/jelu/dao/SeriesRatingTable.kt new file mode 100644 index 00000000..e4915c5c --- /dev/null +++ b/src/main/kotlin/io/github/bayang/jelu/dao/SeriesRatingTable.kt @@ -0,0 +1,35 @@ +package io.github.bayang.jelu.dao + +import io.github.bayang.jelu.dto.SeriesRatingDto +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.UUIDEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.ReferenceOption +import org.jetbrains.exposed.sql.javatime.timestamp +import java.util.UUID + +object SeriesRatingTable : UUIDTable("series_rating") { + val creationDate = timestamp("creation_date") + val modificationDate = timestamp("modification_date") + val user = reference("user", UserTable, onDelete = ReferenceOption.CASCADE) + val series = reference("series", SeriesTable, onDelete = ReferenceOption.CASCADE) + val rating: Column = double(name = "rating") +} +class SeriesRating(id: EntityID) : UUIDEntity(id) { + companion object : UUIDEntityClass(SeriesRatingTable) + var creationDate by SeriesRatingTable.creationDate + var modificationDate by SeriesRatingTable.modificationDate + var user by User referencedOn SeriesRatingTable.user + var series by Series referencedOn SeriesRatingTable.series + var rating by SeriesRatingTable.rating + + fun toSeriesRatingDto(): SeriesRatingDto = SeriesRatingDto( + creationDate = this.creationDate, + modificationDate = this.modificationDate, + rating = this.rating, + userId = this.user.id.value, + seriesId = this.series.id.value, + ) +} diff --git a/src/main/kotlin/io/github/bayang/jelu/dao/SeriesTable.kt b/src/main/kotlin/io/github/bayang/jelu/dao/SeriesTable.kt index d586446d..f62f1750 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dao/SeriesTable.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dao/SeriesTable.kt @@ -17,18 +17,25 @@ object SeriesTable : UUIDTable("series") { val name: Column = varchar("name", 1000) val creationDate = timestamp("creation_date") val modificationDate = timestamp("modification_date") + val description: Column = varchar("description", 500000).nullable() } class Series(id: EntityID) : UUIDEntity(id) { companion object : UUIDEntityClass(SeriesTable) var name by SeriesTable.name var creationDate by SeriesTable.creationDate var modificationDate by SeriesTable.modificationDate + var avgRating: Double? = null + var userRating: Double? = null + var description by SeriesTable.description fun toSeriesDto(): SeriesDto = SeriesDto( id = this.id.value, creationDate = this.creationDate, modificationDate = this.modificationDate, name = this.name, + userRating = this.userRating, + avgRating = this.avgRating, + description = this.description, ) } object BookSeries : UUIDTable(name = "book_series") { diff --git a/src/main/kotlin/io/github/bayang/jelu/dto/BookDto.kt b/src/main/kotlin/io/github/bayang/jelu/dto/BookDto.kt index fe3b4ab2..cbe4a94f 100644 --- a/src/main/kotlin/io/github/bayang/jelu/dto/BookDto.kt +++ b/src/main/kotlin/io/github/bayang/jelu/dto/BookDto.kt @@ -139,12 +139,36 @@ data class SeriesDto( val creationDate: Instant?, val modificationDate: Instant?, val name: String, + var avgRating: Double? = null, + var userRating: Double? = null, + var description: String?, ) data class SeriesOrderDto( val seriesId: UUID? = null, var name: String, var numberInSeries: Double?, ) -data class SeriesUpdateDto( + +data class SeriesCreateDto( val name: String, + val rating: Double?, + val description: String?, +) + +data class SeriesUpdateDto( + val name: String?, + val rating: Double?, + val description: String?, +) + +data class CreateSeriesRatingDto( + val seriesId: UUID, + var rating: Double, +) +data class SeriesRatingDto( + val seriesId: UUID, + val userId: UUID, + var rating: Double, + val creationDate: Instant?, + val modificationDate: Instant?, ) diff --git a/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt b/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt index 592ff978..99fed102 100644 --- a/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt +++ b/src/main/kotlin/io/github/bayang/jelu/service/BookService.kt @@ -14,10 +14,13 @@ import io.github.bayang.jelu.dto.BookCreateDto import io.github.bayang.jelu.dto.BookDto import io.github.bayang.jelu.dto.BookUpdateDto import io.github.bayang.jelu.dto.CreateReadingEventDto +import io.github.bayang.jelu.dto.CreateSeriesRatingDto import io.github.bayang.jelu.dto.CreateUserBookDto import io.github.bayang.jelu.dto.LibraryFilter import io.github.bayang.jelu.dto.Role +import io.github.bayang.jelu.dto.SeriesCreateDto import io.github.bayang.jelu.dto.SeriesDto +import io.github.bayang.jelu.dto.SeriesRatingDto import io.github.bayang.jelu.dto.SeriesUpdateDto import io.github.bayang.jelu.dto.TagDto import io.github.bayang.jelu.dto.UserBookBulkUpdateDto @@ -103,7 +106,7 @@ class BookService( fun findAllTags(name: String?, pageable: Pageable): Page = bookRepository.findAllTags(name, pageable).map { it.toTagDto() } @Transactional - fun findAllSeries(name: String?, pageable: Pageable): Page = bookRepository.findAllSeries(name, pageable).map { it.toSeriesDto() } + fun findAllSeries(name: String?, userId: UUID?, pageable: Pageable): Page = bookRepository.findAllSeries(name, userId, pageable).map { it.toSeriesDto() } @Transactional fun findBookById(bookId: UUID): BookDto = bookRepository.findBookById(bookId).toBookDto() @@ -348,8 +351,8 @@ class BookService( } @Transactional - fun updateSeries(seriesId: UUID, series: SeriesUpdateDto): SeriesDto { - val res = bookRepository.updateSeries(seriesId, series) + fun updateSeries(seriesId: UUID, series: SeriesUpdateDto, user: User): SeriesDto { + val res = bookRepository.updateSeries(seriesId, series, user) searchIndexService.seriesUpdated(seriesId) return res.toSeriesDto() } @@ -395,14 +398,24 @@ class BookService( return bookRepository.findSeriesById(seriesId).toSeriesDto() } + @Transactional + fun findSeriesById(seriesId: UUID, userId: UUID): SeriesDto { + return bookRepository.findSeriesById(seriesId, userId).toSeriesDto() + } + + @Transactional + fun findSeriesRating(seriesId: UUID, userId: UUID): SeriesRatingDto? { + return bookRepository.findSeriesRating(seriesId, userId)?.toSeriesRatingDto() + } + @Transactional fun save(tag: TagDto): TagDto { return bookRepository.save(tag).toTagDto() } @Transactional - fun saveSeries(series: SeriesUpdateDto): SeriesDto { - return bookRepository.saveSeries(series).toSeriesDto() + fun saveSeries(series: SeriesCreateDto, user: User): SeriesDto { + return bookRepository.saveSeries(series, user).toSeriesDto() } @Transactional @@ -533,6 +546,11 @@ class BookService( return res } + @Transactional + fun save(seriesRatingDto: CreateSeriesRatingDto, user: User): SeriesRatingDto { + return bookRepository.save(seriesRatingDto, user).toSeriesRatingDto() + } + @Transactional fun mergeAuthors(authorId: UUID, otherId: UUID, authorUpdateDto: AuthorUpdateDto, user: User): AuthorDto { val pageNum = 0 diff --git a/src/main/resources/liquibase.xml b/src/main/resources/liquibase.xml index 882a3a2f..95b1cb0d 100644 --- a/src/main/resources/liquibase.xml +++ b/src/main/resources/liquibase.xml @@ -550,4 +550,34 @@ ALTER TABLE import_entity ADD review varchar(500000); + + + + + + + + + + + + + + + + + + + + + + + + + ALTER TABLE "series" ADD description varchar(500000); + + diff --git a/src/test/kotlin/io/github/bayang/jelu/service/BookServiceTest.kt b/src/test/kotlin/io/github/bayang/jelu/service/BookServiceTest.kt index 18998246..d751d052 100644 --- a/src/test/kotlin/io/github/bayang/jelu/service/BookServiceTest.kt +++ b/src/test/kotlin/io/github/bayang/jelu/service/BookServiceTest.kt @@ -10,10 +10,14 @@ import io.github.bayang.jelu.dto.AuthorDto import io.github.bayang.jelu.dto.AuthorUpdateDto import io.github.bayang.jelu.dto.BookCreateDto import io.github.bayang.jelu.dto.BookDto +import io.github.bayang.jelu.dto.CreateSeriesRatingDto import io.github.bayang.jelu.dto.CreateUserDto import io.github.bayang.jelu.dto.JeluUser import io.github.bayang.jelu.dto.LibraryFilter +import io.github.bayang.jelu.dto.SeriesCreateDto +import io.github.bayang.jelu.dto.SeriesDto import io.github.bayang.jelu.dto.SeriesOrderDto +import io.github.bayang.jelu.dto.SeriesRatingDto import io.github.bayang.jelu.dto.SeriesUpdateDto import io.github.bayang.jelu.dto.UserBookLightDto import io.github.bayang.jelu.dto.UserBookUpdateDto @@ -60,6 +64,7 @@ class BookServiceTest( @BeforeAll fun setupUser() { userService.save(CreateUserDto(login = "testuser", password = "1234", isAdmin = true)) + userService.save(CreateUserDto(login = "testuser2", password = "1234", isAdmin = false)) jeluProperties.files.images = tempDir.absolutePath luceneHelper.getIndexWriter().use { indexWriter -> indexWriter.deleteDocuments(Term(LuceneEntity.TYPE, LuceneEntity.Book.type)) @@ -88,7 +93,7 @@ class BookServiceTest( bookService.findAllTags(null, Pageable.ofSize(20)).content.forEach { bookService.deleteTagById(it.id!!) } - bookService.findAllSeries(null, Pageable.ofSize(20)).content.forEach { + bookService.findAllSeries(null, null, Pageable.ofSize(20)).content.forEach { bookService.deleteSeriesById(it.id!!) } luceneHelper.getIndexWriter().use { indexWriter -> @@ -97,6 +102,69 @@ class BookServiceTest( } } + @Test + fun testSeriesRatingManualRating() { + val s1: SeriesDto = bookService.saveSeries(SeriesCreateDto("series", null, null), user()) + val r1: SeriesRatingDto = bookService.save(CreateSeriesRatingDto(seriesId = s1.id!!, rating = 4.3), user()) + var findSeriesById = bookService.findSeriesById(s1.id!!, user().id.value) + Assertions.assertEquals(4.3, findSeriesById.userRating) + Assertions.assertEquals(4.3, findSeriesById.avgRating) + + val r2: SeriesRatingDto = bookService.save(CreateSeriesRatingDto(seriesId = s1.id!!, rating = 7.2), user2()) + findSeriesById = bookService.findSeriesById(s1.id!!, user().id.value) + Assertions.assertEquals(4.3, findSeriesById.userRating) + Assertions.assertEquals(5.75, findSeriesById.avgRating) + + val s2: SeriesDto = bookService.saveSeries(SeriesCreateDto("my other series", null, null), user()) + val r3: SeriesRatingDto = bookService.save(CreateSeriesRatingDto(seriesId = s2.id!!, rating = 3.3), user()) + + var findAllSeries = bookService.findAllSeries("seri", null, Pageable.ofSize(20)) + Assertions.assertEquals(2, findAllSeries.totalElements) + Assertions.assertNull(findAllSeries.content[0].userRating) + Assertions.assertNull(findAllSeries.content[1].userRating) + + findAllSeries = bookService.findAllSeries("seri", user().id.value, Pageable.ofSize(20)) + Assertions.assertEquals(2, findAllSeries.totalElements) + Assertions.assertNotNull(findAllSeries.content[0].userRating) + Assertions.assertNotNull(findAllSeries.content[1].userRating) + } + + @Test + fun testSeriesRatingImplicitRating() { + val s1: SeriesDto = bookService.saveSeries(SeriesCreateDto("series", 4.3, null), user()) + var findSeriesById = bookService.findSeriesById(s1.id!!, user().id.value) + Assertions.assertEquals(4.3, findSeriesById.userRating) + Assertions.assertEquals(4.3, findSeriesById.avgRating) + + bookService.updateSeries(s1.id!!, SeriesUpdateDto(name = null, rating = 7.2, null), user2()) + findSeriesById = bookService.findSeriesById(s1.id!!, user().id.value) + Assertions.assertEquals(4.3, findSeriesById.userRating) + Assertions.assertEquals(5.75, findSeriesById.avgRating) + + val s2: SeriesDto = bookService.saveSeries(SeriesCreateDto("my other series", 3.3, null), user()) + + var findAllSeries = bookService.findAllSeries("seri", null, Pageable.ofSize(20)) + Assertions.assertEquals(2, findAllSeries.totalElements) + Assertions.assertNull(findAllSeries.content[0].userRating) + Assertions.assertNull(findAllSeries.content[1].userRating) + + findAllSeries = bookService.findAllSeries("seri", user().id.value, Pageable.ofSize(20)) + Assertions.assertEquals(2, findAllSeries.totalElements) + Assertions.assertNotNull(findAllSeries.content[0].userRating) + Assertions.assertNotNull(findAllSeries.content[1].userRating) + } + + @Test + fun testSeriesDeleteCascadesRating() { + val s1: SeriesDto = bookService.saveSeries(SeriesCreateDto("series", 4.3, null), user()) + val findSeriesById = bookService.findSeriesById(s1.id!!, user().id.value) + Assertions.assertEquals(4.3, findSeriesById.userRating) + Assertions.assertEquals(4.3, findSeriesById.avgRating) + bookService.deleteSeriesById(s1.id!!) + val seriesRating = bookService.findSeriesRating(s1.id!!, user().id.value) + Assertions.assertNull(seriesRating) + } + @Test fun testInsertAuthor() { val author = authorDto() @@ -382,7 +450,7 @@ class BookServiceTest( @Test fun testInsertBooksSeriesBothNoPositionDuplicateExistingSeries() { - val saved = bookService.saveSeries(SeriesUpdateDto("series 1")) + val saved = bookService.saveSeries(SeriesCreateDto("series 1", null, null), user()) Assertions.assertEquals("series 1", saved.name) Assertions.assertNotNull(saved.id) val s1 = SeriesOrderDto(name = "series 1", numberInSeries = null) @@ -438,7 +506,7 @@ class BookServiceTest( @Test fun testInsertBooksSeriesBothNoPositionExistingSeries() { - val saved = bookService.saveSeries(SeriesUpdateDto("series 1")) + val saved = bookService.saveSeries(SeriesCreateDto("series 1", null, null), user()) Assertions.assertEquals("series 1", saved.name) Assertions.assertNotNull(saved.id) val s1 = SeriesOrderDto(name = "series 1", numberInSeries = null) @@ -497,7 +565,7 @@ class BookServiceTest( @Test fun testInsertBooksExistingSeries() { - val saved = bookService.saveSeries(SeriesUpdateDto("series 1")) + val saved = bookService.saveSeries(SeriesCreateDto("series 1", null, null), user()) Assertions.assertEquals("series 1", saved.name) Assertions.assertNotNull(saved.id) val s1 = SeriesOrderDto(name = "series 1", numberInSeries = 1.0) @@ -550,7 +618,7 @@ class BookServiceTest( @Test fun testUpdateBooksSeries() { - val saved = bookService.saveSeries(SeriesUpdateDto("series 1")) + val saved = bookService.saveSeries(SeriesCreateDto("series 1", null, null), user()) Assertions.assertEquals("series 1", saved.name) Assertions.assertNotNull(saved.id) val s1 = SeriesOrderDto(name = "series 1", numberInSeries = 1.0) @@ -615,7 +683,7 @@ class BookServiceTest( Assertions.assertEquals(1.0, res.series?.get(1)?.numberInSeries) // series still exists, without books - val series = bookService.findAllSeries(name = "series2", Pageable.ofSize(5)) + val series = bookService.findAllSeries(name = "series2", null, Pageable.ofSize(5)) Assertions.assertEquals("series2", series.content[0].name) val booksById1 = @@ -629,7 +697,7 @@ class BookServiceTest( @Test fun testInsertBooksExistingSeriesDifferentPosition() { - val saved = bookService.saveSeries(SeriesUpdateDto("series 1")) + val saved = bookService.saveSeries(SeriesCreateDto("series 1", null, null), user()) Assertions.assertEquals("series 1", saved.name) Assertions.assertNotNull(saved.id) val s1 = SeriesOrderDto(name = "series 1", numberInSeries = 1.0) @@ -682,7 +750,7 @@ class BookServiceTest( @Test fun testInsertBooksExistingDuplicateSeries() { - val saved = bookService.saveSeries(SeriesUpdateDto("series 1")) + val saved = bookService.saveSeries(SeriesCreateDto("series 1", null, null), user()) Assertions.assertEquals("series 1", saved.name) Assertions.assertNotNull(saved.id) val s1 = SeriesOrderDto(name = "series 1", numberInSeries = 1.0) @@ -869,7 +937,7 @@ class BookServiceTest( @Test fun testUpdateSeriesRebuildsIndex() { - val saved = bookService.saveSeries(SeriesUpdateDto("series1")) + val saved = bookService.saveSeries(SeriesCreateDto("series1", null, null), user()) Assertions.assertEquals("series1", saved.name) Assertions.assertNotNull(saved.id) val s1 = SeriesOrderDto(name = "series1", numberInSeries = 1.0) @@ -923,11 +991,25 @@ class BookServiceTest( Assertions.assertEquals(1, entitiesIds?.size) Assertions.assertEquals(res.id, UUID.fromString(entitiesIds?.get(0))) - val updateSeries = bookService.updateSeries(saved.id!!, SeriesUpdateDto(name = "series3")) + var updateSeries = bookService.updateSeries( + saved.id!!, + SeriesUpdateDto(name = "series3", rating = null, null), + user(), + ) + Assertions.assertEquals("series3", updateSeries.name) + Assertions.assertNull(updateSeries.description) + + updateSeries = bookService.updateSeries( + saved.id!!, + SeriesUpdateDto(name = null, rating = null, "this is a description of my series.
    A long time ago bla bla bla"), + user(), + ) Assertions.assertEquals("series3", updateSeries.name) + Assertions.assertEquals("this is a description of my series.
    A long time ago bla bla bla", updateSeries.description) val search = bookService.findSeriesById(saved.id!!) Assertions.assertEquals("series3", search.name) + Assertions.assertEquals("this is a description of my series.
    A long time ago bla bla bla", search.description) entitiesIds = luceneHelper.searchEntitiesIds("series:series1", LuceneEntity.Book) Assertions.assertEquals(0, entitiesIds?.size) @@ -2270,4 +2352,9 @@ class BookServiceTest( val userDetail = userService.loadUserByUsername("testuser") return (userDetail as JeluUser).user } + + fun user2(): User { + val userDetail = userService.loadUserByUsername("testuser2") + return (userDetail as JeluUser).user + } }