mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-03-01 05:29:41 +00:00
Fixed error for Ratings page and stats page & added controlls to make Rating system and Ratings page optional (admin can turn it on or off for the server)
This commit is contained in:
parent
3a8075a077
commit
e4e2770fbd
10 changed files with 128 additions and 49 deletions
|
|
@ -68,7 +68,7 @@
|
|||
<div v-show="isNarratorsPage" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
|
||||
</nuxt-link>
|
||||
|
||||
<nuxt-link v-if="isBookLibrary" :to="`/library/${currentLibraryId}/ratings`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isRatingsPage ? 'bg-primary/80' : 'bg-bg/60'">
|
||||
<nuxt-link v-if="isBookLibrary && enableReviews && showReviewsInSidebar" :to="`/library/${currentLibraryId}/ratings`" class="w-full h-20 flex flex-col items-center justify-center text-white/80 border-b border-primary/70 hover:bg-primary cursor-pointer relative" :class="isRatingsPage ? 'bg-primary/80' : 'bg-bg/60'">
|
||||
<span class="material-symbols text-2xl">star</span>
|
||||
|
||||
<p class="pt-1 text-center leading-4" style="font-size: 0.9rem">{{ $strings.ButtonRatings }}</p>
|
||||
|
|
@ -207,6 +207,12 @@ export default {
|
|||
numIssues() {
|
||||
return this.$store.state.libraries.issues || 0
|
||||
},
|
||||
enableReviews() {
|
||||
return this.$store.getters['getServerSetting']('enableReviews')
|
||||
},
|
||||
showReviewsInSidebar() {
|
||||
return this.$store.getters['getServerSetting']('showReviewsInSidebar')
|
||||
},
|
||||
versionData() {
|
||||
return this.$store.state.versionData || {}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<modals-modal v-model="show" name="review-modal" :width="500">
|
||||
<div class="px-6 py-8 w-full rounded-lg bg-bg shadow-lg border border-black-300" style="max-height: 80vh">
|
||||
<div class="px-6 py-8 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||
<h2 class="text-xl font-semibold mb-4">{{ title }}</h2>
|
||||
|
||||
<div class="mb-6">
|
||||
|
|
@ -84,7 +84,7 @@ export default {
|
|||
reviewText: this.reviewText
|
||||
}
|
||||
const review = await this.$axios.$post(`/api/items/${this.libraryItem.id}/review`, payload)
|
||||
this.$emit('review-updated', review)
|
||||
this.$root.$emit('review-updated', review)
|
||||
this.$toast.success('Review submitted')
|
||||
this.show = false
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<div class="grow" />
|
||||
|
||||
<ui-btn small color="bg-success" class="mr-4" @click.stop="writeReview">
|
||||
<ui-btn small :color="userReview ? '' : 'bg-success'" class="mr-4" @click.stop="writeReview">
|
||||
{{ userReview ? $strings.ButtonReviewEdit : $strings.ButtonReviewWrite }}
|
||||
</ui-btn>
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,26 @@
|
|||
<ui-toggle-switch v-model="newServerSettings.allowIframe" :label="$strings.LabelSettingsAllowIframe" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('allowIframe', val)" />
|
||||
<p aria-hidden="true" class="pl-4">{{ $strings.LabelSettingsAllowIframe }}</p>
|
||||
</div>
|
||||
|
||||
<div role="article" :aria-label="$strings.LabelSettingsEnableReviewsHelp" class="flex items-center py-2">
|
||||
<ui-toggle-switch :label="$strings.LabelSettingsEnableReviews" v-model="newServerSettings.enableReviews" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('enableReviews', val)" />
|
||||
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsEnableReviewsHelp">
|
||||
<p class="pl-4">
|
||||
<span id="settings-enable-reviews">{{ $strings.LabelSettingsEnableReviews }}</span>
|
||||
<span class="material-symbols icon-text">info</span>
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
|
||||
<div v-if="newServerSettings.enableReviews" role="article" :aria-label="$strings.LabelSettingsShowRatingsPageHelp" class="flex items-center py-2 mb-2">
|
||||
<ui-toggle-switch :label="$strings.LabelSettingsShowRatingsPage" v-model="newServerSettings.showReviewsInSidebar" :disabled="updatingServerSettings" @input="(val) => updateSettingsKey('showReviewsInSidebar', val)" />
|
||||
<ui-tooltip aria-hidden="true" :text="$strings.LabelSettingsShowRatingsPageHelp">
|
||||
<p class="pl-4">
|
||||
<span id="settings-show-ratings-page">{{ $strings.LabelSettingsShowRatingsPage }}</span>
|
||||
<span class="material-symbols icon-text">info</span>
|
||||
</p>
|
||||
</ui-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1">
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@
|
|||
<button v-if="isDescriptionClamped" class="py-0.5 flex items-center text-slate-300 hover:text-white" @click="showFullDescription = !showFullDescription">{{ showFullDescription ? $strings.ButtonReadLess : $strings.ButtonReadMore }} <span class="material-symbols text-xl pl-1" v-html="showFullDescription ? 'expand_less' : ''" /></button>
|
||||
</div>
|
||||
|
||||
<tables-reviews-table :library-item="libraryItem" class="mt-6" />
|
||||
<tables-reviews-table v-if="enableReviews" :library-item="libraryItem" class="mt-6" />
|
||||
|
||||
<tables-chapters-table v-if="chapters.length" :library-item="libraryItem" class="mt-6" />
|
||||
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
|
||||
<modals-podcast-episode-feed v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" :download-queue="episodeDownloadsQueued" :episodes-downloading="episodesDownloading" />
|
||||
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :playback-rate="1" :library-item-id="libraryItemId" hide-create @select="selectBookmark" />
|
||||
<modals-review-modal />
|
||||
<modals-review-modal v-if="enableReviews" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -346,6 +346,9 @@ export default {
|
|||
isQueued() {
|
||||
return this.$store.getters['getIsMediaQueued'](this.libraryItemId)
|
||||
},
|
||||
enableReviews() {
|
||||
return this.$store.getters['getServerSetting']('enableReviews')
|
||||
},
|
||||
userCanUpdate() {
|
||||
return this.$store.getters['user/getUserCanUpdate']
|
||||
},
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="isBookLibrary && top10RatedItems.length" class="w-80 my-6 mx-auto">
|
||||
<div v-if="isBookLibrary && enableReviews && top10RatedItems.length" class="w-80 my-6 mx-auto">
|
||||
<h1 class="text-2xl mb-4">{{ $strings.HeaderStatsTopRated }}</h1>
|
||||
<template v-for="(ab, index) in top10RatedItems">
|
||||
<div :key="index" class="w-full py-2">
|
||||
|
|
@ -188,6 +188,9 @@ export default {
|
|||
},
|
||||
isBookLibrary() {
|
||||
return this.currentLibraryMediaType === 'book'
|
||||
},
|
||||
enableReviews() {
|
||||
return this.$store.getters['getServerSetting']('enableReviews')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -600,6 +600,10 @@
|
|||
"LabelSettingsEnableWatcher": "Automatically watch libraries for changes",
|
||||
"LabelSettingsEnableWatcherForLibrary": "Automatically watch library for changes",
|
||||
"LabelSettingsEnableWatcherHelp": "Enables the automatic adding/updating of items when file changes are detected. *Requires server restart",
|
||||
"LabelSettingsEnableReviews": "Enable Reviews",
|
||||
"LabelSettingsEnableReviewsHelp": "Allow users to rate and review books",
|
||||
"LabelSettingsShowRatingsPage": "Show Ratings Page in Sidebar",
|
||||
"LabelSettingsShowRatingsPageHelp": "Display the Ratings page link in the library sidebar",
|
||||
"LabelSettingsEpubsAllowScriptedContent": "Allow scripted content in epubs",
|
||||
"LabelSettingsEpubsAllowScriptedContentHelp": "Allow epub files to execute scripts. It is recommended to keep this setting disabled unless you trust the source of the epub files.",
|
||||
"LabelSettingsExperimentalFeatures": "Experimental features",
|
||||
|
|
|
|||
|
|
@ -997,46 +997,45 @@ class LibraryController {
|
|||
stats.numAudioTracks = bookStats.numAudioFiles
|
||||
|
||||
// Get top 10 rated items
|
||||
const topRatedReviews = await Database.reviewModel.findAll({
|
||||
attributes: [
|
||||
'libraryItemId',
|
||||
[Sequelize.fn('AVG', Sequelize.col('rating')), 'avgRating'],
|
||||
[Sequelize.fn('COUNT', Sequelize.col('id')), 'numReviews']
|
||||
],
|
||||
include: [
|
||||
{
|
||||
model: Database.libraryItemModel,
|
||||
attributes: ['id'],
|
||||
where: { libraryId: req.library.id },
|
||||
include: [
|
||||
{
|
||||
model: Database.bookModel,
|
||||
attributes: ['id'],
|
||||
include: [
|
||||
{
|
||||
model: Database.bookMetadataModel,
|
||||
attributes: ['title']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
try {
|
||||
const topRatedReviews = await Database.reviewModel.findAll({
|
||||
attributes: [
|
||||
'libraryItemId',
|
||||
[Sequelize.fn('AVG', Sequelize.col('review.rating')), 'avgRating'],
|
||||
[Sequelize.fn('COUNT', Sequelize.col('review.id')), 'numReviews']
|
||||
],
|
||||
include: [
|
||||
{
|
||||
model: Database.libraryItemModel,
|
||||
attributes: ['id'],
|
||||
where: { libraryId: req.library.id },
|
||||
include: [
|
||||
{
|
||||
model: Database.bookModel,
|
||||
attributes: ['id', 'title']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
group: ['libraryItemId', 'libraryItem.id', 'libraryItem.book.id'],
|
||||
order: [
|
||||
[Sequelize.literal('avgRating'), 'DESC'],
|
||||
[Sequelize.literal('numReviews'), 'DESC']
|
||||
],
|
||||
limit: 10
|
||||
})
|
||||
stats.topRatedItems = topRatedReviews.map((r) => {
|
||||
return {
|
||||
id: r.libraryItemId,
|
||||
title: r.libraryItem?.book?.title || 'Unknown',
|
||||
avgRating: parseFloat(r.getDataValue('avgRating')),
|
||||
numReviews: parseInt(r.getDataValue('numReviews'))
|
||||
}
|
||||
],
|
||||
group: ['libraryItemId', 'libraryItem.id', 'libraryItem.book.id', 'libraryItem.book.bookMetadata.id'],
|
||||
order: [
|
||||
[Sequelize.literal('avgRating'), 'DESC'],
|
||||
[Sequelize.literal('numReviews'), 'DESC']
|
||||
],
|
||||
limit: 10
|
||||
})
|
||||
stats.topRatedItems = topRatedReviews.map((r) => {
|
||||
return {
|
||||
id: r.libraryItemId,
|
||||
title: r.libraryItem?.book?.bookMetadata?.title || 'Unknown',
|
||||
avgRating: parseFloat(r.getDataValue('avgRating')),
|
||||
numReviews: parseInt(r.getDataValue('numReviews'))
|
||||
}
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
Logger.error('[LibraryController] Failed to get top rated items for stats', error)
|
||||
stats.topRatedItems = []
|
||||
}
|
||||
} else {
|
||||
const genres = await libraryItemsPodcastFilters.getGenresWithCount(req.library.id)
|
||||
const podcastStats = await libraryItemsPodcastFilters.getPodcastLibraryStats(req.library.id)
|
||||
|
|
|
|||
|
|
@ -116,6 +116,10 @@ class ReviewController {
|
|||
* @param {import('express').Response} res
|
||||
*/
|
||||
async findAllForUser(req, res) {
|
||||
if (!Database.serverSettings.enableReviews) {
|
||||
return res.status(403).send('Review feature is disabled')
|
||||
}
|
||||
|
||||
try {
|
||||
const reviews = await Database.reviewModel.findAll({
|
||||
where: { userId: req.user.id },
|
||||
|
|
@ -124,10 +128,23 @@ class ReviewController {
|
|||
model: Database.libraryItemModel,
|
||||
include: [
|
||||
{
|
||||
model: Database.bookModel
|
||||
model: Database.bookModel,
|
||||
include: [
|
||||
{
|
||||
model: Database.authorModel,
|
||||
through: { attributes: [] }
|
||||
},
|
||||
{
|
||||
model: Database.seriesModel,
|
||||
through: { attributes: ['id', 'sequence'] }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: Database.podcastModel
|
||||
model: Database.podcastModel,
|
||||
include: {
|
||||
model: Database.podcastEpisodeModel
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -138,7 +155,22 @@ class ReviewController {
|
|||
res.json(reviews.map((r) => {
|
||||
const json = r.toOldJSON()
|
||||
if (r.libraryItem) {
|
||||
json.libraryItem = r.libraryItem.toOldJSONMinified()
|
||||
// Manually set media if missing (Sequelize hooks don't run on nested includes)
|
||||
if (!r.libraryItem.media) {
|
||||
if (r.libraryItem.mediaType === 'book' && r.libraryItem.book) {
|
||||
r.libraryItem.media = r.libraryItem.book
|
||||
} else if (r.libraryItem.mediaType === 'podcast' && r.libraryItem.podcast) {
|
||||
r.libraryItem.media = r.libraryItem.podcast
|
||||
}
|
||||
}
|
||||
|
||||
if (r.libraryItem.media) {
|
||||
try {
|
||||
json.libraryItem = r.libraryItem.toOldJSONMinified()
|
||||
} catch (err) {
|
||||
Logger.error(`[ReviewController] Failed to minify library item ${r.libraryItem.id}`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return json
|
||||
}))
|
||||
|
|
@ -156,6 +188,10 @@ class ReviewController {
|
|||
* @param {import('express').NextFunction} next
|
||||
*/
|
||||
async middleware(req, res, next) {
|
||||
if (!Database.serverSettings.enableReviews) {
|
||||
return res.status(403).send('Review feature is disabled')
|
||||
}
|
||||
|
||||
// Basic library item access check
|
||||
req.libraryItem = await Database.libraryItemModel.getExpandedById(req.params.id)
|
||||
if (!req.libraryItem?.media) return res.sendStatus(404)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ class ServerSettings {
|
|||
this.language = 'en-us'
|
||||
this.allowedOrigins = []
|
||||
|
||||
this.enableReviews = true
|
||||
this.showReviewsInSidebar = true
|
||||
|
||||
this.logLevel = Logger.logLevel
|
||||
|
||||
this.version = packageJson.version
|
||||
|
|
@ -122,6 +125,9 @@ class ServerSettings {
|
|||
this.timeFormat = settings.timeFormat || 'HH:mm'
|
||||
this.language = settings.language || 'en-us'
|
||||
this.allowedOrigins = settings.allowedOrigins || []
|
||||
|
||||
this.enableReviews = settings.enableReviews !== false
|
||||
this.showReviewsInSidebar = settings.showReviewsInSidebar !== false
|
||||
this.logLevel = settings.logLevel || Logger.logLevel
|
||||
this.version = settings.version || null
|
||||
this.buildNumber = settings.buildNumber || 0 // Added v2.4.5
|
||||
|
|
@ -234,6 +240,8 @@ class ServerSettings {
|
|||
timeFormat: this.timeFormat,
|
||||
language: this.language,
|
||||
allowedOrigins: this.allowedOrigins,
|
||||
enableReviews: this.enableReviews,
|
||||
showReviewsInSidebar: this.showReviewsInSidebar,
|
||||
logLevel: this.logLevel,
|
||||
version: this.version,
|
||||
buildNumber: this.buildNumber,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue