aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/video-channel.ts6
-rw-r--r--server/models/video/video-playlist-element.ts10
-rw-r--r--server/models/video/video-playlist.ts92
-rw-r--r--server/models/video/video-share.ts2
-rw-r--r--server/models/video/video.ts38
5 files changed, 109 insertions, 39 deletions
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index c077fb518..ca06048d1 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -67,9 +67,9 @@ type AvailableForListOptions = {
67 ] 67 ]
68}) 68})
69@Scopes({ 69@Scopes({
70 [ScopeNames.SUMMARY]: (required: boolean, withAccount: boolean) => { 70 [ScopeNames.SUMMARY]: (withAccount = false) => {
71 const base: IFindOptions<VideoChannelModel> = { 71 const base: IFindOptions<VideoChannelModel> = {
72 attributes: [ 'name', 'description', 'id' ], 72 attributes: [ 'name', 'description', 'id', 'actorId' ],
73 include: [ 73 include: [
74 { 74 {
75 attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], 75 attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
@@ -225,7 +225,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
225 foreignKey: { 225 foreignKey: {
226 allowNull: true 226 allowNull: true
227 }, 227 },
228 onDelete: 'cascade', 228 onDelete: 'CASCADE',
229 hooks: true 229 hooks: true
230 }) 230 })
231 VideoPlaylists: VideoPlaylistModel[] 231 VideoPlaylists: VideoPlaylistModel[]
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index 5530e0492..a2bd225a1 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -20,6 +20,7 @@ import { getSort, throwIfNotValid } from '../utils'
20import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 20import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
21import { CONSTRAINTS_FIELDS } from '../../initializers' 21import { CONSTRAINTS_FIELDS } from '../../initializers'
22import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' 22import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
23import * as validator from 'validator'
23 24
24@Table({ 25@Table({
25 tableName: 'videoPlaylistElement', 26 tableName: 'videoPlaylistElement',
@@ -35,10 +36,6 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object
35 unique: true 36 unique: true
36 }, 37 },
37 { 38 {
38 fields: [ 'videoPlaylistId', 'position' ],
39 unique: true
40 },
41 {
42 fields: [ 'url' ], 39 fields: [ 'url' ],
43 unique: true 40 unique: true
44 } 41 }
@@ -143,7 +140,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
143 return VideoPlaylistElementModel.findOne(query) 140 return VideoPlaylistElementModel.findOne(query)
144 } 141 }
145 142
146 static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number) { 143 static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number, t?: Sequelize.Transaction) {
147 const query = { 144 const query = {
148 attributes: [ 'url' ], 145 attributes: [ 'url' ],
149 offset: start, 146 offset: start,
@@ -151,7 +148,8 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
151 order: getSort('position'), 148 order: getSort('position'),
152 where: { 149 where: {
153 videoPlaylistId 150 videoPlaylistId
154 } 151 },
152 transaction: t
155 } 153 }
156 154
157 return VideoPlaylistElementModel 155 return VideoPlaylistElementModel
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index 397887ebf..ce49f77ec 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -24,7 +24,14 @@ import {
24 isVideoPlaylistPrivacyValid 24 isVideoPlaylistPrivacyValid
25} from '../../helpers/custom-validators/video-playlists' 25} from '../../helpers/custom-validators/video-playlists'
26import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 26import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
27import { CONFIG, CONSTRAINTS_FIELDS, STATIC_PATHS, THUMBNAILS_SIZE, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers' 27import {
28 CONFIG,
29 CONSTRAINTS_FIELDS,
30 STATIC_PATHS,
31 THUMBNAILS_SIZE,
32 VIDEO_PLAYLIST_PRIVACIES,
33 VIDEO_PLAYLIST_TYPES
34} from '../../initializers'
28import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model' 35import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model'
29import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' 36import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
30import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' 37import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel'
@@ -34,22 +41,25 @@ import { PlaylistObject } from '../../../shared/models/activitypub/objects/playl
34import { activityPubCollectionPagination } from '../../helpers/activitypub' 41import { activityPubCollectionPagination } from '../../helpers/activitypub'
35import { remove } from 'fs-extra' 42import { remove } from 'fs-extra'
36import { logger } from '../../helpers/logger' 43import { logger } from '../../helpers/logger'
44import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
37 45
38enum ScopeNames { 46enum ScopeNames {
39 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', 47 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
40 WITH_VIDEOS_LENGTH = 'WITH_VIDEOS_LENGTH', 48 WITH_VIDEOS_LENGTH = 'WITH_VIDEOS_LENGTH',
41 WITH_ACCOUNT_AND_CHANNEL = 'WITH_ACCOUNT_AND_CHANNEL' 49 WITH_ACCOUNT_AND_CHANNEL_SUMMARY = 'WITH_ACCOUNT_AND_CHANNEL_SUMMARY',
50 WITH_ACCOUNT = 'WITH_ACCOUNT'
42} 51}
43 52
44type AvailableForListOptions = { 53type AvailableForListOptions = {
45 followerActorId: number 54 followerActorId: number
46 accountId?: number, 55 type?: VideoPlaylistType
56 accountId?: number
47 videoChannelId?: number 57 videoChannelId?: number
48 privateAndUnlisted?: boolean 58 privateAndUnlisted?: boolean
49} 59}
50 60
51@Scopes({ 61@Scopes({
52 [ScopeNames.WITH_VIDEOS_LENGTH]: { 62 [ ScopeNames.WITH_VIDEOS_LENGTH ]: {
53 attributes: { 63 attributes: {
54 include: [ 64 include: [
55 [ 65 [
@@ -59,7 +69,15 @@ type AvailableForListOptions = {
59 ] 69 ]
60 } 70 }
61 }, 71 },
62 [ScopeNames.WITH_ACCOUNT_AND_CHANNEL]: { 72 [ ScopeNames.WITH_ACCOUNT ]: {
73 include: [
74 {
75 model: () => AccountModel,
76 required: true
77 }
78 ]
79 },
80 [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: {
63 include: [ 81 include: [
64 { 82 {
65 model: () => AccountModel.scope(AccountScopeNames.SUMMARY), 83 model: () => AccountModel.scope(AccountScopeNames.SUMMARY),
@@ -71,7 +89,7 @@ type AvailableForListOptions = {
71 } 89 }
72 ] 90 ]
73 }, 91 },
74 [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { 92 [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => {
75 // Only list local playlists OR playlists that are on an instance followed by actorId 93 // Only list local playlists OR playlists that are on an instance followed by actorId
76 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) 94 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId)
77 const actorWhere = { 95 const actorWhere = {
@@ -107,6 +125,12 @@ type AvailableForListOptions = {
107 }) 125 })
108 } 126 }
109 127
128 if (options.type) {
129 whereAnd.push({
130 type: options.type
131 })
132 }
133
110 const where = { 134 const where = {
111 [Sequelize.Op.and]: whereAnd 135 [Sequelize.Op.and]: whereAnd
112 } 136 }
@@ -179,6 +203,11 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
179 @Column(DataType.UUID) 203 @Column(DataType.UUID)
180 uuid: string 204 uuid: string
181 205
206 @AllowNull(false)
207 @Default(VideoPlaylistType.REGULAR)
208 @Column
209 type: VideoPlaylistType
210
182 @ForeignKey(() => AccountModel) 211 @ForeignKey(() => AccountModel)
183 @Column 212 @Column
184 ownerAccountId: number 213 ownerAccountId: number
@@ -208,13 +237,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
208 name: 'videoPlaylistId', 237 name: 'videoPlaylistId',
209 allowNull: false 238 allowNull: false
210 }, 239 },
211 onDelete: 'cascade' 240 onDelete: 'CASCADE'
212 }) 241 })
213 VideoPlaylistElements: VideoPlaylistElementModel[] 242 VideoPlaylistElements: VideoPlaylistElementModel[]
214 243
215 // Calculated field
216 videosLength?: number
217
218 @BeforeDestroy 244 @BeforeDestroy
219 static async removeFiles (instance: VideoPlaylistModel) { 245 static async removeFiles (instance: VideoPlaylistModel) {
220 logger.info('Removing files of video playlist %s.', instance.url) 246 logger.info('Removing files of video playlist %s.', instance.url)
@@ -227,6 +253,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
227 start: number, 253 start: number,
228 count: number, 254 count: number,
229 sort: string, 255 sort: string,
256 type?: VideoPlaylistType,
230 accountId?: number, 257 accountId?: number,
231 videoChannelId?: number, 258 videoChannelId?: number,
232 privateAndUnlisted?: boolean 259 privateAndUnlisted?: boolean
@@ -242,6 +269,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
242 method: [ 269 method: [
243 ScopeNames.AVAILABLE_FOR_LIST, 270 ScopeNames.AVAILABLE_FOR_LIST,
244 { 271 {
272 type: options.type,
245 followerActorId: options.followerActorId, 273 followerActorId: options.followerActorId,
246 accountId: options.accountId, 274 accountId: options.accountId,
247 videoChannelId: options.videoChannelId, 275 videoChannelId: options.videoChannelId,
@@ -289,7 +317,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
289 .then(e => !!e) 317 .then(e => !!e)
290 } 318 }
291 319
292 static load (id: number | string, transaction: Sequelize.Transaction) { 320 static loadWithAccountAndChannel (id: number | string, transaction: Sequelize.Transaction) {
293 const where = buildWhereIdOrUUID(id) 321 const where = buildWhereIdOrUUID(id)
294 322
295 const query = { 323 const query = {
@@ -298,14 +326,39 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
298 } 326 }
299 327
300 return VideoPlaylistModel 328 return VideoPlaylistModel
301 .scope([ ScopeNames.WITH_ACCOUNT_AND_CHANNEL, ScopeNames.WITH_VIDEOS_LENGTH ]) 329 .scope([ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY, ScopeNames.WITH_VIDEOS_LENGTH ])
302 .findOne(query) 330 .findOne(query)
303 } 331 }
304 332
333 static loadByUrlAndPopulateAccount (url: string) {
334 const query = {
335 where: {
336 url
337 }
338 }
339
340 return VideoPlaylistModel.scope(ScopeNames.WITH_ACCOUNT).findOne(query)
341 }
342
305 static getPrivacyLabel (privacy: VideoPlaylistPrivacy) { 343 static getPrivacyLabel (privacy: VideoPlaylistPrivacy) {
306 return VIDEO_PLAYLIST_PRIVACIES[privacy] || 'Unknown' 344 return VIDEO_PLAYLIST_PRIVACIES[privacy] || 'Unknown'
307 } 345 }
308 346
347 static getTypeLabel (type: VideoPlaylistType) {
348 return VIDEO_PLAYLIST_TYPES[type] || 'Unknown'
349 }
350
351 static resetPlaylistsOfChannel (videoChannelId: number, transaction: Sequelize.Transaction) {
352 const query = {
353 where: {
354 videoChannelId
355 },
356 transaction
357 }
358
359 return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
360 }
361
309 getThumbnailName () { 362 getThumbnailName () {
310 const extension = '.jpg' 363 const extension = '.jpg'
311 364
@@ -345,7 +398,12 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
345 398
346 thumbnailPath: this.getThumbnailStaticPath(), 399 thumbnailPath: this.getThumbnailStaticPath(),
347 400
348 videosLength: this.videosLength, 401 type: {
402 id: this.type,
403 label: VideoPlaylistModel.getTypeLabel(this.type)
404 },
405
406 videosLength: this.get('videosLength'),
349 407
350 createdAt: this.createdAt, 408 createdAt: this.createdAt,
351 updatedAt: this.updatedAt, 409 updatedAt: this.updatedAt,
@@ -355,18 +413,20 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
355 } 413 }
356 } 414 }
357 415
358 toActivityPubObject (): Promise<PlaylistObject> { 416 toActivityPubObject (page: number, t: Sequelize.Transaction): Promise<PlaylistObject> {
359 const handler = (start: number, count: number) => { 417 const handler = (start: number, count: number) => {
360 return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count) 418 return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t)
361 } 419 }
362 420
363 return activityPubCollectionPagination(this.url, handler, null) 421 return activityPubCollectionPagination(this.url, handler, page)
364 .then(o => { 422 .then(o => {
365 return Object.assign(o, { 423 return Object.assign(o, {
366 type: 'Playlist' as 'Playlist', 424 type: 'Playlist' as 'Playlist',
367 name: this.name, 425 name: this.name,
368 content: this.description, 426 content: this.description,
369 uuid: this.uuid, 427 uuid: this.uuid,
428 published: this.createdAt.toISOString(),
429 updated: this.updatedAt.toISOString(),
370 attributedTo: this.VideoChannel ? [ this.VideoChannel.Actor.url ] : [], 430 attributedTo: this.VideoChannel ? [ this.VideoChannel.Actor.url ] : [],
371 icon: { 431 icon: {
372 type: 'Image' as 'Image', 432 type: 'Image' as 'Image',
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index c87f71277..7df0ed18d 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -125,7 +125,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
125 .then(res => res.map(r => r.Actor)) 125 .then(res => res.map(r => r.Actor))
126 } 126 }
127 127
128 static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> { 128 static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> {
129 const query = { 129 const query = {
130 attributes: [], 130 attributes: [],
131 include: [ 131 include: [
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 7a102b058..a563f78ef 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -225,7 +225,7 @@ type AvailableForListIDsOptions = {
225 }, 225 },
226 include: [ 226 include: [
227 { 227 {
228 model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY) 228 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] })
229 } 229 }
230 ] 230 ]
231 } 231 }
@@ -1535,18 +1535,7 @@ export class VideoModel extends Model<VideoModel> {
1535 1535
1536 if (ids.length === 0) return { data: [], total: count } 1536 if (ids.length === 0) return { data: [], total: count }
1537 1537
1538 // FIXME: typings 1538 const secondQuery: IFindOptions<VideoModel> = {
1539 const apiScope: any[] = [
1540 {
1541 method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ]
1542 }
1543 ]
1544
1545 if (options.user) {
1546 apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
1547 }
1548
1549 const secondQuery = {
1550 offset: 0, 1539 offset: 0,
1551 limit: query.limit, 1540 limit: query.limit,
1552 attributes: query.attributes, 1541 attributes: query.attributes,
@@ -1556,6 +1545,29 @@ export class VideoModel extends Model<VideoModel> {
1556 ) 1545 )
1557 ] 1546 ]
1558 } 1547 }
1548
1549 // FIXME: typing
1550 const apiScope: any[] = []
1551
1552 if (options.user) {
1553 apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] })
1554
1555 // Even if the relation is n:m, we know that a user only have 0..1 video history
1556 // So we won't have multiple rows for the same video
1557 // A subquery adds some bugs in our query so disable it
1558 secondQuery.subQuery = false
1559 }
1560
1561 apiScope.push({
1562 method: [
1563 ScopeNames.FOR_API, {
1564 ids, withFiles:
1565 options.withFiles,
1566 videoPlaylistId: options.videoPlaylistId
1567 } as ForAPIOptions
1568 ]
1569 })
1570
1559 const rows = await VideoModel.scope(apiScope).findAll(secondQuery) 1571 const rows = await VideoModel.scope(apiScope).findAll(secondQuery)
1560 1572
1561 return { 1573 return {