Remove old User object with old MediaProgress & AudioBookmark

This commit is contained in:
advplyr 2024-08-19 17:26:17 -05:00
parent 27b3a44147
commit bb1a72269a
5 changed files with 19 additions and 569 deletions

View File

@ -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<boolean>}
*/
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

View File

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

View File

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

View File

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

View File

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