aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-07-31 15:57:32 +0200
committerChocobozzz <chocobozzz@cpy.re>2019-08-01 09:11:04 +0200
commitbfbd912886eba17b4aa9a40dcef2fddc685d85bf (patch)
tree85e0f22980210a8ccd0888eb5e1790b152074677 /server/models/video
parent85394ba22a07bde1dfccebf3f591a5d6dbe9df56 (diff)
downloadPeerTube-bfbd912886eba17b4aa9a40dcef2fddc685d85bf.tar.gz
PeerTube-bfbd912886eba17b4aa9a40dcef2fddc685d85bf.tar.zst
PeerTube-bfbd912886eba17b4aa9a40dcef2fddc685d85bf.zip
Fix broken playlist api
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/video-blacklist.ts4
-rw-r--r--server/models/video/video-channel.ts15
-rw-r--r--server/models/video/video-format-utils.ts12
-rw-r--r--server/models/video/video-playlist-element.ts103
-rw-r--r--server/models/video/video-playlist.ts8
-rw-r--r--server/models/video/video.ts79
6 files changed, 169 insertions, 52 deletions
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index baef1d6ce..22d949da0 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -1,7 +1,7 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { getSortOnModel, SortType, throwIfNotValid } from '../utils' 2import { getSortOnModel, SortType, throwIfNotValid } from '../utils'
3import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' 3import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
4import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' 4import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' 5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
6import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' 6import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
7import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
@@ -71,7 +71,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
71 required: true, 71 required: true,
72 include: [ 72 include: [
73 { 73 {
74 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }), 74 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),
75 required: true 75 required: true
76 }, 76 },
77 { 77 {
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index b0b261c88..6241a75a3 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -24,7 +24,7 @@ import {
24 isVideoChannelSupportValid 24 isVideoChannelSupportValid
25} from '../../helpers/custom-validators/video-channels' 25} from '../../helpers/custom-validators/video-channels'
26import { sendDeleteActor } from '../../lib/activitypub/send' 26import { sendDeleteActor } from '../../lib/activitypub/send'
27import { AccountModel, ScopeNames as AccountModelScopeNames } from '../account/account' 27import { AccountModel, ScopeNames as AccountModelScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
28import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' 28import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor'
29import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' 29import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
30import { VideoModel } from './video' 30import { VideoModel } from './video'
@@ -58,6 +58,11 @@ type AvailableForListOptions = {
58 actorId: number 58 actorId: number
59} 59}
60 60
61export type SummaryOptions = {
62 withAccount?: boolean // Default: false
63 withAccountBlockerIds?: number[]
64}
65
61@DefaultScope(() => ({ 66@DefaultScope(() => ({
62 include: [ 67 include: [
63 { 68 {
@@ -67,7 +72,7 @@ type AvailableForListOptions = {
67 ] 72 ]
68})) 73}))
69@Scopes(() => ({ 74@Scopes(() => ({
70 [ScopeNames.SUMMARY]: (withAccount = false) => { 75 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
71 const base: FindOptions = { 76 const base: FindOptions = {
72 attributes: [ 'name', 'description', 'id', 'actorId' ], 77 attributes: [ 'name', 'description', 'id', 'actorId' ],
73 include: [ 78 include: [
@@ -90,9 +95,11 @@ type AvailableForListOptions = {
90 ] 95 ]
91 } 96 }
92 97
93 if (withAccount === true) { 98 if (options.withAccount === true) {
94 base.include.push({ 99 base.include.push({
95 model: AccountModel.scope(AccountModelScopeNames.SUMMARY), 100 model: AccountModel.scope({
101 method: [ AccountModelScopeNames.SUMMARY, { withAccountBlockerIds: options.withAccountBlockerIds } as AccountSummaryOptions ]
102 }),
96 required: true 103 required: true
97 }) 104 })
98 } 105 }
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index b947eb16f..284539def 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -26,7 +26,6 @@ export type VideoFormattingJSONOptions = {
26 waitTranscoding?: boolean, 26 waitTranscoding?: boolean,
27 scheduledUpdate?: boolean, 27 scheduledUpdate?: boolean,
28 blacklistInfo?: boolean 28 blacklistInfo?: boolean
29 playlistInfo?: boolean
30 } 29 }
31} 30}
32function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { 31function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video {
@@ -98,17 +97,6 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
98 videoObject.blacklisted = !!video.VideoBlacklist 97 videoObject.blacklisted = !!video.VideoBlacklist
99 videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null 98 videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null
100 } 99 }
101
102 if (options.additionalAttributes.playlistInfo === true) {
103 // We filtered on a specific videoId/videoPlaylistId, that is unique
104 const playlistElement = video.VideoPlaylistElements[0]
105
106 videoObject.playlistElement = {
107 position: playlistElement.position,
108 startTimestamp: playlistElement.startTimestamp,
109 stopTimestamp: playlistElement.stopTimestamp
110 }
111 }
112 } 100 }
113 101
114 return videoObject 102 return videoObject
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index eeb3d6bbd..bed6f8eaf 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -13,14 +13,18 @@ import {
13 Table, 13 Table,
14 UpdatedAt 14 UpdatedAt
15} from 'sequelize-typescript' 15} from 'sequelize-typescript'
16import { VideoModel } from './video' 16import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video'
17import { VideoPlaylistModel } from './video-playlist' 17import { VideoPlaylistModel } from './video-playlist'
18import { getSort, throwIfNotValid } from '../utils' 18import { getSort, throwIfNotValid } from '../utils'
19import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 19import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
20import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 20import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
21import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' 21import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
22import * as validator from 'validator' 22import * as validator from 'validator'
23import { AggregateOptions, Op, Sequelize, Transaction } from 'sequelize' 23import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize'
24import { UserModel } from '../account/user'
25import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model'
26import { AccountModel } from '../account/account'
27import { VideoPrivacy } from '../../../shared/models/videos'
24 28
25@Table({ 29@Table({
26 tableName: 'videoPlaylistElement', 30 tableName: 'videoPlaylistElement',
@@ -90,9 +94,9 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
90 94
91 @BelongsTo(() => VideoModel, { 95 @BelongsTo(() => VideoModel, {
92 foreignKey: { 96 foreignKey: {
93 allowNull: false 97 allowNull: true
94 }, 98 },
95 onDelete: 'CASCADE' 99 onDelete: 'set null'
96 }) 100 })
97 Video: VideoModel 101 Video: VideoModel
98 102
@@ -107,6 +111,57 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
107 return VideoPlaylistElementModel.destroy(query) 111 return VideoPlaylistElementModel.destroy(query)
108 } 112 }
109 113
114 static listForApi (options: {
115 start: number,
116 count: number,
117 videoPlaylistId: number,
118 serverAccount: AccountModel,
119 user?: UserModel
120 }) {
121 const accountIds = [ options.serverAccount.id ]
122 const videoScope: (ScopeOptions | string)[] = [
123 VideoScopeNames.WITH_BLACKLISTED
124 ]
125
126 if (options.user) {
127 accountIds.push(options.user.Account.id)
128 videoScope.push({ method: [ VideoScopeNames.WITH_USER_HISTORY, options.user.id ] })
129 }
130
131 const forApiOptions: ForAPIOptions = { withAccountBlockerIds: accountIds }
132 videoScope.push({
133 method: [
134 VideoScopeNames.FOR_API, forApiOptions
135 ]
136 })
137
138 const findQuery = {
139 offset: options.start,
140 limit: options.count,
141 order: getSort('position'),
142 where: {
143 videoPlaylistId: options.videoPlaylistId
144 },
145 include: [
146 {
147 model: VideoModel.scope(videoScope),
148 required: false
149 }
150 ]
151 }
152
153 const countQuery = {
154 where: {
155 videoPlaylistId: options.videoPlaylistId
156 }
157 }
158
159 return Promise.all([
160 VideoPlaylistElementModel.count(countQuery),
161 VideoPlaylistElementModel.findAll(findQuery)
162 ]).then(([ total, data ]) => ({ total, data }))
163 }
164
110 static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { 165 static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) {
111 const query = { 166 const query = {
112 where: { 167 where: {
@@ -118,6 +173,10 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
118 return VideoPlaylistElementModel.findOne(query) 173 return VideoPlaylistElementModel.findOne(query)
119 } 174 }
120 175
176 static loadById (playlistElementId: number) {
177 return VideoPlaylistElementModel.findByPk(playlistElementId)
178 }
179
121 static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { 180 static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) {
122 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } 181 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId }
123 const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } 182 const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId }
@@ -213,6 +272,42 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
213 return VideoPlaylistElementModel.increment({ position: by }, query) 272 return VideoPlaylistElementModel.increment({ position: by }, query)
214 } 273 }
215 274
275 getType (displayNSFW?: boolean, accountId?: number) {
276 const video = this.Video
277
278 if (!video) return VideoPlaylistElementType.DELETED
279
280 // Owned video, don't filter it
281 if (accountId && video.VideoChannel.Account.id === accountId) return VideoPlaylistElementType.REGULAR
282
283 if (video.privacy === VideoPrivacy.PRIVATE) return VideoPlaylistElementType.PRIVATE
284
285 if (video.isBlacklisted() || video.isBlocked()) return VideoPlaylistElementType.UNAVAILABLE
286 if (video.nsfw === true && displayNSFW === false) return VideoPlaylistElementType.UNAVAILABLE
287
288 return VideoPlaylistElementType.REGULAR
289 }
290
291 getVideoElement (displayNSFW?: boolean, accountId?: number) {
292 if (!this.Video) return null
293 if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null
294
295 return this.Video.toFormattedJSON()
296 }
297
298 toFormattedJSON (options: { displayNSFW?: boolean, accountId?: number } = {}): VideoPlaylistElement {
299 return {
300 id: this.id,
301 position: this.position,
302 startTimestamp: this.startTimestamp,
303 stopTimestamp: this.stopTimestamp,
304
305 type: this.getType(options.displayNSFW, options.accountId),
306
307 video: this.getVideoElement(options.displayNSFW, options.accountId)
308 }
309 }
310
216 toActivityPubObject (): PlaylistElementObject { 311 toActivityPubObject (): PlaylistElementObject {
217 const base: PlaylistElementObject = { 312 const base: PlaylistElementObject = {
218 id: this.url, 313 id: this.url,
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index 63b4a0715..61ff78bd2 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -33,7 +33,7 @@ import {
33 WEBSERVER 33 WEBSERVER
34} from '../../initializers/constants' 34} from '../../initializers/constants'
35import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model' 35import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
36import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' 36import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions } from '../account/account'
37import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' 37import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
38import { join } from 'path' 38import { join } from 'path'
39import { VideoPlaylistElementModel } from './video-playlist-element' 39import { VideoPlaylistElementModel } from './video-playlist-element'
@@ -115,7 +115,7 @@ type AvailableForListOptions = {
115 [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { 115 [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => {
116 // Only list local playlists OR playlists that are on an instance followed by actorId 116 // Only list local playlists OR playlists that are on an instance followed by actorId
117 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) 117 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId)
118 const actorWhere = { 118 const whereActor = {
119 [ Op.or ]: [ 119 [ Op.or ]: [
120 { 120 {
121 serverId: null 121 serverId: null
@@ -159,7 +159,7 @@ type AvailableForListOptions = {
159 } 159 }
160 160
161 const accountScope = { 161 const accountScope = {
162 method: [ AccountScopeNames.SUMMARY, actorWhere ] 162 method: [ AccountScopeNames.SUMMARY, { whereActor } as SummaryOptions ]
163 } 163 }
164 164
165 return { 165 return {
@@ -341,7 +341,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
341 }, 341 },
342 include: [ 342 include: [
343 { 343 {
344 attributes: [ 'videoId', 'startTimestamp', 'stopTimestamp' ], 344 attributes: [ 'id', 'videoId', 'startTimestamp', 'stopTimestamp' ],
345 model: VideoPlaylistElementModel.unscoped(), 345 model: VideoPlaylistElementModel.unscoped(),
346 where: { 346 where: {
347 videoId: { 347 videoId: {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index c7f2658ed..05d625fc1 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -91,7 +91,7 @@ import {
91} from '../utils' 91} from '../utils'
92import { TagModel } from './tag' 92import { TagModel } from './tag'
93import { VideoAbuseModel } from './video-abuse' 93import { VideoAbuseModel } from './video-abuse'
94import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' 94import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
95import { VideoCommentModel } from './video-comment' 95import { VideoCommentModel } from './video-comment'
96import { VideoFileModel } from './video-file' 96import { VideoFileModel } from './video-file'
97import { VideoShareModel } from './video-share' 97import { VideoShareModel } from './video-share'
@@ -190,26 +190,29 @@ export enum ScopeNames {
190 WITH_FILES = 'WITH_FILES', 190 WITH_FILES = 'WITH_FILES',
191 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', 191 WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE',
192 WITH_BLACKLISTED = 'WITH_BLACKLISTED', 192 WITH_BLACKLISTED = 'WITH_BLACKLISTED',
193 WITH_BLOCKLIST = 'WITH_BLOCKLIST',
193 WITH_USER_HISTORY = 'WITH_USER_HISTORY', 194 WITH_USER_HISTORY = 'WITH_USER_HISTORY',
194 WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', 195 WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS',
195 WITH_USER_ID = 'WITH_USER_ID', 196 WITH_USER_ID = 'WITH_USER_ID',
196 WITH_THUMBNAILS = 'WITH_THUMBNAILS' 197 WITH_THUMBNAILS = 'WITH_THUMBNAILS'
197} 198}
198 199
199type ForAPIOptions = { 200export type ForAPIOptions = {
200 ids: number[] 201 ids?: number[]
201 202
202 videoPlaylistId?: number 203 videoPlaylistId?: number
203 204
204 withFiles?: boolean 205 withFiles?: boolean
206
207 withAccountBlockerIds?: number[]
205} 208}
206 209
207type AvailableForListIDsOptions = { 210export type AvailableForListIDsOptions = {
208 serverAccountId: number 211 serverAccountId: number
209 followerActorId: number 212 followerActorId: number
210 includeLocalVideos: boolean 213 includeLocalVideos: boolean
211 214
212 withoutId?: boolean 215 attributesType?: 'none' | 'id' | 'all'
213 216
214 filter?: VideoFilter 217 filter?: VideoFilter
215 categoryOneOf?: number[] 218 categoryOneOf?: number[]
@@ -236,14 +239,16 @@ type AvailableForListIDsOptions = {
236@Scopes(() => ({ 239@Scopes(() => ({
237 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { 240 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
238 const query: FindOptions = { 241 const query: FindOptions = {
239 where: {
240 id: {
241 [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken
242 }
243 },
244 include: [ 242 include: [
245 { 243 {
246 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }), 244 model: VideoChannelModel.scope({
245 method: [
246 VideoChannelScopeNames.SUMMARY, {
247 withAccount: true,
248 withAccountBlockerIds: options.withAccountBlockerIds
249 } as SummaryOptions
250 ]
251 }),
247 required: true 252 required: true
248 }, 253 },
249 { 254 {
@@ -254,6 +259,14 @@ type AvailableForListIDsOptions = {
254 ] 259 ]
255 } 260 }
256 261
262 if (options.ids) {
263 query.where = {
264 id: {
265 [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken
266 }
267 }
268 }
269
257 if (options.withFiles === true) { 270 if (options.withFiles === true) {
258 query.include.push({ 271 query.include.push({
259 model: VideoFileModel.unscoped(), 272 model: VideoFileModel.unscoped(),
@@ -278,10 +291,14 @@ type AvailableForListIDsOptions = {
278 291
279 const query: FindOptions = { 292 const query: FindOptions = {
280 raw: true, 293 raw: true,
281 attributes: options.withoutId === true ? [] : [ 'id' ],
282 include: [] 294 include: []
283 } 295 }
284 296
297 const attributesType = options.attributesType || 'id'
298
299 if (attributesType === 'id') query.attributes = [ 'id' ]
300 else if (attributesType === 'none') query.attributes = [ ]
301
285 whereAnd.push({ 302 whereAnd.push({
286 id: { 303 id: {
287 [ Op.notIn ]: Sequelize.literal( 304 [ Op.notIn ]: Sequelize.literal(
@@ -290,17 +307,19 @@ type AvailableForListIDsOptions = {
290 } 307 }
291 }) 308 })
292 309
293 whereAnd.push({ 310 if (options.serverAccountId) {
294 channelId: { 311 whereAnd.push({
295 [ Op.notIn ]: Sequelize.literal( 312 channelId: {
296 '(' + 313 [ Op.notIn ]: Sequelize.literal(
297 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + 314 '(' +
298 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + 315 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' +
299 ')' + 316 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) +
300 ')' 317 ')' +
301 ) 318 ')'
302 } 319 )
303 }) 320 }
321 })
322 }
304 323
305 // Only list public/published videos 324 // Only list public/published videos
306 if (!options.filter || options.filter !== 'all-local') { 325 if (!options.filter || options.filter !== 'all-local') {
@@ -528,6 +547,9 @@ type AvailableForListIDsOptions = {
528 547
529 return query 548 return query
530 }, 549 },
550 [ScopeNames.WITH_BLOCKLIST]: {
551
552 },
531 [ ScopeNames.WITH_THUMBNAILS ]: { 553 [ ScopeNames.WITH_THUMBNAILS ]: {
532 include: [ 554 include: [
533 { 555 {
@@ -845,9 +867,9 @@ export class VideoModel extends Model<VideoModel> {
845 @HasMany(() => VideoPlaylistElementModel, { 867 @HasMany(() => VideoPlaylistElementModel, {
846 foreignKey: { 868 foreignKey: {
847 name: 'videoId', 869 name: 'videoId',
848 allowNull: false 870 allowNull: true
849 }, 871 },
850 onDelete: 'cascade' 872 onDelete: 'set null'
851 }) 873 })
852 VideoPlaylistElements: VideoPlaylistElementModel[] 874 VideoPlaylistElements: VideoPlaylistElementModel[]
853 875
@@ -1586,7 +1608,7 @@ export class VideoModel extends Model<VideoModel> {
1586 serverAccountId: serverActor.Account.id, 1608 serverAccountId: serverActor.Account.id,
1587 followerActorId, 1609 followerActorId,
1588 includeLocalVideos: true, 1610 includeLocalVideos: true,
1589 withoutId: true // Don't break aggregation 1611 attributesType: 'none' // Don't break aggregation
1590 } 1612 }
1591 1613
1592 const query: FindOptions = { 1614 const query: FindOptions = {
@@ -1719,6 +1741,11 @@ export class VideoModel extends Model<VideoModel> {
1719 return !!this.VideoBlacklist 1741 return !!this.VideoBlacklist
1720 } 1742 }
1721 1743
1744 isBlocked () {
1745 return (this.VideoChannel.Account.Actor.Server && this.VideoChannel.Account.Actor.Server.isBlocked()) ||
1746 this.VideoChannel.Account.isBlocked()
1747 }
1748
1722 getOriginalFile () { 1749 getOriginalFile () {
1723 if (Array.isArray(this.VideoFiles) === false) return undefined 1750 if (Array.isArray(this.VideoFiles) === false) return undefined
1724 1751