Update API media progress endpoints to use new user model. Merge book & episode endpoints

This commit is contained in:
advplyr 2024-08-11 11:53:30 -05:00
parent 68ef3a07a7
commit 9cd92c7b7f
8 changed files with 295 additions and 109 deletions

View File

@ -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>

View File

@ -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) => {

View File

@ -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)))

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))