+ fields: [ 'email' ],
+ unique: true
+ }
+ ]
+})
+export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
+
+ @AllowNull(true)
+ @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true))
+ @Column
+ password: string
+
+ @AllowNull(false)
+ @Is('UserUsername', value => throwIfNotValid(value, isUserUsernameValid, 'user name'))
+ @Column
+ username: string
+
+ @AllowNull(false)
+ @IsEmail
+ @Column(DataType.STRING(400))
+ email: string
+
+ @AllowNull(true)
+ @IsEmail
+ @Column(DataType.STRING(400))
+ pendingEmail: string
+
+ @AllowNull(true)
+ @Default(null)
+ @Is('UserEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true))
+ @Column
+ emailVerified: boolean
+
+ @AllowNull(false)
+ @Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy'))
+ @Column(DataType.ENUM(...values(NSFW_POLICY_TYPES)))
+ nsfwPolicy: NSFWPolicyType
+
+ @AllowNull(false)
+ @Is('p2pEnabled', value => throwIfNotValid(value, isUserP2PEnabledValid, 'P2P enabled'))
+ @Column
+ p2pEnabled: boolean
+
+ @AllowNull(false)
+ @Default(true)
+ @Is('UserVideosHistoryEnabled', value => throwIfNotValid(value, isUserVideosHistoryEnabledValid, 'Videos history enabled'))
+ @Column
+ videosHistoryEnabled: boolean
+
+ @AllowNull(false)
+ @Default(true)
+ @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean'))
+ @Column
+ autoPlayVideo: boolean
+
+ @AllowNull(false)
+ @Default(false)
+ @Is('UserAutoPlayNextVideo', value => throwIfNotValid(value, isUserAutoPlayNextVideoValid, 'auto play next video boolean'))
+ @Column
+ autoPlayNextVideo: boolean
+
+ @AllowNull(false)
+ @Default(true)
+ @Is(
+ 'UserAutoPlayNextVideoPlaylist',
+ value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean')
+ )
+ @Column
+ autoPlayNextVideoPlaylist: boolean
+
+ @AllowNull(true)
+ @Default(null)
+ @Is('UserVideoLanguages', value => throwIfNotValid(value, isUserVideoLanguages, 'video languages'))
+ @Column(DataType.ARRAY(DataType.STRING))
+ videoLanguages: string[]
+
+ @AllowNull(false)
+ @Default(UserAdminFlag.NONE)
+ @Is('UserAdminFlags', value => throwIfNotValid(value, isUserAdminFlagsValid, 'user admin flags'))
+ @Column
+ adminFlags?: UserAdminFlag
+
+ @AllowNull(false)
+ @Default(false)
+ @Is('UserBlocked', value => throwIfNotValid(value, isUserBlockedValid, 'blocked boolean'))
+ @Column
+ blocked: boolean
+
+ @AllowNull(true)
+ @Default(null)
+ @Is('UserBlockedReason', value => throwIfNotValid(value, isUserBlockedReasonValid, 'blocked reason', true))
+ @Column
+ blockedReason: string
+
+ @AllowNull(false)
+ @Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role'))
+ @Column
+ role: number
+
+ @AllowNull(false)
+ @Is('UserVideoQuota', value => throwIfNotValid(value, isUserVideoQuotaValid, 'video quota'))
+ @Column(DataType.BIGINT)
+ videoQuota: number
+
+ @AllowNull(false)
+ @Is('UserVideoQuotaDaily', value => throwIfNotValid(value, isUserVideoQuotaDailyValid, 'video quota daily'))
+ @Column(DataType.BIGINT)
+ videoQuotaDaily: number
+
+ @AllowNull(false)
+ @Default(DEFAULT_USER_THEME_NAME)
+ @Is('UserTheme', value => throwIfNotValid(value, isThemeNameValid, 'theme'))
+ @Column
+ theme: string
+
+ @AllowNull(false)
+ @Default(false)
+ @Is(
+ 'UserNoInstanceConfigWarningModal',
+ value => throwIfNotValid(value, isUserNoModal, 'no instance config warning modal')
+ )
+ @Column
+ noInstanceConfigWarningModal: boolean
+
+ @AllowNull(false)
+ @Default(false)
+ @Is(
+ 'UserNoWelcomeModal',
+ value => throwIfNotValid(value, isUserNoModal, 'no welcome modal')
+ )
+ @Column
+ noWelcomeModal: boolean
+
+ @AllowNull(false)
+ @Default(false)
+ @Is(
+ 'UserNoAccountSetupWarningModal',
+ value => throwIfNotValid(value, isUserNoModal, 'no account setup warning modal')
+ )
+ @Column
+ noAccountSetupWarningModal: boolean
+
+ @AllowNull(true)
+ @Default(null)
+ @Column
+ pluginAuth: string
+
+ @AllowNull(false)
+ @Default(DataType.UUIDV4)
+ @IsUUID(4)
+ @Column(DataType.UUID)
+ feedToken: string
+
+ @AllowNull(true)
+ @Default(null)
+ @Column
+ lastLoginDate: Date
+
+ @CreatedAt
+ createdAt: Date
+
+ @UpdatedAt
+ updatedAt: Date
+
+ @HasOne(() => AccountModel, {
+ foreignKey: 'userId',
+ onDelete: 'cascade',
+ hooks: true
+ })
+ Account: AccountModel
+
+ @HasOne(() => UserNotificationSettingModel, {
+ foreignKey: 'userId',
+ onDelete: 'cascade',
+ hooks: true
+ })
+ NotificationSetting: UserNotificationSettingModel
+
+ @HasMany(() => VideoImportModel, {
+ foreignKey: 'userId',
+ onDelete: 'cascade'
+ })
+ VideoImports: VideoImportModel[]
+
+ @HasMany(() => OAuthTokenModel, {
+ foreignKey: 'userId',
+ onDelete: 'cascade'
+ })
+ OAuthTokens: OAuthTokenModel[]
+
+ @BeforeCreate
+ @BeforeUpdate
+ static cryptPasswordIfNeeded (instance: UserModel) {
+ if (instance.changed('password') && instance.password) {
+ return cryptPassword(instance.password)
+ .then(hash => {
+ instance.password = hash
+ return undefined
+ })
+ }
+ }
+
+ @AfterUpdate
+ @AfterDestroy
+ static removeTokenCache (instance: UserModel) {
+ return TokensCache.Instance.clearCacheByUserId(instance.id)
+ }
+
+ static countTotal () {
+ return this.count()
+ }
+
+ static listForAdminApi (parameters: {
+ start: number
+ count: number
+ sort: string
+ search?: string
+ blocked?: boolean
+ }) {
+ const { start, count, sort, search, blocked } = parameters
+ const where: WhereOptions = {}
+
+ if (search) {
+ Object.assign(where, {
+ [Op.or]: [
+ {
+ email: {
+ [Op.iLike]: '%' + search + '%'
+ }
+ },
+ {
+ username: {
+ [Op.iLike]: '%' + search + '%'
+ }
+ }
+ ]
+ })
+ }
+
+ if (blocked !== undefined) {
+ Object.assign(where, {
+ blocked: blocked
+ })
+ }
+
+ const query: FindOptions = {
+ offset: start,
+ limit: count,
+ order: getAdminUsersSort(sort),
+ where
+ }
+
+ return Promise.all([
+ UserModel.unscoped().count(query),
+ UserModel.scope([ 'defaultScope', ScopeNames.WITH_QUOTA ]).findAll(query)
+ ]).then(([ total, data ]) => ({ total, data }))
+ }
+
+ static listWithRight (right: UserRight): Promise<MUserDefault[]> {
+ const roles = Object.keys(USER_ROLE_LABELS)
+ .map(k => parseInt(k, 10) as UserRole)
+ .filter(role => hasUserRight(role, right))
+
+ const query = {
+ where: {
+ role: {
+ [Op.in]: roles
+ }
+ }
+ }
+
+ return UserModel.findAll(query)
+ }
+
+ static listUserSubscribersOf (actorId: number): Promise<MUserWithNotificationSetting[]> {
+ const query = {
+ include: [