mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Update API media progress endpoints to use new user model. Merge book & episode endpoints
This commit is contained in:
parent
68ef3a07a7
commit
9cd92c7b7f
@ -182,7 +182,7 @@ export default {
|
||||
toggleFinished(confirmed = false) {
|
||||
if (!this.userIsFinished && this.itemProgressPercent > 0 && !confirmed) {
|
||||
const payload = {
|
||||
message: `Are you sure you want to mark "${this.title}" as finished?`,
|
||||
message: `Are you sure you want to mark "${this.episodeTitle}" as finished?`,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.toggleFinished(true)
|
||||
@ -233,4 +233,4 @@ export default {
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
@ -246,7 +246,7 @@ export default {
|
||||
message: newIsFinished ? this.$strings.MessageConfirmMarkAllEpisodesFinished : this.$strings.MessageConfirmMarkAllEpisodesNotFinished,
|
||||
callback: (confirmed) => {
|
||||
if (confirmed) {
|
||||
this.batchUpdateEpisodesFinished(this.episodesSorted, newIsFinished)
|
||||
this.batchUpdateEpisodesFinished(this.episodesCopy, newIsFinished)
|
||||
}
|
||||
},
|
||||
type: 'yesNo'
|
||||
@ -305,6 +305,7 @@ export default {
|
||||
this.batchUpdateEpisodesFinished(this.selectedEpisodes, !this.selectedIsFinished)
|
||||
},
|
||||
batchUpdateEpisodesFinished(episodes, newIsFinished) {
|
||||
if (!episodes.length) return
|
||||
this.processing = true
|
||||
|
||||
const updateProgressPayloads = episodes.map((episode) => {
|
||||
|
@ -400,11 +400,6 @@ class Database {
|
||||
return this.models.mediaProgress.upsertFromOld(oldMediaProgress)
|
||||
}
|
||||
|
||||
removeMediaProgress(mediaProgressId) {
|
||||
if (!this.sequelize) return false
|
||||
return this.models.mediaProgress.removeById(mediaProgressId)
|
||||
}
|
||||
|
||||
updateBulkBooks(oldBooks) {
|
||||
if (!this.sequelize) return false
|
||||
return Promise.all(oldBooks.map((oldBook) => this.models.book.saveFromOld(oldBook)))
|
||||
|
@ -1,3 +1,4 @@
|
||||
const { Request, Response } = require('express')
|
||||
const Logger = require('../Logger')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
const Database = require('../Database')
|
||||
@ -5,16 +6,36 @@ const { sort } = require('../libs/fastSort')
|
||||
const { toNumber } = require('../utils/index')
|
||||
const userStats = require('../utils/queries/userStats')
|
||||
|
||||
/**
|
||||
* @typedef RequestUserObjects
|
||||
* @property {import('../models/User')} userNew
|
||||
* @property {import('../objects/user/User')} user
|
||||
*
|
||||
* @typedef {Request & RequestUserObjects} RequestWithUser
|
||||
*
|
||||
*/
|
||||
|
||||
class MeController {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* GET: /api/me
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
getCurrentUser(req, res) {
|
||||
res.json(req.user.toJSONForBrowser())
|
||||
res.json(req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
// GET: api/me/listening-sessions
|
||||
/**
|
||||
* GET: /api/me/listening-sessions
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getListeningSessions(req, res) {
|
||||
var listeningSessions = await this.getUserListeningSessionsHelper(req.user.id)
|
||||
const listeningSessions = await this.getUserListeningSessionsHelper(req.userNew.id)
|
||||
|
||||
const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10
|
||||
const page = toNumber(req.query.page, 0)
|
||||
@ -38,8 +59,8 @@ class MeController {
|
||||
*
|
||||
* @this import('../routers/ApiRouter')
|
||||
*
|
||||
* @param {import('express').Request} req
|
||||
* @param {import('express').Response} res
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getItemListeningSessions(req, res) {
|
||||
const libraryItem = await Database.libraryItemModel.findByPk(req.params.libraryItemId)
|
||||
@ -51,7 +72,7 @@ class MeController {
|
||||
}
|
||||
|
||||
const mediaItemId = episode?.id || libraryItem.mediaId
|
||||
let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId)
|
||||
let listeningSessions = await this.getUserItemListeningSessionsHelper(req.userNew.id, mediaItemId)
|
||||
|
||||
const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10
|
||||
const page = toNumber(req.query.page, 0)
|
||||
@ -70,102 +91,111 @@ class MeController {
|
||||
res.json(payload)
|
||||
}
|
||||
|
||||
// GET: api/me/listening-stats
|
||||
/**
|
||||
* GET: /api/me/listening-stats
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getListeningStats(req, res) {
|
||||
const listeningStats = await this.getUserListeningStatsHelpers(req.user.id)
|
||||
const listeningStats = await this.getUserListeningStatsHelpers(req.userNew.id)
|
||||
res.json(listeningStats)
|
||||
}
|
||||
|
||||
// GET: api/me/progress/:id/:episodeId?
|
||||
/**
|
||||
* GET: /api/me/progress/:id/:episodeId?
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async getMediaProgress(req, res) {
|
||||
const mediaProgress = req.user.getMediaProgress(req.params.id, req.params.episodeId || null)
|
||||
const mediaProgress = req.userNew.getOldMediaProgress(req.params.id, req.params.episodeId || null)
|
||||
if (!mediaProgress) {
|
||||
return res.sendStatus(404)
|
||||
}
|
||||
res.json(mediaProgress)
|
||||
}
|
||||
|
||||
// DELETE: api/me/progress/:id
|
||||
/**
|
||||
* DELETE: /api/me/progress/:id
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async removeMediaProgress(req, res) {
|
||||
if (!req.user.removeMediaProgress(req.params.id)) {
|
||||
return res.sendStatus(200)
|
||||
}
|
||||
await Database.removeMediaProgress(req.params.id)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
await Database.mediaProgressModel.removeById(req.params.id)
|
||||
req.userNew.mediaProgresses = req.userNew.mediaProgresses.filter((mp) => mp.id !== req.params.id)
|
||||
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// PATCH: api/me/progress/:id
|
||||
/**
|
||||
* PATCH: /api/me/progress/:libraryItemId/:episodeId?
|
||||
* TODO: Update to use mediaItemId and mediaItemType
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async createUpdateMediaProgress(req, res) {
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(req.params.id)
|
||||
if (!libraryItem) {
|
||||
return res.status(404).send('Item not found')
|
||||
const progressUpdatePayload = {
|
||||
...req.body,
|
||||
libraryItemId: req.params.libraryItemId,
|
||||
episodeId: req.params.episodeId
|
||||
}
|
||||
const mediaProgressResponse = await req.userNew.createUpdateMediaProgressFromPayload(progressUpdatePayload)
|
||||
if (mediaProgressResponse.error) {
|
||||
return res.status(mediaProgressResponse.statusCode || 400).send(mediaProgressResponse.error)
|
||||
}
|
||||
|
||||
if (req.user.createUpdateMediaProgress(libraryItem, req.body)) {
|
||||
const mediaProgress = req.user.getMediaProgress(libraryItem.id)
|
||||
if (mediaProgress) await Database.upsertMediaProgress(mediaProgress)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
}
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// PATCH: api/me/progress/:id/:episodeId
|
||||
async createUpdateEpisodeMediaProgress(req, res) {
|
||||
const episodeId = req.params.episodeId
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(req.params.id)
|
||||
if (!libraryItem) {
|
||||
return res.status(404).send('Item not found')
|
||||
}
|
||||
if (!libraryItem.media.episodes.find((ep) => ep.id === episodeId)) {
|
||||
Logger.error(`[MeController] removeEpisode episode ${episodeId} not found for item ${libraryItem.id}`)
|
||||
return res.status(404).send('Episode not found')
|
||||
}
|
||||
|
||||
if (req.user.createUpdateMediaProgress(libraryItem, req.body, episodeId)) {
|
||||
const mediaProgress = req.user.getMediaProgress(libraryItem.id, episodeId)
|
||||
if (mediaProgress) await Database.upsertMediaProgress(mediaProgress)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
}
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// PATCH: api/me/progress/batch/update
|
||||
/**
|
||||
* PATCH: /api/me/progress/batch/update
|
||||
* TODO: Update to use mediaItemId and mediaItemType
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async batchUpdateMediaProgress(req, res) {
|
||||
const itemProgressPayloads = req.body
|
||||
if (!itemProgressPayloads?.length) {
|
||||
return res.status(400).send('Missing request payload')
|
||||
}
|
||||
|
||||
let shouldUpdate = false
|
||||
let hasUpdated = false
|
||||
for (const itemProgress of itemProgressPayloads) {
|
||||
const libraryItem = await Database.libraryItemModel.getOldById(itemProgress.libraryItemId)
|
||||
if (libraryItem) {
|
||||
if (req.user.createUpdateMediaProgress(libraryItem, itemProgress, itemProgress.episodeId)) {
|
||||
const mediaProgress = req.user.getMediaProgress(libraryItem.id, itemProgress.episodeId)
|
||||
if (mediaProgress) await Database.upsertMediaProgress(mediaProgress)
|
||||
shouldUpdate = true
|
||||
}
|
||||
const mediaProgressResponse = await req.userNew.createUpdateMediaProgressFromPayload(itemProgress)
|
||||
if (mediaProgressResponse.error) {
|
||||
Logger.error(`[MeController] batchUpdateMediaProgress: ${mediaProgressResponse.error}`)
|
||||
continue
|
||||
} else {
|
||||
Logger.error(`[MeController] batchUpdateMediaProgress: Library Item does not exist ${itemProgress.id}`)
|
||||
hasUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldUpdate) {
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
if (hasUpdated) {
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.userNew.toOldJSONForBrowser())
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
|
||||
// POST: api/me/item/:id/bookmark
|
||||
/**
|
||||
* POST: /api/me/item/:id/bookmark
|
||||
*
|
||||
* @param {RequestWithUser} req
|
||||
* @param {Response} res
|
||||
*/
|
||||
async createBookmark(req, res) {
|
||||
if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404)
|
||||
|
||||
const { time, title } = req.body
|
||||
const bookmark = req.user.createBookmark(req.params.id, time, title)
|
||||
await Database.updateUser(req.user)
|
||||
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
SocketAuthority.clientEmitter(req.userNew.id, 'user_updated', req.user.toJSONForBrowser())
|
||||
res.json(bookmark)
|
||||
}
|
||||
|
||||
|
@ -182,6 +182,61 @@ class MediaProgress extends Model {
|
||||
finishedAt: this.finishedAt?.valueOf() || null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply update to media progress
|
||||
*
|
||||
* @param {Object} progress
|
||||
* @returns {Promise<MediaProgress>}
|
||||
*/
|
||||
applyProgressUpdate(progressPayload) {
|
||||
if (!this.extraData) this.extraData = {}
|
||||
if (progressPayload.isFinished !== undefined) {
|
||||
if (progressPayload.isFinished && !this.isFinished) {
|
||||
this.finishedAt = Date.now()
|
||||
this.extraData.progress = 1
|
||||
this.changed('extraData', true)
|
||||
delete progressPayload.finishedAt
|
||||
} else if (!progressPayload.isFinished && this.isFinished) {
|
||||
this.finishedAt = null
|
||||
this.extraData.progress = 0
|
||||
this.currentTime = 0
|
||||
this.changed('extraData', true)
|
||||
delete progressPayload.finishedAt
|
||||
delete progressPayload.currentTime
|
||||
}
|
||||
} else if (!isNaN(progressPayload.progress) && progressPayload.progress !== this.progress) {
|
||||
// Old model stored progress on object
|
||||
this.extraData.progress = Math.min(1, Math.max(0, progressPayload.progress))
|
||||
this.changed('extraData', true)
|
||||
}
|
||||
|
||||
this.set(progressPayload)
|
||||
|
||||
// Reset hideFromContinueListening if the progress has changed
|
||||
if (this.changed('currentTime') && !progressPayload.hideFromContinueListening) {
|
||||
this.hideFromContinueListening = false
|
||||
}
|
||||
|
||||
const timeRemaining = this.duration - this.currentTime
|
||||
// Set to finished if time remaining is less than 5 seconds
|
||||
if (!this.isFinished && this.duration && timeRemaining < 5) {
|
||||
this.isFinished = true
|
||||
this.finishedAt = this.finishedAt || Date.now()
|
||||
this.extraData.progress = 1
|
||||
this.changed('extraData', true)
|
||||
} else if (this.isFinished && this.changed('currentTime') && this.currentTime < this.duration) {
|
||||
this.isFinished = false
|
||||
this.finishedAt = null
|
||||
}
|
||||
|
||||
// For local sync
|
||||
if (progressPayload.lastUpdate) {
|
||||
this.updatedAt = progressPayload.lastUpdate
|
||||
}
|
||||
|
||||
return this.save()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MediaProgress
|
||||
|
@ -3,6 +3,8 @@ const sequelize = require('sequelize')
|
||||
const Logger = require('../Logger')
|
||||
const oldUser = require('../objects/user/User')
|
||||
const SocketAuthority = require('../SocketAuthority')
|
||||
const { isNullOrNaN } = require('../utils')
|
||||
|
||||
const { DataTypes, Model } = sequelize
|
||||
|
||||
class User extends Model {
|
||||
@ -577,6 +579,116 @@ class User extends Model {
|
||||
})
|
||||
return mediaProgress?.getOldMediaProgress() || null
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Uses old model and should account for the different between ebook/audiobook progress
|
||||
*
|
||||
* @typedef ProgressUpdatePayload
|
||||
* @property {string} libraryItemId
|
||||
* @property {string} [episodeId]
|
||||
* @property {number} [duration]
|
||||
* @property {number} [progress]
|
||||
* @property {number} [currentTime]
|
||||
* @property {boolean} [isFinished]
|
||||
* @property {boolean} [hideFromContinueListening]
|
||||
* @property {string} [ebookLocation]
|
||||
* @property {number} [ebookProgress]
|
||||
* @property {string} [finishedAt]
|
||||
* @property {number} [lastUpdate]
|
||||
*
|
||||
* @param {ProgressUpdatePayload} progressPayload
|
||||
* @returns {Promise<{ mediaProgress: import('./MediaProgress'), error: [string], statusCode: [number] }>}
|
||||
*/
|
||||
async createUpdateMediaProgressFromPayload(progressPayload) {
|
||||
/** @type {import('./MediaProgress')|null} */
|
||||
let mediaProgress = null
|
||||
let mediaItemId = null
|
||||
if (progressPayload.episodeId) {
|
||||
const podcastEpisode = await this.sequelize.models.podcastEpisode.findByPk(progressPayload.episodeId, {
|
||||
attributes: ['id', 'podcastId'],
|
||||
include: [
|
||||
{
|
||||
model: this.sequelize.models.mediaProgress,
|
||||
where: { userId: this.id },
|
||||
required: false
|
||||
},
|
||||
{
|
||||
model: this.sequelize.models.podcast,
|
||||
attributes: ['id', 'title'],
|
||||
include: {
|
||||
model: this.sequelize.models.libraryItem,
|
||||
attributes: ['id']
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
if (!podcastEpisode) {
|
||||
Logger.error(`[User] createUpdateMediaProgress: episode ${progressPayload.episodeId} not found`)
|
||||
return {
|
||||
error: 'Episode not found',
|
||||
statusCode: 404
|
||||
}
|
||||
}
|
||||
mediaItemId = podcastEpisode.id
|
||||
mediaProgress = podcastEpisode.mediaProgresses?.[0]
|
||||
} else {
|
||||
const libraryItem = await this.sequelize.models.libraryItem.findByPk(progressPayload.libraryItemId, {
|
||||
attributes: ['id', 'mediaId', 'mediaType'],
|
||||
include: {
|
||||
model: this.sequelize.models.book,
|
||||
attributes: ['id', 'title'],
|
||||
required: false,
|
||||
include: {
|
||||
model: this.sequelize.models.mediaProgress,
|
||||
where: { userId: this.id },
|
||||
required: false
|
||||
}
|
||||
}
|
||||
})
|
||||
if (!libraryItem) {
|
||||
Logger.error(`[User] createUpdateMediaProgress: library item ${progressPayload.libraryItemId} not found`)
|
||||
return {
|
||||
error: 'Library item not found',
|
||||
statusCode: 404
|
||||
}
|
||||
}
|
||||
mediaItemId = libraryItem.media.id
|
||||
mediaProgress = libraryItem.media.mediaProgresses?.[0]
|
||||
}
|
||||
|
||||
if (mediaProgress) {
|
||||
mediaProgress = await mediaProgress.applyProgressUpdate(progressPayload)
|
||||
this.mediaProgresses = this.mediaProgresses.map((mp) => (mp.id === mediaProgress.id ? mediaProgress : mp))
|
||||
} else {
|
||||
const newMediaProgressPayload = {
|
||||
userId: this.id,
|
||||
mediaItemId,
|
||||
mediaItemType: progressPayload.episodeId ? 'podcastEpisode' : 'book',
|
||||
duration: isNullOrNaN(progressPayload.duration) ? 0 : Number(progressPayload.duration),
|
||||
currentTime: isNullOrNaN(progressPayload.currentTime) ? 0 : Number(progressPayload.currentTime),
|
||||
isFinished: !!progressPayload.isFinished,
|
||||
hideFromContinueListening: !!progressPayload.hideFromContinueListening,
|
||||
ebookLocation: progressPayload.ebookLocation || null,
|
||||
ebookProgress: isNullOrNaN(progressPayload.ebookProgress) ? 0 : Number(progressPayload.ebookProgress),
|
||||
finishedAt: progressPayload.finishedAt || null,
|
||||
extraData: {
|
||||
libraryItemId: progressPayload.libraryItemId,
|
||||
progress: isNullOrNaN(progressPayload.progress) ? 0 : Number(progressPayload.progress)
|
||||
}
|
||||
}
|
||||
if (newMediaProgressPayload.isFinished) {
|
||||
newMediaProgressPayload.finishedAt = new Date()
|
||||
newMediaProgressPayload.extraData.progress = 1
|
||||
} else {
|
||||
newMediaProgressPayload.finishedAt = null
|
||||
}
|
||||
mediaProgress = await this.sequelize.models.mediaProgress.create(newMediaProgressPayload)
|
||||
this.mediaProgresses.push(mediaProgress)
|
||||
}
|
||||
return {
|
||||
mediaProgress
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User
|
||||
|
@ -86,9 +86,9 @@ class User {
|
||||
pash: this.pash,
|
||||
type: this.type,
|
||||
token: this.token,
|
||||
mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
|
||||
mediaProgress: this.mediaProgress ? this.mediaProgress.map((li) => li.toJSON()) : [],
|
||||
seriesHideFromContinueListening: [...this.seriesHideFromContinueListening],
|
||||
bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
|
||||
bookmarks: this.bookmarks ? this.bookmarks.map((b) => b.toJSON()) : [],
|
||||
isActive: this.isActive,
|
||||
isLocked: this.isLocked,
|
||||
lastSeen: this.lastSeen,
|
||||
@ -107,10 +107,10 @@ class User {
|
||||
username: this.username,
|
||||
email: this.email,
|
||||
type: this.type,
|
||||
token: (this.type === 'root' && hideRootToken) ? '' : this.token,
|
||||
mediaProgress: this.mediaProgress ? this.mediaProgress.map(li => li.toJSON()) : [],
|
||||
token: this.type === 'root' && hideRootToken ? '' : this.token,
|
||||
mediaProgress: this.mediaProgress ? this.mediaProgress.map((li) => li.toJSON()) : [],
|
||||
seriesHideFromContinueListening: [...this.seriesHideFromContinueListening],
|
||||
bookmarks: this.bookmarks ? this.bookmarks.map(b => b.toJSON()) : [],
|
||||
bookmarks: this.bookmarks ? this.bookmarks.map((b) => b.toJSON()) : [],
|
||||
isActive: this.isActive,
|
||||
isLocked: this.isLocked,
|
||||
lastSeen: this.lastSeen,
|
||||
@ -133,7 +133,7 @@ class User {
|
||||
* @returns {object}
|
||||
*/
|
||||
toJSONForPublic(sessions) {
|
||||
const userSession = sessions?.find(s => s.userId === this.id) || null
|
||||
const userSession = sessions?.find((s) => s.userId === this.id) || null
|
||||
const session = userSession?.toJSONForClient() || null
|
||||
return {
|
||||
id: this.id,
|
||||
@ -157,18 +157,18 @@ class User {
|
||||
|
||||
this.mediaProgress = []
|
||||
if (user.mediaProgress) {
|
||||
this.mediaProgress = user.mediaProgress.map(li => new MediaProgress(li)).filter(lip => lip.id)
|
||||
this.mediaProgress = user.mediaProgress.map((li) => new MediaProgress(li)).filter((lip) => lip.id)
|
||||
}
|
||||
|
||||
this.bookmarks = []
|
||||
if (user.bookmarks) {
|
||||
this.bookmarks = user.bookmarks.filter(bm => typeof bm.libraryItemId == 'string').map(bm => new AudioBookmark(bm))
|
||||
this.bookmarks = user.bookmarks.filter((bm) => typeof bm.libraryItemId == 'string').map((bm) => new AudioBookmark(bm))
|
||||
}
|
||||
|
||||
this.seriesHideFromContinueListening = []
|
||||
if (user.seriesHideFromContinueListening) this.seriesHideFromContinueListening = [...user.seriesHideFromContinueListening]
|
||||
|
||||
this.isActive = (user.isActive === undefined || user.type === 'root') ? true : !!user.isActive
|
||||
this.isActive = user.isActive === undefined || user.type === 'root' ? true : !!user.isActive
|
||||
this.isLocked = user.type === 'root' ? false : !!user.isLocked
|
||||
this.lastSeen = user.lastSeen || null
|
||||
this.createdAt = user.createdAt || Date.now()
|
||||
@ -200,7 +200,8 @@ class User {
|
||||
const keysToCheck = ['pash', 'type', 'username', 'email', 'isActive']
|
||||
keysToCheck.forEach((key) => {
|
||||
if (payload[key] !== undefined) {
|
||||
if (key === 'isActive' || payload[key]) { // pash, type, username must evaluate to true (cannot be null or empty)
|
||||
if (key === 'isActive' || payload[key]) {
|
||||
// pash, type, username must evaluate to true (cannot be null or empty)
|
||||
if (payload[key] !== this[key]) {
|
||||
hasUpdates = true
|
||||
this[key] = payload[key]
|
||||
@ -285,7 +286,7 @@ class User {
|
||||
|
||||
/**
|
||||
* Update user permissions from external JSON
|
||||
*
|
||||
*
|
||||
* @param {Object} absPermissions JSON containing user permissions
|
||||
* @returns {boolean} true if updates were made
|
||||
*/
|
||||
@ -294,7 +295,7 @@ class User {
|
||||
let updatedUserPermissions = {}
|
||||
|
||||
// Initialize all permissions to false first
|
||||
Object.keys(User.permissionMapping).forEach(mappingKey => {
|
||||
Object.keys(User.permissionMapping).forEach((mappingKey) => {
|
||||
const userPermKey = User.permissionMapping[mappingKey]
|
||||
if (typeof this.permissions[userPermKey] === 'boolean') {
|
||||
updatedUserPermissions[userPermKey] = false // Default to false for boolean permissions
|
||||
@ -302,7 +303,7 @@ class User {
|
||||
})
|
||||
|
||||
// Map the boolean permissions from absPermissions
|
||||
Object.keys(absPermissions).forEach(absKey => {
|
||||
Object.keys(absPermissions).forEach((absKey) => {
|
||||
const userPermKey = User.permissionMapping[absKey]
|
||||
if (!userPermKey) {
|
||||
throw new Error(`Unexpected permission property: ${absKey}`)
|
||||
@ -326,7 +327,7 @@ class User {
|
||||
hasUpdates = true
|
||||
}
|
||||
} else if (absPermissions.allowedLibraries?.length && absPermissions.allowedLibraries.join(',') !== this.librariesAccessible.join(',')) {
|
||||
if (absPermissions.allowedLibraries.some(lid => typeof lid !== 'string')) {
|
||||
if (absPermissions.allowedLibraries.some((lid) => typeof lid !== 'string')) {
|
||||
throw new Error('Invalid permission property "allowedLibraries", expecting array of strings')
|
||||
}
|
||||
this.librariesAccessible = absPermissions.allowedLibraries
|
||||
@ -340,7 +341,7 @@ class User {
|
||||
hasUpdates = true
|
||||
}
|
||||
} else if (absPermissions.allowedTags?.length && absPermissions.allowedTags.join(',') !== this.itemTagsSelected.join(',')) {
|
||||
if (absPermissions.allowedTags.some(tag => typeof tag !== 'string')) {
|
||||
if (absPermissions.allowedTags.some((tag) => typeof tag !== 'string')) {
|
||||
throw new Error('Invalid permission property "allowedTags", expecting array of strings')
|
||||
}
|
||||
this.itemTagsSelected = absPermissions.allowedTags
|
||||
@ -350,10 +351,9 @@ class User {
|
||||
return hasUpdates
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a sample to show how a JSON for updatePermissionsFromExternalJSON should look like
|
||||
*
|
||||
* Get a sample to show how a JSON for updatePermissionsFromExternalJSON should look like
|
||||
*
|
||||
* @returns {string} JSON string
|
||||
*/
|
||||
static getSampleAbsPermissions() {
|
||||
@ -375,18 +375,18 @@ class User {
|
||||
|
||||
/**
|
||||
* Get first available library id for user
|
||||
*
|
||||
*
|
||||
* @param {string[]} libraryIds
|
||||
* @returns {string|null}
|
||||
*/
|
||||
getDefaultLibraryId(libraryIds) {
|
||||
// Libraries should already be in ascending display order, find first accessible
|
||||
return libraryIds.find(lid => this.checkCanAccessLibrary(lid)) || null
|
||||
return libraryIds.find((lid) => this.checkCanAccessLibrary(lid)) || null
|
||||
}
|
||||
|
||||
getMediaProgress(libraryItemId, episodeId = null) {
|
||||
if (!this.mediaProgress) return null
|
||||
return this.mediaProgress.find(lip => {
|
||||
return this.mediaProgress.find((lip) => {
|
||||
if (episodeId && lip.episodeId !== episodeId) return false
|
||||
return lip.libraryItemId === libraryItemId
|
||||
})
|
||||
@ -394,11 +394,11 @@ class User {
|
||||
|
||||
getAllMediaProgressForLibraryItem(libraryItemId) {
|
||||
if (!this.mediaProgress) return []
|
||||
return this.mediaProgress.filter(li => li.libraryItemId === libraryItemId)
|
||||
return this.mediaProgress.filter((li) => li.libraryItemId === libraryItemId)
|
||||
}
|
||||
|
||||
createUpdateMediaProgress(libraryItem, updatePayload, episodeId = null) {
|
||||
const itemProgress = this.mediaProgress.find(li => {
|
||||
const itemProgress = this.mediaProgress.find((li) => {
|
||||
if (episodeId && li.episodeId !== episodeId) return false
|
||||
return li.libraryItemId === libraryItem.id
|
||||
})
|
||||
@ -415,12 +415,6 @@ class User {
|
||||
return wasUpdated
|
||||
}
|
||||
|
||||
removeMediaProgress(id) {
|
||||
if (!this.mediaProgress.some(mp => mp.id === id)) return false
|
||||
this.mediaProgress = this.mediaProgress.filter(mp => mp.id !== id)
|
||||
return true
|
||||
}
|
||||
|
||||
checkCanAccessLibrary(libraryId) {
|
||||
if (this.permissions.accessAllLibraries) return true
|
||||
if (!this.librariesAccessible) return false
|
||||
@ -431,10 +425,10 @@ class User {
|
||||
if (this.permissions.accessAllTags) return true
|
||||
if (this.permissions.selectedTagsNotAccessible) {
|
||||
if (!tags?.length) return true
|
||||
return tags.every(tag => !this.itemTagsSelected.includes(tag))
|
||||
return tags.every((tag) => !this.itemTagsSelected.includes(tag))
|
||||
}
|
||||
if (!tags?.length) return false
|
||||
return this.itemTagsSelected.some(tag => tags.includes(tag))
|
||||
return this.itemTagsSelected.some((tag) => tags.includes(tag))
|
||||
}
|
||||
|
||||
checkCanAccessLibraryItem(libraryItem) {
|
||||
@ -446,9 +440,9 @@ class User {
|
||||
|
||||
/**
|
||||
* Checks if a user can access a library item
|
||||
* @param {string} libraryId
|
||||
* @param {boolean} explicit
|
||||
* @param {string[]} tags
|
||||
* @param {string} libraryId
|
||||
* @param {boolean} explicit
|
||||
* @param {string[]} tags
|
||||
*/
|
||||
checkCanAccessLibraryItemWithData(libraryId, explicit, tags) {
|
||||
if (!this.checkCanAccessLibrary(libraryId)) return false
|
||||
@ -457,7 +451,7 @@ class User {
|
||||
}
|
||||
|
||||
findBookmark(libraryItemId, time) {
|
||||
return this.bookmarks.find(bm => bm.libraryItemId === libraryItemId && bm.time == time)
|
||||
return this.bookmarks.find((bm) => bm.libraryItemId === libraryItemId && bm.time == time)
|
||||
}
|
||||
|
||||
createBookmark(libraryItemId, time, title) {
|
||||
@ -484,7 +478,7 @@ class User {
|
||||
}
|
||||
|
||||
removeBookmark(libraryItemId, time) {
|
||||
this.bookmarks = this.bookmarks.filter(bm => (bm.libraryItemId !== libraryItemId || bm.time !== time))
|
||||
this.bookmarks = this.bookmarks.filter((bm) => bm.libraryItemId !== libraryItemId || bm.time !== time)
|
||||
}
|
||||
|
||||
checkShouldHideSeriesFromContinueListening(seriesId) {
|
||||
@ -499,12 +493,12 @@ class User {
|
||||
|
||||
removeSeriesFromHideFromContinueListening(seriesId) {
|
||||
if (!this.seriesHideFromContinueListening.includes(seriesId)) return false
|
||||
this.seriesHideFromContinueListening = this.seriesHideFromContinueListening.filter(sid => sid !== seriesId)
|
||||
this.seriesHideFromContinueListening = this.seriesHideFromContinueListening.filter((sid) => sid !== seriesId)
|
||||
return true
|
||||
}
|
||||
|
||||
removeProgressFromContinueListening(progressId) {
|
||||
const progress = this.mediaProgress.find(mp => mp.id === progressId)
|
||||
const progress = this.mediaProgress.find((mp) => mp.id === progressId)
|
||||
if (!progress) return false
|
||||
return progress.removeFromContinueListening()
|
||||
}
|
||||
@ -512,7 +506,7 @@ class User {
|
||||
/**
|
||||
* Number of podcast episodes not finished for library item
|
||||
* Note: libraryItem passed in from libraryHelpers is not a LibraryItem class instance
|
||||
* @param {LibraryItem|object} libraryItem
|
||||
* @param {LibraryItem|object} libraryItem
|
||||
* @returns {number}
|
||||
*/
|
||||
getNumEpisodesIncompleteForPodcast(libraryItem) {
|
||||
@ -527,4 +521,4 @@ class User {
|
||||
return numEpisodesIncomplete
|
||||
}
|
||||
}
|
||||
module.exports = User
|
||||
module.exports = User
|
||||
|
@ -176,9 +176,8 @@ class ApiRouter {
|
||||
this.router.get('/me/progress/:id/remove-from-continue-listening', MeController.removeItemFromContinueListening.bind(this))
|
||||
this.router.get('/me/progress/:id/:episodeId?', MeController.getMediaProgress.bind(this))
|
||||
this.router.patch('/me/progress/batch/update', MeController.batchUpdateMediaProgress.bind(this))
|
||||
this.router.patch('/me/progress/:id', MeController.createUpdateMediaProgress.bind(this))
|
||||
this.router.patch('/me/progress/:libraryItemId/:episodeId?', MeController.createUpdateMediaProgress.bind(this))
|
||||
this.router.delete('/me/progress/:id', MeController.removeMediaProgress.bind(this))
|
||||
this.router.patch('/me/progress/:id/:episodeId', MeController.createUpdateEpisodeMediaProgress.bind(this))
|
||||
this.router.post('/me/item/:id/bookmark', MeController.createBookmark.bind(this))
|
||||
this.router.patch('/me/item/:id/bookmark', MeController.updateBookmark.bind(this))
|
||||
this.router.delete('/me/item/:id/bookmark/:time', MeController.removeBookmark.bind(this))
|
||||
|
Loading…
Reference in New Issue
Block a user