diff --git a/server/models/User.js b/server/models/User.js index f4b3da7a..05c74a7f 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -114,104 +114,6 @@ class User extends Model { } } - /** - * @deprecated - * Get old user model from new - * TODO: Currently only used in dbMigration, replace - * - * @param {User} userExpanded - * @returns {oldUser} - */ - static getOldUser(userExpanded) { - const mediaProgress = userExpanded.mediaProgresses.map((mp) => mp.getOldMediaProgress()) - - const librariesAccessible = [...(userExpanded.permissions?.librariesAccessible || [])] - const itemTagsSelected = [...(userExpanded.permissions?.itemTagsSelected || [])] - const permissions = { ...(userExpanded.permissions || {}) } - delete permissions.librariesAccessible - delete permissions.itemTagsSelected - - const seriesHideFromContinueListening = userExpanded.extraData?.seriesHideFromContinueListening || [] - - return new oldUser({ - id: userExpanded.id, - oldUserId: userExpanded.extraData?.oldUserId || null, - username: userExpanded.username, - email: userExpanded.email || null, - pash: userExpanded.pash, - type: userExpanded.type, - token: userExpanded.token, - mediaProgress, - seriesHideFromContinueListening: [...seriesHideFromContinueListening], - bookmarks: userExpanded.bookmarks, - isActive: userExpanded.isActive, - isLocked: userExpanded.isLocked, - lastSeen: userExpanded.lastSeen?.valueOf() || null, - createdAt: userExpanded.createdAt.valueOf(), - permissions, - librariesAccessible, - itemTagsSelected, - authOpenIDSub: userExpanded.extraData?.authOpenIDSub || null - }) - } - - /** - * @deprecated - * Update User from old user model - * TODO: Currently only used in dbMigration, replace - * - * @param {oldUser} oldUser - * @returns {Promise} - */ - static updateFromOld(oldUser) { - const user = this.getFromOld(oldUser) - return this.update(user, { - where: { - id: user.id - } - }) - .then((result) => result[0] > 0) - .catch((error) => { - Logger.error(`[User] Failed to save user ${oldUser.id}`, error) - return false - }) - } - - /** - * Get new User model from old - * - * @param {oldUser} oldUser - * @returns {Object} - */ - static getFromOld(oldUser) { - const extraData = { - seriesHideFromContinueListening: oldUser.seriesHideFromContinueListening || [], - oldUserId: oldUser.oldUserId - } - if (oldUser.authOpenIDSub) { - extraData.authOpenIDSub = oldUser.authOpenIDSub - } - - return { - id: oldUser.id, - username: oldUser.username, - email: oldUser.email || null, - pash: oldUser.pash || null, - type: oldUser.type || null, - token: oldUser.token || null, - isActive: !!oldUser.isActive, - lastSeen: oldUser.lastSeen || null, - extraData, - createdAt: oldUser.createdAt || Date.now(), - permissions: { - ...oldUser.permissions, - librariesAccessible: oldUser.librariesAccessible || [], - itemTagsSelected: oldUser.itemTagsSelected || [] - }, - bookmarks: oldUser.bookmarks - } - } - /** * Create root user * @param {string} username diff --git a/server/objects/user/AudioBookmark.js b/server/objects/user/AudioBookmark.js deleted file mode 100644 index 98599a8d..00000000 --- a/server/objects/user/AudioBookmark.js +++ /dev/null @@ -1,36 +0,0 @@ -class AudioBookmark { - constructor(bookmark) { - this.libraryItemId = null - this.title = null - this.time = null - this.createdAt = null - - if (bookmark) { - this.construct(bookmark) - } - } - - toJSON() { - return { - libraryItemId: this.libraryItemId, - title: this.title || '', - time: this.time, - createdAt: this.createdAt - } - } - - construct(bookmark) { - this.libraryItemId = bookmark.libraryItemId - this.title = bookmark.title || '' - this.time = bookmark.time || 0 - this.createdAt = bookmark.createdAt - } - - setData(libraryItemId, time, title) { - this.libraryItemId = libraryItemId - this.title = title - this.time = time - this.createdAt = Date.now() - } -} -module.exports = AudioBookmark \ No newline at end of file diff --git a/server/objects/user/MediaProgress.js b/server/objects/user/MediaProgress.js deleted file mode 100644 index 1b043925..00000000 --- a/server/objects/user/MediaProgress.js +++ /dev/null @@ -1,157 +0,0 @@ -const uuidv4 = require("uuid").v4 - -class MediaProgress { - constructor(progress) { - this.id = null - this.userId = null - this.libraryItemId = null - this.episodeId = null // For podcasts - - this.mediaItemId = null // For use in new data model - this.mediaItemType = null // For use in new data model - - this.duration = null - this.progress = null // 0 to 1 - this.currentTime = null // seconds - this.isFinished = false - this.hideFromContinueListening = false - - this.ebookLocation = null // cfi tag for epub, page number for pdf - this.ebookProgress = null // 0 to 1 - - this.lastUpdate = null - this.startedAt = null - this.finishedAt = null - - if (progress) { - this.construct(progress) - } - } - - toJSON() { - return { - id: this.id, - userId: this.userId, - libraryItemId: this.libraryItemId, - episodeId: this.episodeId, - mediaItemId: this.mediaItemId, - mediaItemType: this.mediaItemType, - duration: this.duration, - progress: this.progress, - currentTime: this.currentTime, - isFinished: this.isFinished, - hideFromContinueListening: this.hideFromContinueListening, - ebookLocation: this.ebookLocation, - ebookProgress: this.ebookProgress, - lastUpdate: this.lastUpdate, - startedAt: this.startedAt, - finishedAt: this.finishedAt - } - } - - construct(progress) { - this.id = progress.id - this.userId = progress.userId - this.libraryItemId = progress.libraryItemId - this.episodeId = progress.episodeId - this.mediaItemId = progress.mediaItemId - this.mediaItemType = progress.mediaItemType - this.duration = progress.duration || 0 - this.progress = progress.progress - this.currentTime = progress.currentTime || 0 - this.isFinished = !!progress.isFinished - this.hideFromContinueListening = !!progress.hideFromContinueListening - this.ebookLocation = progress.ebookLocation || null - this.ebookProgress = progress.ebookProgress || null - this.lastUpdate = progress.lastUpdate - this.startedAt = progress.startedAt - this.finishedAt = progress.finishedAt || null - } - - get inProgress() { - return !this.isFinished && (this.progress > 0 || (this.ebookLocation != null && this.ebookProgress > 0)) - } - - get notStarted() { - return !this.isFinished && this.progress == 0 - } - - setData(libraryItem, progress, episodeId, userId) { - this.id = uuidv4() - this.userId = userId - this.libraryItemId = libraryItem.id - this.episodeId = episodeId - - // PodcastEpisodeId or BookId - this.mediaItemId = episodeId || libraryItem.media.id - this.mediaItemType = episodeId ? 'podcastEpisode' : 'book' - - this.duration = progress.duration || 0 - this.progress = Math.min(1, (progress.progress || 0)) - this.currentTime = progress.currentTime || 0 - this.isFinished = !!progress.isFinished || this.progress == 1 - this.hideFromContinueListening = !!progress.hideFromContinueListening - this.ebookLocation = progress.ebookLocation - this.ebookProgress = Math.min(1, (progress.ebookProgress || 0)) - this.lastUpdate = Date.now() - this.finishedAt = null - if (this.isFinished) { - this.finishedAt = progress.finishedAt || Date.now() - this.progress = 1 - } - this.startedAt = progress.startedAt || this.finishedAt || Date.now() - } - - update(payload) { - let hasUpdates = false - for (const key in payload) { - if (this[key] !== undefined && payload[key] !== this[key]) { - if (key === 'isFinished') { - if (!payload[key]) { // Updating to Not Finished - Reset progress and current time - this.finishedAt = null - this.progress = 0 - this.currentTime = 0 - } else { // Updating to Finished - if (!this.finishedAt) this.finishedAt = Date.now() - this.progress = 1 - } - } - - this[key] = payload[key] - hasUpdates = true - } - } - - var timeRemaining = this.duration - this.currentTime - // If time remaining is less than 5 seconds then mark as finished - if ((this.progress >= 1 || (this.duration && !isNaN(timeRemaining) && timeRemaining < 5))) { - this.isFinished = true - this.finishedAt = payload.finishedAt || Date.now() - this.progress = 1 - } else if (this.progress < 1 && this.isFinished) { - this.isFinished = false - this.finishedAt = null - } - - if (!this.startedAt) { - this.startedAt = this.finishedAt || Date.now() - } - if (hasUpdates) { - if (payload.hideFromContinueListening === undefined) { - // Reset this flag when the media progress is updated - this.hideFromContinueListening = false - } - - this.lastUpdate = Date.now() - } - return hasUpdates - } - - removeFromContinueListening() { - if (this.hideFromContinueListening) return false - - this.hideFromContinueListening = true - return true - } -} -module.exports = MediaProgress \ No newline at end of file diff --git a/server/objects/user/User.js b/server/objects/user/User.js deleted file mode 100644 index f98451b0..00000000 --- a/server/objects/user/User.js +++ /dev/null @@ -1,271 +0,0 @@ -const AudioBookmark = require('./AudioBookmark') -const MediaProgress = require('./MediaProgress') - -class User { - constructor(user) { - this.id = null - this.oldUserId = null // TODO: Temp for keeping old access tokens - this.username = null - this.email = null - this.pash = null - this.type = null - this.token = null - this.isActive = true - this.isLocked = false - this.lastSeen = null - this.createdAt = null - - this.mediaProgress = [] - this.seriesHideFromContinueListening = [] // Series IDs that should not show on home page continue listening - this.bookmarks = [] - - this.permissions = {} - this.librariesAccessible = [] // Library IDs (Empty if ALL libraries) - this.itemTagsSelected = [] // Empty if ALL item tags accessible - - this.authOpenIDSub = null - - if (user) { - this.construct(user) - } - } - - get isRoot() { - return this.type === 'root' - } - get isAdmin() { - return this.type === 'admin' - } - get isUser() { - return this.type === 'user' - } - get isGuest() { - return this.type === 'guest' - } - get isAdminOrUp() { - return this.isAdmin || this.isRoot - } - get canDelete() { - return !!this.permissions.delete && this.isActive - } - get canUpdate() { - return !!this.permissions.update && this.isActive - } - get canDownload() { - return !!this.permissions.download && this.isActive - } - get canUpload() { - return !!this.permissions.upload && this.isActive - } - get canAccessExplicitContent() { - return !!this.permissions.accessExplicitContent && this.isActive - } - get hasPw() { - return !!this.pash && !!this.pash.length - } - - getDefaultUserPermissions() { - return { - download: true, - update: this.type === 'root' || this.type === 'admin', - delete: this.type === 'root', - upload: this.type === 'root' || this.type === 'admin', - accessAllLibraries: true, - accessAllTags: true, - accessExplicitContent: true - } - } - - toJSON() { - return { - id: this.id, - oldUserId: this.oldUserId, - username: this.username, - email: this.email, - pash: this.pash, - type: this.type, - token: this.token, - mediaProgress: this.mediaProgress ? this.mediaProgress.map((li) => li.toJSON()) : [], - seriesHideFromContinueListening: [...this.seriesHideFromContinueListening], - bookmarks: this.bookmarks ? this.bookmarks.map((b) => b.toJSON()) : [], - isActive: this.isActive, - isLocked: this.isLocked, - lastSeen: this.lastSeen, - createdAt: this.createdAt, - permissions: this.permissions, - librariesAccessible: [...this.librariesAccessible], - itemTagsSelected: [...this.itemTagsSelected], - authOpenIDSub: this.authOpenIDSub - } - } - - toJSONForBrowser(hideRootToken = false, minimal = false) { - const json = { - id: this.id, - oldUserId: this.oldUserId, - 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()) : [], - seriesHideFromContinueListening: [...this.seriesHideFromContinueListening], - bookmarks: this.bookmarks ? this.bookmarks.map((b) => b.toJSON()) : [], - isActive: this.isActive, - isLocked: this.isLocked, - lastSeen: this.lastSeen, - createdAt: this.createdAt, - permissions: this.permissions, - librariesAccessible: [...this.librariesAccessible], - itemTagsSelected: [...this.itemTagsSelected], - hasOpenIDLink: !!this.authOpenIDSub - } - if (minimal) { - delete json.mediaProgress - delete json.bookmarks - } - return json - } - - /** - * User data for clients - * @param {[oldPlaybackSession[]]} sessions optional array of open playback sessions - * @returns {object} - */ - toJSONForPublic(sessions) { - const userSession = sessions?.find((s) => s.userId === this.id) || null - const session = userSession?.toJSONForClient() || null - return { - id: this.id, - oldUserId: this.oldUserId, - username: this.username, - type: this.type, - session, - lastSeen: this.lastSeen, - createdAt: this.createdAt - } - } - - construct(user) { - this.id = user.id - this.oldUserId = user.oldUserId - this.username = user.username - this.email = user.email || null - this.pash = user.pash - this.type = user.type - this.token = user.token - - this.mediaProgress = [] - if (user.mediaProgress) { - 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.seriesHideFromContinueListening = [] - if (user.seriesHideFromContinueListening) this.seriesHideFromContinueListening = [...user.seriesHideFromContinueListening] - - 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() - this.permissions = user.permissions || this.getDefaultUserPermissions() - // Upload permission added v1.1.13, make sure root user has upload permissions - if (this.type === 'root' && !this.permissions.upload) this.permissions.upload = true - - // Library restriction permissions added v1.4.14, defaults to all libraries - if (this.permissions.accessAllLibraries === undefined) this.permissions.accessAllLibraries = true - // Library restriction permissions added v2.0, defaults to all libraries - if (this.permissions.accessAllTags === undefined) this.permissions.accessAllTags = true - // Explicit content restriction permission added v2.0.18 - if (this.permissions.accessExplicitContent === undefined) this.permissions.accessExplicitContent = true - // itemTagsAccessible was renamed to itemTagsSelected in version v2.2.20 - if (user.itemTagsAccessible?.length) { - this.permissions.selectedTagsNotAccessible = false - user.itemTagsSelected = user.itemTagsAccessible - } - - this.librariesAccessible = [...(user.librariesAccessible || [])] - this.itemTagsSelected = [...(user.itemTagsSelected || [])] - - this.authOpenIDSub = user.authOpenIDSub || null - } - - update(payload) { - var hasUpdates = false - // Update the following keys: - 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 (payload[key] !== this[key]) { - hasUpdates = true - this[key] = payload[key] - } - } - } - }) - - if (payload.seriesHideFromContinueListening && Array.isArray(payload.seriesHideFromContinueListening)) { - if (this.seriesHideFromContinueListening.join(',') !== payload.seriesHideFromContinueListening.join(',')) { - hasUpdates = true - this.seriesHideFromContinueListening = [...payload.seriesHideFromContinueListening] - } - } - - // And update permissions - if (payload.permissions) { - for (const key in payload.permissions) { - if (payload.permissions[key] !== this.permissions[key]) { - hasUpdates = true - this.permissions[key] = payload.permissions[key] - } - } - } - - // Update accessible libraries - if (this.permissions.accessAllLibraries) { - // Access all libraries - if (this.librariesAccessible.length) { - this.librariesAccessible = [] - hasUpdates = true - } - } else if (payload.librariesAccessible !== undefined) { - if (payload.librariesAccessible.length) { - if (payload.librariesAccessible.join(',') !== this.librariesAccessible.join(',')) { - hasUpdates = true - this.librariesAccessible = [...payload.librariesAccessible] - } - } else if (this.librariesAccessible.length > 0) { - hasUpdates = true - this.librariesAccessible = [] - } - } - - // Update accessible tags - if (this.permissions.accessAllTags) { - // Access all tags - if (this.itemTagsSelected.length) { - this.itemTagsSelected = [] - this.permissions.selectedTagsNotAccessible = false - hasUpdates = true - } - } else if (payload.itemTagsSelected !== undefined) { - if (payload.itemTagsSelected.length) { - if (payload.itemTagsSelected.join(',') !== this.itemTagsSelected.join(',')) { - hasUpdates = true - this.itemTagsSelected = [...payload.itemTagsSelected] - } - } else if (this.itemTagsSelected.length > 0) { - hasUpdates = true - this.itemTagsSelected = [] - this.permissions.selectedTagsNotAccessible = false - } - } - return hasUpdates - } -} -module.exports = User diff --git a/server/utils/migrations/dbMigration.js b/server/utils/migrations/dbMigration.js index 85631783..0a48ac60 100644 --- a/server/utils/migrations/dbMigration.js +++ b/server/utils/migrations/dbMigration.js @@ -1289,10 +1289,9 @@ async function handleOldUsers(ctx) { const usersNew = await ctx.userModel.findAll({ include: ctx.models.mediaProgress }) - const users = usersNew.map((u) => ctx.userModel.getOldUser(u)) let usersUpdated = 0 - for (const user of users) { + for (const user of usersNew) { let hasUpdates = false if (user.bookmarks?.length) { user.bookmarks = user.bookmarks @@ -1305,21 +1304,31 @@ async function handleOldUsers(ctx) { return bm }) .filter((bm) => bm.libraryItemId) + if (hasUpdates) { + user.changed('bookmarks', true) + } } + const librariesAccessible = user.permissions?.librariesAccessible || [] + // Convert old library ids to new library ids - if (user.librariesAccessible?.length) { - user.librariesAccessible = user.librariesAccessible + if (librariesAccessible.length) { + user.permissions.librariesAccessible = librariesAccessible .map((lid) => { if (!lid.startsWith('lib_') && lid !== 'main') return lid // Already not an old library id so dont change hasUpdates = true return oldDbIdMap.libraries[lid] }) .filter((lid) => lid) + if (hasUpdates) { + user.changed('permissions', true) + } } - if (user.seriesHideFromContinueListening?.length) { - user.seriesHideFromContinueListening = user.seriesHideFromContinueListening + const seriesHideFromContinueListening = user.extraData?.seriesHideFromContinueListening || [] + + if (seriesHideFromContinueListening.length) { + user.extraData.seriesHideFromContinueListening = seriesHideFromContinueListening .map((seriesId) => { if (seriesId.startsWith('se_')) { hasUpdates = true @@ -1328,10 +1337,13 @@ async function handleOldUsers(ctx) { return seriesId }) .filter((se) => se) + if (hasUpdates) { + user.changed('extraData', true) + } } if (hasUpdates) { - await ctx.models.user.updateFromOld(user) + await user.save() usersUpdated++ } }