mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-09 02:41:35 +00:00
Added User personal audiobook rating
This commit is contained in:
parent
7c3504fe2b
commit
3bfd9f419c
7 changed files with 264 additions and 46 deletions
|
|
@ -36,9 +36,9 @@
|
||||||
|
|
||||||
<p v-if="bookSubtitle" class="text-gray-200 text-xl md:text-2xl">{{ bookSubtitle }}</p>
|
<p v-if="bookSubtitle" class="text-gray-200 text-xl md:text-2xl">{{ bookSubtitle }}</p>
|
||||||
|
|
||||||
<template v-for="(_series, index) in seriesList">
|
<template v-for="(_series, index) in seriesList" :key="_series.id">
|
||||||
<nuxt-link :key="_series.id" :to="`/library/${libraryId}/series/${_series.id}`" class="hover:underline font-sans text-gray-300 text-lg leading-7">{{ _series.text }}</nuxt-link
|
<nuxt-link :to="`/library/${libraryId}/series/${_series.id}`" class="hover:underline font-sans text-gray-300 text-lg leading-7">{{ _series.text }}</nuxt-link
|
||||||
><span :key="index" v-if="index < seriesList.length - 1">, </span>
|
><span v-if="index < seriesList.length - 1">, </span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
|
<p v-if="isPodcast" class="mb-2 mt-0.5 text-gray-200 text-lg md:text-xl">{{ $getString('LabelByAuthor', [podcastAuthor]) }}</p>
|
||||||
|
|
@ -48,8 +48,11 @@
|
||||||
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
<p v-else class="mb-2 mt-0.5 text-gray-200 text-xl">by Unknown</p>
|
||||||
|
|
||||||
<div class="flex items-center space-x-4 mt-2">
|
<div class="flex items-center space-x-4 mt-2">
|
||||||
<div v-if="userRating > 0" class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ui-rating-input :value="userRating" :label="$strings.LabelYourRating" :read-only="true" />
|
<ui-rating-input v-model="personalRating" :label="$strings.LabelYourRating" />
|
||||||
|
</div>
|
||||||
|
<div v-if="globalRating > 0" class="flex items-center">
|
||||||
|
<ui-rating-input :value="globalRating" label="Community Rating" :read-only="true" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="provider === 'audible' && providerRating != null" class="flex items-center bg-zinc-800 rounded-lg p-1.5 space-x-1.5 opacity-80">
|
<div v-if="provider === 'audible' && providerRating != null" class="flex items-center bg-zinc-800 rounded-lg p-1.5 space-x-1.5 opacity-80">
|
||||||
<img src="~/assets/logos/audible.svg" alt="Audible Logo" class="w-12 h-auto" />
|
<img src="~/assets/logos/audible.svg" alt="Audible Logo" class="w-12 h-auto" />
|
||||||
|
|
@ -174,7 +177,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include episode downloads for podcasts
|
// Include episode downloads for podcasts
|
||||||
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=downloads,rssfeed,share`).catch((error) => {
|
var item = await app.$axios.$get(`/api/items/${params.id}?expanded=1&include=downloads,rssfeed,share,progress`).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
@ -182,11 +185,11 @@ export default {
|
||||||
console.error('No item...', params.id)
|
console.error('No item...', params.id)
|
||||||
return redirect('/')
|
return redirect('/')
|
||||||
}
|
}
|
||||||
|
store.commit('libraries/UPDATE_LIBRARY_ITEM', item)
|
||||||
if (store.state.libraries.currentLibraryId !== item.libraryId || !store.state.libraries.filterData) {
|
if (store.state.libraries.currentLibraryId !== item.libraryId || !store.state.libraries.filterData) {
|
||||||
await store.dispatch('libraries/fetch', item.libraryId)
|
await store.dispatch('libraries/fetch', item.libraryId)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
libraryItem: item,
|
|
||||||
rssFeed: item.rssFeed || null,
|
rssFeed: item.rssFeed || null,
|
||||||
mediaItemShare: item.mediaItemShare || null
|
mediaItemShare: item.mediaItemShare || null
|
||||||
}
|
}
|
||||||
|
|
@ -202,10 +205,14 @@ export default {
|
||||||
episodeDownloadsQueued: [],
|
episodeDownloadsQueued: [],
|
||||||
showBookmarksModal: false,
|
showBookmarksModal: false,
|
||||||
isDescriptionClamped: false,
|
isDescriptionClamped: false,
|
||||||
showFullDescription: false
|
showFullDescription: false,
|
||||||
|
localPersonalRating: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
libraryItem() {
|
||||||
|
return this.$store.state.libraries.libraryItemsCache[this.$route.params.id] || {}
|
||||||
|
},
|
||||||
userToken() {
|
userToken() {
|
||||||
return this.$store.getters['user/getToken']
|
return this.$store.getters['user/getToken']
|
||||||
},
|
},
|
||||||
|
|
@ -329,17 +336,23 @@ export default {
|
||||||
description() {
|
description() {
|
||||||
return this.mediaMetadata.description || ''
|
return this.mediaMetadata.description || ''
|
||||||
},
|
},
|
||||||
userRating() {
|
globalRating() {
|
||||||
return this.mediaMetadata.rating || 0
|
return this.mediaMetadata.rating || 0
|
||||||
},
|
},
|
||||||
providerRating() {
|
providerRating() {
|
||||||
console.log('Provider Rating: ', this.media.providerRating)
|
|
||||||
return this.media.providerRating || 0
|
return this.media.providerRating || 0
|
||||||
},
|
},
|
||||||
provider() {
|
provider() {
|
||||||
console.log('Provider: ', this.media.provider)
|
|
||||||
return this.media.provider || null
|
return this.media.provider || null
|
||||||
},
|
},
|
||||||
|
personalRating: {
|
||||||
|
get() {
|
||||||
|
return this.localPersonalRating
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.updatePersonalRating(val)
|
||||||
|
}
|
||||||
|
},
|
||||||
userMediaProgress() {
|
userMediaProgress() {
|
||||||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||||
},
|
},
|
||||||
|
|
@ -464,6 +477,51 @@ export default {
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.localPersonalRating = this.libraryItem.personalRating || 0
|
||||||
|
this.$root.$on('progress-updated', this.progressUpdated)
|
||||||
|
this.$root.$on('libraryitem-updated', this.libraryItemUpdated)
|
||||||
|
this.$root.$on('rss-updated', this.rssUpdated)
|
||||||
|
this.checkDescriptionClamped()
|
||||||
|
|
||||||
|
this.$root.socket.on('playback_session_started', this.playbackSessionStarted)
|
||||||
|
this.$root.socket.on('playback_session_stopped', this.playbackSessionStopped)
|
||||||
|
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
|
||||||
|
this.$root.socket.on('episode_download_progress', this.episodeDownloadProgress)
|
||||||
|
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
||||||
|
this.$root.socket.on('episode_download_queue_cleared', this.episodeDownloadQueueCleared)
|
||||||
|
|
||||||
|
this.episodeDownloadsQueued = this.libraryItem.episodeDownloadsQueued || []
|
||||||
|
this.episodesDownloading = this.libraryItem.episodesDownloading || []
|
||||||
|
|
||||||
|
this.$eventBus.$on(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
||||||
|
this.$root.socket.on('item_updated', this.libraryItemUpdated)
|
||||||
|
this.$root.socket.on('rss_feed_open', this.rssFeedOpen)
|
||||||
|
this.$root.socket.on('rss_feed_closed', this.rssFeedClosed)
|
||||||
|
this.$root.socket.on('share_open', this.shareOpen)
|
||||||
|
this.$root.socket.on('share_closed', this.shareClosed)
|
||||||
|
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
this.$root.$off('progress-updated', this.progressUpdated)
|
||||||
|
this.$root.$off('libraryitem-updated', this.libraryItemUpdated)
|
||||||
|
this.$root.$off('rss-updated', this.rssUpdated)
|
||||||
|
this.$root.socket.off('playback_session_started', this.playbackSessionStarted)
|
||||||
|
this.$root.socket.off('playback_session_stopped', this.playbackSessionStopped)
|
||||||
|
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
|
||||||
|
this.$root.socket.off('episode_download_progress', this.episodeDownloadProgress)
|
||||||
|
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
|
||||||
|
this.$root.socket.off('episode_download_queue_cleared', this.episodeDownloadQueueCleared)
|
||||||
|
this.streamer?.off()
|
||||||
|
|
||||||
|
this.$eventBus.$off(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
||||||
|
this.$root.socket.off('item_updated', this.libraryItemUpdated)
|
||||||
|
this.$root.socket.off('rss_feed_open', this.rssFeedOpen)
|
||||||
|
this.$root.socket.off('rss_feed_closed', this.rssFeedClosed)
|
||||||
|
this.$root.socket.off('share_open', this.shareOpen)
|
||||||
|
this.$root.socket.off('share_closed', this.shareClosed)
|
||||||
|
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectBookmark(bookmark) {
|
selectBookmark(bookmark) {
|
||||||
if (!bookmark) return
|
if (!bookmark) return
|
||||||
|
|
@ -625,9 +683,9 @@ export default {
|
||||||
},
|
},
|
||||||
libraryItemUpdated(libraryItem) {
|
libraryItemUpdated(libraryItem) {
|
||||||
if (libraryItem.id === this.libraryItemId) {
|
if (libraryItem.id === this.libraryItemId) {
|
||||||
console.log('Item was updated', libraryItem)
|
libraryItem.personalRating = this.localPersonalRating
|
||||||
this.libraryItem = libraryItem
|
this.$store.commit('libraries/UPDATE_LIBRARY_ITEM', libraryItem)
|
||||||
this.$nextTick(this.checkDescriptionClamped)
|
this.localPersonalRating = libraryItem.personalRating || 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearProgressClick() {
|
clearProgressClick() {
|
||||||
|
|
@ -808,36 +866,43 @@ export default {
|
||||||
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
|
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
|
||||||
this.$store.commit('globals/setShareModal', this.mediaItemShare)
|
this.$store.commit('globals/setShareModal', this.mediaItemShare)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async updatePersonalRating(rating) {
|
||||||
|
this.localPersonalRating = rating
|
||||||
|
try {
|
||||||
|
await this.$axios.post(`/api/items/${this.libraryItemId}/rate`, { rating })
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
progressUpdated(data) {
|
||||||
this.checkDescriptionClamped()
|
if (data.libraryItemId === this.libraryItemId) {
|
||||||
|
this.$store.dispatch('user/updateMediaProgress', data.mediaProgress)
|
||||||
this.episodeDownloadsQueued = this.libraryItem.episodeDownloadsQueued || []
|
this.$nextTick(this.checkDescriptionClamped)
|
||||||
this.episodesDownloading = this.libraryItem.episodesDownloading || []
|
}
|
||||||
|
|
||||||
this.$eventBus.$on(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
|
||||||
this.$root.socket.on('item_updated', this.libraryItemUpdated)
|
|
||||||
this.$root.socket.on('rss_feed_open', this.rssFeedOpen)
|
|
||||||
this.$root.socket.on('rss_feed_closed', this.rssFeedClosed)
|
|
||||||
this.$root.socket.on('share_open', this.shareOpen)
|
|
||||||
this.$root.socket.on('share_closed', this.shareClosed)
|
|
||||||
this.$root.socket.on('episode_download_queued', this.episodeDownloadQueued)
|
|
||||||
this.$root.socket.on('episode_download_started', this.episodeDownloadStarted)
|
|
||||||
this.$root.socket.on('episode_download_finished', this.episodeDownloadFinished)
|
|
||||||
this.$root.socket.on('episode_download_queue_cleared', this.episodeDownloadQueueCleared)
|
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
rssUpdated(data) {
|
||||||
this.$eventBus.$off(`${this.libraryItem.id}_updated`, this.libraryItemUpdated)
|
if (data.libraryItemId === this.libraryItemId) {
|
||||||
this.$root.socket.off('item_updated', this.libraryItemUpdated)
|
console.log('RSS feed updated', data)
|
||||||
this.$root.socket.off('rss_feed_open', this.rssFeedOpen)
|
this.rssFeed = data
|
||||||
this.$root.socket.off('rss_feed_closed', this.rssFeedClosed)
|
}
|
||||||
this.$root.socket.off('share_open', this.shareOpen)
|
},
|
||||||
this.$root.socket.off('share_closed', this.shareClosed)
|
playbackSessionStarted(data) {
|
||||||
this.$root.socket.off('episode_download_queued', this.episodeDownloadQueued)
|
if (data.entityId === this.libraryItemId) {
|
||||||
this.$root.socket.off('episode_download_started', this.episodeDownloadStarted)
|
this.$store.commit('setStreamLibraryItem', data)
|
||||||
this.$root.socket.off('episode_download_finished', this.episodeDownloadFinished)
|
}
|
||||||
this.$root.socket.off('episode_download_queue_cleared', this.episodeDownloadQueueCleared)
|
},
|
||||||
|
playbackSessionStopped(data) {
|
||||||
|
if (data.entityId === this.libraryItemId) {
|
||||||
|
this.$store.commit('setStreamLibraryItem', null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
episodeDownloadProgress(data) {
|
||||||
|
if (data.libraryItemId === this.libraryItemId) {
|
||||||
|
console.log('Episode download progress', data)
|
||||||
|
// Handle episode download progress update
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Vue from 'vue'
|
||||||
const { Constants } = require('../plugins/constants')
|
const { Constants } = require('../plugins/constants')
|
||||||
|
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
|
|
@ -12,7 +13,8 @@ export const state = () => ({
|
||||||
numUserPlaylists: 0,
|
numUserPlaylists: 0,
|
||||||
collections: [],
|
collections: [],
|
||||||
userPlaylists: [],
|
userPlaylists: [],
|
||||||
ereaderDevices: []
|
ereaderDevices: [],
|
||||||
|
libraryItemsCache: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = {
|
export const getters = {
|
||||||
|
|
@ -170,6 +172,9 @@ export const actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
|
UPDATE_LIBRARY_ITEM(state, libraryItem) {
|
||||||
|
Vue.set(state.libraryItemsCache, libraryItem.id, libraryItem)
|
||||||
|
},
|
||||||
setFolders(state, folders) {
|
setFolders(state, folders) {
|
||||||
state.folders = folders
|
state.folders = folders
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,7 @@ class Database {
|
||||||
require('./models/Setting').init(this.sequelize)
|
require('./models/Setting').init(this.sequelize)
|
||||||
require('./models/CustomMetadataProvider').init(this.sequelize)
|
require('./models/CustomMetadataProvider').init(this.sequelize)
|
||||||
require('./models/MediaItemShare').init(this.sequelize)
|
require('./models/MediaItemShare').init(this.sequelize)
|
||||||
|
require('./models/UserBookRating').init(this.sequelize)
|
||||||
|
|
||||||
return this.sequelize.sync({ force, alter: false })
|
return this.sequelize.sync({ force, alter: false })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,14 @@ class LibraryItemController {
|
||||||
if (req.query.expanded == 1) {
|
if (req.query.expanded == 1) {
|
||||||
const item = req.libraryItem.toOldJSONExpanded()
|
const item = req.libraryItem.toOldJSONExpanded()
|
||||||
|
|
||||||
|
// Include users personal rating
|
||||||
|
const userBookRating = await Database.models.userBookRating.findOne({
|
||||||
|
where: { userId: req.user.id, bookId: req.libraryItem.media.id }
|
||||||
|
})
|
||||||
|
if (userBookRating) {
|
||||||
|
item.personalRating = userBookRating.rating
|
||||||
|
}
|
||||||
|
|
||||||
// Include users media progress
|
// Include users media progress
|
||||||
if (includeEntities.includes('progress')) {
|
if (includeEntities.includes('progress')) {
|
||||||
const episodeId = req.query.episode || null
|
const episodeId = req.query.episode || null
|
||||||
|
|
@ -1181,8 +1189,8 @@ class LibraryItemController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.path.includes('/play')) {
|
if (req.path.includes('/play') || req.path.includes('/rate')) {
|
||||||
// allow POST requests using /play and /play/:episodeId
|
// allow POST requests using /play and /play/:episodeId OR /rate
|
||||||
} else if (req.method == 'DELETE' && !req.user.canDelete) {
|
} else if (req.method == 'DELETE' && !req.user.canDelete) {
|
||||||
Logger.warn(`[LibraryItemController] User "${req.user.username}" attempted to delete without permission`)
|
Logger.warn(`[LibraryItemController] User "${req.user.username}" attempted to delete without permission`)
|
||||||
return res.sendStatus(403)
|
return res.sendStatus(403)
|
||||||
|
|
@ -1193,5 +1201,31 @@ class LibraryItemController {
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST: /api/items/:id/rate
|
||||||
|
*
|
||||||
|
* @param {LibraryItemControllerRequest} req
|
||||||
|
* @param {Response} res
|
||||||
|
*/
|
||||||
|
async rate(req, res) {
|
||||||
|
try {
|
||||||
|
const { rating } = req.body
|
||||||
|
if (rating === null || typeof rating !== 'number' || rating < 0 || rating > 5) {
|
||||||
|
return res.status(400).json({ error: 'Invalid rating' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bookId = req.libraryItem.media.id
|
||||||
|
const userId = req.user.id
|
||||||
|
|
||||||
|
await Database.models.userBookRating.upsert({ userId, bookId, rating })
|
||||||
|
|
||||||
|
res.status(200).json({ success: true })
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error(err)
|
||||||
|
res.status(500).json({ error: 'An error occurred while saving the rating' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = new LibraryItemController()
|
module.exports = new LibraryItemController()
|
||||||
|
|
|
||||||
59
server/migrations/v2.25.3-add-user-book-ratings.js
Normal file
59
server/migrations/v2.25.3-add-user-book-ratings.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
const { DataTypes } = require('sequelize')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: async ({ context: queryInterface }) => {
|
||||||
|
const transaction = await queryInterface.sequelize.transaction()
|
||||||
|
try {
|
||||||
|
await queryInterface.createTable(
|
||||||
|
'userBookRatings',
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
references: { model: 'users', key: 'id' },
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
},
|
||||||
|
bookId: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
references: { model: 'books', key: 'id' },
|
||||||
|
onUpdate: 'CASCADE',
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
},
|
||||||
|
rating: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ transaction }
|
||||||
|
)
|
||||||
|
await queryInterface.addConstraint('userBookRatings', {
|
||||||
|
fields: ['userId', 'bookId'],
|
||||||
|
type: 'unique',
|
||||||
|
name: 'user_book_ratings_unique_constraint',
|
||||||
|
transaction
|
||||||
|
})
|
||||||
|
await transaction.commit()
|
||||||
|
} catch (err) {
|
||||||
|
await transaction.rollback()
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
},
|
||||||
|
down: async ({ context: queryInterface }) => {
|
||||||
|
await queryInterface.dropTable('userBookRatings')
|
||||||
|
}
|
||||||
|
}
|
||||||
53
server/models/UserBookRating.js
Normal file
53
server/models/UserBookRating.js
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
const { DataTypes, Model } = require('sequelize')
|
||||||
|
|
||||||
|
class UserBookRating extends Model {
|
||||||
|
static init(sequelize) {
|
||||||
|
super.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
bookId: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
rating: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
modelName: 'userBookRating',
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['userId', 'bookId']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const { user, book } = sequelize.models
|
||||||
|
|
||||||
|
user.hasMany(UserBookRating, {
|
||||||
|
foreignKey: 'userId'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.belongsTo(user, { foreignKey: 'userId' })
|
||||||
|
|
||||||
|
book.hasMany(this, {
|
||||||
|
foreignKey: 'bookId'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.belongsTo(book, { foreignKey: 'bookId' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = UserBookRating
|
||||||
|
|
@ -125,6 +125,7 @@ class ApiRouter {
|
||||||
this.router.get('/items/:id/file/:fileid/download', LibraryItemController.middleware.bind(this), LibraryItemController.downloadLibraryFile.bind(this))
|
this.router.get('/items/:id/file/:fileid/download', LibraryItemController.middleware.bind(this), LibraryItemController.downloadLibraryFile.bind(this))
|
||||||
this.router.get('/items/:id/ebook/:fileid?', LibraryItemController.middleware.bind(this), LibraryItemController.getEBookFile.bind(this))
|
this.router.get('/items/:id/ebook/:fileid?', LibraryItemController.middleware.bind(this), LibraryItemController.getEBookFile.bind(this))
|
||||||
this.router.patch('/items/:id/ebook/:fileid/status', LibraryItemController.middleware.bind(this), LibraryItemController.updateEbookFileStatus.bind(this))
|
this.router.patch('/items/:id/ebook/:fileid/status', LibraryItemController.middleware.bind(this), LibraryItemController.updateEbookFileStatus.bind(this))
|
||||||
|
this.router.post('/items/:id/rate', LibraryItemController.middleware.bind(this), LibraryItemController.rate.bind(this))
|
||||||
|
|
||||||
//
|
//
|
||||||
// User Routes
|
// User Routes
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue