aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video/video.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r--server/models/video/video.ts169
1 files changed, 88 insertions, 81 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 9840d17fd..329cebd28 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -3,7 +3,18 @@ import { maxBy } from 'lodash'
3import * as magnetUtil from 'magnet-uri' 3import * as magnetUtil from 'magnet-uri'
4import * as parseTorrent from 'parse-torrent' 4import * as parseTorrent from 'parse-torrent'
5import { join } from 'path' 5import { join } from 'path'
6import * as Sequelize from 'sequelize' 6import {
7 CountOptions,
8 FindOptions,
9 IncludeOptions,
10 ModelIndexesOptions,
11 Op,
12 QueryTypes,
13 ScopeOptions,
14 Sequelize,
15 Transaction,
16 WhereOptions
17} from 'sequelize'
7import { 18import {
8 AllowNull, 19 AllowNull,
9 BeforeDestroy, 20 BeforeDestroy,
@@ -16,8 +27,6 @@ import {
16 ForeignKey, 27 ForeignKey,
17 HasMany, 28 HasMany,
18 HasOne, 29 HasOne,
19 IFindOptions,
20 IIncludeOptions,
21 Is, 30 Is,
22 IsInt, 31 IsInt,
23 IsUUID, 32 IsUUID,
@@ -45,7 +54,7 @@ import {
45 isVideoStateValid, 54 isVideoStateValid,
46 isVideoSupportValid 55 isVideoSupportValid
47} from '../../helpers/custom-validators/videos' 56} from '../../helpers/custom-validators/videos'
48import { generateImageFromVideoFile, getVideoFileResolution } from '../../helpers/ffmpeg-utils' 57import { getVideoFileResolution } from '../../helpers/ffmpeg-utils'
49import { logger } from '../../helpers/logger' 58import { logger } from '../../helpers/logger'
50import { getServerActor } from '../../helpers/utils' 59import { getServerActor } from '../../helpers/utils'
51import { 60import {
@@ -54,11 +63,9 @@ import {
54 CONSTRAINTS_FIELDS, 63 CONSTRAINTS_FIELDS,
55 HLS_REDUNDANCY_DIRECTORY, 64 HLS_REDUNDANCY_DIRECTORY,
56 HLS_STREAMING_PLAYLIST_DIRECTORY, 65 HLS_STREAMING_PLAYLIST_DIRECTORY,
57 PREVIEWS_SIZE,
58 REMOTE_SCHEME, 66 REMOTE_SCHEME,
59 STATIC_DOWNLOAD_PATHS, 67 STATIC_DOWNLOAD_PATHS,
60 STATIC_PATHS, 68 STATIC_PATHS,
61 THUMBNAILS_SIZE,
62 VIDEO_CATEGORIES, 69 VIDEO_CATEGORIES,
63 VIDEO_LANGUAGES, 70 VIDEO_LANGUAGES,
64 VIDEO_LICENCES, 71 VIDEO_LICENCES,
@@ -111,7 +118,7 @@ import { ThumbnailModel } from './thumbnail'
111import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 118import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
112 119
113// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 120// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
114const indexes: Sequelize.DefineIndexesOptions[] = [ 121const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [
115 buildTrigramSearchIndex('video_name_trigram', 'name'), 122 buildTrigramSearchIndex('video_name_trigram', 'name'),
116 123
117 { fields: [ 'createdAt' ] }, 124 { fields: [ 'createdAt' ] },
@@ -123,7 +130,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [
123 fields: [ 'originallyPublishedAt' ], 130 fields: [ 'originallyPublishedAt' ],
124 where: { 131 where: {
125 originallyPublishedAt: { 132 originallyPublishedAt: {
126 [Sequelize.Op.ne]: null 133 [Op.ne]: null
127 } 134 }
128 } 135 }
129 }, 136 },
@@ -131,7 +138,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [
131 fields: [ 'category' ], // We don't care videos with an unknown category 138 fields: [ 'category' ], // We don't care videos with an unknown category
132 where: { 139 where: {
133 category: { 140 category: {
134 [Sequelize.Op.ne]: null 141 [Op.ne]: null
135 } 142 }
136 } 143 }
137 }, 144 },
@@ -139,7 +146,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [
139 fields: [ 'licence' ], // We don't care videos with an unknown licence 146 fields: [ 'licence' ], // We don't care videos with an unknown licence
140 where: { 147 where: {
141 licence: { 148 licence: {
142 [Sequelize.Op.ne]: null 149 [Op.ne]: null
143 } 150 }
144 } 151 }
145 }, 152 },
@@ -147,7 +154,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [
147 fields: [ 'language' ], // We don't care videos with an unknown language 154 fields: [ 'language' ], // We don't care videos with an unknown language
148 where: { 155 where: {
149 language: { 156 language: {
150 [Sequelize.Op.ne]: null 157 [Op.ne]: null
151 } 158 }
152 } 159 }
153 }, 160 },
@@ -222,10 +229,10 @@ type AvailableForListIDsOptions = {
222 229
223@Scopes({ 230@Scopes({
224 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { 231 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
225 const query: IFindOptions<VideoModel> = { 232 const query: FindOptions = {
226 where: { 233 where: {
227 id: { 234 id: {
228 [ Sequelize.Op.any ]: options.ids 235 [ Op.in ]: options.ids // FIXME: sequelize any seems broken
229 } 236 }
230 }, 237 },
231 include: [ 238 include: [
@@ -256,21 +263,21 @@ type AvailableForListIDsOptions = {
256 return query 263 return query
257 }, 264 },
258 [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { 265 [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => {
259 const query: IFindOptions<VideoModel> = { 266 const query: FindOptions = {
260 raw: true, 267 raw: true,
261 attributes: [ 'id' ], 268 attributes: [ 'id' ],
262 where: { 269 where: {
263 id: { 270 id: {
264 [ Sequelize.Op.and ]: [ 271 [ Op.and ]: [
265 { 272 {
266 [ Sequelize.Op.notIn ]: Sequelize.literal( 273 [ Op.notIn ]: Sequelize.literal(
267 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' 274 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
268 ) 275 )
269 } 276 }
270 ] 277 ]
271 }, 278 },
272 channelId: { 279 channelId: {
273 [ Sequelize.Op.notIn ]: Sequelize.literal( 280 [ Op.notIn ]: Sequelize.literal(
274 '(' + 281 '(' +
275 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + 282 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' +
276 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + 283 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) +
@@ -288,12 +295,12 @@ type AvailableForListIDsOptions = {
288 // Always list public videos 295 // Always list public videos
289 privacy: VideoPrivacy.PUBLIC, 296 privacy: VideoPrivacy.PUBLIC,
290 // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding 297 // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding
291 [ Sequelize.Op.or ]: [ 298 [ Op.or ]: [
292 { 299 {
293 state: VideoState.PUBLISHED 300 state: VideoState.PUBLISHED
294 }, 301 },
295 { 302 {
296 [ Sequelize.Op.and ]: { 303 [ Op.and ]: {
297 state: VideoState.TO_TRANSCODE, 304 state: VideoState.TO_TRANSCODE,
298 waitTranscoding: false 305 waitTranscoding: false
299 } 306 }
@@ -318,7 +325,7 @@ type AvailableForListIDsOptions = {
318 } 325 }
319 326
320 if (options.filter || options.accountId || options.videoChannelId) { 327 if (options.filter || options.accountId || options.videoChannelId) {
321 const videoChannelInclude: IIncludeOptions = { 328 const videoChannelInclude: IncludeOptions = {
322 attributes: [], 329 attributes: [],
323 model: VideoChannelModel.unscoped(), 330 model: VideoChannelModel.unscoped(),
324 required: true 331 required: true
@@ -331,7 +338,7 @@ type AvailableForListIDsOptions = {
331 } 338 }
332 339
333 if (options.filter || options.accountId) { 340 if (options.filter || options.accountId) {
334 const accountInclude: IIncludeOptions = { 341 const accountInclude: IncludeOptions = {
335 attributes: [], 342 attributes: [],
336 model: AccountModel.unscoped(), 343 model: AccountModel.unscoped(),
337 required: true 344 required: true
@@ -371,8 +378,8 @@ type AvailableForListIDsOptions = {
371 378
372 // Force actorId to be a number to avoid SQL injections 379 // Force actorId to be a number to avoid SQL injections
373 const actorIdNumber = parseInt(options.followerActorId.toString(), 10) 380 const actorIdNumber = parseInt(options.followerActorId.toString(), 10)
374 query.where[ 'id' ][ Sequelize.Op.and ].push({ 381 query.where[ 'id' ][ Op.and ].push({
375 [ Sequelize.Op.in ]: Sequelize.literal( 382 [ Op.in ]: Sequelize.literal(
376 '(' + 383 '(' +
377 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + 384 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
378 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 385 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
@@ -391,8 +398,8 @@ type AvailableForListIDsOptions = {
391 } 398 }
392 399
393 if (options.withFiles === true) { 400 if (options.withFiles === true) {
394 query.where[ 'id' ][ Sequelize.Op.and ].push({ 401 query.where[ 'id' ][ Op.and ].push({
395 [ Sequelize.Op.in ]: Sequelize.literal( 402 [ Op.in ]: Sequelize.literal(
396 '(SELECT "videoId" FROM "videoFile")' 403 '(SELECT "videoId" FROM "videoFile")'
397 ) 404 )
398 }) 405 })
@@ -406,8 +413,8 @@ type AvailableForListIDsOptions = {
406 } 413 }
407 414
408 if (options.tagsOneOf) { 415 if (options.tagsOneOf) {
409 query.where[ 'id' ][ Sequelize.Op.and ].push({ 416 query.where[ 'id' ][ Op.and ].push({
410 [ Sequelize.Op.in ]: Sequelize.literal( 417 [ Op.in ]: Sequelize.literal(
411 '(' + 418 '(' +
412 'SELECT "videoId" FROM "videoTag" ' + 419 'SELECT "videoId" FROM "videoTag" ' +
413 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 420 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -418,8 +425,8 @@ type AvailableForListIDsOptions = {
418 } 425 }
419 426
420 if (options.tagsAllOf) { 427 if (options.tagsAllOf) {
421 query.where[ 'id' ][ Sequelize.Op.and ].push({ 428 query.where[ 'id' ][ Op.and ].push({
422 [ Sequelize.Op.in ]: Sequelize.literal( 429 [ Op.in ]: Sequelize.literal(
423 '(' + 430 '(' +
424 'SELECT "videoId" FROM "videoTag" ' + 431 'SELECT "videoId" FROM "videoTag" ' +
425 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 432 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -437,19 +444,19 @@ type AvailableForListIDsOptions = {
437 444
438 if (options.categoryOneOf) { 445 if (options.categoryOneOf) {
439 query.where[ 'category' ] = { 446 query.where[ 'category' ] = {
440 [ Sequelize.Op.or ]: options.categoryOneOf 447 [ Op.or ]: options.categoryOneOf
441 } 448 }
442 } 449 }
443 450
444 if (options.licenceOneOf) { 451 if (options.licenceOneOf) {
445 query.where[ 'licence' ] = { 452 query.where[ 'licence' ] = {
446 [ Sequelize.Op.or ]: options.licenceOneOf 453 [ Op.or ]: options.licenceOneOf
447 } 454 }
448 } 455 }
449 456
450 if (options.languageOneOf) { 457 if (options.languageOneOf) {
451 query.where[ 'language' ] = { 458 query.where[ 'language' ] = {
452 [ Sequelize.Op.or ]: options.languageOneOf 459 [ Op.or ]: options.languageOneOf
453 } 460 }
454 } 461 }
455 462
@@ -498,7 +505,7 @@ type AvailableForListIDsOptions = {
498 } 505 }
499 ] 506 ]
500 } 507 }
501 ] 508 ] as any // FIXME: sequelize typings
502 }, 509 },
503 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { 510 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
504 include: [ 511 include: [
@@ -550,7 +557,7 @@ type AvailableForListIDsOptions = {
550 } 557 }
551 ] 558 ]
552 } 559 }
553 ] 560 ] as any // FIXME: sequelize typings
554 }, 561 },
555 [ ScopeNames.WITH_TAGS ]: { 562 [ ScopeNames.WITH_TAGS ]: {
556 include: [ () => TagModel ] 563 include: [ () => TagModel ]
@@ -656,19 +663,19 @@ export class VideoModel extends Model<VideoModel> {
656 663
657 @AllowNull(true) 664 @AllowNull(true)
658 @Default(null) 665 @Default(null)
659 @Is('VideoCategory', value => throwIfNotValid(value, isVideoCategoryValid, 'category')) 666 @Is('VideoCategory', value => throwIfNotValid(value, isVideoCategoryValid, 'category', true))
660 @Column 667 @Column
661 category: number 668 category: number
662 669
663 @AllowNull(true) 670 @AllowNull(true)
664 @Default(null) 671 @Default(null)
665 @Is('VideoLicence', value => throwIfNotValid(value, isVideoLicenceValid, 'licence')) 672 @Is('VideoLicence', value => throwIfNotValid(value, isVideoLicenceValid, 'licence', true))
666 @Column 673 @Column
667 licence: number 674 licence: number
668 675
669 @AllowNull(true) 676 @AllowNull(true)
670 @Default(null) 677 @Default(null)
671 @Is('VideoLanguage', value => throwIfNotValid(value, isVideoLanguageValid, 'language')) 678 @Is('VideoLanguage', value => throwIfNotValid(value, isVideoLanguageValid, 'language', true))
672 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.LANGUAGE.max)) 679 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.LANGUAGE.max))
673 language: string 680 language: string
674 681
@@ -684,13 +691,13 @@ export class VideoModel extends Model<VideoModel> {
684 691
685 @AllowNull(true) 692 @AllowNull(true)
686 @Default(null) 693 @Default(null)
687 @Is('VideoDescription', value => throwIfNotValid(value, isVideoDescriptionValid, 'description')) 694 @Is('VideoDescription', value => throwIfNotValid(value, isVideoDescriptionValid, 'description', true))
688 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max)) 695 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max))
689 description: string 696 description: string
690 697
691 @AllowNull(true) 698 @AllowNull(true)
692 @Default(null) 699 @Default(null)
693 @Is('VideoSupport', value => throwIfNotValid(value, isVideoSupportValid, 'support')) 700 @Is('VideoSupport', value => throwIfNotValid(value, isVideoSupportValid, 'support', true))
694 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max)) 701 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max))
695 support: string 702 support: string
696 703
@@ -754,7 +761,7 @@ export class VideoModel extends Model<VideoModel> {
754 updatedAt: Date 761 updatedAt: Date
755 762
756 @AllowNull(false) 763 @AllowNull(false)
757 @Default(Sequelize.NOW) 764 @Default(DataType.NOW)
758 @Column 765 @Column
759 publishedAt: Date 766 publishedAt: Date
760 767
@@ -999,12 +1006,12 @@ export class VideoModel extends Model<VideoModel> {
999 distinct: true, 1006 distinct: true,
1000 offset: start, 1007 offset: start,
1001 limit: count, 1008 limit: count,
1002 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ]), 1009 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings
1003 where: { 1010 where: {
1004 id: { 1011 id: {
1005 [ Sequelize.Op.in ]: Sequelize.literal('(' + rawQuery + ')') 1012 [ Op.in ]: Sequelize.literal('(' + rawQuery + ')')
1006 }, 1013 },
1007 [ Sequelize.Op.or ]: [ 1014 [ Op.or ]: [
1008 { privacy: VideoPrivacy.PUBLIC }, 1015 { privacy: VideoPrivacy.PUBLIC },
1009 { privacy: VideoPrivacy.UNLISTED } 1016 { privacy: VideoPrivacy.UNLISTED }
1010 ] 1017 ]
@@ -1021,10 +1028,10 @@ export class VideoModel extends Model<VideoModel> {
1021 required: false, 1028 required: false,
1022 // We only want videos shared by this actor 1029 // We only want videos shared by this actor
1023 where: { 1030 where: {
1024 [ Sequelize.Op.and ]: [ 1031 [ Op.and ]: [
1025 { 1032 {
1026 id: { 1033 id: {
1027 [ Sequelize.Op.not ]: null 1034 [ Op.not ]: null
1028 } 1035 }
1029 }, 1036 },
1030 { 1037 {
@@ -1070,13 +1077,13 @@ export class VideoModel extends Model<VideoModel> {
1070 return Bluebird.all([ 1077 return Bluebird.all([
1071 // FIXME: typing issue 1078 // FIXME: typing issue
1072 VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any), 1079 VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any),
1073 VideoModel.sequelize.query(rawCountQuery, { type: Sequelize.QueryTypes.SELECT }) 1080 VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT })
1074 ]).then(([ rows, totals ]) => { 1081 ]).then(([ rows, totals ]) => {
1075 // totals: totalVideos + totalVideoShares 1082 // totals: totalVideos + totalVideoShares
1076 let totalVideos = 0 1083 let totalVideos = 0
1077 let totalVideoShares = 0 1084 let totalVideoShares = 0
1078 if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) 1085 if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10)
1079 if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) 1086 if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10)
1080 1087
1081 const total = totalVideos + totalVideoShares 1088 const total = totalVideos + totalVideoShares
1082 return { 1089 return {
@@ -1087,7 +1094,7 @@ export class VideoModel extends Model<VideoModel> {
1087 } 1094 }
1088 1095
1089 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { 1096 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
1090 const query: IFindOptions<VideoModel> = { 1097 const query: FindOptions = {
1091 offset: start, 1098 offset: start,
1092 limit: count, 1099 limit: count,
1093 order: getVideoSort(sort), 1100 order: getVideoSort(sort),
@@ -1158,7 +1165,7 @@ export class VideoModel extends Model<VideoModel> {
1158 throw new Error('Try to filter all-local but no user has not the see all videos right') 1165 throw new Error('Try to filter all-local but no user has not the see all videos right')
1159 } 1166 }
1160 1167
1161 const query: IFindOptions<VideoModel> = { 1168 const query: FindOptions = {
1162 offset: options.start, 1169 offset: options.start,
1163 limit: options.count, 1170 limit: options.count,
1164 order: getVideoSort(options.sort) 1171 order: getVideoSort(options.sort)
@@ -1225,8 +1232,8 @@ export class VideoModel extends Model<VideoModel> {
1225 if (options.startDate || options.endDate) { 1232 if (options.startDate || options.endDate) {
1226 const publishedAtRange = {} 1233 const publishedAtRange = {}
1227 1234
1228 if (options.startDate) publishedAtRange[ Sequelize.Op.gte ] = options.startDate 1235 if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate
1229 if (options.endDate) publishedAtRange[ Sequelize.Op.lte ] = options.endDate 1236 if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate
1230 1237
1231 whereAnd.push({ publishedAt: publishedAtRange }) 1238 whereAnd.push({ publishedAt: publishedAtRange })
1232 } 1239 }
@@ -1234,8 +1241,8 @@ export class VideoModel extends Model<VideoModel> {
1234 if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { 1241 if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) {
1235 const originallyPublishedAtRange = {} 1242 const originallyPublishedAtRange = {}
1236 1243
1237 if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Sequelize.Op.gte ] = options.originallyPublishedStartDate 1244 if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate
1238 if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Sequelize.Op.lte ] = options.originallyPublishedEndDate 1245 if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate
1239 1246
1240 whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) 1247 whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange })
1241 } 1248 }
@@ -1243,8 +1250,8 @@ export class VideoModel extends Model<VideoModel> {
1243 if (options.durationMin || options.durationMax) { 1250 if (options.durationMin || options.durationMax) {
1244 const durationRange = {} 1251 const durationRange = {}
1245 1252
1246 if (options.durationMin) durationRange[ Sequelize.Op.gte ] = options.durationMin 1253 if (options.durationMin) durationRange[ Op.gte ] = options.durationMin
1247 if (options.durationMax) durationRange[ Sequelize.Op.lte ] = options.durationMax 1254 if (options.durationMax) durationRange[ Op.lte ] = options.durationMax
1248 1255
1249 whereAnd.push({ duration: durationRange }) 1256 whereAnd.push({ duration: durationRange })
1250 } 1257 }
@@ -1256,7 +1263,7 @@ export class VideoModel extends Model<VideoModel> {
1256 whereAnd.push( 1263 whereAnd.push(
1257 { 1264 {
1258 id: { 1265 id: {
1259 [ Sequelize.Op.in ]: Sequelize.literal( 1266 [ Op.in ]: Sequelize.literal(
1260 '(' + 1267 '(' +
1261 'SELECT "video"."id" FROM "video" ' + 1268 'SELECT "video"."id" FROM "video" ' +
1262 'WHERE ' + 1269 'WHERE ' +
@@ -1282,7 +1289,7 @@ export class VideoModel extends Model<VideoModel> {
1282 ) 1289 )
1283 } 1290 }
1284 1291
1285 const query: IFindOptions<VideoModel> = { 1292 const query: FindOptions = {
1286 attributes: { 1293 attributes: {
1287 include: attributesInclude 1294 include: attributesInclude
1288 }, 1295 },
@@ -1290,7 +1297,7 @@ export class VideoModel extends Model<VideoModel> {
1290 limit: options.count, 1297 limit: options.count,
1291 order: getVideoSort(options.sort), 1298 order: getVideoSort(options.sort),
1292 where: { 1299 where: {
1293 [ Sequelize.Op.and ]: whereAnd 1300 [ Op.and ]: whereAnd
1294 } 1301 }
1295 } 1302 }
1296 1303
@@ -1312,7 +1319,7 @@ export class VideoModel extends Model<VideoModel> {
1312 return VideoModel.getAvailableForApi(query, queryOptions) 1319 return VideoModel.getAvailableForApi(query, queryOptions)
1313 } 1320 }
1314 1321
1315 static load (id: number | string, t?: Sequelize.Transaction) { 1322 static load (id: number | string, t?: Transaction) {
1316 const where = buildWhereIdOrUUID(id) 1323 const where = buildWhereIdOrUUID(id)
1317 const options = { 1324 const options = {
1318 where, 1325 where,
@@ -1322,7 +1329,7 @@ export class VideoModel extends Model<VideoModel> {
1322 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1329 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1323 } 1330 }
1324 1331
1325 static loadWithRights (id: number | string, t?: Sequelize.Transaction) { 1332 static loadWithRights (id: number | string, t?: Transaction) {
1326 const where = buildWhereIdOrUUID(id) 1333 const where = buildWhereIdOrUUID(id)
1327 const options = { 1334 const options = {
1328 where, 1335 where,
@@ -1336,7 +1343,7 @@ export class VideoModel extends Model<VideoModel> {
1336 ]).findOne(options) 1343 ]).findOne(options)
1337 } 1344 }
1338 1345
1339 static loadOnlyId (id: number | string, t?: Sequelize.Transaction) { 1346 static loadOnlyId (id: number | string, t?: Transaction) {
1340 const where = buildWhereIdOrUUID(id) 1347 const where = buildWhereIdOrUUID(id)
1341 1348
1342 const options = { 1349 const options = {
@@ -1348,7 +1355,7 @@ export class VideoModel extends Model<VideoModel> {
1348 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1355 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1349 } 1356 }
1350 1357
1351 static loadWithFiles (id: number, t?: Sequelize.Transaction, logging?: boolean) { 1358 static loadWithFiles (id: number, t?: Transaction, logging?: boolean) {
1352 return VideoModel.scope([ 1359 return VideoModel.scope([
1353 ScopeNames.WITH_FILES, 1360 ScopeNames.WITH_FILES,
1354 ScopeNames.WITH_STREAMING_PLAYLISTS, 1361 ScopeNames.WITH_STREAMING_PLAYLISTS,
@@ -1366,8 +1373,8 @@ export class VideoModel extends Model<VideoModel> {
1366 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1373 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1367 } 1374 }
1368 1375
1369 static loadByUrl (url: string, transaction?: Sequelize.Transaction) { 1376 static loadByUrl (url: string, transaction?: Transaction) {
1370 const query: IFindOptions<VideoModel> = { 1377 const query: FindOptions = {
1371 where: { 1378 where: {
1372 url 1379 url
1373 }, 1380 },
@@ -1377,8 +1384,8 @@ export class VideoModel extends Model<VideoModel> {
1377 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) 1384 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
1378 } 1385 }
1379 1386
1380 static loadByUrlAndPopulateAccount (url: string, transaction?: Sequelize.Transaction) { 1387 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) {
1381 const query: IFindOptions<VideoModel> = { 1388 const query: FindOptions = {
1382 where: { 1389 where: {
1383 url 1390 url
1384 }, 1391 },
@@ -1393,11 +1400,11 @@ export class VideoModel extends Model<VideoModel> {
1393 ]).findOne(query) 1400 ]).findOne(query)
1394 } 1401 }
1395 1402
1396 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) { 1403 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) {
1397 const where = buildWhereIdOrUUID(id) 1404 const where = buildWhereIdOrUUID(id)
1398 1405
1399 const options = { 1406 const options = {
1400 order: [ [ 'Tags', 'name', 'ASC' ] ], 1407 order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings
1401 where, 1408 where,
1402 transaction: t 1409 transaction: t
1403 } 1410 }
@@ -1421,11 +1428,11 @@ export class VideoModel extends Model<VideoModel> {
1421 .findOne(options) 1428 .findOne(options)
1422 } 1429 }
1423 1430
1424 static loadForGetAPI (id: number | string, t?: Sequelize.Transaction, userId?: number) { 1431 static loadForGetAPI (id: number | string, t?: Transaction, userId?: number) {
1425 const where = buildWhereIdOrUUID(id) 1432 const where = buildWhereIdOrUUID(id)
1426 1433
1427 const options = { 1434 const options = {
1428 order: [ [ 'Tags', 'name', 'ASC' ] ], 1435 order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings
1429 where, 1436 where,
1430 transaction: t 1437 transaction: t
1431 } 1438 }
@@ -1489,7 +1496,7 @@ export class VideoModel extends Model<VideoModel> {
1489 'LIMIT 1' 1496 'LIMIT 1'
1490 1497
1491 const options = { 1498 const options = {
1492 type: Sequelize.QueryTypes.SELECT, 1499 type: QueryTypes.SELECT,
1493 bind: { followerActorId, videoId }, 1500 bind: { followerActorId, videoId },
1494 raw: true 1501 raw: true
1495 } 1502 }
@@ -1509,14 +1516,14 @@ export class VideoModel extends Model<VideoModel> {
1509 includeLocalVideos: true 1516 includeLocalVideos: true
1510 } 1517 }
1511 1518
1512 const query: IFindOptions<VideoModel> = { 1519 const query: FindOptions = {
1513 attributes: [ field ], 1520 attributes: [ field ],
1514 limit: count, 1521 limit: count,
1515 group: field, 1522 group: field,
1516 having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { 1523 having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), {
1517 [ Sequelize.Op.gte ]: threshold 1524 [ Op.gte ]: threshold
1518 }) as any, // FIXME: typings 1525 }) as any, // FIXME: typings
1519 order: [ this.sequelize.random() ] 1526 order: [ (this.sequelize as any).random() ]
1520 } 1527 }
1521 1528
1522 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) 1529 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] })
@@ -1532,7 +1539,7 @@ export class VideoModel extends Model<VideoModel> {
1532 required: false, 1539 required: false,
1533 where: { 1540 where: {
1534 startDate: { 1541 startDate: {
1535 [ Sequelize.Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) 1542 [ Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays)
1536 } 1543 }
1537 } 1544 }
1538 } 1545 }
@@ -1549,11 +1556,11 @@ export class VideoModel extends Model<VideoModel> {
1549 } 1556 }
1550 1557
1551 private static async getAvailableForApi ( 1558 private static async getAvailableForApi (
1552 query: IFindOptions<VideoModel>, 1559 query: FindOptions,
1553 options: AvailableForListIDsOptions, 1560 options: AvailableForListIDsOptions,
1554 countVideos = true 1561 countVideos = true
1555 ) { 1562 ) {
1556 const idsScope = { 1563 const idsScope: ScopeOptions = {
1557 method: [ 1564 method: [
1558 ScopeNames.AVAILABLE_FOR_LIST_IDS, options 1565 ScopeNames.AVAILABLE_FOR_LIST_IDS, options
1559 ] 1566 ]
@@ -1561,8 +1568,8 @@ export class VideoModel extends Model<VideoModel> {
1561 1568
1562 // Remove trending sort on count, because it uses a group by 1569 // Remove trending sort on count, because it uses a group by
1563 const countOptions = Object.assign({}, options, { trendingDays: undefined }) 1570 const countOptions = Object.assign({}, options, { trendingDays: undefined })
1564 const countQuery = Object.assign({}, query, { attributes: undefined, group: undefined }) 1571 const countQuery: CountOptions = Object.assign({}, query, { attributes: undefined, group: undefined })
1565 const countScope = { 1572 const countScope: ScopeOptions = {
1566 method: [ 1573 method: [
1567 ScopeNames.AVAILABLE_FOR_LIST_IDS, countOptions 1574 ScopeNames.AVAILABLE_FOR_LIST_IDS, countOptions
1568 ] 1575 ]
@@ -1576,7 +1583,7 @@ export class VideoModel extends Model<VideoModel> {
1576 1583
1577 if (ids.length === 0) return { data: [], total: count } 1584 if (ids.length === 0) return { data: [], total: count }
1578 1585
1579 const secondQuery: IFindOptions<VideoModel> = { 1586 const secondQuery: FindOptions = {
1580 offset: 0, 1587 offset: 0,
1581 limit: query.limit, 1588 limit: query.limit,
1582 attributes: query.attributes, 1589 attributes: query.attributes,