diff options
Diffstat (limited to 'server/models/video')
-rw-r--r-- | server/models/video/tag.ts | 2 | ||||
-rw-r--r-- | server/models/video/thumbnail.ts | 4 | ||||
-rw-r--r-- | server/models/video/video-caption.ts | 13 | ||||
-rw-r--r-- | server/models/video/video-change-ownership.ts | 14 | ||||
-rw-r--r-- | server/models/video/video-channel.ts | 16 | ||||
-rw-r--r-- | server/models/video/video-comment.ts | 33 | ||||
-rw-r--r-- | server/models/video/video-file.ts | 27 | ||||
-rw-r--r-- | server/models/video/video-format-utils.ts | 10 | ||||
-rw-r--r-- | server/models/video/video-import.ts | 8 | ||||
-rw-r--r-- | server/models/video/video-playlist.ts | 47 | ||||
-rw-r--r-- | server/models/video/video-share.ts | 10 | ||||
-rw-r--r-- | server/models/video/video-streaming-playlist.ts | 6 | ||||
-rw-r--r-- | server/models/video/video.ts | 179 |
13 files changed, 181 insertions, 188 deletions
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 048b47613..0fc3cfd4c 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -75,7 +75,7 @@ export class TagModel extends Model<TagModel> { | |||
75 | type: QueryTypes.SELECT as QueryTypes.SELECT | 75 | type: QueryTypes.SELECT as QueryTypes.SELECT |
76 | } | 76 | } |
77 | 77 | ||
78 | return TagModel.sequelize.query<{ name }>(query, options) | 78 | return TagModel.sequelize.query<{ name: string }>(query, options) |
79 | .then(data => data.map(d => d.name)) | 79 | .then(data => data.map(d => d.name)) |
80 | } | 80 | } |
81 | } | 81 | } |
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index baa5533ac..ec945893f 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -75,8 +75,8 @@ export class ThumbnailModel extends Model<ThumbnailModel> { | |||
75 | updatedAt: Date | 75 | updatedAt: Date |
76 | 76 | ||
77 | private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { | 77 | private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { |
78 | [ThumbnailType.THUMBNAIL]: { | 78 | [ThumbnailType.MINIATURE]: { |
79 | label: 'thumbnail', | 79 | label: 'miniature', |
80 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, | 80 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, |
81 | staticPath: STATIC_PATHS.THUMBNAILS | 81 | staticPath: STATIC_PATHS.THUMBNAILS |
82 | }, | 82 | }, |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index 45c60e26b..76243bf48 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -12,7 +12,7 @@ import { | |||
12 | Table, | 12 | Table, |
13 | UpdatedAt | 13 | UpdatedAt |
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { throwIfNotValid } from '../utils' | 15 | import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' |
16 | import { VideoModel } from './video' | 16 | import { VideoModel } from './video' |
17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
@@ -26,17 +26,17 @@ export enum ScopeNames { | |||
26 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 26 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
27 | } | 27 | } |
28 | 28 | ||
29 | @Scopes({ | 29 | @Scopes(() => ({ |
30 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { | 30 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { |
31 | include: [ | 31 | include: [ |
32 | { | 32 | { |
33 | attributes: [ 'uuid', 'remote' ], | 33 | attributes: [ 'uuid', 'remote' ], |
34 | model: () => VideoModel.unscoped(), | 34 | model: VideoModel.unscoped(), |
35 | required: true | 35 | required: true |
36 | } | 36 | } |
37 | ] | 37 | ] |
38 | } | 38 | } |
39 | }) | 39 | })) |
40 | 40 | ||
41 | @Table({ | 41 | @Table({ |
42 | tableName: 'videoCaption', | 42 | tableName: 'videoCaption', |
@@ -97,12 +97,9 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
97 | const videoInclude = { | 97 | const videoInclude = { |
98 | model: VideoModel.unscoped(), | 98 | model: VideoModel.unscoped(), |
99 | attributes: [ 'id', 'remote', 'uuid' ], | 99 | attributes: [ 'id', 'remote', 'uuid' ], |
100 | where: { } | 100 | where: buildWhereIdOrUUID(videoId) |
101 | } | 101 | } |
102 | 102 | ||
103 | if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId | ||
104 | else videoInclude.where['id'] = videoId | ||
105 | |||
106 | const query = { | 103 | const query = { |
107 | where: { | 104 | where: { |
108 | language | 105 | language |
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index a4f4d53f1..171d4574d 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts | |||
@@ -23,29 +23,29 @@ enum ScopeNames { | |||
23 | } | 23 | } |
24 | ] | 24 | ] |
25 | }) | 25 | }) |
26 | @Scopes({ | 26 | @Scopes(() => ({ |
27 | [ScopeNames.FULL]: { | 27 | [ScopeNames.FULL]: { |
28 | include: [ | 28 | include: [ |
29 | { | 29 | { |
30 | model: () => AccountModel, | 30 | model: AccountModel, |
31 | as: 'Initiator', | 31 | as: 'Initiator', |
32 | required: true | 32 | required: true |
33 | }, | 33 | }, |
34 | { | 34 | { |
35 | model: () => AccountModel, | 35 | model: AccountModel, |
36 | as: 'NextOwner', | 36 | as: 'NextOwner', |
37 | required: true | 37 | required: true |
38 | }, | 38 | }, |
39 | { | 39 | { |
40 | model: () => VideoModel, | 40 | model: VideoModel, |
41 | required: true, | 41 | required: true, |
42 | include: [ | 42 | include: [ |
43 | { model: () => VideoFileModel } | 43 | { model: VideoFileModel } |
44 | ] | 44 | ] |
45 | } | 45 | } |
46 | ] as any // FIXME: sequelize typings | 46 | ] |
47 | } | 47 | } |
48 | }) | 48 | })) |
49 | export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> { | 49 | export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> { |
50 | @CreatedAt | 50 | @CreatedAt |
51 | createdAt: Date | 51 | createdAt: Date |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 901006dea..fb70e6625 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -58,15 +58,15 @@ type AvailableForListOptions = { | |||
58 | actorId: number | 58 | actorId: number |
59 | } | 59 | } |
60 | 60 | ||
61 | @DefaultScope({ | 61 | @DefaultScope(() => ({ |
62 | include: [ | 62 | include: [ |
63 | { | 63 | { |
64 | model: () => ActorModel, | 64 | model: ActorModel, |
65 | required: true | 65 | required: true |
66 | } | 66 | } |
67 | ] | 67 | ] |
68 | }) | 68 | })) |
69 | @Scopes({ | 69 | @Scopes(() => ({ |
70 | [ScopeNames.SUMMARY]: (withAccount = false) => { | 70 | [ScopeNames.SUMMARY]: (withAccount = false) => { |
71 | const base: FindOptions = { | 71 | const base: FindOptions = { |
72 | attributes: [ 'name', 'description', 'id', 'actorId' ], | 72 | attributes: [ 'name', 'description', 'id', 'actorId' ], |
@@ -142,22 +142,22 @@ type AvailableForListOptions = { | |||
142 | [ScopeNames.WITH_ACCOUNT]: { | 142 | [ScopeNames.WITH_ACCOUNT]: { |
143 | include: [ | 143 | include: [ |
144 | { | 144 | { |
145 | model: () => AccountModel, | 145 | model: AccountModel, |
146 | required: true | 146 | required: true |
147 | } | 147 | } |
148 | ] | 148 | ] |
149 | }, | 149 | }, |
150 | [ScopeNames.WITH_VIDEOS]: { | 150 | [ScopeNames.WITH_VIDEOS]: { |
151 | include: [ | 151 | include: [ |
152 | () => VideoModel | 152 | VideoModel |
153 | ] | 153 | ] |
154 | }, | 154 | }, |
155 | [ScopeNames.WITH_ACTOR]: { | 155 | [ScopeNames.WITH_ACTOR]: { |
156 | include: [ | 156 | include: [ |
157 | () => ActorModel | 157 | ActorModel |
158 | ] | 158 | ] |
159 | } | 159 | } |
160 | }) | 160 | })) |
161 | @Table({ | 161 | @Table({ |
162 | tableName: 'videoChannel', | 162 | tableName: 'videoChannel', |
163 | indexes | 163 | indexes |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 5f7cd3671..fee11ec5f 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -30,7 +30,7 @@ import { UserModel } from '../account/user' | |||
30 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | 30 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' |
31 | import { regexpCapture } from '../../helpers/regexp' | 31 | import { regexpCapture } from '../../helpers/regexp' |
32 | import { uniq } from 'lodash' | 32 | import { uniq } from 'lodash' |
33 | import { FindOptions, Op, Order, Sequelize, Transaction } from 'sequelize' | 33 | import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' |
34 | 34 | ||
35 | enum ScopeNames { | 35 | enum ScopeNames { |
36 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 36 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -39,7 +39,7 @@ enum ScopeNames { | |||
39 | ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' | 39 | ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' |
40 | } | 40 | } |
41 | 41 | ||
42 | @Scopes({ | 42 | @Scopes(() => ({ |
43 | [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { | 43 | [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { |
44 | return { | 44 | return { |
45 | attributes: { | 45 | attributes: { |
@@ -63,34 +63,34 @@ enum ScopeNames { | |||
63 | ] | 63 | ] |
64 | ] | 64 | ] |
65 | } | 65 | } |
66 | } | 66 | } as FindOptions |
67 | }, | 67 | }, |
68 | [ScopeNames.WITH_ACCOUNT]: { | 68 | [ScopeNames.WITH_ACCOUNT]: { |
69 | include: [ | 69 | include: [ |
70 | { | 70 | { |
71 | model: () => AccountModel, | 71 | model: AccountModel, |
72 | include: [ | 72 | include: [ |
73 | { | 73 | { |
74 | model: () => ActorModel, | 74 | model: ActorModel, |
75 | include: [ | 75 | include: [ |
76 | { | 76 | { |
77 | model: () => ServerModel, | 77 | model: ServerModel, |
78 | required: false | 78 | required: false |
79 | }, | 79 | }, |
80 | { | 80 | { |
81 | model: () => AvatarModel, | 81 | model: AvatarModel, |
82 | required: false | 82 | required: false |
83 | } | 83 | } |
84 | ] | 84 | ] |
85 | } | 85 | } |
86 | ] | 86 | ] |
87 | } | 87 | } |
88 | ] as any // FIXME: sequelize typings | 88 | ] |
89 | }, | 89 | }, |
90 | [ScopeNames.WITH_IN_REPLY_TO]: { | 90 | [ScopeNames.WITH_IN_REPLY_TO]: { |
91 | include: [ | 91 | include: [ |
92 | { | 92 | { |
93 | model: () => VideoCommentModel, | 93 | model: VideoCommentModel, |
94 | as: 'InReplyToVideoComment' | 94 | as: 'InReplyToVideoComment' |
95 | } | 95 | } |
96 | ] | 96 | ] |
@@ -98,19 +98,19 @@ enum ScopeNames { | |||
98 | [ScopeNames.WITH_VIDEO]: { | 98 | [ScopeNames.WITH_VIDEO]: { |
99 | include: [ | 99 | include: [ |
100 | { | 100 | { |
101 | model: () => VideoModel, | 101 | model: VideoModel, |
102 | required: true, | 102 | required: true, |
103 | include: [ | 103 | include: [ |
104 | { | 104 | { |
105 | model: () => VideoChannelModel.unscoped(), | 105 | model: VideoChannelModel.unscoped(), |
106 | required: true, | 106 | required: true, |
107 | include: [ | 107 | include: [ |
108 | { | 108 | { |
109 | model: () => AccountModel, | 109 | model: AccountModel, |
110 | required: true, | 110 | required: true, |
111 | include: [ | 111 | include: [ |
112 | { | 112 | { |
113 | model: () => ActorModel, | 113 | model: ActorModel, |
114 | required: true | 114 | required: true |
115 | } | 115 | } |
116 | ] | 116 | ] |
@@ -119,9 +119,9 @@ enum ScopeNames { | |||
119 | } | 119 | } |
120 | ] | 120 | ] |
121 | } | 121 | } |
122 | ] as any // FIXME: sequelize typings | 122 | ] |
123 | } | 123 | } |
124 | }) | 124 | })) |
125 | @Table({ | 125 | @Table({ |
126 | tableName: 'videoComment', | 126 | tableName: 'videoComment', |
127 | indexes: [ | 127 | indexes: [ |
@@ -313,8 +313,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
313 | } | 313 | } |
314 | } | 314 | } |
315 | 315 | ||
316 | // FIXME: typings | 316 | const scopes: (string | ScopeOptions)[] = [ |
317 | const scopes: any[] = [ | ||
318 | ScopeNames.WITH_ACCOUNT, | 317 | ScopeNames.WITH_ACCOUNT, |
319 | { | 318 | { |
320 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] | 319 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index c14d96bc5..2203a7aba 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -19,11 +19,11 @@ import { | |||
19 | isVideoFileSizeValid, | 19 | isVideoFileSizeValid, |
20 | isVideoFPSResolutionValid | 20 | isVideoFPSResolutionValid |
21 | } from '../../helpers/custom-validators/videos' | 21 | } from '../../helpers/custom-validators/videos' |
22 | import { throwIfNotValid } from '../utils' | 22 | import { parseAggregateResult, throwIfNotValid } from '../utils' |
23 | import { VideoModel } from './video' | 23 | import { VideoModel } from './video' |
24 | import * as Sequelize from 'sequelize' | ||
25 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 24 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
26 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 25 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
26 | import { FindOptions, QueryTypes, Transaction } from 'sequelize' | ||
27 | 27 | ||
28 | @Table({ | 28 | @Table({ |
29 | tableName: 'videoFile', | 29 | tableName: 'videoFile', |
@@ -97,15 +97,13 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
97 | static doesInfohashExist (infoHash: string) { | 97 | static doesInfohashExist (infoHash: string) { |
98 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' | 98 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' |
99 | const options = { | 99 | const options = { |
100 | type: Sequelize.QueryTypes.SELECT, | 100 | type: QueryTypes.SELECT, |
101 | bind: { infoHash }, | 101 | bind: { infoHash }, |
102 | raw: true | 102 | raw: true |
103 | } | 103 | } |
104 | 104 | ||
105 | return VideoModel.sequelize.query(query, options) | 105 | return VideoModel.sequelize.query(query, options) |
106 | .then(results => { | 106 | .then(results => results.length === 1) |
107 | return results.length === 1 | ||
108 | }) | ||
109 | } | 107 | } |
110 | 108 | ||
111 | static loadWithVideo (id: number) { | 109 | static loadWithVideo (id: number) { |
@@ -121,7 +119,7 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
121 | return VideoFileModel.findByPk(id, options) | 119 | return VideoFileModel.findByPk(id, options) |
122 | } | 120 | } |
123 | 121 | ||
124 | static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Sequelize.Transaction) { | 122 | static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) { |
125 | const query = { | 123 | const query = { |
126 | include: [ | 124 | include: [ |
127 | { | 125 | { |
@@ -144,8 +142,8 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
144 | return VideoFileModel.findAll(query) | 142 | return VideoFileModel.findAll(query) |
145 | } | 143 | } |
146 | 144 | ||
147 | static async getStats () { | 145 | static getStats () { |
148 | let totalLocalVideoFilesSize = await VideoFileModel.sum('size', { | 146 | const query: FindOptions = { |
149 | include: [ | 147 | include: [ |
150 | { | 148 | { |
151 | attributes: [], | 149 | attributes: [], |
@@ -155,13 +153,12 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
155 | } | 153 | } |
156 | } | 154 | } |
157 | ] | 155 | ] |
158 | } as any) | ||
159 | // Sequelize could return null... | ||
160 | if (!totalLocalVideoFilesSize) totalLocalVideoFilesSize = 0 | ||
161 | |||
162 | return { | ||
163 | totalLocalVideoFilesSize | ||
164 | } | 156 | } |
157 | |||
158 | return VideoFileModel.aggregate('size', 'SUM', query) | ||
159 | .then(result => ({ | ||
160 | totalLocalVideoFilesSize: parseAggregateResult(result) | ||
161 | })) | ||
165 | } | 162 | } |
166 | 163 | ||
167 | hasSameUniqueKeysThan (other: VideoFileModel) { | 164 | hasSameUniqueKeysThan (other: VideoFileModel) { |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 89992a5a8..877fcbc57 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -59,7 +59,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
59 | views: video.views, | 59 | views: video.views, |
60 | likes: video.likes, | 60 | likes: video.likes, |
61 | dislikes: video.dislikes, | 61 | dislikes: video.dislikes, |
62 | thumbnailPath: video.getThumbnailStaticPath(), | 62 | thumbnailPath: video.getMiniatureStaticPath(), |
63 | previewPath: video.getPreviewStaticPath(), | 63 | previewPath: video.getPreviewStaticPath(), |
64 | embedPath: video.getEmbedStaticPath(), | 64 | embedPath: video.getEmbedStaticPath(), |
65 | createdAt: video.createdAt, | 65 | createdAt: video.createdAt, |
@@ -301,6 +301,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
301 | }) | 301 | }) |
302 | } | 302 | } |
303 | 303 | ||
304 | const miniature = video.getMiniature() | ||
305 | |||
304 | return { | 306 | return { |
305 | type: 'Video' as 'Video', | 307 | type: 'Video' as 'Video', |
306 | id: video.url, | 308 | id: video.url, |
@@ -326,10 +328,10 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
326 | subtitleLanguage, | 328 | subtitleLanguage, |
327 | icon: { | 329 | icon: { |
328 | type: 'Image', | 330 | type: 'Image', |
329 | url: video.getThumbnail().getUrl(), | 331 | url: miniature.getUrl(), |
330 | mediaType: 'image/jpeg', | 332 | mediaType: 'image/jpeg', |
331 | width: video.getThumbnail().width, | 333 | width: miniature.width, |
332 | height: video.getThumbnail().height | 334 | height: miniature.height |
333 | }, | 335 | }, |
334 | url, | 336 | url, |
335 | likes: getVideoLikesActivityPubUrl(video), | 337 | likes: getVideoLikesActivityPubUrl(video), |
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 588a13a4f..480a671c8 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -21,18 +21,18 @@ import { VideoImport, VideoImportState } from '../../../shared' | |||
21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' | 21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' |
22 | import { UserModel } from '../account/user' | 22 | import { UserModel } from '../account/user' |
23 | 23 | ||
24 | @DefaultScope({ | 24 | @DefaultScope(() => ({ |
25 | include: [ | 25 | include: [ |
26 | { | 26 | { |
27 | model: () => UserModel.unscoped(), | 27 | model: UserModel.unscoped(), |
28 | required: true | 28 | required: true |
29 | }, | 29 | }, |
30 | { | 30 | { |
31 | model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), | 31 | model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), |
32 | required: false | 32 | required: false |
33 | } | 33 | } |
34 | ] | 34 | ] |
35 | }) | 35 | })) |
36 | 36 | ||
37 | @Table({ | 37 | @Table({ |
38 | tableName: 'videoImport', | 38 | tableName: 'videoImport', |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index 3e436acfc..63b4a0715 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -42,7 +42,7 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub' | |||
42 | import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model' | 42 | import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model' |
43 | import { ThumbnailModel } from './thumbnail' | 43 | import { ThumbnailModel } from './thumbnail' |
44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' | 44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' |
45 | import { fn, literal, Op, Transaction } from 'sequelize' | 45 | import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' |
46 | 46 | ||
47 | enum ScopeNames { | 47 | enum ScopeNames { |
48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
@@ -61,11 +61,11 @@ type AvailableForListOptions = { | |||
61 | privateAndUnlisted?: boolean | 61 | privateAndUnlisted?: boolean |
62 | } | 62 | } |
63 | 63 | ||
64 | @Scopes({ | 64 | @Scopes(() => ({ |
65 | [ ScopeNames.WITH_THUMBNAIL ]: { | 65 | [ ScopeNames.WITH_THUMBNAIL ]: { |
66 | include: [ | 66 | include: [ |
67 | { | 67 | { |
68 | model: () => ThumbnailModel, | 68 | model: ThumbnailModel, |
69 | required: false | 69 | required: false |
70 | } | 70 | } |
71 | ] | 71 | ] |
@@ -74,20 +74,16 @@ type AvailableForListOptions = { | |||
74 | attributes: { | 74 | attributes: { |
75 | include: [ | 75 | include: [ |
76 | [ | 76 | [ |
77 | fn('COUNT', 'toto'), | ||
78 | 'coucou' | ||
79 | ], | ||
80 | [ | ||
81 | literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'), | 77 | literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'), |
82 | 'videosLength' | 78 | 'videosLength' |
83 | ] | 79 | ] |
84 | ] | 80 | ] |
85 | } | 81 | } |
86 | }, | 82 | } as FindOptions, |
87 | [ ScopeNames.WITH_ACCOUNT ]: { | 83 | [ ScopeNames.WITH_ACCOUNT ]: { |
88 | include: [ | 84 | include: [ |
89 | { | 85 | { |
90 | model: () => AccountModel, | 86 | model: AccountModel, |
91 | required: true | 87 | required: true |
92 | } | 88 | } |
93 | ] | 89 | ] |
@@ -95,11 +91,11 @@ type AvailableForListOptions = { | |||
95 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { | 91 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { |
96 | include: [ | 92 | include: [ |
97 | { | 93 | { |
98 | model: () => AccountModel.scope(AccountScopeNames.SUMMARY), | 94 | model: AccountModel.scope(AccountScopeNames.SUMMARY), |
99 | required: true | 95 | required: true |
100 | }, | 96 | }, |
101 | { | 97 | { |
102 | model: () => VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), | 98 | model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), |
103 | required: false | 99 | required: false |
104 | } | 100 | } |
105 | ] | 101 | ] |
@@ -107,11 +103,11 @@ type AvailableForListOptions = { | |||
107 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { | 103 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { |
108 | include: [ | 104 | include: [ |
109 | { | 105 | { |
110 | model: () => AccountModel, | 106 | model: AccountModel, |
111 | required: true | 107 | required: true |
112 | }, | 108 | }, |
113 | { | 109 | { |
114 | model: () => VideoChannelModel, | 110 | model: VideoChannelModel, |
115 | required: false | 111 | required: false |
116 | } | 112 | } |
117 | ] | 113 | ] |
@@ -132,7 +128,7 @@ type AvailableForListOptions = { | |||
132 | ] | 128 | ] |
133 | } | 129 | } |
134 | 130 | ||
135 | const whereAnd: any[] = [] | 131 | const whereAnd: WhereOptions[] = [] |
136 | 132 | ||
137 | if (options.privateAndUnlisted !== true) { | 133 | if (options.privateAndUnlisted !== true) { |
138 | whereAnd.push({ | 134 | whereAnd.push({ |
@@ -178,9 +174,9 @@ type AvailableForListOptions = { | |||
178 | required: false | 174 | required: false |
179 | } | 175 | } |
180 | ] | 176 | ] |
181 | } | 177 | } as FindOptions |
182 | } | 178 | } |
183 | }) | 179 | })) |
184 | 180 | ||
185 | @Table({ | 181 | @Table({ |
186 | tableName: 'videoPlaylist', | 182 | tableName: 'videoPlaylist', |
@@ -269,6 +265,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
269 | VideoPlaylistElements: VideoPlaylistElementModel[] | 265 | VideoPlaylistElements: VideoPlaylistElementModel[] |
270 | 266 | ||
271 | @HasOne(() => ThumbnailModel, { | 267 | @HasOne(() => ThumbnailModel, { |
268 | |||
272 | foreignKey: { | 269 | foreignKey: { |
273 | name: 'videoPlaylistId', | 270 | name: 'videoPlaylistId', |
274 | allowNull: true | 271 | allowNull: true |
@@ -294,7 +291,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
294 | order: getSort(options.sort) | 291 | order: getSort(options.sort) |
295 | } | 292 | } |
296 | 293 | ||
297 | const scopes = [ | 294 | const scopes: (string | ScopeOptions)[] = [ |
298 | { | 295 | { |
299 | method: [ | 296 | method: [ |
300 | ScopeNames.AVAILABLE_FOR_LIST, | 297 | ScopeNames.AVAILABLE_FOR_LIST, |
@@ -306,7 +303,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
306 | privateAndUnlisted: options.privateAndUnlisted | 303 | privateAndUnlisted: options.privateAndUnlisted |
307 | } as AvailableForListOptions | 304 | } as AvailableForListOptions |
308 | ] | 305 | ] |
309 | } as any, // FIXME: typings | 306 | }, |
310 | ScopeNames.WITH_VIDEOS_LENGTH, | 307 | ScopeNames.WITH_VIDEOS_LENGTH, |
311 | ScopeNames.WITH_THUMBNAIL | 308 | ScopeNames.WITH_THUMBNAIL |
312 | ] | 309 | ] |
@@ -348,7 +345,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
348 | model: VideoPlaylistElementModel.unscoped(), | 345 | model: VideoPlaylistElementModel.unscoped(), |
349 | where: { | 346 | where: { |
350 | videoId: { | 347 | videoId: { |
351 | [Op.any]: videoIds | 348 | [Op.in]: videoIds // FIXME: sequelize ANY seems broken |
352 | } | 349 | } |
353 | }, | 350 | }, |
354 | required: true | 351 | required: true |
@@ -427,12 +424,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
427 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) | 424 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) |
428 | } | 425 | } |
429 | 426 | ||
430 | setThumbnail (thumbnail: ThumbnailModel) { | 427 | async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { |
431 | this.Thumbnail = thumbnail | 428 | thumbnail.videoPlaylistId = this.id |
432 | } | ||
433 | 429 | ||
434 | getThumbnail () { | 430 | this.Thumbnail = await thumbnail.save({ transaction: t }) |
435 | return this.Thumbnail | ||
436 | } | 431 | } |
437 | 432 | ||
438 | hasThumbnail () { | 433 | hasThumbnail () { |
@@ -448,13 +443,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
448 | getThumbnailUrl () { | 443 | getThumbnailUrl () { |
449 | if (!this.hasThumbnail()) return null | 444 | if (!this.hasThumbnail()) return null |
450 | 445 | ||
451 | return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.getThumbnail().filename | 446 | return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename |
452 | } | 447 | } |
453 | 448 | ||
454 | getThumbnailStaticPath () { | 449 | getThumbnailStaticPath () { |
455 | if (!this.hasThumbnail()) return null | 450 | if (!this.hasThumbnail()) return null |
456 | 451 | ||
457 | return join(STATIC_PATHS.THUMBNAILS, this.getThumbnail().filename) | 452 | return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) |
458 | } | 453 | } |
459 | 454 | ||
460 | setAsRefreshed () { | 455 | setAsRefreshed () { |
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index c83f6c5b0..fda2d7cea 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -14,15 +14,15 @@ enum ScopeNames { | |||
14 | WITH_ACTOR = 'WITH_ACTOR' | 14 | WITH_ACTOR = 'WITH_ACTOR' |
15 | } | 15 | } |
16 | 16 | ||
17 | @Scopes({ | 17 | @Scopes(() => ({ |
18 | [ScopeNames.FULL]: { | 18 | [ScopeNames.FULL]: { |
19 | include: [ | 19 | include: [ |
20 | { | 20 | { |
21 | model: () => ActorModel, | 21 | model: ActorModel, |
22 | required: true | 22 | required: true |
23 | }, | 23 | }, |
24 | { | 24 | { |
25 | model: () => VideoModel, | 25 | model: VideoModel, |
26 | required: true | 26 | required: true |
27 | } | 27 | } |
28 | ] | 28 | ] |
@@ -30,12 +30,12 @@ enum ScopeNames { | |||
30 | [ScopeNames.WITH_ACTOR]: { | 30 | [ScopeNames.WITH_ACTOR]: { |
31 | include: [ | 31 | include: [ |
32 | { | 32 | { |
33 | model: () => ActorModel, | 33 | model: ActorModel, |
34 | required: true | 34 | required: true |
35 | } | 35 | } |
36 | ] | 36 | ] |
37 | } | 37 | } |
38 | }) | 38 | })) |
39 | @Table({ | 39 | @Table({ |
40 | tableName: 'videoShare', | 40 | tableName: 'videoShare', |
41 | indexes: [ | 41 | indexes: [ |
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index b30267e09..31dc82c54 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -26,7 +26,7 @@ import { QueryTypes, Op } from 'sequelize' | |||
26 | fields: [ 'p2pMediaLoaderInfohashes' ], | 26 | fields: [ 'p2pMediaLoaderInfohashes' ], |
27 | using: 'gin' | 27 | using: 'gin' |
28 | } | 28 | } |
29 | ] as any // FIXME: sequelize typings | 29 | ] |
30 | }) | 30 | }) |
31 | export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> { | 31 | export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> { |
32 | @CreatedAt | 32 | @CreatedAt |
@@ -46,7 +46,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
46 | 46 | ||
47 | @AllowNull(false) | 47 | @AllowNull(false) |
48 | @Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes')) | 48 | @Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes')) |
49 | @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: typings | 49 | @Column(DataType.ARRAY(DataType.STRING)) |
50 | p2pMediaLoaderInfohashes: string[] | 50 | p2pMediaLoaderInfohashes: string[] |
51 | 51 | ||
52 | @AllowNull(false) | 52 | @AllowNull(false) |
@@ -87,7 +87,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
87 | raw: true | 87 | raw: true |
88 | } | 88 | } |
89 | 89 | ||
90 | return VideoModel.sequelize.query<any>(query, options) | 90 | return VideoModel.sequelize.query<object>(query, options) |
91 | .then(results => results.length === 1) | 91 | .then(results => results.length === 1) |
92 | } | 92 | } |
93 | 93 | ||
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) |