diff options
Diffstat (limited to 'server/models/video/video.ts')
-rw-r--r-- | server/models/video/video.ts | 243 |
1 files changed, 112 insertions, 131 deletions
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 9e26f9bbe..1f940a50d 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -21,12 +21,14 @@ import { | |||
21 | IsUUID, | 21 | IsUUID, |
22 | Min, | 22 | Min, |
23 | Model, | 23 | Model, |
24 | Scopes, | ||
24 | Table, | 25 | Table, |
25 | UpdatedAt | 26 | UpdatedAt |
26 | } from 'sequelize-typescript' | 27 | } from 'sequelize-typescript' |
27 | import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions' | 28 | import { IIncludeOptions } from 'sequelize-typescript/lib/interfaces/IIncludeOptions' |
28 | import { VideoPrivacy, VideoResolution } from '../../../shared' | 29 | import { VideoPrivacy, VideoResolution } from '../../../shared' |
29 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 30 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
31 | import { Video, VideoDetails } from '../../../shared/models/videos' | ||
30 | import { | 32 | import { |
31 | activityPubCollection, | 33 | activityPubCollection, |
32 | createTorrentPromise, | 34 | createTorrentPromise, |
@@ -76,6 +78,79 @@ import { VideoFileModel } from './video-file' | |||
76 | import { VideoShareModel } from './video-share' | 78 | import { VideoShareModel } from './video-share' |
77 | import { VideoTagModel } from './video-tag' | 79 | import { VideoTagModel } from './video-tag' |
78 | 80 | ||
81 | enum ScopeNames { | ||
82 | NOT_IN_BLACKLIST = 'NOT_IN_BLACKLIST', | ||
83 | PUBLIC = 'PUBLIC', | ||
84 | WITH_ACCOUNT = 'WITH_ACCOUNT', | ||
85 | WITH_TAGS = 'WITH_TAGS', | ||
86 | WITH_FILES = 'WITH_FILES', | ||
87 | WITH_SHARES = 'WITH_SHARES', | ||
88 | WITH_RATES = 'WITH_RATES' | ||
89 | } | ||
90 | |||
91 | @Scopes({ | ||
92 | [ScopeNames.NOT_IN_BLACKLIST]: { | ||
93 | where: { | ||
94 | id: { | ||
95 | [Sequelize.Op.notIn]: Sequelize.literal( | ||
96 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' | ||
97 | ) | ||
98 | } | ||
99 | } | ||
100 | }, | ||
101 | [ScopeNames.PUBLIC]: { | ||
102 | where: { | ||
103 | privacy: VideoPrivacy.PUBLIC | ||
104 | } | ||
105 | }, | ||
106 | [ScopeNames.WITH_ACCOUNT]: { | ||
107 | include: [ | ||
108 | { | ||
109 | model: () => VideoChannelModel, | ||
110 | required: true, | ||
111 | include: [ | ||
112 | { | ||
113 | model: () => AccountModel, | ||
114 | required: true, | ||
115 | include: [ | ||
116 | { | ||
117 | model: () => ServerModel, | ||
118 | required: false | ||
119 | } | ||
120 | ] | ||
121 | } | ||
122 | ] | ||
123 | } | ||
124 | ] | ||
125 | }, | ||
126 | [ScopeNames.WITH_TAGS]: { | ||
127 | include: [ () => TagModel ] | ||
128 | }, | ||
129 | [ScopeNames.WITH_FILES]: { | ||
130 | include: [ | ||
131 | { | ||
132 | model: () => VideoFileModel, | ||
133 | required: true | ||
134 | } | ||
135 | ] | ||
136 | }, | ||
137 | [ScopeNames.WITH_SHARES]: { | ||
138 | include: [ | ||
139 | { | ||
140 | model: () => VideoShareModel, | ||
141 | include: [ () => AccountModel ] | ||
142 | } | ||
143 | ] | ||
144 | }, | ||
145 | [ScopeNames.WITH_RATES]: { | ||
146 | include: [ | ||
147 | { | ||
148 | model: () => AccountVideoRateModel, | ||
149 | include: [ () => AccountModel ] | ||
150 | } | ||
151 | ] | ||
152 | } | ||
153 | }) | ||
79 | @Table({ | 154 | @Table({ |
80 | tableName: 'video', | 155 | tableName: 'video', |
81 | indexes: [ | 156 | indexes: [ |
@@ -273,11 +348,7 @@ export class VideoModel extends Model<VideoModel> { | |||
273 | } | 348 | } |
274 | 349 | ||
275 | static list () { | 350 | static list () { |
276 | const query = { | 351 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll() |
277 | include: [ VideoFileModel ] | ||
278 | } | ||
279 | |||
280 | return VideoModel.findAll(query) | ||
281 | } | 352 | } |
282 | 353 | ||
283 | static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) { | 354 | static listAllAndSharedByAccountForOutbox (accountId: number, start: number, count: number) { |
@@ -363,10 +434,9 @@ export class VideoModel extends Model<VideoModel> { | |||
363 | 434 | ||
364 | static listUserVideosForApi (userId: number, start: number, count: number, sort: string) { | 435 | static listUserVideosForApi (userId: number, start: number, count: number, sort: string) { |
365 | const query = { | 436 | const query = { |
366 | distinct: true, | ||
367 | offset: start, | 437 | offset: start, |
368 | limit: count, | 438 | limit: count, |
369 | order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ], | 439 | order: [ getSort(sort) ], |
370 | include: [ | 440 | include: [ |
371 | { | 441 | { |
372 | model: VideoChannelModel, | 442 | model: VideoChannelModel, |
@@ -380,8 +450,7 @@ export class VideoModel extends Model<VideoModel> { | |||
380 | required: true | 450 | required: true |
381 | } | 451 | } |
382 | ] | 452 | ] |
383 | }, | 453 | } |
384 | TagModel | ||
385 | ] | 454 | ] |
386 | } | 455 | } |
387 | 456 | ||
@@ -395,74 +464,35 @@ export class VideoModel extends Model<VideoModel> { | |||
395 | 464 | ||
396 | static listForApi (start: number, count: number, sort: string) { | 465 | static listForApi (start: number, count: number, sort: string) { |
397 | const query = { | 466 | const query = { |
398 | distinct: true, | ||
399 | offset: start, | 467 | offset: start, |
400 | limit: count, | 468 | limit: count, |
401 | order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ], | 469 | order: [ getSort(sort) ] |
402 | include: [ | ||
403 | { | ||
404 | model: VideoChannelModel, | ||
405 | required: true, | ||
406 | include: [ | ||
407 | { | ||
408 | model: AccountModel, | ||
409 | required: true, | ||
410 | include: [ | ||
411 | { | ||
412 | model: ServerModel, | ||
413 | required: false | ||
414 | } | ||
415 | ] | ||
416 | } | ||
417 | ] | ||
418 | }, | ||
419 | TagModel | ||
420 | ], | ||
421 | where: this.createBaseVideosWhere() | ||
422 | } | 470 | } |
423 | 471 | ||
424 | return VideoModel.findAndCountAll(query).then(({ rows, count }) => { | 472 | return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC, ScopeNames.WITH_ACCOUNT ]) |
425 | return { | 473 | .findAndCountAll(query) |
426 | data: rows, | 474 | .then(({ rows, count }) => { |
427 | total: count | 475 | return { |
428 | } | 476 | data: rows, |
429 | }) | 477 | total: count |
478 | } | ||
479 | }) | ||
430 | } | 480 | } |
431 | 481 | ||
432 | static load (id: number) { | 482 | static load (id: number) { |
433 | return VideoModel.findById(id) | 483 | return VideoModel.findById(id) |
434 | } | 484 | } |
435 | 485 | ||
436 | static loadByUUID (uuid: string, t?: Sequelize.Transaction) { | ||
437 | const query: IFindOptions<VideoModel> = { | ||
438 | where: { | ||
439 | uuid | ||
440 | }, | ||
441 | include: [ VideoFileModel ] | ||
442 | } | ||
443 | |||
444 | if (t !== undefined) query.transaction = t | ||
445 | |||
446 | return VideoModel.findOne(query) | ||
447 | } | ||
448 | |||
449 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { | 486 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { |
450 | const query: IFindOptions<VideoModel> = { | 487 | const query: IFindOptions<VideoModel> = { |
451 | where: { | 488 | where: { |
452 | url | 489 | url |
453 | }, | 490 | } |
454 | include: [ | ||
455 | VideoFileModel, | ||
456 | { | ||
457 | model: VideoChannelModel, | ||
458 | include: [ AccountModel ] | ||
459 | } | ||
460 | ] | ||
461 | } | 491 | } |
462 | 492 | ||
463 | if (t !== undefined) query.transaction = t | 493 | if (t !== undefined) query.transaction = t |
464 | 494 | ||
465 | return VideoModel.findOne(query) | 495 | return VideoModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_FILES ]).findOne(query) |
466 | } | 496 | } |
467 | 497 | ||
468 | static loadByUUIDOrURL (uuid: string, url: string, t?: Sequelize.Transaction) { | 498 | static loadByUUIDOrURL (uuid: string, url: string, t?: Sequelize.Transaction) { |
@@ -472,42 +502,22 @@ export class VideoModel extends Model<VideoModel> { | |||
472 | { uuid }, | 502 | { uuid }, |
473 | { url } | 503 | { url } |
474 | ] | 504 | ] |
475 | }, | 505 | } |
476 | include: [ VideoFileModel ] | ||
477 | } | 506 | } |
478 | 507 | ||
479 | if (t !== undefined) query.transaction = t | 508 | if (t !== undefined) query.transaction = t |
480 | 509 | ||
481 | return VideoModel.findOne(query) | 510 | return VideoModel.scope(ScopeNames.WITH_FILES).findOne(query) |
482 | } | 511 | } |
483 | 512 | ||
484 | static loadAndPopulateAccountAndServerAndTags (id: number) { | 513 | static loadAndPopulateAccountAndServerAndTags (id: number) { |
485 | const options = { | 514 | const options = { |
486 | order: [ [ 'Tags', 'name', 'ASC' ] ], | 515 | order: [ [ 'Tags', 'name', 'ASC' ] ] |
487 | include: [ | ||
488 | { | ||
489 | model: VideoChannelModel, | ||
490 | include: [ | ||
491 | { | ||
492 | model: AccountModel, | ||
493 | include: [ { model: ServerModel, required: false } ] | ||
494 | } | ||
495 | ] | ||
496 | }, | ||
497 | { | ||
498 | model: AccountVideoRateModel, | ||
499 | include: [ AccountModel ] | ||
500 | }, | ||
501 | { | ||
502 | model: VideoShareModel, | ||
503 | include: [ AccountModel ] | ||
504 | }, | ||
505 | TagModel, | ||
506 | VideoFileModel | ||
507 | ] | ||
508 | } | 516 | } |
509 | 517 | ||
510 | return VideoModel.findById(id, options) | 518 | return VideoModel |
519 | .scope([ ScopeNames.WITH_RATES, ScopeNames.WITH_SHARES, ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT ]) | ||
520 | .findById(id, options) | ||
511 | } | 521 | } |
512 | 522 | ||
513 | static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) { | 523 | static loadByUUIDAndPopulateAccountAndServerAndTags (uuid: string) { |
@@ -515,31 +525,12 @@ export class VideoModel extends Model<VideoModel> { | |||
515 | order: [ [ 'Tags', 'name', 'ASC' ] ], | 525 | order: [ [ 'Tags', 'name', 'ASC' ] ], |
516 | where: { | 526 | where: { |
517 | uuid | 527 | uuid |
518 | }, | 528 | } |
519 | include: [ | ||
520 | { | ||
521 | model: VideoChannelModel, | ||
522 | include: [ | ||
523 | { | ||
524 | model: AccountModel, | ||
525 | include: [ { model: ServerModel, required: false } ] | ||
526 | } | ||
527 | ] | ||
528 | }, | ||
529 | { | ||
530 | model: AccountVideoRateModel, | ||
531 | include: [ AccountModel ] | ||
532 | }, | ||
533 | { | ||
534 | model: VideoShareModel, | ||
535 | include: [ AccountModel ] | ||
536 | }, | ||
537 | TagModel, | ||
538 | VideoFileModel | ||
539 | ] | ||
540 | } | 529 | } |
541 | 530 | ||
542 | return VideoModel.findOne(options) | 531 | return VideoModel |
532 | .scope([ ScopeNames.WITH_RATES, ScopeNames.WITH_SHARES, ScopeNames.WITH_TAGS, ScopeNames.WITH_FILES, ScopeNames.WITH_ACCOUNT ]) | ||
533 | .findOne(options) | ||
543 | } | 534 | } |
544 | 535 | ||
545 | static searchAndPopulateAccountAndServerAndTags (value: string, start: number, count: number, sort: string) { | 536 | static searchAndPopulateAccountAndServerAndTags (value: string, start: number, count: number, sort: string) { |
@@ -564,11 +555,11 @@ export class VideoModel extends Model<VideoModel> { | |||
564 | } | 555 | } |
565 | 556 | ||
566 | const query: IFindOptions<VideoModel> = { | 557 | const query: IFindOptions<VideoModel> = { |
567 | distinct: true, | 558 | distinct: true, // Because we have tags |
568 | where: this.createBaseVideosWhere(), | ||
569 | offset: start, | 559 | offset: start, |
570 | limit: count, | 560 | limit: count, |
571 | order: [ getSort(sort), [ 'Tags', 'name', 'ASC' ] ] | 561 | order: [ getSort(sort) ], |
562 | where: {} | ||
572 | } | 563 | } |
573 | 564 | ||
574 | // TODO: search on tags too | 565 | // TODO: search on tags too |
@@ -595,23 +586,13 @@ export class VideoModel extends Model<VideoModel> { | |||
595 | videoChannelInclude, tagInclude | 586 | videoChannelInclude, tagInclude |
596 | ] | 587 | ] |
597 | 588 | ||
598 | return VideoModel.findAndCountAll(query).then(({ rows, count }) => { | 589 | return VideoModel.scope([ ScopeNames.NOT_IN_BLACKLIST, ScopeNames.PUBLIC ]) |
599 | return { | 590 | .findAndCountAll(query).then(({ rows, count }) => { |
600 | data: rows, | 591 | return { |
601 | total: count | 592 | data: rows, |
602 | } | 593 | total: count |
603 | }) | 594 | } |
604 | } | 595 | }) |
605 | |||
606 | private static createBaseVideosWhere () { | ||
607 | return { | ||
608 | id: { | ||
609 | [Sequelize.Op.notIn]: VideoModel.sequelize.literal( | ||
610 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' | ||
611 | ) | ||
612 | }, | ||
613 | privacy: VideoPrivacy.PUBLIC | ||
614 | } | ||
615 | } | 596 | } |
616 | 597 | ||
617 | getOriginalFile () { | 598 | getOriginalFile () { |
@@ -733,13 +714,12 @@ export class VideoModel extends Model<VideoModel> { | |||
733 | views: this.views, | 714 | views: this.views, |
734 | likes: this.likes, | 715 | likes: this.likes, |
735 | dislikes: this.dislikes, | 716 | dislikes: this.dislikes, |
736 | tags: map<TagModel, string>(this.Tags, 'name'), | ||
737 | thumbnailPath: this.getThumbnailPath(), | 717 | thumbnailPath: this.getThumbnailPath(), |
738 | previewPath: this.getPreviewPath(), | 718 | previewPath: this.getPreviewPath(), |
739 | embedPath: this.getEmbedPath(), | 719 | embedPath: this.getEmbedPath(), |
740 | createdAt: this.createdAt, | 720 | createdAt: this.createdAt, |
741 | updatedAt: this.updatedAt | 721 | updatedAt: this.updatedAt |
742 | } | 722 | } as Video |
743 | } | 723 | } |
744 | 724 | ||
745 | toFormattedDetailsJSON () { | 725 | toFormattedDetailsJSON () { |
@@ -755,6 +735,7 @@ export class VideoModel extends Model<VideoModel> { | |||
755 | descriptionPath: this.getDescriptionPath(), | 735 | descriptionPath: this.getDescriptionPath(), |
756 | channel: this.VideoChannel.toFormattedJSON(), | 736 | channel: this.VideoChannel.toFormattedJSON(), |
757 | account: this.VideoChannel.Account.toFormattedJSON(), | 737 | account: this.VideoChannel.Account.toFormattedJSON(), |
738 | tags: map<TagModel, string>(this.Tags, 'name'), | ||
758 | files: [] | 739 | files: [] |
759 | } | 740 | } |
760 | 741 | ||
@@ -779,7 +760,7 @@ export class VideoModel extends Model<VideoModel> { | |||
779 | return -1 | 760 | return -1 |
780 | }) | 761 | }) |
781 | 762 | ||
782 | return Object.assign(formattedJson, detailsJson) | 763 | return Object.assign(formattedJson, detailsJson) as VideoDetails |
783 | } | 764 | } |
784 | 765 | ||
785 | toActivityPubObject (): VideoTorrentObject { | 766 | toActivityPubObject (): VideoTorrentObject { |