From 7d9ba5c08999c6482f0bc5e0c09c6f55b7724090 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 11 May 2021 11:15:29 +0200 Subject: Cleanup models directory organization --- server/models/account/account-blocklist.ts | 2 +- server/models/account/account-video-rate.ts | 2 +- server/models/account/account.ts | 8 +- server/models/account/actor-image.ts | 100 --- server/models/account/user-notification-setting.ts | 221 ----- server/models/account/user-notification.ts | 665 -------------- server/models/account/user-video-history.ts | 100 --- server/models/account/user.ts | 967 --------------------- 8 files changed, 6 insertions(+), 2059 deletions(-) delete mode 100644 server/models/account/actor-image.ts delete mode 100644 server/models/account/user-notification-setting.ts delete mode 100644 server/models/account/user-notification.ts delete mode 100644 server/models/account/user-video-history.ts delete mode 100644 server/models/account/user.ts (limited to 'server/models/account') diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index fe9168ab8..9f3be22bd 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts @@ -2,7 +2,7 @@ import { Op } from 'sequelize' import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' import { AccountBlock } from '../../../shared/models' -import { ActorModel } from '../activitypub/actor' +import { ActorModel } from '../actor/actor' import { ServerModel } from '../server/server' import { getSort, searchAttribute } from '../utils' import { AccountModel } from './account' diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 801f76bba..576a44576 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts @@ -11,7 +11,7 @@ import { AccountVideoRate } from '../../../shared' import { VideoRateType } from '../../../shared/models/videos' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants' -import { ActorModel } from '../activitypub/actor' +import { ActorModel } from '../actor/actor' import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' import { VideoModel } from '../video/video' import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' diff --git a/server/models/account/account.ts b/server/models/account/account.ts index d33353af7..7b2af706d 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -30,19 +30,19 @@ import { MAccountSummaryFormattable, MChannelActor } from '../../types/models' -import { ActorModel } from '../activitypub/actor' -import { ActorFollowModel } from '../activitypub/actor-follow' +import { ActorModel } from '../actor/actor' +import { ActorFollowModel } from '../actor/actor-follow' +import { ActorImageModel } from '../actor/actor-image' import { ApplicationModel } from '../application/application' -import { ActorImageModel } from './actor-image' import { ServerModel } from '../server/server' import { ServerBlocklistModel } from '../server/server-blocklist' +import { UserModel } from '../user/user' import { getSort, throwIfNotValid } from '../utils' import { VideoModel } from '../video/video' import { VideoChannelModel } from '../video/video-channel' import { VideoCommentModel } from '../video/video-comment' import { VideoPlaylistModel } from '../video/video-playlist' import { AccountBlocklistModel } from './account-blocklist' -import { UserModel } from './user' export enum ScopeNames { SUMMARY = 'SUMMARY' diff --git a/server/models/account/actor-image.ts b/server/models/account/actor-image.ts deleted file mode 100644 index ae05b4969..000000000 --- a/server/models/account/actor-image.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { remove } from 'fs-extra' -import { join } from 'path' -import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' -import { MActorImageFormattable } from '@server/types/models' -import { ActorImageType } from '@shared/models' -import { ActorImage } from '../../../shared/models/actors/actor-image.model' -import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { logger } from '../../helpers/logger' -import { CONFIG } from '../../initializers/config' -import { LAZY_STATIC_PATHS } from '../../initializers/constants' -import { throwIfNotValid } from '../utils' - -@Table({ - tableName: 'actorImage', - indexes: [ - { - fields: [ 'filename' ], - unique: true - } - ] -}) -export class ActorImageModel extends Model { - - @AllowNull(false) - @Column - filename: string - - @AllowNull(true) - @Default(null) - @Column - height: number - - @AllowNull(true) - @Default(null) - @Column - width: number - - @AllowNull(true) - @Is('ActorImageFileUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'fileUrl', true)) - @Column - fileUrl: string - - @AllowNull(false) - @Column - onDisk: boolean - - @AllowNull(false) - @Column - type: ActorImageType - - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @AfterDestroy - static removeFilesAndSendDelete (instance: ActorImageModel) { - logger.info('Removing actor image file %s.', instance.filename) - - // Don't block the transaction - instance.removeImage() - .catch(err => logger.error('Cannot remove actor image file %s.', instance.filename, err)) - } - - static loadByName (filename: string) { - const query = { - where: { - filename - } - } - - return ActorImageModel.findOne(query) - } - - toFormattedJSON (this: MActorImageFormattable): ActorImage { - return { - path: this.getStaticPath(), - createdAt: this.createdAt, - updatedAt: this.updatedAt - } - } - - getStaticPath () { - if (this.type === ActorImageType.AVATAR) { - return join(LAZY_STATIC_PATHS.AVATARS, this.filename) - } - - return join(LAZY_STATIC_PATHS.BANNERS, this.filename) - } - - getPath () { - return join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename) - } - - removeImage () { - const imagePath = join(CONFIG.STORAGE.ACTOR_IMAGES, this.filename) - return remove(imagePath) - } -} diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts deleted file mode 100644 index 138051528..000000000 --- a/server/models/account/user-notification-setting.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { - AfterDestroy, - AfterUpdate, - AllowNull, - BelongsTo, - Column, - CreatedAt, - Default, - ForeignKey, - Is, - Model, - Table, - UpdatedAt -} from 'sequelize-typescript' -import { TokensCache } from '@server/lib/auth/tokens-cache' -import { MNotificationSettingFormattable } from '@server/types/models' -import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' -import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' -import { throwIfNotValid } from '../utils' -import { UserModel } from './user' - -@Table({ - tableName: 'userNotificationSetting', - indexes: [ - { - fields: [ 'userId' ], - unique: true - } - ] -}) -export class UserNotificationSettingModel extends Model { - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewVideoFromSubscription', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'newVideoFromSubscription') - ) - @Column - newVideoFromSubscription: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewCommentOnMyVideo', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'newCommentOnMyVideo') - ) - @Column - newCommentOnMyVideo: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingAbuseAsModerator', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'abuseAsModerator') - ) - @Column - abuseAsModerator: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingVideoAutoBlacklistAsModerator', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'videoAutoBlacklistAsModerator') - ) - @Column - videoAutoBlacklistAsModerator: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingBlacklistOnMyVideo', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'blacklistOnMyVideo') - ) - @Column - blacklistOnMyVideo: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingMyVideoPublished', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoPublished') - ) - @Column - myVideoPublished: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingMyVideoImportFinished', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoImportFinished') - ) - @Column - myVideoImportFinished: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewUserRegistration', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'newUserRegistration') - ) - @Column - newUserRegistration: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewInstanceFollower', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'newInstanceFollower') - ) - @Column - newInstanceFollower: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewInstanceFollower', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'autoInstanceFollowing') - ) - @Column - autoInstanceFollowing: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewFollow', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') - ) - @Column - newFollow: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingCommentMention', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'commentMention') - ) - @Column - commentMention: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingAbuseStateChange', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'abuseStateChange') - ) - @Column - abuseStateChange: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingAbuseNewMessage', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'abuseNewMessage') - ) - @Column - abuseNewMessage: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewPeerTubeVersion', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'newPeerTubeVersion') - ) - @Column - newPeerTubeVersion: UserNotificationSettingValue - - @AllowNull(false) - @Default(null) - @Is( - 'UserNotificationSettingNewPeerPluginVersion', - value => throwIfNotValid(value, isUserNotificationSettingValid, 'newPluginVersion') - ) - @Column - newPluginVersion: UserNotificationSettingValue - - @ForeignKey(() => UserModel) - @Column - userId: number - - @BelongsTo(() => UserModel, { - foreignKey: { - allowNull: false - }, - onDelete: 'cascade' - }) - User: UserModel - - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @AfterUpdate - @AfterDestroy - static removeTokenCache (instance: UserNotificationSettingModel) { - return TokensCache.Instance.clearCacheByUserId(instance.userId) - } - - toFormattedJSON (this: MNotificationSettingFormattable): UserNotificationSetting { - return { - newCommentOnMyVideo: this.newCommentOnMyVideo, - newVideoFromSubscription: this.newVideoFromSubscription, - abuseAsModerator: this.abuseAsModerator, - videoAutoBlacklistAsModerator: this.videoAutoBlacklistAsModerator, - blacklistOnMyVideo: this.blacklistOnMyVideo, - myVideoPublished: this.myVideoPublished, - myVideoImportFinished: this.myVideoImportFinished, - newUserRegistration: this.newUserRegistration, - commentMention: this.commentMention, - newFollow: this.newFollow, - newInstanceFollower: this.newInstanceFollower, - autoInstanceFollowing: this.autoInstanceFollowing, - abuseNewMessage: this.abuseNewMessage, - abuseStateChange: this.abuseStateChange, - newPeerTubeVersion: this.newPeerTubeVersion, - newPluginVersion: this.newPluginVersion - } - } -} diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts deleted file mode 100644 index 805095002..000000000 --- a/server/models/account/user-notification.ts +++ /dev/null @@ -1,665 +0,0 @@ -import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' -import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' -import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' -import { UserNotification, UserNotificationType } from '../../../shared' -import { isBooleanValid } from '../../helpers/custom-validators/misc' -import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' -import { AbuseModel } from '../abuse/abuse' -import { VideoAbuseModel } from '../abuse/video-abuse' -import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' -import { ActorModel } from '../activitypub/actor' -import { ActorFollowModel } from '../activitypub/actor-follow' -import { ApplicationModel } from '../application/application' -import { PluginModel } from '../server/plugin' -import { ServerModel } from '../server/server' -import { getSort, throwIfNotValid } from '../utils' -import { VideoModel } from '../video/video' -import { VideoBlacklistModel } from '../video/video-blacklist' -import { VideoChannelModel } from '../video/video-channel' -import { VideoCommentModel } from '../video/video-comment' -import { VideoImportModel } from '../video/video-import' -import { AccountModel } from './account' -import { ActorImageModel } from './actor-image' -import { UserModel } from './user' - -enum ScopeNames { - WITH_ALL = 'WITH_ALL' -} - -function buildActorWithAvatarInclude () { - return { - attributes: [ 'preferredUsername' ], - model: ActorModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'filename' ], - as: 'Avatar', - model: ActorImageModel.unscoped(), - required: false - }, - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - } - ] - } -} - -function buildVideoInclude (required: boolean) { - return { - attributes: [ 'id', 'uuid', 'name' ], - model: VideoModel.unscoped(), - required - } -} - -function buildChannelInclude (required: boolean, withActor = false) { - return { - required, - attributes: [ 'id', 'name' ], - model: VideoChannelModel.unscoped(), - include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] - } -} - -function buildAccountInclude (required: boolean, withActor = false) { - return { - required, - attributes: [ 'id', 'name' ], - model: AccountModel.unscoped(), - include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] - } -} - -@Scopes(() => ({ - [ScopeNames.WITH_ALL]: { - include: [ - Object.assign(buildVideoInclude(false), { - include: [ buildChannelInclude(true, true) ] - }), - - { - attributes: [ 'id', 'originCommentId' ], - model: VideoCommentModel.unscoped(), - required: false, - include: [ - buildAccountInclude(true, true), - buildVideoInclude(true) - ] - }, - - { - attributes: [ 'id', 'state' ], - model: AbuseModel.unscoped(), - required: false, - include: [ - { - attributes: [ 'id' ], - model: VideoAbuseModel.unscoped(), - required: false, - include: [ buildVideoInclude(false) ] - }, - { - attributes: [ 'id' ], - model: VideoCommentAbuseModel.unscoped(), - required: false, - include: [ - { - attributes: [ 'id', 'originCommentId' ], - model: VideoCommentModel.unscoped(), - required: false, - include: [ - { - attributes: [ 'id', 'name', 'uuid' ], - model: VideoModel.unscoped(), - required: false - } - ] - } - ] - }, - { - model: AccountModel, - as: 'FlaggedAccount', - required: false, - include: [ buildActorWithAvatarInclude() ] - } - ] - }, - - { - attributes: [ 'id' ], - model: VideoBlacklistModel.unscoped(), - required: false, - include: [ buildVideoInclude(true) ] - }, - - { - attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ], - model: VideoImportModel.unscoped(), - required: false, - include: [ buildVideoInclude(false) ] - }, - - { - attributes: [ 'id', 'name', 'type', 'latestVersion' ], - model: PluginModel.unscoped(), - required: false - }, - - { - attributes: [ 'id', 'latestPeerTubeVersion' ], - model: ApplicationModel.unscoped(), - required: false - }, - - { - attributes: [ 'id', 'state' ], - model: ActorFollowModel.unscoped(), - required: false, - include: [ - { - attributes: [ 'preferredUsername' ], - model: ActorModel.unscoped(), - required: true, - as: 'ActorFollower', - include: [ - { - attributes: [ 'id', 'name' ], - model: AccountModel.unscoped(), - required: true - }, - { - attributes: [ 'filename' ], - as: 'Avatar', - model: ActorImageModel.unscoped(), - required: false - }, - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - } - ] - }, - { - attributes: [ 'preferredUsername', 'type' ], - model: ActorModel.unscoped(), - required: true, - as: 'ActorFollowing', - include: [ - buildChannelInclude(false), - buildAccountInclude(false), - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - } - ] - } - ] - }, - - buildAccountInclude(false, true) - ] - } -})) -@Table({ - tableName: 'userNotification', - indexes: [ - { - fields: [ 'userId' ] - }, - { - fields: [ 'videoId' ], - where: { - videoId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'commentId' ], - where: { - commentId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'abuseId' ], - where: { - abuseId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'videoBlacklistId' ], - where: { - videoBlacklistId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'videoImportId' ], - where: { - videoImportId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'accountId' ], - where: { - accountId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'actorFollowId' ], - where: { - actorFollowId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'pluginId' ], - where: { - pluginId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'applicationId' ], - where: { - applicationId: { - [Op.ne]: null - } - } - } - ] as (ModelIndexesOptions & { where?: WhereOptions })[] -}) -export class UserNotificationModel extends Model { - - @AllowNull(false) - @Default(null) - @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type')) - @Column - type: UserNotificationType - - @AllowNull(false) - @Default(false) - @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read')) - @Column - read: boolean - - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @ForeignKey(() => UserModel) - @Column - userId: number - - @BelongsTo(() => UserModel, { - foreignKey: { - allowNull: false - }, - onDelete: 'cascade' - }) - User: UserModel - - @ForeignKey(() => VideoModel) - @Column - videoId: number - - @BelongsTo(() => VideoModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - Video: VideoModel - - @ForeignKey(() => VideoCommentModel) - @Column - commentId: number - - @BelongsTo(() => VideoCommentModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - Comment: VideoCommentModel - - @ForeignKey(() => AbuseModel) - @Column - abuseId: number - - @BelongsTo(() => AbuseModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - Abuse: AbuseModel - - @ForeignKey(() => VideoBlacklistModel) - @Column - videoBlacklistId: number - - @BelongsTo(() => VideoBlacklistModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - VideoBlacklist: VideoBlacklistModel - - @ForeignKey(() => VideoImportModel) - @Column - videoImportId: number - - @BelongsTo(() => VideoImportModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - VideoImport: VideoImportModel - - @ForeignKey(() => AccountModel) - @Column - accountId: number - - @BelongsTo(() => AccountModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - Account: AccountModel - - @ForeignKey(() => ActorFollowModel) - @Column - actorFollowId: number - - @BelongsTo(() => ActorFollowModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - ActorFollow: ActorFollowModel - - @ForeignKey(() => PluginModel) - @Column - pluginId: number - - @BelongsTo(() => PluginModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - Plugin: PluginModel - - @ForeignKey(() => ApplicationModel) - @Column - applicationId: number - - @BelongsTo(() => ApplicationModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - Application: ApplicationModel - - static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { - const where = { userId } - - const query: FindOptions = { - offset: start, - limit: count, - order: getSort(sort), - where - } - - if (unread !== undefined) query.where['read'] = !unread - - return Promise.all([ - UserNotificationModel.count({ where }) - .then(count => count || 0), - - count === 0 - ? [] - : UserNotificationModel.scope(ScopeNames.WITH_ALL).findAll(query) - ]).then(([ total, data ]) => ({ total, data })) - } - - static markAsRead (userId: number, notificationIds: number[]) { - const query = { - where: { - userId, - id: { - [Op.in]: notificationIds - } - } - } - - return UserNotificationModel.update({ read: true }, query) - } - - static markAllAsRead (userId: number) { - const query = { where: { userId } } - - return UserNotificationModel.update({ read: true }, query) - } - - static removeNotificationsOf (options: { id: number, type: 'account' | 'server', forUserId?: number }) { - const id = parseInt(options.id + '', 10) - - function buildAccountWhereQuery (base: string) { - const whereSuffix = options.forUserId - ? ` AND "userNotification"."userId" = ${options.forUserId}` - : '' - - if (options.type === 'account') { - return base + - ` WHERE "account"."id" = ${id} ${whereSuffix}` - } - - return base + - ` WHERE "actor"."serverId" = ${id} ${whereSuffix}` - } - - const queries = [ - buildAccountWhereQuery( - `SELECT "userNotification"."id" FROM "userNotification" ` + - `INNER JOIN "account" ON "userNotification"."accountId" = "account"."id" ` + - `INNER JOIN actor ON "actor"."id" = "account"."actorId" ` - ), - - // Remove notifications from muted accounts that followed ours - buildAccountWhereQuery( - `SELECT "userNotification"."id" FROM "userNotification" ` + - `INNER JOIN "actorFollow" ON "actorFollow".id = "userNotification"."actorFollowId" ` + - `INNER JOIN actor ON actor.id = "actorFollow"."actorId" ` + - `INNER JOIN account ON account."actorId" = actor.id ` - ), - - // Remove notifications from muted accounts that commented something - buildAccountWhereQuery( - `SELECT "userNotification"."id" FROM "userNotification" ` + - `INNER JOIN "actorFollow" ON "actorFollow".id = "userNotification"."actorFollowId" ` + - `INNER JOIN actor ON actor.id = "actorFollow"."actorId" ` + - `INNER JOIN account ON account."actorId" = actor.id ` - ), - - buildAccountWhereQuery( - `SELECT "userNotification"."id" FROM "userNotification" ` + - `INNER JOIN "videoComment" ON "videoComment".id = "userNotification"."commentId" ` + - `INNER JOIN account ON account.id = "videoComment"."accountId" ` + - `INNER JOIN actor ON "actor"."id" = "account"."actorId" ` - ) - ] - - const query = `DELETE FROM "userNotification" WHERE id IN (${queries.join(' UNION ')})` - - return UserNotificationModel.sequelize.query(query) - } - - toFormattedJSON (this: UserNotificationModelForApi): UserNotification { - const video = this.Video - ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) }) - : undefined - - const videoImport = this.VideoImport - ? { - id: this.VideoImport.id, - video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined, - torrentName: this.VideoImport.torrentName, - magnetUri: this.VideoImport.magnetUri, - targetUrl: this.VideoImport.targetUrl - } - : undefined - - const comment = this.Comment - ? { - id: this.Comment.id, - threadId: this.Comment.getThreadId(), - account: this.formatActor(this.Comment.Account), - video: this.formatVideo(this.Comment.Video) - } - : undefined - - const abuse = this.Abuse ? this.formatAbuse(this.Abuse) : undefined - - const videoBlacklist = this.VideoBlacklist - ? { - id: this.VideoBlacklist.id, - video: this.formatVideo(this.VideoBlacklist.Video) - } - : undefined - - const account = this.Account ? this.formatActor(this.Account) : undefined - - const actorFollowingType = { - Application: 'instance' as 'instance', - Group: 'channel' as 'channel', - Person: 'account' as 'account' - } - const actorFollow = this.ActorFollow - ? { - id: this.ActorFollow.id, - state: this.ActorFollow.state, - follower: { - id: this.ActorFollow.ActorFollower.Account.id, - displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), - name: this.ActorFollow.ActorFollower.preferredUsername, - avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined, - host: this.ActorFollow.ActorFollower.getHost() - }, - following: { - type: actorFollowingType[this.ActorFollow.ActorFollowing.type], - displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), - name: this.ActorFollow.ActorFollowing.preferredUsername, - host: this.ActorFollow.ActorFollowing.getHost() - } - } - : undefined - - const plugin = this.Plugin - ? { - name: this.Plugin.name, - type: this.Plugin.type, - latestVersion: this.Plugin.latestVersion - } - : undefined - - const peertube = this.Application - ? { latestVersion: this.Application.latestPeerTubeVersion } - : undefined - - return { - id: this.id, - type: this.type, - read: this.read, - video, - videoImport, - comment, - abuse, - videoBlacklist, - account, - actorFollow, - plugin, - peertube, - createdAt: this.createdAt.toISOString(), - updatedAt: this.updatedAt.toISOString() - } - } - - formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) { - return { - id: video.id, - uuid: video.uuid, - name: video.name - } - } - - formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) { - const commentAbuse = abuse.VideoCommentAbuse?.VideoComment - ? { - threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), - - video: abuse.VideoCommentAbuse.VideoComment.Video - ? { - id: abuse.VideoCommentAbuse.VideoComment.Video.id, - name: abuse.VideoCommentAbuse.VideoComment.Video.name, - uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid - } - : undefined - } - : undefined - - const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined - - const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) ? this.formatActor(abuse.FlaggedAccount) : undefined - - return { - id: abuse.id, - state: abuse.state, - video: videoAbuse, - comment: commentAbuse, - account: accountAbuse - } - } - - formatActor ( - this: UserNotificationModelForApi, - accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor - ) { - const avatar = accountOrChannel.Actor.Avatar - ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } - : undefined - - return { - id: accountOrChannel.id, - displayName: accountOrChannel.getDisplayName(), - name: accountOrChannel.Actor.preferredUsername, - host: accountOrChannel.Actor.getHost(), - avatar - } - } -} diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts deleted file mode 100644 index 6be1d65ea..000000000 --- a/server/models/account/user-video-history.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' -import { VideoModel } from '../video/video' -import { UserModel } from './user' -import { DestroyOptions, Op, Transaction } from 'sequelize' -import { MUserAccountId, MUserId } from '@server/types/models' - -@Table({ - tableName: 'userVideoHistory', - indexes: [ - { - fields: [ 'userId', 'videoId' ], - unique: true - }, - { - fields: [ 'userId' ] - }, - { - fields: [ 'videoId' ] - } - ] -}) -export class UserVideoHistoryModel extends Model { - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @AllowNull(false) - @IsInt - @Column - currentTime: number - - @ForeignKey(() => VideoModel) - @Column - videoId: number - - @BelongsTo(() => VideoModel, { - foreignKey: { - allowNull: false - }, - onDelete: 'CASCADE' - }) - Video: VideoModel - - @ForeignKey(() => UserModel) - @Column - userId: number - - @BelongsTo(() => UserModel, { - foreignKey: { - allowNull: false - }, - onDelete: 'CASCADE' - }) - User: UserModel - - static listForApi (user: MUserAccountId, start: number, count: number, search?: string) { - return VideoModel.listForApi({ - start, - count, - search, - sort: '-"userVideoHistory"."updatedAt"', - nsfw: null, // All - includeLocalVideos: true, - withFiles: false, - user, - historyOfUser: user - }) - } - - static removeUserHistoryBefore (user: MUserId, beforeDate: string, t: Transaction) { - const query: DestroyOptions = { - where: { - userId: user.id - }, - transaction: t - } - - if (beforeDate) { - query.where['updatedAt'] = { - [Op.lt]: beforeDate - } - } - - return UserVideoHistoryModel.destroy(query) - } - - static removeOldHistory (beforeDate: string) { - const query: DestroyOptions = { - where: { - updatedAt: { - [Op.lt]: beforeDate - } - } - } - - return UserVideoHistoryModel.destroy(query) - } -} diff --git a/server/models/account/user.ts b/server/models/account/user.ts deleted file mode 100644 index 513455773..000000000 --- a/server/models/account/user.ts +++ /dev/null @@ -1,967 +0,0 @@ -import { values } from 'lodash' -import { col, FindOptions, fn, literal, Op, QueryTypes, where, WhereOptions } from 'sequelize' -import { - AfterDestroy, - AfterUpdate, - AllowNull, - BeforeCreate, - BeforeUpdate, - Column, - CreatedAt, - DataType, - Default, - DefaultScope, - HasMany, - HasOne, - Is, - IsEmail, - IsUUID, - Model, - Scopes, - Table, - UpdatedAt -} from 'sequelize-typescript' -import { TokensCache } from '@server/lib/auth/tokens-cache' -import { - MMyUserFormattable, - MUser, - MUserDefault, - MUserFormattable, - MUserNotifSettingChannelDefault, - MUserWithNotificationSetting, - MVideoWithRights -} from '@server/types/models' -import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users' -import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models' -import { User, UserRole } from '../../../shared/models/users' -import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' -import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' -import { isThemeNameValid } from '../../helpers/custom-validators/plugins' -import { - isNoInstanceConfigWarningModal, - isNoWelcomeModal, - isUserAdminFlagsValid, - isUserAutoPlayNextVideoPlaylistValid, - isUserAutoPlayNextVideoValid, - isUserAutoPlayVideoValid, - isUserBlockedReasonValid, - isUserBlockedValid, - isUserEmailVerifiedValid, - isUserNSFWPolicyValid, - isUserPasswordValid, - isUserRoleValid, - isUserUsernameValid, - isUserVideoLanguages, - isUserVideoQuotaDailyValid, - isUserVideoQuotaValid, - isUserVideosHistoryEnabledValid, - isUserWebTorrentEnabledValid -} from '../../helpers/custom-validators/users' -import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' -import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' -import { getThemeOrDefault } from '../../lib/plugins/theme-utils' -import { ActorModel } from '../activitypub/actor' -import { ActorFollowModel } from '../activitypub/actor-follow' -import { OAuthTokenModel } from '../oauth/oauth-token' -import { getSort, throwIfNotValid } from '../utils' -import { VideoModel } from '../video/video' -import { VideoChannelModel } from '../video/video-channel' -import { VideoImportModel } from '../video/video-import' -import { VideoLiveModel } from '../video/video-live' -import { VideoPlaylistModel } from '../video/video-playlist' -import { AccountModel } from './account' -import { UserNotificationSettingModel } from './user-notification-setting' -import { ActorImageModel } from './actor-image' - -enum ScopeNames { - FOR_ME_API = 'FOR_ME_API', - WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS', - WITH_STATS = 'WITH_STATS' -} - -@DefaultScope(() => ({ - include: [ - { - model: AccountModel, - required: true - }, - { - model: UserNotificationSettingModel, - required: true - } - ] -})) -@Scopes(() => ({ - [ScopeNames.FOR_ME_API]: { - include: [ - { - model: AccountModel, - include: [ - { - model: VideoChannelModel.unscoped(), - include: [ - { - model: ActorModel, - required: true, - include: [ - { - model: ActorImageModel, - as: 'Banner', - required: false - } - ] - } - ] - }, - { - attributes: [ 'id', 'name', 'type' ], - model: VideoPlaylistModel.unscoped(), - required: true, - where: { - type: { - [Op.ne]: VideoPlaylistType.REGULAR - } - } - } - ] - }, - { - model: UserNotificationSettingModel, - required: true - } - ] - }, - [ScopeNames.WITH_VIDEOCHANNELS]: { - include: [ - { - model: AccountModel, - include: [ - { - model: VideoChannelModel - }, - { - attributes: [ 'id', 'name', 'type' ], - model: VideoPlaylistModel.unscoped(), - required: true, - where: { - type: { - [Op.ne]: VideoPlaylistType.REGULAR - } - } - } - ] - } - ] - }, - [ScopeNames.WITH_STATS]: { - attributes: { - include: [ - [ - literal( - '(' + - UserModel.generateUserQuotaBaseSQL({ - withSelect: false, - whereUserId: '"UserModel"."id"' - }) + - ')' - ), - 'videoQuotaUsed' - ], - [ - literal( - '(' + - 'SELECT COUNT("video"."id") ' + - 'FROM "video" ' + - 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + - 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + - 'WHERE "account"."userId" = "UserModel"."id"' + - ')' - ), - 'videosCount' - ], - [ - literal( - '(' + - `SELECT concat_ws(':', "abuses", "acceptedAbuses") ` + - 'FROM (' + - 'SELECT COUNT("abuse"."id") AS "abuses", ' + - `COUNT("abuse"."id") FILTER (WHERE "abuse"."state" = ${AbuseState.ACCEPTED}) AS "acceptedAbuses" ` + - 'FROM "abuse" ' + - 'INNER JOIN "account" ON "account"."id" = "abuse"."flaggedAccountId" ' + - 'WHERE "account"."userId" = "UserModel"."id"' + - ') t' + - ')' - ), - 'abusesCount' - ], - [ - literal( - '(' + - 'SELECT COUNT("abuse"."id") ' + - 'FROM "abuse" ' + - 'INNER JOIN "account" ON "account"."id" = "abuse"."reporterAccountId" ' + - 'WHERE "account"."userId" = "UserModel"."id"' + - ')' - ), - 'abusesCreatedCount' - ], - [ - literal( - '(' + - 'SELECT COUNT("videoComment"."id") ' + - 'FROM "videoComment" ' + - 'INNER JOIN "account" ON "account"."id" = "videoComment"."accountId" ' + - 'WHERE "account"."userId" = "UserModel"."id"' + - ')' - ), - 'videoCommentsCount' - ] - ] - } - } -})) -@Table({ - tableName: 'user', - indexes: [ - { - fields: [ 'username' ], - unique: true - }, - { - fields: [ 'email' ], - unique: true - } - ] -}) -export class UserModel extends Model { - - @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) - @Default(true) - @Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled')) - @Column - webTorrentEnabled: 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, isNoInstanceConfigWarningModal, 'no instance config warning modal') - ) - @Column - noInstanceConfigWarningModal: boolean - - @AllowNull(false) - @Default(false) - @Is( - 'UserNoInstanceConfigWarningModal', - value => throwIfNotValid(value, isNoWelcomeModal, 'no welcome modal') - ) - @Column - noWelcomeModal: 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 listForApi (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 = { - attributes: { - include: [ - [ - literal( - '(' + - UserModel.generateUserQuotaBaseSQL({ - withSelect: false, - whereUserId: '"UserModel"."id"' - }) + - ')' - ), - 'videoQuotaUsed' - ] as any // FIXME: typings - ] - }, - offset: start, - limit: count, - order: getSort(sort), - where - } - - return UserModel.findAndCountAll(query) - .then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) - } - - static listWithRight (right: UserRight): Promise { - 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 { - const query = { - include: [ - { - model: UserNotificationSettingModel.unscoped(), - required: true - }, - { - attributes: [ 'userId' ], - model: AccountModel.unscoped(), - required: true, - include: [ - { - attributes: [], - model: ActorModel.unscoped(), - required: true, - where: { - serverId: null - }, - include: [ - { - attributes: [], - as: 'ActorFollowings', - model: ActorFollowModel.unscoped(), - required: true, - where: { - targetActorId: actorId - } - } - ] - } - ] - } - ] - } - - return UserModel.unscoped().findAll(query) - } - - static listByUsernames (usernames: string[]): Promise { - const query = { - where: { - username: usernames - } - } - - return UserModel.findAll(query) - } - - static loadById (id: number): Promise { - return UserModel.unscoped().findByPk(id) - } - - static loadByIdFull (id: number): Promise { - return UserModel.findByPk(id) - } - - static loadByIdWithChannels (id: number, withStats = false): Promise { - const scopes = [ - ScopeNames.WITH_VIDEOCHANNELS - ] - - if (withStats) scopes.push(ScopeNames.WITH_STATS) - - return UserModel.scope(scopes).findByPk(id) - } - - static loadByUsername (username: string): Promise { - const query = { - where: { - username - } - } - - return UserModel.findOne(query) - } - - static loadForMeAPI (id: number): Promise { - const query = { - where: { - id - } - } - - return UserModel.scope(ScopeNames.FOR_ME_API).findOne(query) - } - - static loadByEmail (email: string): Promise { - const query = { - where: { - email - } - } - - return UserModel.findOne(query) - } - - static loadByUsernameOrEmail (username: string, email?: string): Promise { - if (!email) email = username - - const query = { - where: { - [Op.or]: [ - where(fn('lower', col('username')), fn('lower', username)), - - { email } - ] - } - } - - return UserModel.findOne(query) - } - - static loadByVideoId (videoId: number): Promise { - const query = { - include: [ - { - required: true, - attributes: [ 'id' ], - model: AccountModel.unscoped(), - include: [ - { - required: true, - attributes: [ 'id' ], - model: VideoChannelModel.unscoped(), - include: [ - { - required: true, - attributes: [ 'id' ], - model: VideoModel.unscoped(), - where: { - id: videoId - } - } - ] - } - ] - } - ] - } - - return UserModel.findOne(query) - } - - static loadByVideoImportId (videoImportId: number): Promise { - const query = { - include: [ - { - required: true, - attributes: [ 'id' ], - model: VideoImportModel.unscoped(), - where: { - id: videoImportId - } - } - ] - } - - return UserModel.findOne(query) - } - - static loadByChannelActorId (videoChannelActorId: number): Promise { - const query = { - include: [ - { - required: true, - attributes: [ 'id' ], - model: AccountModel.unscoped(), - include: [ - { - required: true, - attributes: [ 'id' ], - model: VideoChannelModel.unscoped(), - where: { - actorId: videoChannelActorId - } - } - ] - } - ] - } - - return UserModel.findOne(query) - } - - static loadByAccountActorId (accountActorId: number): Promise { - const query = { - include: [ - { - required: true, - attributes: [ 'id' ], - model: AccountModel.unscoped(), - where: { - actorId: accountActorId - } - } - ] - } - - return UserModel.findOne(query) - } - - static loadByLiveId (liveId: number): Promise { - const query = { - include: [ - { - attributes: [ 'id' ], - model: AccountModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'id' ], - model: VideoChannelModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'id' ], - model: VideoModel.unscoped(), - required: true, - include: [ - { - attributes: [], - model: VideoLiveModel.unscoped(), - required: true, - where: { - id: liveId - } - } - ] - } - ] - } - ] - } - ] - } - - return UserModel.unscoped().findOne(query) - } - - static generateUserQuotaBaseSQL (options: { - whereUserId: '$userId' | '"UserModel"."id"' - withSelect: boolean - where?: string - }) { - const andWhere = options.where - ? 'AND ' + options.where - : '' - - const videoChannelJoin = 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + - 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + - `WHERE "account"."userId" = ${options.whereUserId} ${andWhere}` - - const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + - 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + - videoChannelJoin - - const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + - 'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' + - 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" ' + - videoChannelJoin - - return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' + - 'FROM (' + - `SELECT MAX("t1"."size") AS "size" FROM (${webtorrentFiles} UNION ${hlsFiles}) t1 ` + - 'GROUP BY "t1"."videoId"' + - ') t2' - } - - static getTotalRawQuery (query: string, userId: number) { - const options = { - bind: { userId }, - type: QueryTypes.SELECT as QueryTypes.SELECT - } - - return UserModel.sequelize.query<{ total: string }>(query, options) - .then(([ { total } ]) => { - if (total === null) return 0 - - return parseInt(total, 10) - }) - } - - static async getStats () { - function getActiveUsers (days: number) { - const query = { - where: { - [Op.and]: [ - literal(`"lastLoginDate" > NOW() - INTERVAL '${days}d'`) - ] - } - } - - return UserModel.count(query) - } - - const totalUsers = await UserModel.count() - const totalDailyActiveUsers = await getActiveUsers(1) - const totalWeeklyActiveUsers = await getActiveUsers(7) - const totalMonthlyActiveUsers = await getActiveUsers(30) - const totalHalfYearActiveUsers = await getActiveUsers(180) - - return { - totalUsers, - totalDailyActiveUsers, - totalWeeklyActiveUsers, - totalMonthlyActiveUsers, - totalHalfYearActiveUsers - } - } - - static autoComplete (search: string) { - const query = { - where: { - username: { - [Op.like]: `%${search}%` - } - }, - limit: 10 - } - - return UserModel.findAll(query) - .then(u => u.map(u => u.username)) - } - - canGetVideo (video: MVideoWithRights) { - const videoUserId = video.VideoChannel.Account.userId - - if (video.isBlacklisted()) { - return videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) - } - - if (video.privacy === VideoPrivacy.PRIVATE) { - return video.VideoChannel && videoUserId === this.id || this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) - } - - if (video.privacy === VideoPrivacy.INTERNAL) return true - - return false - } - - hasRight (right: UserRight) { - return hasUserRight(this.role, right) - } - - hasAdminFlag (flag: UserAdminFlag) { - return this.adminFlags & flag - } - - isPasswordMatch (password: string) { - return comparePassword(password, this.password) - } - - toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { - const videoQuotaUsed = this.get('videoQuotaUsed') - const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') - const videosCount = this.get('videosCount') - const [ abusesCount, abusesAcceptedCount ] = (this.get('abusesCount') as string || ':').split(':') - const abusesCreatedCount = this.get('abusesCreatedCount') - const videoCommentsCount = this.get('videoCommentsCount') - - const json: User = { - id: this.id, - username: this.username, - email: this.email, - theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), - - pendingEmail: this.pendingEmail, - emailVerified: this.emailVerified, - - nsfwPolicy: this.nsfwPolicy, - webTorrentEnabled: this.webTorrentEnabled, - videosHistoryEnabled: this.videosHistoryEnabled, - autoPlayVideo: this.autoPlayVideo, - autoPlayNextVideo: this.autoPlayNextVideo, - autoPlayNextVideoPlaylist: this.autoPlayNextVideoPlaylist, - videoLanguages: this.videoLanguages, - - role: this.role, - roleLabel: USER_ROLE_LABELS[this.role], - - videoQuota: this.videoQuota, - videoQuotaDaily: this.videoQuotaDaily, - videoQuotaUsed: videoQuotaUsed !== undefined - ? parseInt(videoQuotaUsed + '', 10) - : undefined, - videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined - ? parseInt(videoQuotaUsedDaily + '', 10) - : undefined, - videosCount: videosCount !== undefined - ? parseInt(videosCount + '', 10) - : undefined, - abusesCount: abusesCount - ? parseInt(abusesCount, 10) - : undefined, - abusesAcceptedCount: abusesAcceptedCount - ? parseInt(abusesAcceptedCount, 10) - : undefined, - abusesCreatedCount: abusesCreatedCount !== undefined - ? parseInt(abusesCreatedCount + '', 10) - : undefined, - videoCommentsCount: videoCommentsCount !== undefined - ? parseInt(videoCommentsCount + '', 10) - : undefined, - - noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, - noWelcomeModal: this.noWelcomeModal, - - blocked: this.blocked, - blockedReason: this.blockedReason, - - account: this.Account.toFormattedJSON(), - - notificationSettings: this.NotificationSetting - ? this.NotificationSetting.toFormattedJSON() - : undefined, - - videoChannels: [], - - createdAt: this.createdAt, - - pluginAuth: this.pluginAuth, - - lastLoginDate: this.lastLoginDate - } - - if (parameters.withAdminFlags) { - Object.assign(json, { adminFlags: this.adminFlags }) - } - - if (Array.isArray(this.Account.VideoChannels) === true) { - json.videoChannels = this.Account.VideoChannels - .map(c => c.toFormattedJSON()) - .sort((v1, v2) => { - if (v1.createdAt < v2.createdAt) return -1 - if (v1.createdAt === v2.createdAt) return 0 - - return 1 - }) - } - - return json - } - - toMeFormattedJSON (this: MMyUserFormattable): MyUser { - const formatted = this.toFormattedJSON() - - const specialPlaylists = this.Account.VideoPlaylists - .map(p => ({ id: p.id, name: p.name, type: p.type })) - - return Object.assign(formatted, { specialPlaylists }) - } -} -- cgit v1.2.3