mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-31 10:27:01 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			376 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const uuidv4 = require("uuid").v4
 | |
| const sequelize = require('sequelize')
 | |
| const Logger = require('../Logger')
 | |
| const oldUser = require('../objects/user/User')
 | |
| const SocketAuthority = require('../SocketAuthority')
 | |
| const { DataTypes, Model } = sequelize
 | |
| 
 | |
| class User extends Model {
 | |
|   constructor(values, options) {
 | |
|     super(values, options)
 | |
| 
 | |
|     /** @type {UUIDV4} */
 | |
|     this.id
 | |
|     /** @type {string} */
 | |
|     this.username
 | |
|     /** @type {string} */
 | |
|     this.email
 | |
|     /** @type {string} */
 | |
|     this.pash
 | |
|     /** @type {string} */
 | |
|     this.type
 | |
|     /** @type {boolean} */
 | |
|     this.isActive
 | |
|     /** @type {boolean} */
 | |
|     this.isLocked
 | |
|     /** @type {Date} */
 | |
|     this.lastSeen
 | |
|     /** @type {Object} */
 | |
|     this.permissions
 | |
|     /** @type {Object} */
 | |
|     this.bookmarks
 | |
|     /** @type {Object} */
 | |
|     this.extraData
 | |
|     /** @type {Date} */
 | |
|     this.createdAt
 | |
|     /** @type {Date} */
 | |
|     this.updatedAt
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get all oldUsers
 | |
|    * @returns {Promise<oldUser>}
 | |
|    */
 | |
|   static async getOldUsers() {
 | |
|     const users = await this.findAll({
 | |
|       include: this.sequelize.models.mediaProgress
 | |
|     })
 | |
|     return users.map(u => this.getOldUser(u))
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get old user model from new
 | |
|    * 
 | |
|    * @param {Object} 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
 | |
| 
 | |
|     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: userExpanded.extraData?.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
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 
 | |
|    * @param {oldUser} oldUser 
 | |
|    * @returns {Promise<User>}
 | |
|    */
 | |
|   static createFromOld(oldUser) {
 | |
|     const user = this.getFromOld(oldUser)
 | |
|     return this.create(user)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Update User from old user model
 | |
|    * 
 | |
|    * @param {oldUser} oldUser 
 | |
|    * @param {boolean} [hooks=true] Run before / after bulk update hooks?
 | |
|    * @returns {Promise<boolean>}
 | |
|    */
 | |
|   static updateFromOld(oldUser, hooks = true) {
 | |
|     const user = this.getFromOld(oldUser)
 | |
|     return this.update(user, {
 | |
|       hooks: !!hooks,
 | |
|       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
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static removeById(userId) {
 | |
|     return this.destroy({
 | |
|       where: {
 | |
|         id: userId
 | |
|       }
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Create root user
 | |
|    * @param {string} username 
 | |
|    * @param {string} pash 
 | |
|    * @param {Auth} auth 
 | |
|    * @returns {Promise<oldUser>}
 | |
|    */
 | |
|   static async createRootUser(username, pash, auth) {
 | |
|     const userId = uuidv4()
 | |
| 
 | |
|     const token = await auth.generateAccessToken({ id: userId, username })
 | |
| 
 | |
|     const newRoot = new oldUser({
 | |
|       id: userId,
 | |
|       type: 'root',
 | |
|       username,
 | |
|       pash,
 | |
|       token,
 | |
|       isActive: true,
 | |
|       createdAt: Date.now()
 | |
|     })
 | |
|     await this.createFromOld(newRoot)
 | |
|     return newRoot
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Create user from openid userinfo
 | |
|    * @param {Object} userinfo 
 | |
|    * @param {Auth} auth 
 | |
|    * @returns {Promise<oldUser>}
 | |
|    */
 | |
|   static async createUserFromOpenIdUserInfo(userinfo, auth) {
 | |
|     const userId = uuidv4()
 | |
|     // TODO: Ensure username is unique?
 | |
|     const username = userinfo.preferred_username || userinfo.name || userinfo.sub
 | |
|     const email = (userinfo.email && userinfo.email_verified) ? userinfo.email : null
 | |
| 
 | |
|     const token = await auth.generateAccessToken({ id: userId, username })
 | |
| 
 | |
|     const newUser = new oldUser({
 | |
|       id: userId,
 | |
|       type: 'user',
 | |
|       username,
 | |
|       email,
 | |
|       pash: null,
 | |
|       token,
 | |
|       isActive: true,
 | |
|       authOpenIDSub: userinfo.sub,
 | |
|       createdAt: Date.now()
 | |
|     })
 | |
|     if (await this.createFromOld(newUser)) {
 | |
|       SocketAuthority.adminEmitter('user_added', newUser.toJSONForBrowser())
 | |
|       return newUser
 | |
|     }
 | |
|     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
 | |
|    * @param {string} username 
 | |
|    * @returns {Promise<oldUser|null>} returns null if not found
 | |
|    */
 | |
|   static async getUserByUsername(username) {
 | |
|     if (!username) return null
 | |
|     const user = await this.findOne({
 | |
|       where: {
 | |
|         username: {
 | |
|           [sequelize.Op.like]: username
 | |
|         }
 | |
|       },
 | |
|       include: this.sequelize.models.mediaProgress
 | |
|     })
 | |
|     if (!user) return null
 | |
|     return this.getOldUser(user)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get user by email case insensitive
 | |
|    * @param {string} username 
 | |
|    * @returns {Promise<oldUser|null>} returns null if not found
 | |
|    */
 | |
|   static async getUserByEmail(email) {
 | |
|     if (!email) return null
 | |
|     const user = await this.findOne({
 | |
|       where: {
 | |
|         email: {
 | |
|           [sequelize.Op.like]: email
 | |
|         }
 | |
|       },
 | |
|       include: this.sequelize.models.mediaProgress
 | |
|     })
 | |
|     if (!user) return null
 | |
|     return this.getOldUser(user)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get user by id
 | |
|    * @param {string} userId 
 | |
|    * @returns {Promise<oldUser|null>} returns null if not found
 | |
|    */
 | |
|   static async getUserById(userId) {
 | |
|     if (!userId) return null
 | |
|     const user = await this.findByPk(userId, {
 | |
|       include: this.sequelize.models.mediaProgress
 | |
|     })
 | |
|     if (!user) return null
 | |
|     return this.getOldUser(user)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get user by openid sub
 | |
|    * @param {string} sub 
 | |
|    * @returns {Promise<oldUser|null>} returns null if not found
 | |
|    */
 | |
|   static async getUserByOpenIDSub(sub) {
 | |
|     if (!sub) return null
 | |
|     const user = await this.findOne({
 | |
|       where: sequelize.where(sequelize.literal(`extraData->>"authOpenIDSub"`), sub),
 | |
|       include: this.sequelize.models.mediaProgress
 | |
|     })
 | |
|     if (!user) return null
 | |
|     return this.getOldUser(user)
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get array of user id and username
 | |
|    * @returns {object[]} { id, username }
 | |
|    */
 | |
|   static async getMinifiedUserObjects() {
 | |
|     const users = await this.findAll({
 | |
|       attributes: ['id', 'username']
 | |
|     })
 | |
|     return users.map(u => {
 | |
|       return {
 | |
|         id: u.id,
 | |
|         username: u.username
 | |
|       }
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Return true if root user exists
 | |
|    * @returns {boolean}
 | |
|    */
 | |
|   static async getHasRootUser() {
 | |
|     const count = await this.count({
 | |
|       where: {
 | |
|         type: 'root'
 | |
|       }
 | |
|     })
 | |
|     return count > 0
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Initialize model
 | |
|    * @param {import('../Database').sequelize} sequelize 
 | |
|    */
 | |
|   static init(sequelize) {
 | |
|     super.init({
 | |
|       id: {
 | |
|         type: DataTypes.UUID,
 | |
|         defaultValue: DataTypes.UUIDV4,
 | |
|         primaryKey: true
 | |
|       },
 | |
|       username: DataTypes.STRING,
 | |
|       email: DataTypes.STRING,
 | |
|       pash: DataTypes.STRING,
 | |
|       type: DataTypes.STRING,
 | |
|       token: DataTypes.STRING,
 | |
|       isActive: {
 | |
|         type: DataTypes.BOOLEAN,
 | |
|         defaultValue: false
 | |
|       },
 | |
|       isLocked: {
 | |
|         type: DataTypes.BOOLEAN,
 | |
|         defaultValue: false
 | |
|       },
 | |
|       lastSeen: DataTypes.DATE,
 | |
|       permissions: DataTypes.JSON,
 | |
|       bookmarks: DataTypes.JSON,
 | |
|       extraData: DataTypes.JSON
 | |
|     }, {
 | |
|       sequelize,
 | |
|       modelName: 'user'
 | |
|     })
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = User |