aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video')
-rw-r--r--server/models/video/tag.ts2
-rw-r--r--server/models/video/thumbnail.ts4
-rw-r--r--server/models/video/video-caption.ts13
-rw-r--r--server/models/video/video-change-ownership.ts14
-rw-r--r--server/models/video/video-channel.ts16
-rw-r--r--server/models/video/video-comment.ts33
-rw-r--r--server/models/video/video-file.ts27
-rw-r--r--server/models/video/video-format-utils.ts10
-rw-r--r--server/models/video/video-import.ts8
-rw-r--r--server/models/video/video-playlist.ts47
-rw-r--r--server/models/video/video-share.ts10
-rw-r--r--server/models/video/video-streaming-playlist.ts6
-rw-r--r--server/models/video/video.ts179
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'
15import { throwIfNotValid } from '../utils' 15import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
16import { VideoModel } from './video' 16import { VideoModel } from './video'
17import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' 17import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
18import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' 18import { 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}))
49export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> { 49export 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'
30import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' 30import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
31import { regexpCapture } from '../../helpers/regexp' 31import { regexpCapture } from '../../helpers/regexp'
32import { uniq } from 'lodash' 32import { uniq } from 'lodash'
33import { FindOptions, Op, Order, Sequelize, Transaction } from 'sequelize' 33import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
34 34
35enum ScopeNames { 35enum 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'
22import { throwIfNotValid } from '../utils' 22import { parseAggregateResult, throwIfNotValid } from '../utils'
23import { VideoModel } from './video' 23import { VideoModel } from './video'
24import * as Sequelize from 'sequelize'
25import { VideoRedundancyModel } from '../redundancy/video-redundancy' 24import { VideoRedundancyModel } from '../redundancy/video-redundancy'
26import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 25import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
26import { 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'
21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' 21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
22import { UserModel } from '../account/user' 22import { 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'
42import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model' 42import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
43import { ThumbnailModel } from './thumbnail' 43import { ThumbnailModel } from './thumbnail'
44import { ActivityIconObject } from '../../../shared/models/activitypub/objects' 44import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
45import { fn, literal, Op, Transaction } from 'sequelize' 45import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
46 46
47enum ScopeNames { 47enum 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})
31export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> { 31export 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)