diff options
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r-- | server/models/video/video.ts | 179 |
1 files changed, 91 insertions, 88 deletions
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 = { | |||
227 | historyOfUser?: UserModel | 227 | historyOfUser?: UserModel |
228 | } | 228 | } |
229 | 229 | ||
230 | @Scopes({ | 230 | @Scopes(() => ({ |
231 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { | 231 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { |
232 | const query: FindOptions = { | 232 | const query: FindOptions = { |
233 | where: { | 233 | where: { |
234 | id: { | 234 | id: { |
235 | [ Op.in ]: options.ids // FIXME: sequelize any seems broken | 235 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken |
236 | } | 236 | } |
237 | }, | 237 | }, |
238 | include: [ | 238 | include: [ |
@@ -486,7 +486,7 @@ type AvailableForListIDsOptions = { | |||
486 | [ ScopeNames.WITH_THUMBNAILS ]: { | 486 | [ ScopeNames.WITH_THUMBNAILS ]: { |
487 | include: [ | 487 | include: [ |
488 | { | 488 | { |
489 | model: () => ThumbnailModel, | 489 | model: ThumbnailModel, |
490 | required: false | 490 | required: false |
491 | } | 491 | } |
492 | ] | 492 | ] |
@@ -495,48 +495,48 @@ type AvailableForListIDsOptions = { | |||
495 | include: [ | 495 | include: [ |
496 | { | 496 | { |
497 | attributes: [ 'accountId' ], | 497 | attributes: [ 'accountId' ], |
498 | model: () => VideoChannelModel.unscoped(), | 498 | model: VideoChannelModel.unscoped(), |
499 | required: true, | 499 | required: true, |
500 | include: [ | 500 | include: [ |
501 | { | 501 | { |
502 | attributes: [ 'userId' ], | 502 | attributes: [ 'userId' ], |
503 | model: () => AccountModel.unscoped(), | 503 | model: AccountModel.unscoped(), |
504 | required: true | 504 | required: true |
505 | } | 505 | } |
506 | ] | 506 | ] |
507 | } | 507 | } |
508 | ] as any // FIXME: sequelize typings | 508 | ] |
509 | }, | 509 | }, |
510 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { | 510 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { |
511 | include: [ | 511 | include: [ |
512 | { | 512 | { |
513 | model: () => VideoChannelModel.unscoped(), | 513 | model: VideoChannelModel.unscoped(), |
514 | required: true, | 514 | required: true, |
515 | include: [ | 515 | include: [ |
516 | { | 516 | { |
517 | attributes: { | 517 | attributes: { |
518 | exclude: [ 'privateKey', 'publicKey' ] | 518 | exclude: [ 'privateKey', 'publicKey' ] |
519 | }, | 519 | }, |
520 | model: () => ActorModel.unscoped(), | 520 | model: ActorModel.unscoped(), |
521 | required: true, | 521 | required: true, |
522 | include: [ | 522 | include: [ |
523 | { | 523 | { |
524 | attributes: [ 'host' ], | 524 | attributes: [ 'host' ], |
525 | model: () => ServerModel.unscoped(), | 525 | model: ServerModel.unscoped(), |
526 | required: false | 526 | required: false |
527 | }, | 527 | }, |
528 | { | 528 | { |
529 | model: () => AvatarModel.unscoped(), | 529 | model: AvatarModel.unscoped(), |
530 | required: false | 530 | required: false |
531 | } | 531 | } |
532 | ] | 532 | ] |
533 | }, | 533 | }, |
534 | { | 534 | { |
535 | model: () => AccountModel.unscoped(), | 535 | model: AccountModel.unscoped(), |
536 | required: true, | 536 | required: true, |
537 | include: [ | 537 | include: [ |
538 | { | 538 | { |
539 | model: () => ActorModel.unscoped(), | 539 | model: ActorModel.unscoped(), |
540 | attributes: { | 540 | attributes: { |
541 | exclude: [ 'privateKey', 'publicKey' ] | 541 | exclude: [ 'privateKey', 'publicKey' ] |
542 | }, | 542 | }, |
@@ -544,11 +544,11 @@ type AvailableForListIDsOptions = { | |||
544 | include: [ | 544 | include: [ |
545 | { | 545 | { |
546 | attributes: [ 'host' ], | 546 | attributes: [ 'host' ], |
547 | model: () => ServerModel.unscoped(), | 547 | model: ServerModel.unscoped(), |
548 | required: false | 548 | required: false |
549 | }, | 549 | }, |
550 | { | 550 | { |
551 | model: () => AvatarModel.unscoped(), | 551 | model: AvatarModel.unscoped(), |
552 | required: false | 552 | required: false |
553 | } | 553 | } |
554 | ] | 554 | ] |
@@ -557,16 +557,16 @@ type AvailableForListIDsOptions = { | |||
557 | } | 557 | } |
558 | ] | 558 | ] |
559 | } | 559 | } |
560 | ] as any // FIXME: sequelize typings | 560 | ] |
561 | }, | 561 | }, |
562 | [ ScopeNames.WITH_TAGS ]: { | 562 | [ ScopeNames.WITH_TAGS ]: { |
563 | include: [ () => TagModel ] | 563 | include: [ TagModel ] |
564 | }, | 564 | }, |
565 | [ ScopeNames.WITH_BLACKLISTED ]: { | 565 | [ ScopeNames.WITH_BLACKLISTED ]: { |
566 | include: [ | 566 | include: [ |
567 | { | 567 | { |
568 | attributes: [ 'id', 'reason' ], | 568 | attributes: [ 'id', 'reason' ], |
569 | model: () => VideoBlacklistModel, | 569 | model: VideoBlacklistModel, |
570 | required: false | 570 | required: false |
571 | } | 571 | } |
572 | ] | 572 | ] |
@@ -588,8 +588,7 @@ type AvailableForListIDsOptions = { | |||
588 | include: [ | 588 | include: [ |
589 | { | 589 | { |
590 | model: VideoFileModel.unscoped(), | 590 | model: VideoFileModel.unscoped(), |
591 | // FIXME: typings | 591 | separate: true, // We may have multiple files, having multiple redundancies so let's separate this join |
592 | [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join | ||
593 | required: false, | 592 | required: false, |
594 | include: subInclude | 593 | include: subInclude |
595 | } | 594 | } |
@@ -613,8 +612,7 @@ type AvailableForListIDsOptions = { | |||
613 | include: [ | 612 | include: [ |
614 | { | 613 | { |
615 | model: VideoStreamingPlaylistModel.unscoped(), | 614 | model: VideoStreamingPlaylistModel.unscoped(), |
616 | // FIXME: typings | 615 | separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join |
617 | [ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join | ||
618 | required: false, | 616 | required: false, |
619 | include: subInclude | 617 | include: subInclude |
620 | } | 618 | } |
@@ -624,7 +622,7 @@ type AvailableForListIDsOptions = { | |||
624 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { | 622 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { |
625 | include: [ | 623 | include: [ |
626 | { | 624 | { |
627 | model: () => ScheduleVideoUpdateModel.unscoped(), | 625 | model: ScheduleVideoUpdateModel.unscoped(), |
628 | required: false | 626 | required: false |
629 | } | 627 | } |
630 | ] | 628 | ] |
@@ -643,7 +641,7 @@ type AvailableForListIDsOptions = { | |||
643 | ] | 641 | ] |
644 | } | 642 | } |
645 | } | 643 | } |
646 | }) | 644 | })) |
647 | @Table({ | 645 | @Table({ |
648 | tableName: 'video', | 646 | tableName: 'video', |
649 | indexes | 647 | indexes |
@@ -1075,15 +1073,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1075 | } | 1073 | } |
1076 | 1074 | ||
1077 | return Bluebird.all([ | 1075 | return Bluebird.all([ |
1078 | // FIXME: typing issue | 1076 | VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query), |
1079 | VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any), | 1077 | VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT }) |
1080 | VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT }) | ||
1081 | ]).then(([ rows, totals ]) => { | 1078 | ]).then(([ rows, totals ]) => { |
1082 | // totals: totalVideos + totalVideoShares | 1079 | // totals: totalVideos + totalVideoShares |
1083 | let totalVideos = 0 | 1080 | let totalVideos = 0 |
1084 | let totalVideoShares = 0 | 1081 | let totalVideoShares = 0 |
1085 | if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10) | 1082 | if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) |
1086 | if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10) | 1083 | if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) |
1087 | 1084 | ||
1088 | const total = totalVideos + totalVideoShares | 1085 | const total = totalVideos + totalVideoShares |
1089 | return { | 1086 | return { |
@@ -1094,50 +1091,58 @@ export class VideoModel extends Model<VideoModel> { | |||
1094 | } | 1091 | } |
1095 | 1092 | ||
1096 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { | 1093 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { |
1097 | const query: FindOptions = { | 1094 | function buildBaseQuery (): FindOptions { |
1098 | offset: start, | 1095 | return { |
1099 | limit: count, | 1096 | offset: start, |
1100 | order: getVideoSort(sort), | 1097 | limit: count, |
1101 | include: [ | 1098 | order: getVideoSort(sort), |
1102 | { | 1099 | include: [ |
1103 | model: VideoChannelModel, | 1100 | { |
1104 | required: true, | 1101 | model: VideoChannelModel, |
1105 | include: [ | 1102 | required: true, |
1106 | { | 1103 | include: [ |
1107 | model: AccountModel, | 1104 | { |
1108 | where: { | 1105 | model: AccountModel, |
1109 | id: accountId | 1106 | where: { |
1110 | }, | 1107 | id: accountId |
1111 | required: true | 1108 | }, |
1112 | } | 1109 | required: true |
1113 | ] | 1110 | } |
1114 | }, | 1111 | ] |
1115 | { | 1112 | } |
1116 | model: ScheduleVideoUpdateModel, | 1113 | ] |
1117 | required: false | 1114 | } |
1118 | }, | ||
1119 | { | ||
1120 | model: VideoBlacklistModel, | ||
1121 | required: false | ||
1122 | } | ||
1123 | ] | ||
1124 | } | 1115 | } |
1125 | 1116 | ||
1117 | const countQuery = buildBaseQuery() | ||
1118 | const findQuery = buildBaseQuery() | ||
1119 | |||
1120 | findQuery.include.push({ | ||
1121 | model: ScheduleVideoUpdateModel, | ||
1122 | required: false | ||
1123 | }) | ||
1124 | |||
1125 | findQuery.include.push({ | ||
1126 | model: VideoBlacklistModel, | ||
1127 | required: false | ||
1128 | }) | ||
1129 | |||
1126 | if (withFiles === true) { | 1130 | if (withFiles === true) { |
1127 | query.include.push({ | 1131 | findQuery.include.push({ |
1128 | model: VideoFileModel.unscoped(), | 1132 | model: VideoFileModel.unscoped(), |
1129 | required: true | 1133 | required: true |
1130 | }) | 1134 | }) |
1131 | } | 1135 | } |
1132 | 1136 | ||
1133 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS) | 1137 | return Promise.all([ |
1134 | .findAndCountAll(query) | 1138 | VideoModel.count(countQuery), |
1135 | .then(({ rows, count }) => { | 1139 | VideoModel.findAll(findQuery) |
1136 | return { | 1140 | ]).then(([ count, rows ]) => { |
1137 | data: rows, | 1141 | return { |
1138 | total: count | 1142 | data: rows, |
1139 | } | 1143 | total: count |
1140 | }) | 1144 | } |
1145 | }) | ||
1141 | } | 1146 | } |
1142 | 1147 | ||
1143 | static async listForApi (options: { | 1148 | static async listForApi (options: { |
@@ -1404,12 +1409,12 @@ export class VideoModel extends Model<VideoModel> { | |||
1404 | const where = buildWhereIdOrUUID(id) | 1409 | const where = buildWhereIdOrUUID(id) |
1405 | 1410 | ||
1406 | const options = { | 1411 | const options = { |
1407 | order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings | 1412 | order: [ [ 'Tags', 'name', 'ASC' ] ] as any, |
1408 | where, | 1413 | where, |
1409 | transaction: t | 1414 | transaction: t |
1410 | } | 1415 | } |
1411 | 1416 | ||
1412 | const scopes = [ | 1417 | const scopes: (string | ScopeOptions)[] = [ |
1413 | ScopeNames.WITH_TAGS, | 1418 | ScopeNames.WITH_TAGS, |
1414 | ScopeNames.WITH_BLACKLISTED, | 1419 | ScopeNames.WITH_BLACKLISTED, |
1415 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1420 | ScopeNames.WITH_ACCOUNT_DETAILS, |
@@ -1420,7 +1425,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1420 | ] | 1425 | ] |
1421 | 1426 | ||
1422 | if (userId) { | 1427 | if (userId) { |
1423 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings | 1428 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) |
1424 | } | 1429 | } |
1425 | 1430 | ||
1426 | return VideoModel | 1431 | return VideoModel |
@@ -1437,18 +1442,18 @@ export class VideoModel extends Model<VideoModel> { | |||
1437 | transaction: t | 1442 | transaction: t |
1438 | } | 1443 | } |
1439 | 1444 | ||
1440 | const scopes = [ | 1445 | const scopes: (string | ScopeOptions)[] = [ |
1441 | ScopeNames.WITH_TAGS, | 1446 | ScopeNames.WITH_TAGS, |
1442 | ScopeNames.WITH_BLACKLISTED, | 1447 | ScopeNames.WITH_BLACKLISTED, |
1443 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1448 | ScopeNames.WITH_ACCOUNT_DETAILS, |
1444 | ScopeNames.WITH_SCHEDULED_UPDATE, | 1449 | ScopeNames.WITH_SCHEDULED_UPDATE, |
1445 | ScopeNames.WITH_THUMBNAILS, | 1450 | ScopeNames.WITH_THUMBNAILS, |
1446 | { method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings | 1451 | { method: [ ScopeNames.WITH_FILES, true ] }, |
1447 | { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings | 1452 | { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } |
1448 | ] | 1453 | ] |
1449 | 1454 | ||
1450 | if (userId) { | 1455 | if (userId) { |
1451 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings | 1456 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) |
1452 | } | 1457 | } |
1453 | 1458 | ||
1454 | return VideoModel | 1459 | return VideoModel |
@@ -1520,9 +1525,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1520 | attributes: [ field ], | 1525 | attributes: [ field ], |
1521 | limit: count, | 1526 | limit: count, |
1522 | group: field, | 1527 | group: field, |
1523 | having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { | 1528 | having: Sequelize.where( |
1524 | [ Op.gte ]: threshold | 1529 | Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } |
1525 | }) as any, // FIXME: typings | 1530 | ), |
1526 | order: [ (this.sequelize as any).random() ] | 1531 | order: [ (this.sequelize as any).random() ] |
1527 | } | 1532 | } |
1528 | 1533 | ||
@@ -1594,16 +1599,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1594 | ] | 1599 | ] |
1595 | } | 1600 | } |
1596 | 1601 | ||
1597 | // FIXME: typing | 1602 | const apiScope: (string | ScopeOptions)[] = [ ScopeNames.WITH_THUMBNAILS ] |
1598 | const apiScope: any[] = [ ScopeNames.WITH_THUMBNAILS ] | ||
1599 | 1603 | ||
1600 | if (options.user) { | 1604 | if (options.user) { |
1601 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) | 1605 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) |
1602 | |||
1603 | // Even if the relation is n:m, we know that a user only have 0..1 video history | ||
1604 | // So we won't have multiple rows for the same video | ||
1605 | // A subquery adds some bugs in our query so disable it | ||
1606 | secondQuery.subQuery = false | ||
1607 | } | 1606 | } |
1608 | 1607 | ||
1609 | apiScope.push({ | 1608 | apiScope.push({ |
@@ -1651,13 +1650,17 @@ export class VideoModel extends Model<VideoModel> { | |||
1651 | return maxBy(this.VideoFiles, file => file.resolution) | 1650 | return maxBy(this.VideoFiles, file => file.resolution) |
1652 | } | 1651 | } |
1653 | 1652 | ||
1654 | addThumbnail (thumbnail: ThumbnailModel) { | 1653 | async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { |
1654 | thumbnail.videoId = this.id | ||
1655 | |||
1656 | const savedThumbnail = await thumbnail.save({ transaction }) | ||
1657 | |||
1655 | if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = [] | 1658 | if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = [] |
1656 | 1659 | ||
1657 | // Already have this thumbnail, skip | 1660 | // Already have this thumbnail, skip |
1658 | if (this.Thumbnails.find(t => t.id === thumbnail.id)) return | 1661 | if (this.Thumbnails.find(t => t.id === savedThumbnail.id)) return |
1659 | 1662 | ||
1660 | this.Thumbnails.push(thumbnail) | 1663 | this.Thumbnails.push(savedThumbnail) |
1661 | } | 1664 | } |
1662 | 1665 | ||
1663 | getVideoFilename (videoFile: VideoFileModel) { | 1666 | getVideoFilename (videoFile: VideoFileModel) { |
@@ -1668,10 +1671,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1668 | return this.uuid + '.jpg' | 1671 | return this.uuid + '.jpg' |
1669 | } | 1672 | } |
1670 | 1673 | ||
1671 | getThumbnail () { | 1674 | getMiniature () { |
1672 | if (Array.isArray(this.Thumbnails) === false) return undefined | 1675 | if (Array.isArray(this.Thumbnails) === false) return undefined |
1673 | 1676 | ||
1674 | return this.Thumbnails.find(t => t.type === ThumbnailType.THUMBNAIL) | 1677 | return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE) |
1675 | } | 1678 | } |
1676 | 1679 | ||
1677 | generatePreviewName () { | 1680 | generatePreviewName () { |
@@ -1732,8 +1735,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1732 | return '/videos/embed/' + this.uuid | 1735 | return '/videos/embed/' + this.uuid |
1733 | } | 1736 | } |
1734 | 1737 | ||
1735 | getThumbnailStaticPath () { | 1738 | getMiniatureStaticPath () { |
1736 | const thumbnail = this.getThumbnail() | 1739 | const thumbnail = this.getMiniature() |
1737 | if (!thumbnail) return null | 1740 | if (!thumbnail) return null |
1738 | 1741 | ||
1739 | return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) | 1742 | return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) |