Skip to content

Commit

Permalink
feat: display and sort by ratings bayang#94
Browse files Browse the repository at this point in the history
  • Loading branch information
bayang committed Jan 14, 2024
1 parent d19570b commit 8e591d6
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 16 deletions.
16 changes: 16 additions & 0 deletions src/jelu-ui/src/components/BookCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,22 @@ watch(checked, (newVal, oldVal) => {
>
#{{ book.book.series[0].numberInSeries }}
</span>
<span
v-if="book.userAvgRating"
v-tooltip="t('labels.user_avg_rating', {rating : book.userAvgRating})"
class="icon text-info"
>
<i class="mdi mdi-star mdi-18px" />
{{ book.userAvgRating }}
</span>
<span
v-if="book.avgRating"
v-tooltip="t('labels.avg_rating', {rating : book.avgRating})"
class="icon text-info"
>
<i class="mdi mdi-star-outline mdi-18px" />
{{ book.avgRating }}
</span>
<span
v-if="book.owned"
v-tooltip="t('book.owned')"
Expand Down
16 changes: 16 additions & 0 deletions src/jelu-ui/src/components/BookList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,22 @@ try {
{{ t('sorting.page_count') }}
</o-radio>
</div>
<div class="field">
<o-radio
v-model="sortBy"
native-value="usrAvgRating"
>
{{ t('sorting.user_avg_rating') }}
</o-radio>
</div>
<div class="field">
<o-radio
v-model="sortBy"
native-value="avgRating"
>
{{ t('sorting.avg_rating') }}
</o-radio>
</div>
</template>
<template #filters>
<div class="field capitalize flex flex-col gap-1">
Expand Down
16 changes: 16 additions & 0 deletions src/jelu-ui/src/components/ToReadList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ getToRead()
{{ t('sorting.page_count') }}
</o-radio>
</div>
<div class="field">
<o-radio
v-model="sortBy"
native-value="usrAvgRating"
>
{{ t('sorting.user_avg_rating') }}
</o-radio>
</div>
<div class="field">
<o-radio
v-model="sortBy"
native-value="avgRating"
>
{{ t('sorting.avg_rating') }}
</o-radio>
</div>
</template>
<template #filters>
<div class="field flex flex-col capitalize gap-1">
Expand Down
8 changes: 6 additions & 2 deletions src/jelu-ui/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@
"operation-success" : "operation successful",
"delete_this_tag" : "Delete this tag from {nb} book(s) and from database ?",
"current" : "current",
"set_progress" : "set your current progress"
"set_progress" : "set your current progress",
"user_avg_rating" : "my average rating : {rating}",
"avg_rating" : "average rating : {rating}"
},
"settings" : {
"pick_language" : "Pick your language",
Expand Down Expand Up @@ -149,7 +151,9 @@
"ascending" : "Ascending",
"publication_date" : "Publication date",
"modification_date" : "Modification date",
"page_count" : "Page count"
"page_count" : "Page count",
"user_avg_rating" : "my average rating",
"avg_rating" : "average rating"
},
"reading_events" : {
"finished" : "finished",
Expand Down
2 changes: 2 additions & 0 deletions src/jelu-ui/src/model/Book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export interface UserBook {
readingEvents?: Array<ReadingEvent>|null,
percentRead? : number|null,
currentPageNumber?: number|null,
avgRating?: number|null,
userAvgRating?: number|null,
}
export interface UserBookBulkUpdate {
ids: Array<string>,
Expand Down
52 changes: 44 additions & 8 deletions src/main/kotlin/io/github/bayang/jelu/dao/BookRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import mu.KotlinLogging
import org.jetbrains.exposed.dao.load
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Expression
import org.jetbrains.exposed.sql.ExpressionAlias
import org.jetbrains.exposed.sql.JoinType
import org.jetbrains.exposed.sql.Query
import org.jetbrains.exposed.sql.ResultRow
Expand All @@ -32,6 +33,7 @@ import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.alias
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.andWhere
import org.jetbrains.exposed.sql.avg
import org.jetbrains.exposed.sql.batchInsert
import org.jetbrains.exposed.sql.count
import org.jetbrains.exposed.sql.deleteWhere
Expand All @@ -46,22 +48,26 @@ import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Repository
import java.math.BigDecimal
import java.time.Instant
import java.util.UUID

private val logger = KotlinLogging.logger {}

fun parseSorts(sort: Sort, defaultSort: Pair<Expression<*>, SortOrder>, vararg tables: Table): Array<Pair<Expression<*>, SortOrder>> {
fun parseSorts(sort: Sort, defaultSort: Pair<Expression<*>, SortOrder>, columns: List<Expression<*>>): Array<Pair<Expression<*>, SortOrder>> {
val orders = mutableListOf<Pair<Expression<*>, SortOrder>>()
val columns = mutableListOf<Column<*>>()
for (table in tables) {
columns.addAll(table.columns)
}
// will fail for common names in multiple tables like creationDate, so
// put most important table first
// eg if sort by creationDate should be applied on BookTable rather than UserBookTable put it first
for (o in sort) {
val found = columns.find { column -> column.name.replace("_", "", true).equals(o.property, true) }
// val found = columns.find { column -> column.name.replace("_", "", true).equals(o.property, true) }
val found = columns.find { column ->
when (column) {
is Column -> column.name.replace("_", "", true).equals(o.property, true)
is ExpressionAlias -> column.alias.replace("_", "", true).equals(o.property, true)
else -> false
}
}
if (found != null) {
orders.add(Pair(found, if (o.isAscending) SortOrder.ASC_NULLS_LAST else SortOrder.DESC_NULLS_LAST))
}
Expand All @@ -72,6 +78,14 @@ fun parseSorts(sort: Sort, defaultSort: Pair<Expression<*>, SortOrder>, vararg t
return orders.toTypedArray()
}

fun parseSorts(sort: Sort, defaultSort: Pair<Expression<*>, SortOrder>, vararg tables: Table): Array<Pair<Expression<*>, SortOrder>> {
val columns = mutableListOf<Column<*>>()
for (table in tables) {
columns.addAll(table.columns)
}
return parseSorts(sort, defaultSort, columns)
}

fun formatLike(input: String): String {
return "%$input%"
}
Expand Down Expand Up @@ -975,9 +989,19 @@ class BookRepository(
val cols = mutableListOf<Expression<*>>()
cols.addAll(UserBookTable.columns)
cols.addAll(BookTable.columns)
// avg for all reviews
val reviewAlias = ReviewTable.alias("rvt")
val ratingAlias = reviewAlias[ReviewTable.rating].avg().alias("avgRating")
// avg for current user reviews (thanks to additionalConstraint in JOIN below)
val userRatingAlias = ReviewTable.rating.avg().alias("usrAvgRating")
cols.add(ratingAlias)
cols.add(userRatingAlias)
val query: Query = UserBookTable.join(BookTable, JoinType.LEFT)
.join(ReviewTable, JoinType.LEFT, additionalConstraint = { ReviewTable.book eq BookTable.id and(ReviewTable.user eq userID) })
.join(reviewAlias, JoinType.LEFT, onColumn = reviewAlias[ReviewTable.book], otherColumn = BookTable.id)
.slice(cols).selectAll()
.andWhere { UserBookTable.user eq userID }
.groupBy(UserBookTable.id)
.withDistinct(true)
if (bookId != null) {
query.andWhere { UserBookTable.book eq bookId }
Expand Down Expand Up @@ -1014,16 +1038,28 @@ class BookRepository(
}
val total = query.count()
query.limit(pageable.pageSize, pageable.offset)
val orders: Array<Pair<Expression<*>, SortOrder>> = parseSorts(pageable.sort, Pair(UserBookTable.lastReadingEventDate, SortOrder.DESC_NULLS_LAST), UserBookTable, BookTable)
val orders: Array<Pair<Expression<*>, SortOrder>> = parseSorts(pageable.sort, Pair(UserBookTable.lastReadingEventDate, SortOrder.DESC_NULLS_LAST), cols)
query.orderBy(*orders)
val res = UserBook.wrapRows(query).toList()
val res = query.map { resultRow -> wrapUserBookRow(resultRow, ratingAlias, userRatingAlias) }
return PageImpl(
res,
pageable,
total,
)
}

private fun wrapUserBookRow(
resultRow: ResultRow,
ratingAlias: ExpressionAlias<BigDecimal?>,
userRatingAlias: ExpressionAlias<BigDecimal?>,
): UserBook {
val r = UserBook.wrapRow(resultRow)
val rating = resultRow[ratingAlias]
r.avgRating = rating?.toDouble()
r.userAvgRating = resultRow[userRatingAlias]?.toDouble()
return r
}

// FIXME put a limit on the number of ids that can be passed for modification
fun bulkEditUserbooks(userBookBulkUpdateDto: UserBookBulkUpdateDto): Int {
var tagAdded = 0
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/io/github/bayang/jelu/dao/UserBookTable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class UserBook(id: EntityID<UUID>) : UUIDEntity(id) {
var percentRead by UserBookTable.percentRead
var currentPageNumber by UserBookTable.currentPageNumber
var borrowed by UserBookTable.borrowed
var avgRating: Double? = null
var userAvgRating: Double? = null

fun toUserBookDto(): UserBookDto =
UserBookDto(
Expand Down Expand Up @@ -92,6 +94,8 @@ class UserBook(id: EntityID<UUID>) : UUIDEntity(id) {
percentRead = this.percentRead,
currentPageNumber = this.currentPageNumber,
borrowed = this.borrowed,
avgRating = this.avgRating,
userAvgRating = this.userAvgRating,
)
fun toUserBookWithoutEventsDto(): UserBookWithoutEventsDto =
UserBookWithoutEventsDto(
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/io/github/bayang/jelu/dto/UserBookDto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ data class UserBookWithoutEventsAndUserDto(
val percentRead: Int?,
val currentPageNumber: Int?,
val borrowed: Boolean?,
val avgRating: Double? = null,
val userAvgRating: Double? = null,
)
data class UserBookWithoutEventsDto(
val id: UUID?,
Expand Down
Loading

0 comments on commit 8e591d6

Please sign in to comment.