From 3acc50844047a37698f0618fa235c138e386a053 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Apr 2019 09:50:57 +0200 Subject: Upgrade sequelize --- server/models/account/account-blocklist.ts | 10 +- server/models/account/account.ts | 10 +- server/models/account/user-notification.ts | 44 +++--- server/models/account/user.ts | 58 ++++---- server/models/activitypub/actor.ts | 47 ++++--- server/models/application/application.ts | 6 +- server/models/oauth/oauth-client.ts | 4 +- server/models/oauth/oauth-token.ts | 22 +-- server/models/redundancy/video-redundancy.ts | 56 ++++---- server/models/server/server-blocklist.ts | 8 +- server/models/utils.ts | 12 +- server/models/video/tag.ts | 2 +- server/models/video/thumbnail.ts | 4 +- server/models/video/video-caption.ts | 13 +- server/models/video/video-change-ownership.ts | 14 +- server/models/video/video-channel.ts | 16 +-- server/models/video/video-comment.ts | 33 +++-- server/models/video/video-file.ts | 27 ++-- server/models/video/video-format-utils.ts | 10 +- server/models/video/video-import.ts | 8 +- server/models/video/video-playlist.ts | 47 +++---- server/models/video/video-share.ts | 10 +- server/models/video/video-streaming-playlist.ts | 6 +- server/models/video/video.ts | 179 ++++++++++++------------ 24 files changed, 322 insertions(+), 324 deletions(-) (limited to 'server/models') diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index efd6ed59e..d5746ad76 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts @@ -8,22 +8,22 @@ enum ScopeNames { WITH_ACCOUNTS = 'WITH_ACCOUNTS' } -@Scopes({ +@Scopes(() => ({ [ScopeNames.WITH_ACCOUNTS]: { include: [ { - model: () => AccountModel, + model: AccountModel, required: true, as: 'ByAccount' }, { - model: () => AccountModel, + model: AccountModel, required: true, as: 'BlockedAccount' } ] } -}) +})) @Table({ tableName: 'accountBlocklist', @@ -83,7 +83,7 @@ export class AccountBlocklistModel extends Model { attributes: [ 'accountId', 'id' ], where: { accountId: { - [Op.any]: accountIds + [Op.in]: accountIds // FIXME: sequelize ANY seems broken }, targetAccountId }, diff --git a/server/models/account/account.ts b/server/models/account/account.ts index bf2ed0a61..c53312990 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -33,15 +33,15 @@ export enum ScopeNames { SUMMARY = 'SUMMARY' } -@DefaultScope({ +@DefaultScope(() => ({ include: [ { - model: () => ActorModel, // Default scope includes avatar and server + model: ActorModel, // Default scope includes avatar and server required: true } ] -}) -@Scopes({ +})) +@Scopes(() => ({ [ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => { return { attributes: [ 'id', 'name' ], @@ -66,7 +66,7 @@ export enum ScopeNames { ] } } -}) +})) @Table({ tableName: 'account', indexes: [ diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index 08388f268..a4f97037b 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts @@ -6,7 +6,7 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use import { UserModel } from './user' import { VideoModel } from '../video/video' import { VideoCommentModel } from '../video/video-comment' -import { FindOptions, Op } from 'sequelize' +import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' import { VideoChannelModel } from '../video/video-channel' import { AccountModel } from './account' import { VideoAbuseModel } from '../video/video-abuse' @@ -24,17 +24,17 @@ enum ScopeNames { function buildActorWithAvatarInclude () { return { attributes: [ 'preferredUsername' ], - model: () => ActorModel.unscoped(), + model: ActorModel.unscoped(), required: true, include: [ { attributes: [ 'filename' ], - model: () => AvatarModel.unscoped(), + model: AvatarModel.unscoped(), required: false }, { attributes: [ 'host' ], - model: () => ServerModel.unscoped(), + model: ServerModel.unscoped(), required: false } ] @@ -44,7 +44,7 @@ function buildActorWithAvatarInclude () { function buildVideoInclude (required: boolean) { return { attributes: [ 'id', 'uuid', 'name' ], - model: () => VideoModel.unscoped(), + model: VideoModel.unscoped(), required } } @@ -53,7 +53,7 @@ function buildChannelInclude (required: boolean, withActor = false) { return { required, attributes: [ 'id', 'name' ], - model: () => VideoChannelModel.unscoped(), + model: VideoChannelModel.unscoped(), include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] } } @@ -62,12 +62,12 @@ function buildAccountInclude (required: boolean, withActor = false) { return { required, attributes: [ 'id', 'name' ], - model: () => AccountModel.unscoped(), + model: AccountModel.unscoped(), include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] } } -@Scopes({ +@Scopes(() => ({ [ScopeNames.WITH_ALL]: { include: [ Object.assign(buildVideoInclude(false), { @@ -76,7 +76,7 @@ function buildAccountInclude (required: boolean, withActor = false) { { attributes: [ 'id', 'originCommentId' ], - model: () => VideoCommentModel.unscoped(), + model: VideoCommentModel.unscoped(), required: false, include: [ buildAccountInclude(true, true), @@ -86,56 +86,56 @@ function buildAccountInclude (required: boolean, withActor = false) { { attributes: [ 'id' ], - model: () => VideoAbuseModel.unscoped(), + model: VideoAbuseModel.unscoped(), required: false, include: [ buildVideoInclude(true) ] }, { attributes: [ 'id' ], - model: () => VideoBlacklistModel.unscoped(), + model: VideoBlacklistModel.unscoped(), required: false, include: [ buildVideoInclude(true) ] }, { attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ], - model: () => VideoImportModel.unscoped(), + model: VideoImportModel.unscoped(), required: false, include: [ buildVideoInclude(false) ] }, { attributes: [ 'id', 'state' ], - model: () => ActorFollowModel.unscoped(), + model: ActorFollowModel.unscoped(), required: false, include: [ { attributes: [ 'preferredUsername' ], - model: () => ActorModel.unscoped(), + model: ActorModel.unscoped(), required: true, as: 'ActorFollower', include: [ { attributes: [ 'id', 'name' ], - model: () => AccountModel.unscoped(), + model: AccountModel.unscoped(), required: true }, { attributes: [ 'filename' ], - model: () => AvatarModel.unscoped(), + model: AvatarModel.unscoped(), required: false }, { attributes: [ 'host' ], - model: () => ServerModel.unscoped(), + model: ServerModel.unscoped(), required: false } ] }, { attributes: [ 'preferredUsername' ], - model: () => ActorModel.unscoped(), + model: ActorModel.unscoped(), required: true, as: 'ActorFollowing', include: [ @@ -147,9 +147,9 @@ function buildAccountInclude (required: boolean, withActor = false) { }, buildAccountInclude(false, true) - ] as any // FIXME: sequelize typings + ] } -}) +})) @Table({ tableName: 'userNotification', indexes: [ @@ -212,7 +212,7 @@ function buildAccountInclude (required: boolean, withActor = false) { } } } - ] as any // FIXME: sequelize typings + ] as (ModelIndexesOptions & { where?: WhereOptions })[] }) export class UserNotificationModel extends Model { @@ -357,7 +357,7 @@ export class UserNotificationModel extends Model { where: { userId, id: { - [Op.any]: notificationIds + [Op.in]: notificationIds // FIXME: sequelize ANY seems broken } } } diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 8bd0397dd..4a9acd703 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -1,4 +1,4 @@ -import * as Sequelize from 'sequelize' +import { FindOptions, literal, Op, QueryTypes } from 'sequelize' import { AfterDestroy, AfterUpdate, @@ -56,33 +56,33 @@ enum ScopeNames { WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' } -@DefaultScope({ +@DefaultScope(() => ({ include: [ { - model: () => AccountModel, + model: AccountModel, required: true }, { - model: () => UserNotificationSettingModel, + model: UserNotificationSettingModel, required: true } ] -}) -@Scopes({ +})) +@Scopes(() => ({ [ScopeNames.WITH_VIDEO_CHANNEL]: { include: [ { - model: () => AccountModel, + model: AccountModel, required: true, - include: [ () => VideoChannelModel ] + include: [ VideoChannelModel ] }, { - model: () => UserNotificationSettingModel, + model: UserNotificationSettingModel, required: true } - ] as any // FIXME: sequelize typings + ] } -}) +})) @Table({ tableName: 'user', indexes: [ @@ -233,26 +233,26 @@ export class UserModel extends Model { let where = undefined if (search) { where = { - [Sequelize.Op.or]: [ + [Op.or]: [ { email: { - [Sequelize.Op.iLike]: '%' + search + '%' + [Op.iLike]: '%' + search + '%' } }, { username: { - [ Sequelize.Op.iLike ]: '%' + search + '%' + [ Op.iLike ]: '%' + search + '%' } } ] } } - const query = { + const query: FindOptions = { attributes: { include: [ [ - Sequelize.literal( + literal( '(' + 'SELECT COALESCE(SUM("size"), 0) ' + 'FROM (' + @@ -265,7 +265,7 @@ export class UserModel extends Model { ')' ), 'videoQuotaUsed' - ] as any // FIXME: typings + ] ] }, offset: start, @@ -291,7 +291,7 @@ export class UserModel extends Model { const query = { where: { role: { - [Sequelize.Op.in]: roles + [Op.in]: roles } } } @@ -387,7 +387,7 @@ export class UserModel extends Model { const query = { where: { - [ Sequelize.Op.or ]: [ { username }, { email } ] + [ Op.or ]: [ { username }, { email } ] } } @@ -510,7 +510,7 @@ export class UserModel extends Model { const query = { where: { username: { - [ Sequelize.Op.like ]: `%${search}%` + [ Op.like ]: `%${search}%` } }, limit: 10 @@ -591,15 +591,11 @@ export class UserModel extends Model { const uploadedTotal = videoFile.size + totalBytes const uploadedDaily = videoFile.size + totalBytesDaily - if (this.videoQuotaDaily === -1) { - return uploadedTotal < this.videoQuota - } - if (this.videoQuota === -1) { - return uploadedDaily < this.videoQuotaDaily - } - return (uploadedTotal < this.videoQuota) && - (uploadedDaily < this.videoQuotaDaily) + if (this.videoQuotaDaily === -1) return uploadedTotal < this.videoQuota + if (this.videoQuota === -1) return uploadedDaily < this.videoQuotaDaily + + return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily } private static generateUserQuotaBaseSQL (where?: string) { @@ -619,14 +615,14 @@ export class UserModel extends Model { private static getTotalRawQuery (query: string, userId: number) { const options = { bind: { userId }, - type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT + type: QueryTypes.SELECT as QueryTypes.SELECT } - return UserModel.sequelize.query<{ total: number }>(query, options) + return UserModel.sequelize.query<{ total: string }>(query, options) .then(([ { total } ]) => { if (total === null) return 0 - return parseInt(total + '', 10) + return parseInt(total, 10) }) } } diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 1ebee8df5..4a466441c 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -56,46 +56,46 @@ export const unusedActorAttributesForAPI = [ 'updatedAt' ] -@DefaultScope({ +@DefaultScope(() => ({ include: [ { - model: () => ServerModel, + model: ServerModel, required: false }, { - model: () => AvatarModel, + model: AvatarModel, required: false } ] -}) -@Scopes({ +})) +@Scopes(() => ({ [ScopeNames.FULL]: { include: [ { - model: () => AccountModel.unscoped(), + model: AccountModel.unscoped(), required: false }, { - model: () => VideoChannelModel.unscoped(), + model: VideoChannelModel.unscoped(), required: false, include: [ { - model: () => AccountModel, + model: AccountModel, required: true } ] }, { - model: () => ServerModel, + model: ServerModel, required: false }, { - model: () => AvatarModel, + model: AvatarModel, required: false } - ] as any // FIXME: sequelize typings + ] } -}) +})) @Table({ tableName: 'actor', indexes: [ @@ -131,7 +131,7 @@ export const unusedActorAttributesForAPI = [ export class ActorModel extends Model { @AllowNull(false) - @Column({ type: DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)) }) // FIXME: sequelize typings + @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) type: ActivityPubActorType @AllowNull(false) @@ -280,14 +280,16 @@ export class ActorModel extends Model { attributes: [ 'id' ], model: VideoChannelModel.unscoped(), required: true, - include: { - attributes: [ 'id' ], - model: VideoModel.unscoped(), - required: true, - where: { - id: videoId + include: [ + { + attributes: [ 'id' ], + model: VideoModel.unscoped(), + required: true, + where: { + id: videoId + } } - } + ] } ] } @@ -295,7 +297,7 @@ export class ActorModel extends Model { transaction } - return ActorModel.unscoped().findOne(query as any) // FIXME: typings + return ActorModel.unscoped().findOne(query) } static isActorUrlExist (url: string) { @@ -389,8 +391,7 @@ export class ActorModel extends Model { } static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) { - // FIXME: typings - return (ActorModel as any).increment(column, { + return ActorModel.increment(column, { by, where: { id diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 854a5fb36..a02208b4e 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts @@ -1,14 +1,14 @@ import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' import { AccountModel } from '../account/account' -@DefaultScope({ +@DefaultScope(() => ({ include: [ { - model: () => AccountModel, + model: AccountModel, required: true } ] -}) +})) @Table({ tableName: 'application' }) diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts index b4a841edd..42c59bb79 100644 --- a/server/models/oauth/oauth-client.ts +++ b/server/models/oauth/oauth-client.ts @@ -24,10 +24,10 @@ export class OAuthClientModel extends Model { @Column clientSecret: string - @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings + @Column(DataType.ARRAY(DataType.STRING)) grants: string[] - @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings + @Column(DataType.ARRAY(DataType.STRING)) redirectUris: string[] @CreatedAt diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 3f41ee63b..903d551df 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts @@ -34,30 +34,30 @@ enum ScopeNames { WITH_USER = 'WITH_USER' } -@Scopes({ +@Scopes(() => ({ [ScopeNames.WITH_USER]: { include: [ { - model: () => UserModel.unscoped(), + model: UserModel.unscoped(), required: true, include: [ { attributes: [ 'id' ], - model: () => AccountModel.unscoped(), + model: AccountModel.unscoped(), required: true, include: [ { attributes: [ 'id', 'url' ], - model: () => ActorModel.unscoped(), + model: ActorModel.unscoped(), required: true } ] } ] } - ] as any // FIXME: sequelize typings + ] } -}) +})) @Table({ tableName: 'oAuthToken', indexes: [ @@ -167,11 +167,13 @@ export class OAuthTokenModel extends Model { } } - return OAuthTokenModel.scope(ScopeNames.WITH_USER).findOne(query).then(token => { - if (token) token['user'] = token.User + return OAuthTokenModel.scope(ScopeNames.WITH_USER) + .findOne(query) + .then(token => { + if (token) token[ 'user' ] = token.User - return token - }) + return token + }) } static getByRefreshTokenAndPopulateUser (refreshToken: string) { diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index cbeaa662b..eb2222256 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts @@ -13,7 +13,7 @@ import { UpdatedAt } from 'sequelize-typescript' import { ActorModel } from '../activitypub/actor' -import { getVideoSort, throwIfNotValid } from '../utils' +import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' import { VideoFileModel } from '../video/video-file' @@ -27,7 +27,7 @@ import { ServerModel } from '../server/server' import { sample } from 'lodash' import { isTestInstance } from '../../helpers/core-utils' import * as Bluebird from 'bluebird' -import * as Sequelize from 'sequelize' +import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' import { CONFIG } from '../../initializers/config' @@ -35,32 +35,32 @@ export enum ScopeNames { WITH_VIDEO = 'WITH_VIDEO' } -@Scopes({ +@Scopes(() => ({ [ ScopeNames.WITH_VIDEO ]: { include: [ { - model: () => VideoFileModel, + model: VideoFileModel, required: false, include: [ { - model: () => VideoModel, + model: VideoModel, required: true } ] }, { - model: () => VideoStreamingPlaylistModel, + model: VideoStreamingPlaylistModel, required: false, include: [ { - model: () => VideoModel, + model: VideoModel, required: true } ] } - ] as any // FIXME: sequelize typings + ] } -}) +})) @Table({ tableName: 'videoRedundancy', @@ -192,7 +192,7 @@ export class VideoRedundancyModel extends Model { return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) } - static loadByUrl (url: string, transaction?: Sequelize.Transaction) { + static loadByUrl (url: string, transaction?: Transaction) { const query = { where: { url @@ -292,7 +292,7 @@ export class VideoRedundancyModel extends Model { where: { privacy: VideoPrivacy.PUBLIC, views: { - [ Sequelize.Op.gte ]: minViews + [ Op.gte ]: minViews } }, include: [ @@ -315,7 +315,7 @@ export class VideoRedundancyModel extends Model { actorId: actor.id, strategy, createdAt: { - [ Sequelize.Op.lt ]: expiredDate + [ Op.lt ]: expiredDate } } } @@ -326,7 +326,7 @@ export class VideoRedundancyModel extends Model { static async getTotalDuplicated (strategy: VideoRedundancyStrategy) { const actor = await getServerActor() - const options = { + const query: FindOptions = { include: [ { attributes: [], @@ -340,12 +340,8 @@ export class VideoRedundancyModel extends Model { ] } - return VideoFileModel.sum('size', options as any) // FIXME: typings - .then(v => { - if (!v || isNaN(v)) return 0 - - return v - }) + return VideoFileModel.aggregate('size', 'SUM', query) + .then(result => parseAggregateResult(result)) } static async listLocalExpired () { @@ -355,7 +351,7 @@ export class VideoRedundancyModel extends Model { where: { actorId: actor.id, expiresOn: { - [ Sequelize.Op.lt ]: new Date() + [ Op.lt ]: new Date() } } } @@ -369,10 +365,10 @@ export class VideoRedundancyModel extends Model { const query = { where: { actorId: { - [Sequelize.Op.ne]: actor.id + [Op.ne]: actor.id }, expiresOn: { - [ Sequelize.Op.lt ]: new Date() + [ Op.lt ]: new Date() } } } @@ -428,12 +424,12 @@ export class VideoRedundancyModel extends Model { static async getStats (strategy: VideoRedundancyStrategy) { const actor = await getServerActor() - const query = { + const query: FindOptions = { raw: true, attributes: [ - [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ], - [ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', Sequelize.col('videoId'))), 'totalVideos' ], - [ Sequelize.fn('COUNT', Sequelize.col('videoFileId')), 'totalVideoFiles' ] + [ fn('COALESCE', fn('SUM', col('VideoFile.size')), '0'), 'totalUsed' ], + [ fn('COUNT', fn('DISTINCT', col('videoId'))), 'totalVideos' ], + [ fn('COUNT', col('videoFileId')), 'totalVideoFiles' ] ], where: { strategy, @@ -448,9 +444,9 @@ export class VideoRedundancyModel extends Model { ] } - return VideoRedundancyModel.findOne(query as any) // FIXME: typings + return VideoRedundancyModel.findOne(query) .then((r: any) => ({ - totalUsed: parseInt(r.totalUsed.toString(), 10), + totalUsed: parseAggregateResult(r.totalUsed), totalVideos: r.totalVideos, totalVideoFiles: r.totalVideoFiles })) @@ -503,7 +499,7 @@ export class VideoRedundancyModel extends Model { private static async buildVideoFileForDuplication () { const actor = await getServerActor() - const notIn = Sequelize.literal( + const notIn = literal( '(' + `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + ')' @@ -515,7 +511,7 @@ export class VideoRedundancyModel extends Model { required: true, where: { id: { - [ Sequelize.Op.notIn ]: notIn + [ Op.notIn ]: notIn } } } diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 450f27152..92c01f642 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts @@ -9,11 +9,11 @@ enum ScopeNames { WITH_SERVER = 'WITH_SERVER' } -@Scopes({ +@Scopes(() => ({ [ScopeNames.WITH_ACCOUNT]: { include: [ { - model: () => AccountModel, + model: AccountModel, required: true } ] @@ -21,12 +21,12 @@ enum ScopeNames { [ScopeNames.WITH_SERVER]: { include: [ { - model: () => ServerModel, + model: ServerModel, required: true } ] } -}) +})) @Table({ tableName: 'serverBlocklist', diff --git a/server/models/utils.ts b/server/models/utils.ts index 98170a00e..2b172f608 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts @@ -118,6 +118,15 @@ function buildWhereIdOrUUID (id: number | string) { return validator.isInt('' + id) ? { id } : { uuid: id } } +function parseAggregateResult (result: any) { + if (!result) return 0 + + const total = parseInt(result + '', 10) + if (isNaN(total)) return 0 + + return total +} + // --------------------------------------------------------------------------- export { @@ -131,7 +140,8 @@ export { buildServerIdsFollowedBy, buildTrigramSearchIndex, buildWhereIdOrUUID, - isOutdated + isOutdated, + parseAggregateResult } // --------------------------------------------------------------------------- diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 048b47613..0fc3cfd4c 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts @@ -75,7 +75,7 @@ export class TagModel extends Model { type: QueryTypes.SELECT as QueryTypes.SELECT } - return TagModel.sequelize.query<{ name }>(query, options) + return TagModel.sequelize.query<{ name: string }>(query, options) .then(data => data.map(d => d.name)) } } diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index baa5533ac..ec945893f 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts @@ -75,8 +75,8 @@ export class ThumbnailModel extends Model { updatedAt: Date private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { - [ThumbnailType.THUMBNAIL]: { - label: 'thumbnail', + [ThumbnailType.MINIATURE]: { + label: 'miniature', directory: CONFIG.STORAGE.THUMBNAILS_DIR, staticPath: STATIC_PATHS.THUMBNAILS }, diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index 45c60e26b..76243bf48 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts @@ -12,7 +12,7 @@ import { Table, UpdatedAt } from 'sequelize-typescript' -import { throwIfNotValid } from '../utils' +import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' import { VideoModel } from './video' import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' @@ -26,17 +26,17 @@ export enum ScopeNames { WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' } -@Scopes({ +@Scopes(() => ({ [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { include: [ { attributes: [ 'uuid', 'remote' ], - model: () => VideoModel.unscoped(), + model: VideoModel.unscoped(), required: true } ] } -}) +})) @Table({ tableName: 'videoCaption', @@ -97,12 +97,9 @@ export class VideoCaptionModel extends Model { const videoInclude = { model: VideoModel.unscoped(), attributes: [ 'id', 'remote', 'uuid' ], - where: { } + where: buildWhereIdOrUUID(videoId) } - if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId - else videoInclude.where['id'] = videoId - const query = { where: { language diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index a4f4d53f1..171d4574d 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts @@ -23,29 +23,29 @@ enum ScopeNames { } ] }) -@Scopes({ +@Scopes(() => ({ [ScopeNames.FULL]: { include: [ { - model: () => AccountModel, + model: AccountModel, as: 'Initiator', required: true }, { - model: () => AccountModel, + model: AccountModel, as: 'NextOwner', required: true }, { - model: () => VideoModel, + model: VideoModel, required: true, include: [ - { model: () => VideoFileModel } + { model: VideoFileModel } ] } - ] as any // FIXME: sequelize typings + ] } -}) +})) export class VideoChangeOwnershipModel extends Model { @CreatedAt createdAt: Date diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 901006dea..fb70e6625 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts @@ -58,15 +58,15 @@ type AvailableForListOptions = { actorId: number } -@DefaultScope({ +@DefaultScope(() => ({ include: [ { - model: () => ActorModel, + model: ActorModel, required: true } ] -}) -@Scopes({ +})) +@Scopes(() => ({ [ScopeNames.SUMMARY]: (withAccount = false) => { const base: FindOptions = { attributes: [ 'name', 'description', 'id', 'actorId' ], @@ -142,22 +142,22 @@ type AvailableForListOptions = { [ScopeNames.WITH_ACCOUNT]: { include: [ { - model: () => AccountModel, + model: AccountModel, required: true } ] }, [ScopeNames.WITH_VIDEOS]: { include: [ - () => VideoModel + VideoModel ] }, [ScopeNames.WITH_ACTOR]: { include: [ - () => ActorModel + ActorModel ] } -}) +})) @Table({ tableName: 'videoChannel', indexes diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 5f7cd3671..fee11ec5f 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -30,7 +30,7 @@ import { UserModel } from '../account/user' import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' import { regexpCapture } from '../../helpers/regexp' import { uniq } from 'lodash' -import { FindOptions, Op, Order, Sequelize, Transaction } from 'sequelize' +import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' enum ScopeNames { WITH_ACCOUNT = 'WITH_ACCOUNT', @@ -39,7 +39,7 @@ enum ScopeNames { ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' } -@Scopes({ +@Scopes(() => ({ [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { return { attributes: { @@ -63,34 +63,34 @@ enum ScopeNames { ] ] } - } + } as FindOptions }, [ScopeNames.WITH_ACCOUNT]: { include: [ { - model: () => AccountModel, + model: AccountModel, include: [ { - model: () => ActorModel, + model: ActorModel, include: [ { - model: () => ServerModel, + model: ServerModel, required: false }, { - model: () => AvatarModel, + model: AvatarModel, required: false } ] } ] } - ] as any // FIXME: sequelize typings + ] }, [ScopeNames.WITH_IN_REPLY_TO]: { include: [ { - model: () => VideoCommentModel, + model: VideoCommentModel, as: 'InReplyToVideoComment' } ] @@ -98,19 +98,19 @@ enum ScopeNames { [ScopeNames.WITH_VIDEO]: { include: [ { - model: () => VideoModel, + model: VideoModel, required: true, include: [ { - model: () => VideoChannelModel.unscoped(), + model: VideoChannelModel.unscoped(), required: true, include: [ { - model: () => AccountModel, + model: AccountModel, required: true, include: [ { - model: () => ActorModel, + model: ActorModel, required: true } ] @@ -119,9 +119,9 @@ enum ScopeNames { } ] } - ] as any // FIXME: sequelize typings + ] } -}) +})) @Table({ tableName: 'videoComment', indexes: [ @@ -313,8 +313,7 @@ export class VideoCommentModel extends Model { } } - // FIXME: typings - const scopes: any[] = [ + const scopes: (string | ScopeOptions)[] = [ ScopeNames.WITH_ACCOUNT, { method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index c14d96bc5..2203a7aba 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts @@ -19,11 +19,11 @@ import { isVideoFileSizeValid, isVideoFPSResolutionValid } from '../../helpers/custom-validators/videos' -import { throwIfNotValid } from '../utils' +import { parseAggregateResult, throwIfNotValid } from '../utils' import { VideoModel } from './video' -import * as Sequelize from 'sequelize' import { VideoRedundancyModel } from '../redundancy/video-redundancy' import { VideoStreamingPlaylistModel } from './video-streaming-playlist' +import { FindOptions, QueryTypes, Transaction } from 'sequelize' @Table({ tableName: 'videoFile', @@ -97,15 +97,13 @@ export class VideoFileModel extends Model { static doesInfohashExist (infoHash: string) { const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' const options = { - type: Sequelize.QueryTypes.SELECT, + type: QueryTypes.SELECT, bind: { infoHash }, raw: true } return VideoModel.sequelize.query(query, options) - .then(results => { - return results.length === 1 - }) + .then(results => results.length === 1) } static loadWithVideo (id: number) { @@ -121,7 +119,7 @@ export class VideoFileModel extends Model { return VideoFileModel.findByPk(id, options) } - static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Sequelize.Transaction) { + static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) { const query = { include: [ { @@ -144,8 +142,8 @@ export class VideoFileModel extends Model { return VideoFileModel.findAll(query) } - static async getStats () { - let totalLocalVideoFilesSize = await VideoFileModel.sum('size', { + static getStats () { + const query: FindOptions = { include: [ { attributes: [], @@ -155,13 +153,12 @@ export class VideoFileModel extends Model { } } ] - } as any) - // Sequelize could return null... - if (!totalLocalVideoFilesSize) totalLocalVideoFilesSize = 0 - - return { - totalLocalVideoFilesSize } + + return VideoFileModel.aggregate('size', 'SUM', query) + .then(result => ({ + totalLocalVideoFilesSize: parseAggregateResult(result) + })) } hasSameUniqueKeysThan (other: VideoFileModel) { diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 89992a5a8..877fcbc57 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts @@ -59,7 +59,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting views: video.views, likes: video.likes, dislikes: video.dislikes, - thumbnailPath: video.getThumbnailStaticPath(), + thumbnailPath: video.getMiniatureStaticPath(), previewPath: video.getPreviewStaticPath(), embedPath: video.getEmbedStaticPath(), createdAt: video.createdAt, @@ -301,6 +301,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { }) } + const miniature = video.getMiniature() + return { type: 'Video' as 'Video', id: video.url, @@ -326,10 +328,10 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { subtitleLanguage, icon: { type: 'Image', - url: video.getThumbnail().getUrl(), + url: miniature.getUrl(), mediaType: 'image/jpeg', - width: video.getThumbnail().width, - height: video.getThumbnail().height + width: miniature.width, + height: miniature.height }, url, likes: getVideoLikesActivityPubUrl(video), diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 588a13a4f..480a671c8 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts @@ -21,18 +21,18 @@ import { VideoImport, VideoImportState } from '../../../shared' import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' import { UserModel } from '../account/user' -@DefaultScope({ +@DefaultScope(() => ({ include: [ { - model: () => UserModel.unscoped(), + model: UserModel.unscoped(), required: true }, { - model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), + model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), required: false } ] -}) +})) @Table({ tableName: 'videoImport', diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index 3e436acfc..63b4a0715 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts @@ -42,7 +42,7 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub' import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model' import { ThumbnailModel } from './thumbnail' import { ActivityIconObject } from '../../../shared/models/activitypub/objects' -import { fn, literal, Op, Transaction } from 'sequelize' +import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' enum ScopeNames { AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', @@ -61,11 +61,11 @@ type AvailableForListOptions = { privateAndUnlisted?: boolean } -@Scopes({ +@Scopes(() => ({ [ ScopeNames.WITH_THUMBNAIL ]: { include: [ { - model: () => ThumbnailModel, + model: ThumbnailModel, required: false } ] @@ -73,21 +73,17 @@ type AvailableForListOptions = { [ ScopeNames.WITH_VIDEOS_LENGTH ]: { attributes: { include: [ - [ - fn('COUNT', 'toto'), - 'coucou' - ], [ literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'), 'videosLength' ] ] } - }, + } as FindOptions, [ ScopeNames.WITH_ACCOUNT ]: { include: [ { - model: () => AccountModel, + model: AccountModel, required: true } ] @@ -95,11 +91,11 @@ type AvailableForListOptions = { [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { include: [ { - model: () => AccountModel.scope(AccountScopeNames.SUMMARY), + model: AccountModel.scope(AccountScopeNames.SUMMARY), required: true }, { - model: () => VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), + model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), required: false } ] @@ -107,11 +103,11 @@ type AvailableForListOptions = { [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { include: [ { - model: () => AccountModel, + model: AccountModel, required: true }, { - model: () => VideoChannelModel, + model: VideoChannelModel, required: false } ] @@ -132,7 +128,7 @@ type AvailableForListOptions = { ] } - const whereAnd: any[] = [] + const whereAnd: WhereOptions[] = [] if (options.privateAndUnlisted !== true) { whereAnd.push({ @@ -178,9 +174,9 @@ type AvailableForListOptions = { required: false } ] - } + } as FindOptions } -}) +})) @Table({ tableName: 'videoPlaylist', @@ -269,6 +265,7 @@ export class VideoPlaylistModel extends Model { VideoPlaylistElements: VideoPlaylistElementModel[] @HasOne(() => ThumbnailModel, { + foreignKey: { name: 'videoPlaylistId', allowNull: true @@ -294,7 +291,7 @@ export class VideoPlaylistModel extends Model { order: getSort(options.sort) } - const scopes = [ + const scopes: (string | ScopeOptions)[] = [ { method: [ ScopeNames.AVAILABLE_FOR_LIST, @@ -306,7 +303,7 @@ export class VideoPlaylistModel extends Model { privateAndUnlisted: options.privateAndUnlisted } as AvailableForListOptions ] - } as any, // FIXME: typings + }, ScopeNames.WITH_VIDEOS_LENGTH, ScopeNames.WITH_THUMBNAIL ] @@ -348,7 +345,7 @@ export class VideoPlaylistModel extends Model { model: VideoPlaylistElementModel.unscoped(), where: { videoId: { - [Op.any]: videoIds + [Op.in]: videoIds // FIXME: sequelize ANY seems broken } }, required: true @@ -427,12 +424,10 @@ export class VideoPlaylistModel extends Model { return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) } - setThumbnail (thumbnail: ThumbnailModel) { - this.Thumbnail = thumbnail - } + async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { + thumbnail.videoPlaylistId = this.id - getThumbnail () { - return this.Thumbnail + this.Thumbnail = await thumbnail.save({ transaction: t }) } hasThumbnail () { @@ -448,13 +443,13 @@ export class VideoPlaylistModel extends Model { getThumbnailUrl () { if (!this.hasThumbnail()) return null - return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.getThumbnail().filename + return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename } getThumbnailStaticPath () { if (!this.hasThumbnail()) return null - return join(STATIC_PATHS.THUMBNAILS, this.getThumbnail().filename) + return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) } setAsRefreshed () { diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index c83f6c5b0..fda2d7cea 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts @@ -14,15 +14,15 @@ enum ScopeNames { WITH_ACTOR = 'WITH_ACTOR' } -@Scopes({ +@Scopes(() => ({ [ScopeNames.FULL]: { include: [ { - model: () => ActorModel, + model: ActorModel, required: true }, { - model: () => VideoModel, + model: VideoModel, required: true } ] @@ -30,12 +30,12 @@ enum ScopeNames { [ScopeNames.WITH_ACTOR]: { include: [ { - model: () => ActorModel, + model: ActorModel, required: true } ] } -}) +})) @Table({ tableName: 'videoShare', indexes: [ diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index b30267e09..31dc82c54 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts @@ -26,7 +26,7 @@ import { QueryTypes, Op } from 'sequelize' fields: [ 'p2pMediaLoaderInfohashes' ], using: 'gin' } - ] as any // FIXME: sequelize typings + ] }) export class VideoStreamingPlaylistModel extends Model { @CreatedAt @@ -46,7 +46,7 @@ export class VideoStreamingPlaylistModel extends Model throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes')) - @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: typings + @Column(DataType.ARRAY(DataType.STRING)) p2pMediaLoaderInfohashes: string[] @AllowNull(false) @@ -87,7 +87,7 @@ export class VideoStreamingPlaylistModel extends Model(query, options) + return VideoModel.sequelize.query(query, options) .then(results => results.length === 1) } diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 329cebd28..18f18795e 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -227,12 +227,12 @@ type AvailableForListIDsOptions = { historyOfUser?: UserModel } -@Scopes({ +@Scopes(() => ({ [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { const query: FindOptions = { where: { id: { - [ Op.in ]: options.ids // FIXME: sequelize any seems broken + [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken } }, include: [ @@ -486,7 +486,7 @@ type AvailableForListIDsOptions = { [ ScopeNames.WITH_THUMBNAILS ]: { include: [ { - model: () => ThumbnailModel, + model: ThumbnailModel, required: false } ] @@ -495,48 +495,48 @@ type AvailableForListIDsOptions = { include: [ { attributes: [ 'accountId' ], - model: () => VideoChannelModel.unscoped(), + model: VideoChannelModel.unscoped(), required: true, include: [ { attributes: [ 'userId' ], - model: () => AccountModel.unscoped(), + model: AccountModel.unscoped(), required: true } ] } - ] as any // FIXME: sequelize typings + ] }, [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { include: [ { - model: () => VideoChannelModel.unscoped(), + model: VideoChannelModel.unscoped(), required: true, include: [ { attributes: { exclude: [ 'privateKey', 'publicKey' ] }, - model: () => ActorModel.unscoped(), + model: ActorModel.unscoped(), required: true, include: [ { attributes: [ 'host' ], - model: () => ServerModel.unscoped(), + model: ServerModel.unscoped(), required: false }, { - model: () => AvatarModel.unscoped(), + model: AvatarModel.unscoped(), required: false } ] }, { - model: () => AccountModel.unscoped(), + model: AccountModel.unscoped(), required: true, include: [ { - model: () => ActorModel.unscoped(), + model: ActorModel.unscoped(), attributes: { exclude: [ 'privateKey', 'publicKey' ] }, @@ -544,11 +544,11 @@ type AvailableForListIDsOptions = { include: [ { attributes: [ 'host' ], - model: () => ServerModel.unscoped(), + model: ServerModel.unscoped(), required: false }, { - model: () => AvatarModel.unscoped(), + model: AvatarModel.unscoped(), required: false } ] @@ -557,16 +557,16 @@ type AvailableForListIDsOptions = { } ] } - ] as any // FIXME: sequelize typings + ] }, [ ScopeNames.WITH_TAGS ]: { - include: [ () => TagModel ] + include: [ TagModel ] }, [ ScopeNames.WITH_BLACKLISTED ]: { include: [ { attributes: [ 'id', 'reason' ], - model: () => VideoBlacklistModel, + model: VideoBlacklistModel, required: false } ] @@ -588,8 +588,7 @@ type AvailableForListIDsOptions = { include: [ { model: VideoFileModel.unscoped(), - // FIXME: typings - [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join + separate: true, // We may have multiple files, having multiple redundancies so let's separate this join required: false, include: subInclude } @@ -613,8 +612,7 @@ type AvailableForListIDsOptions = { include: [ { model: VideoStreamingPlaylistModel.unscoped(), - // FIXME: typings - [ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join + separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join required: false, include: subInclude } @@ -624,7 +622,7 @@ type AvailableForListIDsOptions = { [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { include: [ { - model: () => ScheduleVideoUpdateModel.unscoped(), + model: ScheduleVideoUpdateModel.unscoped(), required: false } ] @@ -643,7 +641,7 @@ type AvailableForListIDsOptions = { ] } } -}) +})) @Table({ tableName: 'video', indexes @@ -1075,15 +1073,14 @@ export class VideoModel extends Model { } return Bluebird.all([ - // FIXME: typing issue - VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any), - VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT }) + VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query), + VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT }) ]).then(([ rows, totals ]) => { // totals: totalVideos + totalVideoShares let totalVideos = 0 let totalVideoShares = 0 - if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10) - if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10) + if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) + if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) const total = totalVideos + totalVideoShares return { @@ -1094,50 +1091,58 @@ export class VideoModel extends Model { } static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { - const query: FindOptions = { - offset: start, - limit: count, - order: getVideoSort(sort), - include: [ - { - model: VideoChannelModel, - required: true, - include: [ - { - model: AccountModel, - where: { - id: accountId - }, - required: true - } - ] - }, - { - model: ScheduleVideoUpdateModel, - required: false - }, - { - model: VideoBlacklistModel, - required: false - } - ] + function buildBaseQuery (): FindOptions { + return { + offset: start, + limit: count, + order: getVideoSort(sort), + include: [ + { + model: VideoChannelModel, + required: true, + include: [ + { + model: AccountModel, + where: { + id: accountId + }, + required: true + } + ] + } + ] + } } + const countQuery = buildBaseQuery() + const findQuery = buildBaseQuery() + + findQuery.include.push({ + model: ScheduleVideoUpdateModel, + required: false + }) + + findQuery.include.push({ + model: VideoBlacklistModel, + required: false + }) + if (withFiles === true) { - query.include.push({ + findQuery.include.push({ model: VideoFileModel.unscoped(), required: true }) } - return VideoModel.scope(ScopeNames.WITH_THUMBNAILS) - .findAndCountAll(query) - .then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) + return Promise.all([ + VideoModel.count(countQuery), + VideoModel.findAll(findQuery) + ]).then(([ count, rows ]) => { + return { + data: rows, + total: count + } + }) } static async listForApi (options: { @@ -1404,12 +1409,12 @@ export class VideoModel extends Model { const where = buildWhereIdOrUUID(id) const options = { - order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings + order: [ [ 'Tags', 'name', 'ASC' ] ] as any, where, transaction: t } - const scopes = [ + const scopes: (string | ScopeOptions)[] = [ ScopeNames.WITH_TAGS, ScopeNames.WITH_BLACKLISTED, ScopeNames.WITH_ACCOUNT_DETAILS, @@ -1420,7 +1425,7 @@ export class VideoModel extends Model { ] if (userId) { - scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings + scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) } return VideoModel @@ -1437,18 +1442,18 @@ export class VideoModel extends Model { transaction: t } - const scopes = [ + const scopes: (string | ScopeOptions)[] = [ ScopeNames.WITH_TAGS, ScopeNames.WITH_BLACKLISTED, ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_SCHEDULED_UPDATE, ScopeNames.WITH_THUMBNAILS, - { method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings - { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings + { method: [ ScopeNames.WITH_FILES, true ] }, + { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } ] if (userId) { - scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings + scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) } return VideoModel @@ -1520,9 +1525,9 @@ export class VideoModel extends Model { attributes: [ field ], limit: count, group: field, - having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { - [ Op.gte ]: threshold - }) as any, // FIXME: typings + having: Sequelize.where( + Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } + ), order: [ (this.sequelize as any).random() ] } @@ -1594,16 +1599,10 @@ export class VideoModel extends Model { ] } - // FIXME: typing - const apiScope: any[] = [ ScopeNames.WITH_THUMBNAILS ] + const apiScope: (string | ScopeOptions)[] = [ ScopeNames.WITH_THUMBNAILS ] if (options.user) { apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) - - // Even if the relation is n:m, we know that a user only have 0..1 video history - // So we won't have multiple rows for the same video - // A subquery adds some bugs in our query so disable it - secondQuery.subQuery = false } apiScope.push({ @@ -1651,13 +1650,17 @@ export class VideoModel extends Model { return maxBy(this.VideoFiles, file => file.resolution) } - addThumbnail (thumbnail: ThumbnailModel) { + async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { + thumbnail.videoId = this.id + + const savedThumbnail = await thumbnail.save({ transaction }) + if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = [] // Already have this thumbnail, skip - if (this.Thumbnails.find(t => t.id === thumbnail.id)) return + if (this.Thumbnails.find(t => t.id === savedThumbnail.id)) return - this.Thumbnails.push(thumbnail) + this.Thumbnails.push(savedThumbnail) } getVideoFilename (videoFile: VideoFileModel) { @@ -1668,10 +1671,10 @@ export class VideoModel extends Model { return this.uuid + '.jpg' } - getThumbnail () { + getMiniature () { if (Array.isArray(this.Thumbnails) === false) return undefined - return this.Thumbnails.find(t => t.type === ThumbnailType.THUMBNAIL) + return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE) } generatePreviewName () { @@ -1732,8 +1735,8 @@ export class VideoModel extends Model { return '/videos/embed/' + this.uuid } - getThumbnailStaticPath () { - const thumbnail = this.getThumbnail() + getMiniatureStaticPath () { + const thumbnail = this.getMiniature() if (!thumbnail) return null return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) -- cgit v1.2.3