mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-04-20 01:17:45 +02:00
Update:Auth to use new user model
- Express requests include userNew to start migrating API controllers to new user model
This commit is contained in:
parent
59370cae81
commit
202ceb02b5
@ -157,10 +157,6 @@ export default {
|
|||||||
this.init()
|
this.init()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (this.$refs.accountModal) {
|
|
||||||
this.$refs.accountModal.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$root.socket) {
|
if (this.$root.socket) {
|
||||||
this.$root.socket.off('user_added', this.addUpdateUser)
|
this.$root.socket.off('user_added', this.addUpdateUser)
|
||||||
this.$root.socket.off('user_updated', this.addUpdateUser)
|
this.$root.socket.off('user_updated', this.addUpdateUser)
|
||||||
|
@ -39,6 +39,11 @@ export default {
|
|||||||
this.showAccountModal = true
|
this.showAccountModal = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.$refs.accountModal) {
|
||||||
|
this.$refs.accountModal.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -16,7 +16,7 @@ export const state = () => ({
|
|||||||
authorSortBy: 'name',
|
authorSortBy: 'name',
|
||||||
authorSortDesc: false,
|
authorSortDesc: false,
|
||||||
jumpForwardAmount: 10,
|
jumpForwardAmount: 10,
|
||||||
jumpBackwardAmount: 10,
|
jumpBackwardAmount: 10
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -26,7 +26,9 @@ export const getters = {
|
|||||||
getToken: (state) => {
|
getToken: (state) => {
|
||||||
return state.user?.token || null
|
return state.user?.token || null
|
||||||
},
|
},
|
||||||
getUserMediaProgress: (state) => (libraryItemId, episodeId = null) => {
|
getUserMediaProgress:
|
||||||
|
(state) =>
|
||||||
|
(libraryItemId, episodeId = null) => {
|
||||||
if (!state.user.mediaProgress) return null
|
if (!state.user.mediaProgress) return null
|
||||||
return state.user.mediaProgress.find((li) => {
|
return state.user.mediaProgress.find((li) => {
|
||||||
if (episodeId && li.episodeId !== episodeId) return false
|
if (episodeId && li.episodeId !== episodeId) return false
|
||||||
@ -153,7 +155,7 @@ export const mutations = {
|
|||||||
},
|
},
|
||||||
setUserToken(state, token) {
|
setUserToken(state, token) {
|
||||||
state.user.token = token
|
state.user.token = token
|
||||||
localStorage.setItem('token', user.token)
|
localStorage.setItem('token', token)
|
||||||
},
|
},
|
||||||
updateMediaProgress(state, { id, data }) {
|
updateMediaProgress(state, { id, data }) {
|
||||||
if (!state.user) return
|
if (!state.user) return
|
||||||
|
@ -213,8 +213,11 @@ class Auth {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
user.authOpenIDSub = userinfo.sub
|
// Update user with OpenID sub
|
||||||
await Database.userModel.updateFromOld(user)
|
if (!user.extraData) user.extraData = {}
|
||||||
|
user.extraData.authOpenIDSub = userinfo.sub
|
||||||
|
user.changed('extraData', true)
|
||||||
|
await user.save()
|
||||||
|
|
||||||
Logger.debug(`[Auth] openid: User found by email/username`)
|
Logger.debug(`[Auth] openid: User found by email/username`)
|
||||||
return user
|
return user
|
||||||
@ -788,12 +791,14 @@ class Auth {
|
|||||||
await Database.updateServerSettings()
|
await Database.updateServerSettings()
|
||||||
|
|
||||||
// New token secret creation added in v2.1.0 so generate new API tokens for each user
|
// New token secret creation added in v2.1.0 so generate new API tokens for each user
|
||||||
const users = await Database.userModel.getOldUsers()
|
const users = await Database.userModel.findAll({
|
||||||
|
attributes: ['id', 'username', 'token']
|
||||||
|
})
|
||||||
if (users.length) {
|
if (users.length) {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
user.token = await this.generateAccessToken(user)
|
user.token = await this.generateAccessToken(user)
|
||||||
|
await user.save({ hooks: false })
|
||||||
}
|
}
|
||||||
await Database.updateBulkUsers(users)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,13 +884,13 @@ class Auth {
|
|||||||
/**
|
/**
|
||||||
* Return the login info payload for a user
|
* Return the login info payload for a user
|
||||||
*
|
*
|
||||||
* @param {Object} user
|
* @param {import('./models/User')} user
|
||||||
* @returns {Promise<Object>} jsonPayload
|
* @returns {Promise<Object>} jsonPayload
|
||||||
*/
|
*/
|
||||||
async getUserLoginResponsePayload(user) {
|
async getUserLoginResponsePayload(user) {
|
||||||
const libraryIds = await Database.libraryModel.getAllLibraryIds()
|
const libraryIds = await Database.libraryModel.getAllLibraryIds()
|
||||||
return {
|
return {
|
||||||
user: user.toJSONForBrowser(),
|
user: user.toOldJSONForBrowser(),
|
||||||
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
|
userDefaultLibraryId: user.getDefaultLibraryId(libraryIds),
|
||||||
serverSettings: Database.serverSettings.toJSONForBrowser(),
|
serverSettings: Database.serverSettings.toJSONForBrowser(),
|
||||||
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
|
ereaderDevices: Database.emailSettings.getEReaderDevices(user),
|
||||||
@ -907,6 +912,7 @@ class Auth {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* User changes their password from request
|
* User changes their password from request
|
||||||
|
* TODO: Update responses to use error status codes
|
||||||
*
|
*
|
||||||
* @param {import('express').Request} req
|
* @param {import('express').Request} req
|
||||||
* @param {import('express').Response} res
|
* @param {import('express').Response} res
|
||||||
@ -941,19 +947,27 @@ class Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
matchingUser.pash = pw
|
Database.userModel
|
||||||
|
.update(
|
||||||
const success = await Database.updateUser(matchingUser)
|
{
|
||||||
if (success) {
|
pash: pw
|
||||||
|
},
|
||||||
|
{
|
||||||
|
where: { id: matchingUser.id }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
Logger.info(`[Auth] User "${matchingUser.username}" changed password`)
|
Logger.info(`[Auth] User "${matchingUser.username}" changed password`)
|
||||||
res.json({
|
res.json({
|
||||||
success: true
|
success: true
|
||||||
})
|
})
|
||||||
} else {
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
Logger.error(`[Auth] User "${matchingUser.username}" failed to change password`, error)
|
||||||
res.json({
|
res.json({
|
||||||
error: 'Unknown error'
|
error: 'Unknown error'
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +363,7 @@ class Database {
|
|||||||
*/
|
*/
|
||||||
async createRootUser(username, pash, auth) {
|
async createRootUser(username, pash, auth) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
await this.models.user.createRootUser(username, pash, auth)
|
await this.userModel.createRootUser(username, pash, auth)
|
||||||
this.hasRootUser = true
|
this.hasRootUser = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -390,11 +390,6 @@ class Database {
|
|||||||
return this.models.user.updateFromOld(oldUser)
|
return this.models.user.updateFromOld(oldUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBulkUsers(oldUsers) {
|
|
||||||
if (!this.sequelize) return false
|
|
||||||
return Promise.all(oldUsers.map((u) => this.updateUser(u)))
|
|
||||||
}
|
|
||||||
|
|
||||||
removeUser(userId) {
|
removeUser(userId) {
|
||||||
if (!this.sequelize) return false
|
if (!this.sequelize) return false
|
||||||
return this.models.user.removeById(userId)
|
return this.models.user.removeById(userId)
|
||||||
|
@ -89,9 +89,25 @@ class Server {
|
|||||||
this.io = null
|
this.io = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware to check if the current request is authenticated
|
||||||
|
* req.user is set if authenticated to the OLD user object
|
||||||
|
* req.userNew is set if authenticated to the NEW user object
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
authMiddleware(req, res, next) {
|
authMiddleware(req, res, next) {
|
||||||
// ask passportjs if the current request is authenticated
|
// ask passportjs if the current request is authenticated
|
||||||
this.auth.isAuthenticated(req, res, next)
|
this.auth.isAuthenticated(req, res, () => {
|
||||||
|
if (req.user) {
|
||||||
|
// TODO: req.userNew to become req.user
|
||||||
|
req.userNew = req.user
|
||||||
|
req.user = Database.userModel.getOldUser(req.user)
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelLibraryScan(libraryId) {
|
cancelLibraryScan(libraryId) {
|
||||||
|
@ -3,11 +3,20 @@ const Logger = require('./Logger')
|
|||||||
const Database = require('./Database')
|
const Database = require('./Database')
|
||||||
const Auth = require('./Auth')
|
const Auth = require('./Auth')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef SocketClient
|
||||||
|
* @property {string} id socket id
|
||||||
|
* @property {SocketIO.Socket} socket
|
||||||
|
* @property {number} connected_at
|
||||||
|
* @property {import('./models/User')} user
|
||||||
|
*/
|
||||||
|
|
||||||
class SocketAuthority {
|
class SocketAuthority {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.Server = null
|
this.Server = null
|
||||||
this.io = null
|
this.io = null
|
||||||
|
|
||||||
|
/** @type {Object.<string, SocketClient>} */
|
||||||
this.clients = {}
|
this.clients = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,7 +27,9 @@ class SocketAuthority {
|
|||||||
*/
|
*/
|
||||||
getUsersOnline() {
|
getUsersOnline() {
|
||||||
const onlineUsersMap = {}
|
const onlineUsersMap = {}
|
||||||
Object.values(this.clients).filter(c => c.user).forEach(client => {
|
Object.values(this.clients)
|
||||||
|
.filter((c) => c.user)
|
||||||
|
.forEach((client) => {
|
||||||
if (onlineUsersMap[client.user.id]) {
|
if (onlineUsersMap[client.user.id]) {
|
||||||
onlineUsersMap[client.user.id].connections++
|
onlineUsersMap[client.user.id].connections++
|
||||||
} else {
|
} else {
|
||||||
@ -32,7 +43,7 @@ class SocketAuthority {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getClientsForUser(userId) {
|
getClientsForUser(userId) {
|
||||||
return Object.values(this.clients).filter(c => c.user && c.user.id === userId)
|
return Object.values(this.clients).filter((c) => c.user?.id === userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,7 +78,7 @@ class SocketAuthority {
|
|||||||
// Emits event to all admin user clients
|
// Emits event to all admin user clients
|
||||||
adminEmitter(evt, data) {
|
adminEmitter(evt, data) {
|
||||||
for (const socketId in this.clients) {
|
for (const socketId in this.clients) {
|
||||||
if (this.clients[socketId].user && this.clients[socketId].user.isAdminOrUp) {
|
if (this.clients[socketId].user?.isAdminOrUp) {
|
||||||
this.clients[socketId].socket.emit(evt, data)
|
this.clients[socketId].socket.emit(evt, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,10 +92,8 @@ class SocketAuthority {
|
|||||||
close(callback) {
|
close(callback) {
|
||||||
Logger.info('[SocketAuthority] Shutting down')
|
Logger.info('[SocketAuthority] Shutting down')
|
||||||
// This will close all open socket connections, and also close the underlying http server
|
// This will close all open socket connections, and also close the underlying http server
|
||||||
if (this.io)
|
if (this.io) this.io.close(callback)
|
||||||
this.io.close(callback)
|
else callback()
|
||||||
else
|
|
||||||
callback()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize(Server) {
|
initialize(Server) {
|
||||||
@ -93,7 +102,7 @@ class SocketAuthority {
|
|||||||
this.io = new SocketIO.Server(this.Server.server, {
|
this.io = new SocketIO.Server(this.Server.server, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: '*',
|
origin: '*',
|
||||||
methods: ["GET", "POST"]
|
methods: ['GET', 'POST']
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -144,7 +153,7 @@ class SocketAuthority {
|
|||||||
// admin user can send a message to all authenticated users
|
// admin user can send a message to all authenticated users
|
||||||
// displays on the web app as a toast
|
// displays on the web app as a toast
|
||||||
const client = this.clients[socket.id] || {}
|
const client = this.clients[socket.id] || {}
|
||||||
if (client.user && client.user.isAdminOrUp) {
|
if (client.user?.isAdminOrUp) {
|
||||||
this.emitter('admin_message', payload.message || '')
|
this.emitter('admin_message', payload.message || '')
|
||||||
} else {
|
} else {
|
||||||
Logger.error(`[SocketAuthority] Non-admin user sent the message_all_users event`)
|
Logger.error(`[SocketAuthority] Non-admin user sent the message_all_users event`)
|
||||||
@ -176,6 +185,7 @@ class SocketAuthority {
|
|||||||
Logger.error('Cannot validate socket - invalid token')
|
Logger.error('Cannot validate socket - invalid token')
|
||||||
return socket.emit('invalid_token')
|
return socket.emit('invalid_token')
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the user via the id from the decoded jwt.
|
// get the user via the id from the decoded jwt.
|
||||||
const user = await Database.userModel.getUserByIdOrOldId(token_data.userId)
|
const user = await Database.userModel.getUserByIdOrOldId(token_data.userId)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@ -196,18 +206,13 @@ class SocketAuthority {
|
|||||||
|
|
||||||
client.user = user
|
client.user = user
|
||||||
|
|
||||||
if (!client.user.toJSONForBrowser) {
|
|
||||||
Logger.error('Invalid user...', client.user)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.debug(`[SocketAuthority] User Online ${client.user.username}`)
|
Logger.debug(`[SocketAuthority] User Online ${client.user.username}`)
|
||||||
|
|
||||||
this.adminEmitter('user_online', client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions))
|
this.adminEmitter('user_online', client.user.toJSONForPublic(this.Server.playbackSessionManager.sessions))
|
||||||
|
|
||||||
// Update user lastSeen without firing sequelize bulk update hooks
|
// Update user lastSeen without firing sequelize bulk update hooks
|
||||||
user.lastSeen = Date.now()
|
user.lastSeen = Date.now()
|
||||||
await Database.userModel.updateFromOld(user, false)
|
await user.save({ hooks: false })
|
||||||
|
|
||||||
const initialPayload = {
|
const initialPayload = {
|
||||||
userId: client.user.id,
|
userId: client.user.id,
|
||||||
|
@ -223,7 +223,7 @@ class LibraryController {
|
|||||||
|
|
||||||
// Only emit to users with access to library
|
// Only emit to users with access to library
|
||||||
const userFilter = (user) => {
|
const userFilter = (user) => {
|
||||||
return user.checkCanAccessLibrary && user.checkCanAccessLibrary(library.id)
|
return user.checkCanAccessLibrary?.(library.id)
|
||||||
}
|
}
|
||||||
SocketAuthority.emitter('library_updated', library.toJSON(), userFilter)
|
SocketAuthority.emitter('library_updated', library.toJSON(), userFilter)
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class MiscController {
|
|||||||
if (!library) {
|
if (!library) {
|
||||||
return res.status(404).send(`Library not found with id ${libraryId}`)
|
return res.status(404).send(`Library not found with id ${libraryId}`)
|
||||||
}
|
}
|
||||||
const folder = library.folders.find(fold => fold.id === folderId)
|
const folder = library.folders.find((fold) => fold.id === folderId)
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
return res.status(404).send(`Folder not found with id ${folderId} in library ${library.name}`)
|
return res.status(404).send(`Folder not found with id ${folderId} in library ${library.name}`)
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@ class MiscController {
|
|||||||
// `.filter(Boolean)` to strip out all the potentially missing details (eg: `author`)
|
// `.filter(Boolean)` to strip out all the potentially missing details (eg: `author`)
|
||||||
// before sanitizing all the directory parts to remove illegal chars and finally prepending
|
// before sanitizing all the directory parts to remove illegal chars and finally prepending
|
||||||
// the base folder path
|
// the base folder path
|
||||||
const cleanedOutputDirectoryParts = outputDirectoryParts.filter(Boolean).map(part => sanitizeFilename(part))
|
const cleanedOutputDirectoryParts = outputDirectoryParts.filter(Boolean).map((part) => sanitizeFilename(part))
|
||||||
const outputDirectory = Path.join(...[folder.fullPath, ...cleanedOutputDirectoryParts])
|
const outputDirectory = Path.join(...[folder.fullPath, ...cleanedOutputDirectoryParts])
|
||||||
|
|
||||||
await fs.ensureDir(outputDirectory)
|
await fs.ensureDir(outputDirectory)
|
||||||
@ -66,7 +66,8 @@ class MiscController {
|
|||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const path = Path.join(outputDirectory, sanitizeFilename(file.name))
|
const path = Path.join(outputDirectory, sanitizeFilename(file.name))
|
||||||
|
|
||||||
await file.mv(path)
|
await file
|
||||||
|
.mv(path)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@ -89,7 +90,7 @@ class MiscController {
|
|||||||
const includeArray = (req.query.include || '').split(',')
|
const includeArray = (req.query.include || '').split(',')
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
tasks: TaskManager.tasks.map(t => t.toJSON())
|
tasks: TaskManager.tasks.map((t) => t.toJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeArray.includes('queue')) {
|
if (includeArray.includes('queue')) {
|
||||||
@ -148,7 +149,7 @@ class MiscController {
|
|||||||
if (!sortingPrefixes?.length || !Array.isArray(sortingPrefixes)) {
|
if (!sortingPrefixes?.length || !Array.isArray(sortingPrefixes)) {
|
||||||
return res.status(400).send('Invalid request body')
|
return res.status(400).send('Invalid request body')
|
||||||
}
|
}
|
||||||
sortingPrefixes = [...new Set(sortingPrefixes.map(p => p?.trim?.().toLowerCase()).filter(p => p))]
|
sortingPrefixes = [...new Set(sortingPrefixes.map((p) => p?.trim?.().toLowerCase()).filter((p) => p))]
|
||||||
if (!sortingPrefixes.length) {
|
if (!sortingPrefixes.length) {
|
||||||
return res.status(400).send('Invalid sortingPrefixes in request body')
|
return res.status(400).send('Invalid sortingPrefixes in request body')
|
||||||
}
|
}
|
||||||
@ -234,6 +235,8 @@ class MiscController {
|
|||||||
* POST: /api/authorize
|
* POST: /api/authorize
|
||||||
* Used to authorize an API token
|
* Used to authorize an API token
|
||||||
*
|
*
|
||||||
|
* @this import('../routers/ApiRouter')
|
||||||
|
*
|
||||||
* @param {import('express').Request} req
|
* @param {import('express').Request} req
|
||||||
* @param {import('express').Response} res
|
* @param {import('express').Response} res
|
||||||
*/
|
*/
|
||||||
@ -242,7 +245,7 @@ class MiscController {
|
|||||||
Logger.error('Invalid user in authorize')
|
Logger.error('Invalid user in authorize')
|
||||||
return res.sendStatus(401)
|
return res.sendStatus(401)
|
||||||
}
|
}
|
||||||
const userResponse = await this.auth.getUserLoginResponsePayload(req.user)
|
const userResponse = await this.auth.getUserLoginResponsePayload(req.userNew)
|
||||||
res.json(userResponse)
|
res.json(userResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +324,7 @@ class MiscController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (libraryItem.media.tags.includes(tag)) {
|
if (libraryItem.media.tags.includes(tag)) {
|
||||||
libraryItem.media.tags = libraryItem.media.tags.filter(t => t !== tag) // Remove old tag
|
libraryItem.media.tags = libraryItem.media.tags.filter((t) => t !== tag) // Remove old tag
|
||||||
if (!libraryItem.media.tags.includes(newTag)) {
|
if (!libraryItem.media.tags.includes(newTag)) {
|
||||||
libraryItem.media.tags.push(newTag)
|
libraryItem.media.tags.push(newTag)
|
||||||
}
|
}
|
||||||
@ -367,7 +370,7 @@ class MiscController {
|
|||||||
// Remove tag from items
|
// Remove tag from items
|
||||||
for (const libraryItem of libraryItemsWithTag) {
|
for (const libraryItem of libraryItemsWithTag) {
|
||||||
Logger.debug(`[MiscController] Remove tag "${tag}" from item "${libraryItem.media.title}"`)
|
Logger.debug(`[MiscController] Remove tag "${tag}" from item "${libraryItem.media.title}"`)
|
||||||
libraryItem.media.tags = libraryItem.media.tags.filter(t => t !== tag)
|
libraryItem.media.tags = libraryItem.media.tags.filter((t) => t !== tag)
|
||||||
await libraryItem.media.update({
|
await libraryItem.media.update({
|
||||||
tags: libraryItem.media.tags
|
tags: libraryItem.media.tags
|
||||||
})
|
})
|
||||||
@ -456,7 +459,7 @@ class MiscController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (libraryItem.media.genres.includes(genre)) {
|
if (libraryItem.media.genres.includes(genre)) {
|
||||||
libraryItem.media.genres = libraryItem.media.genres.filter(t => t !== genre) // Remove old genre
|
libraryItem.media.genres = libraryItem.media.genres.filter((t) => t !== genre) // Remove old genre
|
||||||
if (!libraryItem.media.genres.includes(newGenre)) {
|
if (!libraryItem.media.genres.includes(newGenre)) {
|
||||||
libraryItem.media.genres.push(newGenre)
|
libraryItem.media.genres.push(newGenre)
|
||||||
}
|
}
|
||||||
@ -502,7 +505,7 @@ class MiscController {
|
|||||||
// Remove genre from items
|
// Remove genre from items
|
||||||
for (const libraryItem of libraryItemsWithGenre) {
|
for (const libraryItem of libraryItemsWithGenre) {
|
||||||
Logger.debug(`[MiscController] Remove genre "${genre}" from item "${libraryItem.media.title}"`)
|
Logger.debug(`[MiscController] Remove genre "${genre}" from item "${libraryItem.media.title}"`)
|
||||||
libraryItem.media.genres = libraryItem.media.genres.filter(g => g !== genre)
|
libraryItem.media.genres = libraryItem.media.genres.filter((g) => g !== genre)
|
||||||
await libraryItem.media.update({
|
await libraryItem.media.update({
|
||||||
genres: libraryItem.media.genres
|
genres: libraryItem.media.genres
|
||||||
})
|
})
|
||||||
@ -642,15 +645,13 @@ class MiscController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const uris = settingsUpdate[key]
|
const uris = settingsUpdate[key]
|
||||||
if (!Array.isArray(uris) ||
|
if (!Array.isArray(uris) || (uris.includes('*') && uris.length > 1) || uris.some((uri) => uri !== '*' && !isValidRedirectURI(uri))) {
|
||||||
(uris.includes('*') && uris.length > 1) ||
|
|
||||||
uris.some(uri => uri !== '*' && !isValidRedirectURI(uri))) {
|
|
||||||
Logger.warn(`[MiscController] Invalid value for authOpenIDMobileRedirectURIs`)
|
Logger.warn(`[MiscController] Invalid value for authOpenIDMobileRedirectURIs`)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the URIs
|
// Update the URIs
|
||||||
if (Database.serverSettings[key].some(uri => !uris.includes(uri)) || uris.some(uri => !Database.serverSettings[key].includes(uri))) {
|
if (Database.serverSettings[key].some((uri) => !uris.includes(uri)) || uris.some((uri) => !Database.serverSettings[key].includes(uri))) {
|
||||||
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${Database.serverSettings[key]}" to "${uris}"`)
|
Logger.debug(`[MiscController] Updating auth settings key "${key}" from "${Database.serverSettings[key]}" to "${uris}"`)
|
||||||
Database.serverSettings[key] = uris
|
Database.serverSettings[key] = uris
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
|
@ -31,8 +31,8 @@ class UserController {
|
|||||||
const includes = (req.query.include || '').split(',').map((i) => i.trim())
|
const includes = (req.query.include || '').split(',').map((i) => i.trim())
|
||||||
|
|
||||||
// Minimal toJSONForBrowser does not include mediaProgress and bookmarks
|
// Minimal toJSONForBrowser does not include mediaProgress and bookmarks
|
||||||
const allUsers = await Database.userModel.getOldUsers()
|
const allUsers = await Database.userModel.findAll()
|
||||||
const users = allUsers.map((u) => u.toJSONForBrowser(hideRootToken, true))
|
const users = allUsers.map((u) => u.toOldJSONForBrowser(hideRootToken, true))
|
||||||
|
|
||||||
if (includes.includes('latestSession')) {
|
if (includes.includes('latestSession')) {
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
@ -106,7 +106,7 @@ class UserController {
|
|||||||
const account = req.body
|
const account = req.body
|
||||||
const username = account.username
|
const username = account.username
|
||||||
|
|
||||||
const usernameExists = await Database.userModel.getUserByUsername(username)
|
const usernameExists = await Database.userModel.checkUserExistsWithUsername(username)
|
||||||
if (usernameExists) {
|
if (usernameExists) {
|
||||||
return res.status(500).send('Username already taken')
|
return res.status(500).send('Username already taken')
|
||||||
}
|
}
|
||||||
@ -149,7 +149,7 @@ class UserController {
|
|||||||
|
|
||||||
// When changing username create a new API token
|
// When changing username create a new API token
|
||||||
if (account.username !== undefined && account.username !== user.username) {
|
if (account.username !== undefined && account.username !== user.username) {
|
||||||
const usernameExists = await Database.userModel.getUserByUsername(account.username)
|
const usernameExists = await Database.userModel.checkUserExistsWithUsername(account.username)
|
||||||
if (usernameExists) {
|
if (usernameExists) {
|
||||||
return res.status(500).send('Username already taken')
|
return res.status(500).send('Username already taken')
|
||||||
}
|
}
|
||||||
@ -272,7 +272,8 @@ class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (req.params.id) {
|
if (req.params.id) {
|
||||||
req.reqUser = await Database.userModel.getUserById(req.params.id)
|
// TODO: Update to use new user model
|
||||||
|
req.reqUser = await Database.userModel.getOldUserById(req.params.id)
|
||||||
if (!req.reqUser) {
|
if (!req.reqUser) {
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
@ -34,29 +34,6 @@ class MediaProgress extends Model {
|
|||||||
this.createdAt
|
this.createdAt
|
||||||
}
|
}
|
||||||
|
|
||||||
getOldMediaProgress() {
|
|
||||||
const isPodcastEpisode = this.mediaItemType === 'podcastEpisode'
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: this.id,
|
|
||||||
userId: this.userId,
|
|
||||||
libraryItemId: this.extraData?.libraryItemId || null,
|
|
||||||
episodeId: isPodcastEpisode ? this.mediaItemId : null,
|
|
||||||
mediaItemId: this.mediaItemId,
|
|
||||||
mediaItemType: this.mediaItemType,
|
|
||||||
duration: this.duration,
|
|
||||||
progress: this.extraData?.progress || 0,
|
|
||||||
currentTime: this.currentTime,
|
|
||||||
isFinished: !!this.isFinished,
|
|
||||||
hideFromContinueListening: !!this.hideFromContinueListening,
|
|
||||||
ebookLocation: this.ebookLocation,
|
|
||||||
ebookProgress: this.ebookProgress,
|
|
||||||
lastUpdate: this.updatedAt.valueOf(),
|
|
||||||
startedAt: this.createdAt.valueOf(),
|
|
||||||
finishedAt: this.finishedAt?.valueOf() || null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static upsertFromOld(oldMediaProgress) {
|
static upsertFromOld(oldMediaProgress) {
|
||||||
const mediaProgress = this.getFromOld(oldMediaProgress)
|
const mediaProgress = this.getFromOld(oldMediaProgress)
|
||||||
return this.upsert(mediaProgress)
|
return this.upsert(mediaProgress)
|
||||||
@ -182,6 +159,29 @@ class MediaProgress extends Model {
|
|||||||
})
|
})
|
||||||
MediaProgress.belongsTo(user)
|
MediaProgress.belongsTo(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOldMediaProgress() {
|
||||||
|
const isPodcastEpisode = this.mediaItemType === 'podcastEpisode'
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
userId: this.userId,
|
||||||
|
libraryItemId: this.extraData?.libraryItemId || null,
|
||||||
|
episodeId: isPodcastEpisode ? this.mediaItemId : null,
|
||||||
|
mediaItemId: this.mediaItemId,
|
||||||
|
mediaItemType: this.mediaItemType,
|
||||||
|
duration: this.duration,
|
||||||
|
progress: this.extraData?.progress || 0,
|
||||||
|
currentTime: this.currentTime,
|
||||||
|
isFinished: !!this.isFinished,
|
||||||
|
hideFromContinueListening: !!this.hideFromContinueListening,
|
||||||
|
ebookLocation: this.ebookLocation,
|
||||||
|
ebookProgress: this.ebookProgress,
|
||||||
|
lastUpdate: this.updatedAt.valueOf(),
|
||||||
|
startedAt: this.createdAt.valueOf(),
|
||||||
|
finishedAt: this.finishedAt?.valueOf() || null
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = MediaProgress
|
module.exports = MediaProgress
|
||||||
|
@ -42,31 +42,41 @@ class User extends Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all oldUsers
|
*
|
||||||
* @returns {Promise<oldUser>}
|
* @param {string} type
|
||||||
|
* @returns
|
||||||
*/
|
*/
|
||||||
static async getOldUsers() {
|
static getDefaultPermissionsForUserType(type) {
|
||||||
const users = await this.findAll({
|
return {
|
||||||
include: this.sequelize.models.mediaProgress
|
download: true,
|
||||||
})
|
update: type === 'root' || type === 'admin',
|
||||||
return users.map((u) => this.getOldUser(u))
|
delete: type === 'root',
|
||||||
|
upload: type === 'root' || type === 'admin',
|
||||||
|
accessAllLibraries: true,
|
||||||
|
accessAllTags: true,
|
||||||
|
accessExplicitContent: true,
|
||||||
|
librariesAccessible: [],
|
||||||
|
itemTagsSelected: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get old user model from new
|
* Get old user model from new
|
||||||
*
|
*
|
||||||
* @param {Object} userExpanded
|
* @param {User} userExpanded
|
||||||
* @returns {oldUser}
|
* @returns {oldUser}
|
||||||
*/
|
*/
|
||||||
static getOldUser(userExpanded) {
|
static getOldUser(userExpanded) {
|
||||||
const mediaProgress = userExpanded.mediaProgresses.map((mp) => mp.getOldMediaProgress())
|
const mediaProgress = userExpanded.mediaProgresses.map((mp) => mp.getOldMediaProgress())
|
||||||
|
|
||||||
const librariesAccessible = userExpanded.permissions?.librariesAccessible || []
|
const librariesAccessible = [...(userExpanded.permissions?.librariesAccessible || [])]
|
||||||
const itemTagsSelected = userExpanded.permissions?.itemTagsSelected || []
|
const itemTagsSelected = [...(userExpanded.permissions?.itemTagsSelected || [])]
|
||||||
const permissions = userExpanded.permissions || {}
|
const permissions = { ...(userExpanded.permissions || {}) }
|
||||||
delete permissions.librariesAccessible
|
delete permissions.librariesAccessible
|
||||||
delete permissions.itemTagsSelected
|
delete permissions.itemTagsSelected
|
||||||
|
|
||||||
|
const seriesHideFromContinueListening = userExpanded.extraData?.seriesHideFromContinueListening || []
|
||||||
|
|
||||||
return new oldUser({
|
return new oldUser({
|
||||||
id: userExpanded.id,
|
id: userExpanded.id,
|
||||||
oldUserId: userExpanded.extraData?.oldUserId || null,
|
oldUserId: userExpanded.extraData?.oldUserId || null,
|
||||||
@ -76,7 +86,7 @@ class User extends Model {
|
|||||||
type: userExpanded.type,
|
type: userExpanded.type,
|
||||||
token: userExpanded.token,
|
token: userExpanded.token,
|
||||||
mediaProgress,
|
mediaProgress,
|
||||||
seriesHideFromContinueListening: userExpanded.extraData?.seriesHideFromContinueListening || [],
|
seriesHideFromContinueListening: [...seriesHideFromContinueListening],
|
||||||
bookmarks: userExpanded.bookmarks,
|
bookmarks: userExpanded.bookmarks,
|
||||||
isActive: userExpanded.isActive,
|
isActive: userExpanded.isActive,
|
||||||
isLocked: userExpanded.isLocked,
|
isLocked: userExpanded.isLocked,
|
||||||
@ -168,32 +178,35 @@ class User extends Model {
|
|||||||
* Create root user
|
* Create root user
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @param {string} pash
|
* @param {string} pash
|
||||||
* @param {Auth} auth
|
* @param {import('../Auth')} auth
|
||||||
* @returns {Promise<oldUser>}
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async createRootUser(username, pash, auth) {
|
static async createRootUser(username, pash, auth) {
|
||||||
const userId = uuidv4()
|
const userId = uuidv4()
|
||||||
|
|
||||||
const token = await auth.generateAccessToken({ id: userId, username })
|
const token = await auth.generateAccessToken({ id: userId, username })
|
||||||
|
|
||||||
const newRoot = new oldUser({
|
const newUser = {
|
||||||
id: userId,
|
id: userId,
|
||||||
type: 'root',
|
type: 'root',
|
||||||
username,
|
username,
|
||||||
pash,
|
pash,
|
||||||
token,
|
token,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
createdAt: Date.now()
|
permissions: this.getDefaultPermissionsForUserType('root'),
|
||||||
})
|
bookmarks: [],
|
||||||
await this.createFromOld(newRoot)
|
extraData: {
|
||||||
return newRoot
|
seriesHideFromContinueListening: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.create(newUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create user from openid userinfo
|
* Create user from openid userinfo
|
||||||
* @param {Object} userinfo
|
* @param {Object} userinfo
|
||||||
* @param {Auth} auth
|
* @param {import('../Auth')} auth
|
||||||
* @returns {Promise<oldUser>}
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async createUserFromOpenIdUserInfo(userinfo, auth) {
|
static async createUserFromOpenIdUserInfo(userinfo, auth) {
|
||||||
const userId = uuidv4()
|
const userId = uuidv4()
|
||||||
@ -203,7 +216,7 @@ class User extends Model {
|
|||||||
|
|
||||||
const token = await auth.generateAccessToken({ id: userId, username })
|
const token = await auth.generateAccessToken({ id: userId, username })
|
||||||
|
|
||||||
const newUser = new oldUser({
|
const newUser = {
|
||||||
id: userId,
|
id: userId,
|
||||||
type: 'user',
|
type: 'user',
|
||||||
username,
|
username,
|
||||||
@ -211,51 +224,30 @@ class User extends Model {
|
|||||||
pash: null,
|
pash: null,
|
||||||
token,
|
token,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
permissions: this.getDefaultPermissionsForUserType('user'),
|
||||||
|
bookmarks: [],
|
||||||
|
extraData: {
|
||||||
authOpenIDSub: userinfo.sub,
|
authOpenIDSub: userinfo.sub,
|
||||||
createdAt: Date.now()
|
seriesHideFromContinueListening: []
|
||||||
})
|
}
|
||||||
if (await this.createFromOld(newUser)) {
|
}
|
||||||
SocketAuthority.adminEmitter('user_added', newUser.toJSONForBrowser())
|
const user = await this.create(newUser)
|
||||||
return newUser
|
|
||||||
|
if (user) {
|
||||||
|
SocketAuthority.adminEmitter('user_added', user.toOldJSONForBrowser())
|
||||||
|
return user
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a user by id or by the old database id
|
|
||||||
* @temp User ids were updated in v2.3.0 migration and old API tokens may still use that id
|
|
||||||
* @param {string} userId
|
|
||||||
* @returns {Promise<oldUser|null>} null if not found
|
|
||||||
*/
|
|
||||||
static async getUserByIdOrOldId(userId) {
|
|
||||||
if (!userId) return null
|
|
||||||
const user = await this.findOne({
|
|
||||||
where: {
|
|
||||||
[sequelize.Op.or]: [
|
|
||||||
{
|
|
||||||
id: userId
|
|
||||||
},
|
|
||||||
{
|
|
||||||
extraData: {
|
|
||||||
[sequelize.Op.substring]: userId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
include: this.sequelize.models.mediaProgress
|
|
||||||
})
|
|
||||||
if (!user) return null
|
|
||||||
return this.getOldUser(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user by username case insensitive
|
* Get user by username case insensitive
|
||||||
* @param {string} username
|
* @param {string} username
|
||||||
* @returns {Promise<oldUser|null>} returns null if not found
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async getUserByUsername(username) {
|
static async getUserByUsername(username) {
|
||||||
if (!username) return null
|
if (!username) return null
|
||||||
const user = await this.findOne({
|
return this.findOne({
|
||||||
where: {
|
where: {
|
||||||
username: {
|
username: {
|
||||||
[sequelize.Op.like]: username
|
[sequelize.Op.like]: username
|
||||||
@ -263,18 +255,16 @@ class User extends Model {
|
|||||||
},
|
},
|
||||||
include: this.sequelize.models.mediaProgress
|
include: this.sequelize.models.mediaProgress
|
||||||
})
|
})
|
||||||
if (!user) return null
|
|
||||||
return this.getOldUser(user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user by email case insensitive
|
* Get user by email case insensitive
|
||||||
* @param {string} username
|
* @param {string} email
|
||||||
* @returns {Promise<oldUser|null>} returns null if not found
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async getUserByEmail(email) {
|
static async getUserByEmail(email) {
|
||||||
if (!email) return null
|
if (!email) return null
|
||||||
const user = await this.findOne({
|
return this.findOne({
|
||||||
where: {
|
where: {
|
||||||
email: {
|
email: {
|
||||||
[sequelize.Op.like]: email
|
[sequelize.Op.like]: email
|
||||||
@ -282,20 +272,45 @@ class User extends Model {
|
|||||||
},
|
},
|
||||||
include: this.sequelize.models.mediaProgress
|
include: this.sequelize.models.mediaProgress
|
||||||
})
|
})
|
||||||
if (!user) return null
|
|
||||||
return this.getOldUser(user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user by id
|
* Get user by id
|
||||||
* @param {string} userId
|
* @param {string} userId
|
||||||
* @returns {Promise<oldUser|null>} returns null if not found
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async getUserById(userId) {
|
static async getUserById(userId) {
|
||||||
if (!userId) return null
|
if (!userId) return null
|
||||||
const user = await this.findByPk(userId, {
|
return this.findByPk(userId, {
|
||||||
include: this.sequelize.models.mediaProgress
|
include: this.sequelize.models.mediaProgress
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user by id or old id
|
||||||
|
* JWT tokens generated before 2.3.0 used old user ids
|
||||||
|
*
|
||||||
|
* @param {string} userId
|
||||||
|
* @returns {Promise<User>}
|
||||||
|
*/
|
||||||
|
static async getUserByIdOrOldId(userId) {
|
||||||
|
if (!userId) return null
|
||||||
|
return this.findOne({
|
||||||
|
where: {
|
||||||
|
[sequelize.Op.or]: [{ id: userId }, { 'extraData.oldUserId': userId }]
|
||||||
|
},
|
||||||
|
include: this.sequelize.models.mediaProgress
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* Get old user by id
|
||||||
|
* @param {string} userId
|
||||||
|
* @returns {Promise<oldUser|null>} returns null if not found
|
||||||
|
*/
|
||||||
|
static async getOldUserById(userId) {
|
||||||
|
const user = await this.getUserById(userId)
|
||||||
if (!user) return null
|
if (!user) return null
|
||||||
return this.getOldUser(user)
|
return this.getOldUser(user)
|
||||||
}
|
}
|
||||||
@ -303,16 +318,14 @@ class User extends Model {
|
|||||||
/**
|
/**
|
||||||
* Get user by openid sub
|
* Get user by openid sub
|
||||||
* @param {string} sub
|
* @param {string} sub
|
||||||
* @returns {Promise<oldUser|null>} returns null if not found
|
* @returns {Promise<User>}
|
||||||
*/
|
*/
|
||||||
static async getUserByOpenIDSub(sub) {
|
static async getUserByOpenIDSub(sub) {
|
||||||
if (!sub) return null
|
if (!sub) return null
|
||||||
const user = await this.findOne({
|
return this.findOne({
|
||||||
where: sequelize.where(sequelize.literal(`extraData->>"authOpenIDSub"`), sub),
|
where: sequelize.where(sequelize.literal(`extraData->>"authOpenIDSub"`), sub),
|
||||||
include: this.sequelize.models.mediaProgress
|
include: this.sequelize.models.mediaProgress
|
||||||
})
|
})
|
||||||
if (!user) return null
|
|
||||||
return this.getOldUser(user)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -344,6 +357,20 @@ class User extends Model {
|
|||||||
return count > 0
|
return count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user exists with username
|
||||||
|
* @param {string} username
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
static async checkUserExistsWithUsername(username) {
|
||||||
|
const count = await this.count({
|
||||||
|
where: {
|
||||||
|
username
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return count > 0
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize model
|
* Initialize model
|
||||||
* @param {import('../Database').sequelize} sequelize
|
* @param {import('../Database').sequelize} sequelize
|
||||||
@ -380,6 +407,99 @@ class User extends Model {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isAdminOrUp() {
|
||||||
|
return this.type === 'root' || this.type === 'admin'
|
||||||
|
}
|
||||||
|
get isUser() {
|
||||||
|
return this.type === 'user'
|
||||||
|
}
|
||||||
|
/** @type {string|null} */
|
||||||
|
get authOpenIDSub() {
|
||||||
|
return this.extraData?.authOpenIDSub || null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User data for clients
|
||||||
|
* Emitted on socket events user_online, user_offline and user_stream_update
|
||||||
|
*
|
||||||
|
* @param {import('../objects/PlaybackSession')[]} sessions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
toJSONForPublic(sessions) {
|
||||||
|
const session = sessions?.find((s) => s.userId === this.id)?.toJSONForClient() || null
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
username: this.username,
|
||||||
|
type: this.type,
|
||||||
|
session,
|
||||||
|
lastSeen: this.lastSeen?.valueOf() || null,
|
||||||
|
createdAt: this.createdAt.valueOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User data for browser using old model
|
||||||
|
*
|
||||||
|
* @param {boolean} [hideRootToken=false]
|
||||||
|
* @param {boolean} [minimal=false]
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
toOldJSONForBrowser(hideRootToken = false, minimal = false) {
|
||||||
|
const seriesHideFromContinueListening = this.extraData?.seriesHideFromContinueListening || []
|
||||||
|
const librariesAccessible = this.permissions?.librariesAccessible || []
|
||||||
|
const itemTagsSelected = this.permissions?.itemTagsSelected || []
|
||||||
|
const permissions = { ...this.permissions }
|
||||||
|
delete permissions.librariesAccessible
|
||||||
|
delete permissions.itemTagsSelected
|
||||||
|
|
||||||
|
const json = {
|
||||||
|
id: this.id,
|
||||||
|
username: this.username,
|
||||||
|
email: this.email,
|
||||||
|
type: this.type,
|
||||||
|
token: this.type === 'root' && hideRootToken ? '' : this.token,
|
||||||
|
mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [],
|
||||||
|
seriesHideFromContinueListening: [...seriesHideFromContinueListening],
|
||||||
|
bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [],
|
||||||
|
isActive: this.isActive,
|
||||||
|
isLocked: this.isLocked,
|
||||||
|
lastSeen: this.lastSeen?.valueOf() || null,
|
||||||
|
createdAt: this.createdAt.valueOf(),
|
||||||
|
permissions: permissions,
|
||||||
|
librariesAccessible: [...librariesAccessible],
|
||||||
|
itemTagsSelected: [...itemTagsSelected],
|
||||||
|
hasOpenIDLink: !!this.authOpenIDSub
|
||||||
|
}
|
||||||
|
if (minimal) {
|
||||||
|
delete json.mediaProgress
|
||||||
|
delete json.bookmarks
|
||||||
|
}
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check user has access to library
|
||||||
|
*
|
||||||
|
* @param {string} libraryId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
checkCanAccessLibrary(libraryId) {
|
||||||
|
if (this.permissions?.accessAllLibraries) return true
|
||||||
|
if (!this.permissions?.librariesAccessible) return false
|
||||||
|
return this.permissions.librariesAccessible.includes(libraryId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = User
|
module.exports = User
|
||||||
|
@ -140,7 +140,7 @@ class EmailSettings {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {EreaderDeviceObject} device
|
* @param {EreaderDeviceObject} device
|
||||||
* @param {import('../user/User')} user
|
* @param {import('../../models/User')} user
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
checkUserCanAccessDevice(device, user) {
|
checkUserCanAccessDevice(device, user) {
|
||||||
@ -158,7 +158,7 @@ class EmailSettings {
|
|||||||
/**
|
/**
|
||||||
* Get ereader devices accessible to user
|
* Get ereader devices accessible to user
|
||||||
*
|
*
|
||||||
* @param {import('../user/User')} user
|
* @param {import('../../models/User')} user
|
||||||
* @returns {EreaderDeviceObject[]}
|
* @returns {EreaderDeviceObject[]}
|
||||||
*/
|
*/
|
||||||
getEReaderDevices(user) {
|
getEReaderDevices(user) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const { DataTypes, QueryInterface } = require('sequelize')
|
const { DataTypes, QueryInterface } = require('sequelize')
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const uuidv4 = require("uuid").v4
|
const uuidv4 = require('uuid').v4
|
||||||
const Logger = require('../../Logger')
|
const Logger = require('../../Logger')
|
||||||
const fs = require('../../libs/fsExtra')
|
const fs = require('../../libs/fsExtra')
|
||||||
const oldDbFiles = require('./oldDbFiles')
|
const oldDbFiles = require('./oldDbFiles')
|
||||||
@ -36,18 +36,7 @@ function getDeviceInfoString(deviceInfo, UserId) {
|
|||||||
if (!deviceInfo) return null
|
if (!deviceInfo) return null
|
||||||
if (deviceInfo.deviceId) return deviceInfo.deviceId
|
if (deviceInfo.deviceId) return deviceInfo.deviceId
|
||||||
|
|
||||||
const keys = [
|
const keys = [UserId, deviceInfo.browserName || null, deviceInfo.browserVersion || null, deviceInfo.osName || null, deviceInfo.osVersion || null, deviceInfo.clientVersion || null, deviceInfo.manufacturer || null, deviceInfo.model || null, deviceInfo.sdkVersion || null, deviceInfo.ipAddress || null].map((k) => k || '')
|
||||||
UserId,
|
|
||||||
deviceInfo.browserName || null,
|
|
||||||
deviceInfo.browserVersion || null,
|
|
||||||
deviceInfo.osName || null,
|
|
||||||
deviceInfo.osVersion || null,
|
|
||||||
deviceInfo.clientVersion || null,
|
|
||||||
deviceInfo.manufacturer || null,
|
|
||||||
deviceInfo.model || null,
|
|
||||||
deviceInfo.sdkVersion || null,
|
|
||||||
deviceInfo.ipAddress || null
|
|
||||||
].map(k => k || '')
|
|
||||||
return 'temp-' + Buffer.from(keys.join('-'), 'utf-8').toString('base64')
|
return 'temp-' + Buffer.from(keys.join('-'), 'utf-8').toString('base64')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +56,7 @@ function migrateBook(oldLibraryItem, LibraryItem) {
|
|||||||
bookAuthor: []
|
bookAuthor: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const tracks = (oldBook.audioFiles || []).filter(af => !af.exclude && !af.invalid)
|
const tracks = (oldBook.audioFiles || []).filter((af) => !af.exclude && !af.invalid)
|
||||||
let duration = 0
|
let duration = 0
|
||||||
for (const track of tracks) {
|
for (const track of tracks) {
|
||||||
if (track.duration !== null && !isNaN(track.duration)) {
|
if (track.duration !== null && !isNaN(track.duration)) {
|
||||||
@ -298,7 +287,7 @@ function migrateLibraryItems(oldLibraryItems) {
|
|||||||
updatedAt: oldLibraryItem.updatedAt,
|
updatedAt: oldLibraryItem.updatedAt,
|
||||||
libraryId,
|
libraryId,
|
||||||
libraryFolderId,
|
libraryFolderId,
|
||||||
libraryFiles: oldLibraryItem.libraryFiles.map(lf => {
|
libraryFiles: oldLibraryItem.libraryFiles.map((lf) => {
|
||||||
if (lf.isSupplementary === undefined) lf.isSupplementary = null
|
if (lf.isSupplementary === undefined) lf.isSupplementary = null
|
||||||
return lf
|
return lf
|
||||||
})
|
})
|
||||||
@ -390,13 +379,19 @@ function migrateAuthors(oldAuthors, oldLibraryItems) {
|
|||||||
const _newRecords = []
|
const _newRecords = []
|
||||||
for (const oldAuthor of oldAuthors) {
|
for (const oldAuthor of oldAuthors) {
|
||||||
// Get an array of NEW library ids that have this author
|
// Get an array of NEW library ids that have this author
|
||||||
const librariesWithThisAuthor = [...new Set(oldLibraryItems.map(li => {
|
const librariesWithThisAuthor = [
|
||||||
if (!li.media.metadata.authors?.some(au => au.id === oldAuthor.id)) return null
|
...new Set(
|
||||||
|
oldLibraryItems
|
||||||
|
.map((li) => {
|
||||||
|
if (!li.media.metadata.authors?.some((au) => au.id === oldAuthor.id)) return null
|
||||||
if (!oldDbIdMap.libraries[li.libraryId]) {
|
if (!oldDbIdMap.libraries[li.libraryId]) {
|
||||||
Logger.warn(`[dbMigration] Authors library id ${li.libraryId} was not migrated`)
|
Logger.warn(`[dbMigration] Authors library id ${li.libraryId} was not migrated`)
|
||||||
}
|
}
|
||||||
return oldDbIdMap.libraries[li.libraryId]
|
return oldDbIdMap.libraries[li.libraryId]
|
||||||
}).filter(lid => lid))]
|
})
|
||||||
|
.filter((lid) => lid)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
if (!librariesWithThisAuthor.length) {
|
if (!librariesWithThisAuthor.length) {
|
||||||
Logger.error(`[dbMigration] Author ${oldAuthor.name} was not found in any libraries`)
|
Logger.error(`[dbMigration] Author ${oldAuthor.name} was not found in any libraries`)
|
||||||
@ -436,10 +431,16 @@ function migrateSeries(oldSerieses, oldLibraryItems) {
|
|||||||
// Series will be separate between libraries
|
// Series will be separate between libraries
|
||||||
for (const oldSeries of oldSerieses) {
|
for (const oldSeries of oldSerieses) {
|
||||||
// Get an array of NEW library ids that have this series
|
// Get an array of NEW library ids that have this series
|
||||||
const librariesWithThisSeries = [...new Set(oldLibraryItems.map(li => {
|
const librariesWithThisSeries = [
|
||||||
if (!li.media.metadata.series?.some(se => se.id === oldSeries.id)) return null
|
...new Set(
|
||||||
|
oldLibraryItems
|
||||||
|
.map((li) => {
|
||||||
|
if (!li.media.metadata.series?.some((se) => se.id === oldSeries.id)) return null
|
||||||
return oldDbIdMap.libraries[li.libraryId]
|
return oldDbIdMap.libraries[li.libraryId]
|
||||||
}).filter(lid => lid))]
|
})
|
||||||
|
.filter((lid) => lid)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
if (!librariesWithThisSeries.length) {
|
if (!librariesWithThisSeries.length) {
|
||||||
Logger.error(`[dbMigration] Series ${oldSeries.name} was not found in any libraries`)
|
Logger.error(`[dbMigration] Series ${oldSeries.name} was not found in any libraries`)
|
||||||
@ -478,16 +479,19 @@ function migrateUsers(oldUsers) {
|
|||||||
// Migrate User
|
// Migrate User
|
||||||
//
|
//
|
||||||
// Convert old library ids to new ids
|
// Convert old library ids to new ids
|
||||||
const librariesAccessible = (oldUser.librariesAccessible || []).map((lid) => oldDbIdMap.libraries[lid]).filter(li => li)
|
const librariesAccessible = (oldUser.librariesAccessible || []).map((lid) => oldDbIdMap.libraries[lid]).filter((li) => li)
|
||||||
|
|
||||||
// Convert old library item ids to new ids
|
// Convert old library item ids to new ids
|
||||||
const bookmarks = (oldUser.bookmarks || []).map(bm => {
|
const bookmarks = (oldUser.bookmarks || [])
|
||||||
|
.map((bm) => {
|
||||||
bm.libraryItemId = oldDbIdMap.libraryItems[bm.libraryItemId]
|
bm.libraryItemId = oldDbIdMap.libraryItems[bm.libraryItemId]
|
||||||
return bm
|
return bm
|
||||||
}).filter(bm => bm.libraryItemId)
|
})
|
||||||
|
.filter((bm) => bm.libraryItemId)
|
||||||
|
|
||||||
// Convert old series ids to new
|
// Convert old series ids to new
|
||||||
const seriesHideFromContinueListening = (oldUser.seriesHideFromContinueListening || []).map(oldSeriesId => {
|
const seriesHideFromContinueListening = (oldUser.seriesHideFromContinueListening || [])
|
||||||
|
.map((oldSeriesId) => {
|
||||||
// Series were split to be per library
|
// Series were split to be per library
|
||||||
// This will use the first series it finds
|
// This will use the first series it finds
|
||||||
for (const libraryId in oldDbIdMap.series) {
|
for (const libraryId in oldDbIdMap.series) {
|
||||||
@ -496,7 +500,8 @@ function migrateUsers(oldUsers) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}).filter(se => se)
|
})
|
||||||
|
.filter((se) => se)
|
||||||
|
|
||||||
const User = {
|
const User = {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
@ -705,7 +710,7 @@ function migrateCollections(oldCollections) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
const BookIds = oldCollection.books.map(lid => oldDbIdMap.books[lid]).filter(bid => bid)
|
const BookIds = oldCollection.books.map((lid) => oldDbIdMap.books[lid]).filter((bid) => bid)
|
||||||
if (!BookIds.length) {
|
if (!BookIds.length) {
|
||||||
Logger.warn(`[dbMigration] migrateCollections: Collection "${oldCollection.name}" has no books`)
|
Logger.warn(`[dbMigration] migrateCollections: Collection "${oldCollection.name}" has no books`)
|
||||||
continue
|
continue
|
||||||
@ -912,9 +917,9 @@ function migrateFeeds(oldFeeds) {
|
|||||||
*/
|
*/
|
||||||
function migrateSettings(oldSettings) {
|
function migrateSettings(oldSettings) {
|
||||||
const _newRecords = []
|
const _newRecords = []
|
||||||
const serverSettings = oldSettings.find(s => s.id === 'server-settings')
|
const serverSettings = oldSettings.find((s) => s.id === 'server-settings')
|
||||||
const notificationSettings = oldSettings.find(s => s.id === 'notification-settings')
|
const notificationSettings = oldSettings.find((s) => s.id === 'notification-settings')
|
||||||
const emailSettings = oldSettings.find(s => s.id === 'email-settings')
|
const emailSettings = oldSettings.find((s) => s.id === 'email-settings')
|
||||||
|
|
||||||
if (serverSettings) {
|
if (serverSettings) {
|
||||||
_newRecords.push({
|
_newRecords.push({
|
||||||
@ -1055,7 +1060,6 @@ async function handleMigrateSessions(DatabaseModels) {
|
|||||||
await DatabaseModels[model].bulkCreate(newSessionRecords[model])
|
await DatabaseModels[model].bulkCreate(newSessionRecords[model])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1156,17 +1160,32 @@ module.exports.checkShouldMigrate = async () => {
|
|||||||
*/
|
*/
|
||||||
async function migrationPatchNewColumns(queryInterface) {
|
async function migrationPatchNewColumns(queryInterface) {
|
||||||
try {
|
try {
|
||||||
return queryInterface.sequelize.transaction(t => {
|
return queryInterface.sequelize.transaction((t) => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
queryInterface.addColumn('libraryItems', 'extraData', {
|
queryInterface.addColumn(
|
||||||
|
'libraryItems',
|
||||||
|
'extraData',
|
||||||
|
{
|
||||||
type: DataTypes.JSON
|
type: DataTypes.JSON
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('podcastEpisodes', 'extraData', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'podcastEpisodes',
|
||||||
|
'extraData',
|
||||||
|
{
|
||||||
type: DataTypes.JSON
|
type: DataTypes.JSON
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('libraries', 'extraData', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'libraries',
|
||||||
|
'extraData',
|
||||||
|
{
|
||||||
type: DataTypes.JSON
|
type: DataTypes.JSON
|
||||||
}, { transaction: t })
|
},
|
||||||
|
{ transaction: t }
|
||||||
|
)
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -1188,7 +1207,7 @@ async function handleOldLibraryItems(ctx) {
|
|||||||
|
|
||||||
for (const libraryItem of libraryItems) {
|
for (const libraryItem of libraryItems) {
|
||||||
// Find matching old library item by ino
|
// Find matching old library item by ino
|
||||||
const matchingOldLibraryItem = oldLibraryItems.find(oli => oli.ino === libraryItem.ino)
|
const matchingOldLibraryItem = oldLibraryItems.find((oli) => oli.ino === libraryItem.ino)
|
||||||
if (matchingOldLibraryItem) {
|
if (matchingOldLibraryItem) {
|
||||||
oldDbIdMap.libraryItems[matchingOldLibraryItem.id] = libraryItem.id
|
oldDbIdMap.libraryItems[matchingOldLibraryItem.id] = libraryItem.id
|
||||||
|
|
||||||
@ -1202,7 +1221,7 @@ async function handleOldLibraryItems(ctx) {
|
|||||||
if (libraryItem.media.episodes?.length && matchingOldLibraryItem.media.episodes?.length) {
|
if (libraryItem.media.episodes?.length && matchingOldLibraryItem.media.episodes?.length) {
|
||||||
for (const podcastEpisode of libraryItem.media.episodes) {
|
for (const podcastEpisode of libraryItem.media.episodes) {
|
||||||
// Find matching old episode by audio file ino
|
// Find matching old episode by audio file ino
|
||||||
const matchingOldPodcastEpisode = matchingOldLibraryItem.media.episodes.find(oep => oep.audioFile?.ino && oep.audioFile.ino === podcastEpisode.audioFile?.ino)
|
const matchingOldPodcastEpisode = matchingOldLibraryItem.media.episodes.find((oep) => oep.audioFile?.ino && oep.audioFile.ino === podcastEpisode.audioFile?.ino)
|
||||||
if (matchingOldPodcastEpisode) {
|
if (matchingOldPodcastEpisode) {
|
||||||
oldDbIdMap.podcastEpisodes[matchingOldPodcastEpisode.id] = podcastEpisode.id
|
oldDbIdMap.podcastEpisodes[matchingOldPodcastEpisode.id] = podcastEpisode.id
|
||||||
|
|
||||||
@ -1244,11 +1263,11 @@ async function handleOldLibraries(ctx) {
|
|||||||
let librariesUpdated = 0
|
let librariesUpdated = 0
|
||||||
for (const library of libraries) {
|
for (const library of libraries) {
|
||||||
// Find matching old library using exact match on folder paths, exact match on library name
|
// Find matching old library using exact match on folder paths, exact match on library name
|
||||||
const matchingOldLibrary = oldLibraries.find(ol => {
|
const matchingOldLibrary = oldLibraries.find((ol) => {
|
||||||
if (ol.name !== library.name) {
|
if (ol.name !== library.name) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const folderPaths = ol.folders?.map(f => f.fullPath) || []
|
const folderPaths = ol.folders?.map((f) => f.fullPath) || []
|
||||||
return folderPaths.join(',') === library.folderPaths.join(',')
|
return folderPaths.join(',') === library.folderPaths.join(',')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1264,42 +1283,51 @@ async function handleOldLibraries(ctx) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Migration from 2.3.0 to 2.3.1 - fixing librariesAccessible and bookmarks
|
* Migration from 2.3.0 to 2.3.1 - fixing librariesAccessible and bookmarks
|
||||||
* @param {/src/Database} ctx
|
* @param {import('../../Database')} ctx
|
||||||
*/
|
*/
|
||||||
async function handleOldUsers(ctx) {
|
async function handleOldUsers(ctx) {
|
||||||
const users = await ctx.models.user.getOldUsers()
|
const usersNew = await ctx.userModel.findAll({
|
||||||
|
include: ctx.models.mediaProgress
|
||||||
|
})
|
||||||
|
const users = usersNew.map((u) => ctx.userModel.getOldUser(u))
|
||||||
|
|
||||||
let usersUpdated = 0
|
let usersUpdated = 0
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
let hasUpdates = false
|
let hasUpdates = false
|
||||||
if (user.bookmarks?.length) {
|
if (user.bookmarks?.length) {
|
||||||
user.bookmarks = user.bookmarks.map(bm => {
|
user.bookmarks = user.bookmarks
|
||||||
|
.map((bm) => {
|
||||||
// Only update if this is not the old id format
|
// Only update if this is not the old id format
|
||||||
if (!bm.libraryItemId.startsWith('li_')) return bm
|
if (!bm.libraryItemId.startsWith('li_')) return bm
|
||||||
|
|
||||||
bm.libraryItemId = oldDbIdMap.libraryItems[bm.libraryItemId]
|
bm.libraryItemId = oldDbIdMap.libraryItems[bm.libraryItemId]
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
return bm
|
return bm
|
||||||
}).filter(bm => bm.libraryItemId)
|
})
|
||||||
|
.filter((bm) => bm.libraryItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert old library ids to new library ids
|
// Convert old library ids to new library ids
|
||||||
if (user.librariesAccessible?.length) {
|
if (user.librariesAccessible?.length) {
|
||||||
user.librariesAccessible = user.librariesAccessible.map(lid => {
|
user.librariesAccessible = user.librariesAccessible
|
||||||
|
.map((lid) => {
|
||||||
if (!lid.startsWith('lib_') && lid !== 'main') return lid // Already not an old library id so dont change
|
if (!lid.startsWith('lib_') && lid !== 'main') return lid // Already not an old library id so dont change
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
return oldDbIdMap.libraries[lid]
|
return oldDbIdMap.libraries[lid]
|
||||||
}).filter(lid => lid)
|
})
|
||||||
|
.filter((lid) => lid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.seriesHideFromContinueListening?.length) {
|
if (user.seriesHideFromContinueListening?.length) {
|
||||||
user.seriesHideFromContinueListening = user.seriesHideFromContinueListening.map((seriesId) => {
|
user.seriesHideFromContinueListening = user.seriesHideFromContinueListening
|
||||||
|
.map((seriesId) => {
|
||||||
if (seriesId.startsWith('se_')) {
|
if (seriesId.startsWith('se_')) {
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
return null // Filter out old series ids
|
return null // Filter out old series ids
|
||||||
}
|
}
|
||||||
return seriesId
|
return seriesId
|
||||||
}).filter(se => se)
|
})
|
||||||
|
.filter((se) => se)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUpdates) {
|
if (hasUpdates) {
|
||||||
@ -1328,7 +1356,7 @@ module.exports.migrationPatch = async (ctx) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const oldDbPath = Path.join(global.ConfigPath, 'oldDb.zip')
|
const oldDbPath = Path.join(global.ConfigPath, 'oldDb.zip')
|
||||||
if (!await fs.pathExists(oldDbPath)) {
|
if (!(await fs.pathExists(oldDbPath))) {
|
||||||
Logger.info(`[dbMigration] Migration patch 2.3.0+ unnecessary - no oldDb.zip found`)
|
Logger.info(`[dbMigration] Migration patch 2.3.0+ unnecessary - no oldDb.zip found`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1337,7 +1365,7 @@ module.exports.migrationPatch = async (ctx) => {
|
|||||||
Logger.info(`[dbMigration] Applying migration patch from 2.3.0+`)
|
Logger.info(`[dbMigration] Applying migration patch from 2.3.0+`)
|
||||||
|
|
||||||
// Extract from oldDb.zip
|
// Extract from oldDb.zip
|
||||||
if (!await oldDbFiles.checkExtractItemsUsersAndLibraries()) {
|
if (!(await oldDbFiles.checkExtractItemsUsersAndLibraries())) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1368,7 +1396,7 @@ async function migrationPatch2LibraryItems(ctx, offset = 0) {
|
|||||||
for (const libraryItem of libraryItems) {
|
for (const libraryItem of libraryItems) {
|
||||||
if (libraryItem.libraryFiles?.length) {
|
if (libraryItem.libraryFiles?.length) {
|
||||||
let size = 0
|
let size = 0
|
||||||
libraryItem.libraryFiles.forEach(lf => {
|
libraryItem.libraryFiles.forEach((lf) => {
|
||||||
if (!isNaN(lf.metadata?.size)) {
|
if (!isNaN(lf.metadata?.size)) {
|
||||||
size += Number(lf.metadata.size)
|
size += Number(lf.metadata.size)
|
||||||
}
|
}
|
||||||
@ -1411,7 +1439,7 @@ async function migrationPatch2Books(ctx, offset = 0) {
|
|||||||
let duration = 0
|
let duration = 0
|
||||||
|
|
||||||
if (book.audioFiles?.length) {
|
if (book.audioFiles?.length) {
|
||||||
const tracks = book.audioFiles.filter(af => !af.exclude && !af.invalid)
|
const tracks = book.audioFiles.filter((af) => !af.exclude && !af.invalid)
|
||||||
for (const track of tracks) {
|
for (const track of tracks) {
|
||||||
if (track.duration !== null && !isNaN(track.duration)) {
|
if (track.duration !== null && !isNaN(track.duration)) {
|
||||||
duration += track.duration
|
duration += track.duration
|
||||||
@ -1631,44 +1659,95 @@ module.exports.migrationPatch2 = async (ctx) => {
|
|||||||
Logger.info(`[dbMigration] Applying migration patch from 2.3.3+`)
|
Logger.info(`[dbMigration] Applying migration patch from 2.3.3+`)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await queryInterface.sequelize.transaction(t => {
|
await queryInterface.sequelize.transaction((t) => {
|
||||||
const queries = []
|
const queries = []
|
||||||
if (!bookAuthorsTableDescription?.createdAt) {
|
if (!bookAuthorsTableDescription?.createdAt) {
|
||||||
queries.push(...[
|
queries.push(
|
||||||
queryInterface.addColumn('bookAuthors', 'createdAt', {
|
...[
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'bookAuthors',
|
||||||
|
'createdAt',
|
||||||
|
{
|
||||||
type: DataTypes.DATE
|
type: DataTypes.DATE
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('bookSeries', 'createdAt', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'bookSeries',
|
||||||
|
'createdAt',
|
||||||
|
{
|
||||||
type: DataTypes.DATE
|
type: DataTypes.DATE
|
||||||
}, { transaction: t }),
|
},
|
||||||
])
|
{ transaction: t }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (!authorsTableDescription?.lastFirst) {
|
if (!authorsTableDescription?.lastFirst) {
|
||||||
queries.push(...[
|
queries.push(
|
||||||
queryInterface.addColumn('authors', 'lastFirst', {
|
...[
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'authors',
|
||||||
|
'lastFirst',
|
||||||
|
{
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('libraryItems', 'size', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'libraryItems',
|
||||||
|
'size',
|
||||||
|
{
|
||||||
type: DataTypes.BIGINT
|
type: DataTypes.BIGINT
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('books', 'duration', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'books',
|
||||||
|
'duration',
|
||||||
|
{
|
||||||
type: DataTypes.FLOAT
|
type: DataTypes.FLOAT
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('books', 'titleIgnorePrefix', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'books',
|
||||||
|
'titleIgnorePrefix',
|
||||||
|
{
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('podcasts', 'titleIgnorePrefix', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'podcasts',
|
||||||
|
'titleIgnorePrefix',
|
||||||
|
{
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
}, { transaction: t }),
|
},
|
||||||
queryInterface.addColumn('series', 'nameIgnorePrefix', {
|
{ transaction: t }
|
||||||
|
),
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'series',
|
||||||
|
'nameIgnorePrefix',
|
||||||
|
{
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
}, { transaction: t }),
|
},
|
||||||
])
|
{ transaction: t }
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (!feedTableDescription?.coverPath) {
|
if (!feedTableDescription?.coverPath) {
|
||||||
queries.push(queryInterface.addColumn('feeds', 'coverPath', {
|
queries.push(
|
||||||
|
queryInterface.addColumn(
|
||||||
|
'feeds',
|
||||||
|
'coverPath',
|
||||||
|
{
|
||||||
type: DataTypes.STRING
|
type: DataTypes.STRING
|
||||||
}, { transaction: t }))
|
},
|
||||||
|
{ transaction: t }
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return Promise.all(queries)
|
return Promise.all(queries)
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user