Browse Source

Upgrade sequelize

Chocobozzz 11 months ago
parent
commit
3acc508440
No account linked to committer's email address
48 changed files with 457 additions and 466 deletions
  1. 1
    1
      .gitignore
  2. 3
    3
      package.json
  3. 7
    13
      server/controllers/api/video-playlist.ts
  4. 5
    11
      server/controllers/api/videos/import.ts
  5. 11
    20
      server/controllers/api/videos/index.ts
  6. 1
    1
      server/controllers/bots.ts
  7. 1
    1
      server/controllers/feeds.ts
  8. 6
    6
      server/controllers/static.ts
  9. 3
    3
      server/initializers/database.ts
  10. 6
    8
      server/lib/activitypub/playlist.ts
  11. 1
    2
      server/lib/activitypub/video-comments.ts
  12. 12
    22
      server/lib/activitypub/videos.ts
  13. 11
    7
      server/lib/files-cache/abstract-video-static-file-cache.ts
  14. 7
    2
      server/lib/files-cache/videos-caption-cache.ts
  15. 4
    2
      server/lib/files-cache/videos-preview-cache.ts
  16. 7
    13
      server/lib/job-queue/handlers/video-import.ts
  17. 2
    0
      server/lib/oauth-model.ts
  18. 13
    13
      server/lib/thumbnail.ts
  19. 2
    0
      server/middlewares/oauth.ts
  20. 1
    0
      server/middlewares/validators/videos/videos.ts
  21. 5
    5
      server/models/account/account-blocklist.ts
  22. 5
    5
      server/models/account/account.ts
  23. 22
    22
      server/models/account/user-notification.ts
  24. 27
    31
      server/models/account/user.ts
  25. 24
    23
      server/models/activitypub/actor.ts
  26. 3
    3
      server/models/application/application.ts
  27. 2
    2
      server/models/oauth/oauth-client.ts
  28. 12
    10
      server/models/oauth/oauth-token.ts
  29. 26
    30
      server/models/redundancy/video-redundancy.ts
  30. 4
    4
      server/models/server/server-blocklist.ts
  31. 11
    1
      server/models/utils.ts
  32. 1
    1
      server/models/video/tag.ts
  33. 2
    2
      server/models/video/thumbnail.ts
  34. 5
    8
      server/models/video/video-caption.ts
  35. 7
    7
      server/models/video/video-change-ownership.ts
  36. 8
    8
      server/models/video/video-channel.ts
  37. 16
    17
      server/models/video/video-comment.ts
  38. 12
    15
      server/models/video/video-file.ts
  39. 6
    4
      server/models/video/video-format-utils.ts
  40. 4
    4
      server/models/video/video-import.ts
  41. 21
    26
      server/models/video/video-playlist.ts
  42. 5
    5
      server/models/video/video-share.ts
  43. 3
    3
      server/models/video/video-streaming-playlist.ts
  44. 91
    88
      server/models/video/video.ts
  45. 18
    0
      server/typings/sequelize.ts
  46. 0
    1
      shared/extra-utils/miscs/sql.ts
  47. 1
    1
      shared/models/videos/thumbnail.type.ts
  48. 12
    12
      yarn.lock

+ 1
- 1
.gitignore View File

@@ -16,8 +16,8 @@
16 16
 /config/production.yaml
17 17
 /config/local*
18 18
 /ffmpeg/
19
-/ffmpeg-4/
20 19
 /ffmpeg-3/
20
+/ffmpeg-4/
21 21
 /thumbnails/
22 22
 /torrents/
23 23
 /videos/

+ 3
- 3
package.json View File

@@ -142,8 +142,8 @@
142 142
     "reflect-metadata": "^0.1.12",
143 143
     "request": "^2.81.0",
144 144
     "scripty": "^1.5.0",
145
-    "sequelize": "5.6.1",
146
-    "sequelize-typescript": "^1.0.0-beta.1",
145
+    "sequelize": "5.7.4",
146
+    "sequelize-typescript": "1.0.0-beta.2",
147 147
     "sharp": "^0.22.0",
148 148
     "sitemap": "^2.1.0",
149 149
     "socket.io": "^2.2.0",
@@ -212,7 +212,7 @@
212 212
     "ts-node": "8.0.3",
213 213
     "tslint": "^5.7.0",
214 214
     "tslint-config-standard": "^8.0.1",
215
-    "typescript": "^3.1.6",
215
+    "typescript": "^3.4.3",
216 216
     "xliff": "^4.0.0"
217 217
   },
218 218
   "scripty": {

+ 7
- 13
server/controllers/api/video-playlist.ts View File

@@ -41,7 +41,7 @@ import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/vid
41 41
 import { JobQueue } from '../../lib/job-queue'
42 42
 import { CONFIG } from '../../initializers/config'
43 43
 import { sequelizeTypescript } from '../../initializers/database'
44
-import { createPlaylistThumbnailFromExisting } from '../../lib/thumbnail'
44
+import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
45 45
 
46 46
 const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
47 47
 
@@ -174,16 +174,13 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
174 174
 
175 175
   const thumbnailField = req.files['thumbnailfile']
176 176
   const thumbnailModel = thumbnailField
177
-    ? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylist)
177
+    ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist)
178 178
     : undefined
179 179
 
180 180
   const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
181 181
     const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
182 182
 
183
-    if (thumbnailModel) {
184
-      thumbnailModel.videoPlaylistId = videoPlaylistCreated.id
185
-      videoPlaylistCreated.setThumbnail(await thumbnailModel.save({ transaction: t }))
186
-    }
183
+    if (thumbnailModel) await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t)
187 184
 
188 185
     // We need more attributes for the federation
189 186
     videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t)
@@ -210,7 +207,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
210 207
 
211 208
   const thumbnailField = req.files['thumbnailfile']
212 209
   const thumbnailModel = thumbnailField
213
-    ? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylistInstance)
210
+    ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance)
214 211
     : undefined
215 212
 
216 213
   try {
@@ -239,10 +236,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
239 236
 
240 237
       const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
241 238
 
242
-      if (thumbnailModel) {
243
-        thumbnailModel.videoPlaylistId = playlistUpdated.id
244
-        playlistUpdated.setThumbnail(await thumbnailModel.save({ transaction: t }))
245
-      }
239
+      if (thumbnailModel) await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t)
246 240
 
247 241
       const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE
248 242
 
@@ -313,8 +307,8 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
313 307
   if (playlistElement.position === 1 && videoPlaylist.hasThumbnail() === false) {
314 308
     logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
315 309
 
316
-    const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnail().filename)
317
-    const thumbnailModel = await createPlaylistThumbnailFromExisting(inputPath, videoPlaylist, true)
310
+    const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
311
+    const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true)
318 312
 
319 313
     thumbnailModel.videoPlaylistId = videoPlaylist.id
320 314
 

+ 5
- 11
server/controllers/api/videos/import.ts View File

@@ -23,7 +23,7 @@ import { move, readFile } from 'fs-extra'
23 23
 import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
24 24
 import { CONFIG } from '../../../initializers/config'
25 25
 import { sequelizeTypescript } from '../../../initializers/database'
26
-import { createVideoThumbnailFromExisting } from '../../../lib/thumbnail'
26
+import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
27 27
 import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
28 28
 import { ThumbnailModel } from '../../../models/video/thumbnail'
29 29
 
@@ -204,7 +204,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) {
204 204
   if (thumbnailField) {
205 205
     const thumbnailPhysicalFile = thumbnailField[ 0 ]
206 206
 
207
-    return createVideoThumbnailFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.THUMBNAIL)
207
+    return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE)
208 208
   }
209 209
 
210 210
   return undefined
@@ -215,7 +215,7 @@ async function processPreview (req: express.Request, video: VideoModel) {
215 215
   if (previewField) {
216 216
     const previewPhysicalFile = previewField[0]
217 217
 
218
-    return createVideoThumbnailFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
218
+    return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
219 219
   }
220 220
 
221 221
   return undefined
@@ -238,14 +238,8 @@ function insertIntoDB (parameters: {
238 238
     const videoCreated = await video.save(sequelizeOptions)
239 239
     videoCreated.VideoChannel = videoChannel
240 240
 
241
-    if (thumbnailModel) {
242
-      thumbnailModel.videoId = videoCreated.id
243
-      videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
244
-    }
245
-    if (previewModel) {
246
-      previewModel.videoId = videoCreated.id
247
-      videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
248
-    }
241
+    if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
242
+    if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
249 243
 
250 244
     await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t)
251 245
 

+ 11
- 20
server/controllers/api/videos/index.ts View File

@@ -52,7 +52,7 @@ import { Notifier } from '../../../lib/notifier'
52 52
 import { sendView } from '../../../lib/activitypub/send/send-view'
53 53
 import { CONFIG } from '../../../initializers/config'
54 54
 import { sequelizeTypescript } from '../../../initializers/database'
55
-import { createVideoThumbnailFromExisting, generateVideoThumbnail } from '../../../lib/thumbnail'
55
+import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
56 56
 import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
57 57
 
58 58
 const auditLogger = auditLoggerFactory('videos')
@@ -214,14 +214,14 @@ async function addVideo (req: express.Request, res: express.Response) {
214 214
   // Process thumbnail or create it from the video
215 215
   const thumbnailField = req.files['thumbnailfile']
216 216
   const thumbnailModel = thumbnailField
217
-    ? await createVideoThumbnailFromExisting(thumbnailField[0].path, video, ThumbnailType.THUMBNAIL)
218
-    : await generateVideoThumbnail(video, videoFile, ThumbnailType.THUMBNAIL)
217
+    ? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE)
218
+    : await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE)
219 219
 
220 220
   // Process preview or create it from the video
221 221
   const previewField = req.files['previewfile']
222 222
   const previewModel = previewField
223
-    ? await createVideoThumbnailFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
224
-    : await generateVideoThumbnail(video, videoFile, ThumbnailType.PREVIEW)
223
+    ? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
224
+    : await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW)
225 225
 
226 226
   // Create the torrent file
227 227
   await video.createTorrentAndSetInfoHash(videoFile)
@@ -231,11 +231,8 @@ async function addVideo (req: express.Request, res: express.Response) {
231 231
 
232 232
     const videoCreated = await video.save(sequelizeOptions)
233 233
 
234
-    thumbnailModel.videoId = videoCreated.id
235
-    previewModel.videoId = videoCreated.id
236
-
237
-    videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
238
-    videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
234
+    await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
235
+    await videoCreated.addAndSaveThumbnail(previewModel, t)
239 236
 
240 237
     // Do not forget to add video channel information to the created video
241 238
     videoCreated.VideoChannel = res.locals.videoChannel
@@ -308,11 +305,11 @@ async function updateVideo (req: express.Request, res: express.Response) {
308 305
 
309 306
   // Process thumbnail or create it from the video
310 307
   const thumbnailModel = req.files && req.files['thumbnailfile']
311
-    ? await createVideoThumbnailFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.THUMBNAIL)
308
+    ? await createVideoMiniatureFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.MINIATURE)
312 309
     : undefined
313 310
 
314 311
   const previewModel = req.files && req.files['previewfile']
315
-    ? await createVideoThumbnailFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW)
312
+    ? await createVideoMiniatureFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW)
316 313
     : undefined
317 314
 
318 315
   try {
@@ -346,14 +343,8 @@ async function updateVideo (req: express.Request, res: express.Response) {
346 343
 
347 344
       const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
348 345
 
349
-      if (thumbnailModel) {
350
-        thumbnailModel.videoId = videoInstanceUpdated.id
351
-        videoInstanceUpdated.addThumbnail(await thumbnailModel.save({ transaction: t }))
352
-      }
353
-      if (previewModel) {
354
-        previewModel.videoId = videoInstanceUpdated.id
355
-        videoInstanceUpdated.addThumbnail(await previewModel.save({ transaction: t }))
356
-      }
346
+      if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
347
+      if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
357 348
 
358 349
       // Video tags update?
359 350
       if (videoInfoToUpdate.tags !== undefined) {

+ 1
- 1
server/controllers/bots.ts View File

@@ -85,7 +85,7 @@ async function getSitemapLocalVideoUrls () {
85 85
         // Sitemap description should be < 2000 characters
86 86
         description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
87 87
         player_loc: WEBSERVER.URL + '/videos/embed/' + v.uuid,
88
-        thumbnail_loc: WEBSERVER.URL + v.getThumbnailStaticPath()
88
+        thumbnail_loc: WEBSERVER.URL + v.getMiniatureStaticPath()
89 89
       }
90 90
     ]
91 91
   }))

+ 1
- 1
server/controllers/feeds.ts View File

@@ -137,7 +137,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) {
137 137
       torrent: torrents,
138 138
       thumbnail: [
139 139
         {
140
-          url: WEBSERVER.URL + video.getThumbnailStaticPath(),
140
+          url: WEBSERVER.URL + video.getMiniatureStaticPath(),
141 141
           height: THUMBNAILS_SIZE.height,
142 142
           width: THUMBNAILS_SIZE.width
143 143
         }

+ 6
- 6
server/controllers/static.ts View File

@@ -165,20 +165,20 @@ export {
165 165
 // ---------------------------------------------------------------------------
166 166
 
167 167
 async function getPreview (req: express.Request, res: express.Response) {
168
-  const path = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
169
-  if (!path) return res.sendStatus(404)
168
+  const result = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
169
+  if (!result) return res.sendStatus(404)
170 170
 
171
-  return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
171
+  return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
172 172
 }
173 173
 
174 174
 async function getVideoCaption (req: express.Request, res: express.Response) {
175
-  const path = await VideosCaptionCache.Instance.getFilePath({
175
+  const result = await VideosCaptionCache.Instance.getFilePath({
176 176
     videoId: req.params.videoId,
177 177
     language: req.params.captionLanguage
178 178
   })
179
-  if (!path) return res.sendStatus(404)
179
+  if (!result) return res.sendStatus(404)
180 180
 
181
-  return res.sendFile(path, { maxAge: STATIC_MAX_AGE })
181
+  return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
182 182
 }
183 183
 
184 184
 async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {

+ 3
- 3
server/initializers/database.ts View File

@@ -140,15 +140,15 @@ async function checkPostgresExtensions () {
140 140
 }
141 141
 
142 142
 async function checkPostgresExtension (extension: string) {
143
-  const query = `SELECT true AS enabled FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;`
143
+  const query = `SELECT 1 FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;`
144 144
   const options = {
145 145
     type: QueryTypes.SELECT as QueryTypes.SELECT,
146 146
     raw: true
147 147
   }
148 148
 
149
-  const res = await sequelizeTypescript.query<{ enabled: boolean }>(query, options)
149
+  const res = await sequelizeTypescript.query<object>(query, options)
150 150
 
151
-  if (!res || res.length === 0 || res[ 0 ][ 'enabled' ] !== true) {
151
+  if (!res || res.length === 0) {
152 152
     // Try to create the extension ourselves
153 153
     try {
154 154
       await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true })

+ 6
- 8
server/lib/activitypub/playlist.ts View File

@@ -16,7 +16,8 @@ import { VideoPlaylistElementModel } from '../../models/video/video-playlist-ele
16 16
 import { VideoModel } from '../../models/video/video'
17 17
 import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
18 18
 import { sequelizeTypescript } from '../../initializers/database'
19
-import { createPlaylistThumbnailFromUrl } from '../thumbnail'
19
+import { createPlaylistMiniatureFromUrl } from '../thumbnail'
20
+import { FilteredModelAttributes } from '../../typings/sequelize'
20 21
 
21 22
 function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) {
22 23
   const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED
@@ -86,8 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
86 87
     }
87 88
   }
88 89
 
89
-  // FIXME: sequelize typings
90
-  const [ playlist ] = (await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) as any)
90
+  const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true })
91 91
 
92 92
   let accItems: string[] = []
93 93
   await crawlCollectionPage<string>(playlistObject.id, items => {
@@ -100,10 +100,8 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
100 100
 
101 101
   if (playlistObject.icon) {
102 102
     try {
103
-      const thumbnailModel = await createPlaylistThumbnailFromUrl(playlistObject.icon.url, refreshedPlaylist)
104
-      thumbnailModel.videoPlaylistId = refreshedPlaylist.id
105
-
106
-      refreshedPlaylist.setThumbnail(await thumbnailModel.save())
103
+      const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist)
104
+      await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined)
107 105
     } catch (err) {
108 106
       logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })
109 107
     }
@@ -156,7 +154,7 @@ export {
156 154
 // ---------------------------------------------------------------------------
157 155
 
158 156
 async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) {
159
-  const elementsToCreate: object[] = [] // FIXME: sequelize typings
157
+  const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = []
160 158
 
161 159
   await Bluebird.map(elementUrls, async elementUrl => {
162 160
     try {

+ 1
- 2
server/lib/activitypub/video-comments.ts View File

@@ -73,8 +73,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
73 73
   const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
74 74
   if (!entry) return { created: false }
75 75
 
76
-  // FIXME: sequelize typings
77
-  const [ comment, created ] = (await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true }) as any)
76
+  const [ comment, created ] = await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true })
78 77
   comment.Account = actor.Account
79 78
   comment.Video = videoInstance
80 79
 

+ 12
- 22
server/lib/activitypub/videos.ts View File

@@ -49,10 +49,11 @@ import { AccountVideoRateModel } from '../../models/account/account-video-rate'
49 49
 import { VideoShareModel } from '../../models/video/video-share'
50 50
 import { VideoCommentModel } from '../../models/video/video-comment'
51 51
 import { sequelizeTypescript } from '../../initializers/database'
52
-import { createPlaceholderThumbnail, createVideoThumbnailFromUrl } from '../thumbnail'
52
+import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail'
53 53
 import { ThumbnailModel } from '../../models/video/thumbnail'
54 54
 import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
55 55
 import { join } from 'path'
56
+import { FilteredModelAttributes } from '../../typings/sequelize'
56 57
 
57 58
 async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
58 59
   // If the video is not private and is published, we federate it
@@ -247,7 +248,7 @@ async function updateVideoFromAP (options: {
247 248
     let thumbnailModel: ThumbnailModel
248 249
 
249 250
     try {
250
-      thumbnailModel = await createVideoThumbnailFromUrl(options.videoObject.icon.url, options.video, ThumbnailType.THUMBNAIL)
251
+      thumbnailModel = await createVideoMiniatureFromUrl(options.videoObject.icon.url, options.video, ThumbnailType.MINIATURE)
251 252
     } catch (err) {
252 253
       logger.warn('Cannot generate thumbnail of %s.', options.videoObject.id, { err })
253 254
     }
@@ -288,16 +289,12 @@ async function updateVideoFromAP (options: {
288 289
 
289 290
       await options.video.save(sequelizeOptions)
290 291
 
291
-      if (thumbnailModel) {
292
-        thumbnailModel.videoId = options.video.id
293
-        options.video.addThumbnail(await thumbnailModel.save({ transaction: t }))
294
-      }
292
+      if (thumbnailModel) if (thumbnailModel) await options.video.addAndSaveThumbnail(thumbnailModel, t)
295 293
 
296 294
       // FIXME: use icon URL instead
297 295
       const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename))
298 296
       const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
299
-
300
-      options.video.addThumbnail(await previewModel.save({ transaction: t }))
297
+      await options.video.addAndSaveThumbnail(previewModel, t)
301 298
 
302 299
       {
303 300
         const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject)
@@ -311,7 +308,7 @@ async function updateVideoFromAP (options: {
311 308
 
312 309
         // Update or add other one
313 310
         const upsertTasks = videoFileAttributes.map(a => {
314
-          return (VideoFileModel.upsert<VideoFileModel>(a, { returning: true, transaction: t }) as any) // FIXME: sequelize typings
311
+          return VideoFileModel.upsert<VideoFileModel>(a, { returning: true, transaction: t })
315 312
             .then(([ file ]) => file)
316 313
         })
317 314
 
@@ -334,8 +331,7 @@ async function updateVideoFromAP (options: {
334 331
 
335 332
         // Update or add other one
336 333
         const upsertTasks = streamingPlaylistAttributes.map(a => {
337
-          // FIXME: sequelize typings
338
-          return (VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t }) as any)
334
+          return VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t })
339 335
                                .then(([ streamingPlaylist ]) => streamingPlaylist)
340 336
         })
341 337
 
@@ -464,7 +460,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
464 460
   const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to)
465 461
   const video = VideoModel.build(videoData)
466 462
 
467
-  const promiseThumbnail = createVideoThumbnailFromUrl(videoObject.icon.url, video, ThumbnailType.THUMBNAIL)
463
+  const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE)
468 464
 
469 465
   let thumbnailModel: ThumbnailModel
470 466
   if (waitThumbnail === true) {
@@ -477,18 +473,12 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor
477 473
     const videoCreated = await video.save(sequelizeOptions)
478 474
     videoCreated.VideoChannel = channelActor.VideoChannel
479 475
 
480
-    if (thumbnailModel) {
481
-      thumbnailModel.videoId = videoCreated.id
482
-
483
-      videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t }))
484
-    }
476
+    if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
485 477
 
486 478
     // FIXME: use icon URL instead
487 479
     const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName()))
488 480
     const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
489
-    previewModel.videoId = videoCreated.id
490
-
491
-    videoCreated.addThumbnail(await previewModel.save({ transaction: t }))
481
+    if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
492 482
 
493 483
     // Process files
494 484
     const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject)
@@ -594,7 +584,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
594 584
     throw new Error('Cannot find video files for ' + video.url)
595 585
   }
596 586
 
597
-  const attributes: object[] = [] // FIXME: add typings
587
+  const attributes: FilteredModelAttributes<VideoFileModel>[] = []
598 588
   for (const fileUrl of fileUrls) {
599 589
     // Fetch associated magnet uri
600 590
     const magnet = videoObject.url.find(u => {
@@ -629,7 +619,7 @@ function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObj
629 619
   const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[]
630 620
   if (playlistUrls.length === 0) return []
631 621
 
632
-  const attributes: object[] = [] // FIXME: add typings
622
+  const attributes: FilteredModelAttributes<VideoStreamingPlaylistModel>[] = []
633 623
   for (const playlistUrlObject of playlistUrls) {
634 624
     const segmentsSha256UrlObject = playlistUrlObject.tag
635 625
                                                      .find(t => {

+ 11
- 7
server/lib/files-cache/abstract-video-static-file-cache.ts View File

@@ -4,24 +4,28 @@ import { VideoModel } from '../../models/video/video'
4 4
 import { fetchRemoteVideoStaticFile } from '../activitypub'
5 5
 import * as memoizee from 'memoizee'
6 6
 
7
+type GetFilePathResult = { isOwned: boolean, path: string } | undefined
8
+
7 9
 export abstract class AbstractVideoStaticFileCache <T> {
8 10
 
9
-  getFilePath: (params: T) => Promise<string>
11
+  getFilePath: (params: T) => Promise<GetFilePathResult>
10 12
 
11
-  abstract getFilePathImpl (params: T): Promise<string>
13
+  abstract getFilePathImpl (params: T): Promise<GetFilePathResult>
12 14
 
13 15
   // Load and save the remote file, then return the local path from filesystem
14
-  protected abstract loadRemoteFile (key: string): Promise<string>
16
+  protected abstract loadRemoteFile (key: string): Promise<GetFilePathResult>
15 17
 
16 18
   init (max: number, maxAge: number) {
17 19
     this.getFilePath = memoizee(this.getFilePathImpl, {
18 20
       maxAge,
19 21
       max,
20 22
       promise: true,
21
-      dispose: (value: string) => {
22
-        remove(value)
23
-          .then(() => logger.debug('%s evicted from %s', value, this.constructor.name))
24
-          .catch(err => logger.error('Cannot remove %s from cache %s.', value, this.constructor.name, { err }))
23
+      dispose: (result: GetFilePathResult) => {
24
+        if (result.isOwned !== true) {
25
+          remove(result.path)
26
+            .then(() => logger.debug('%s removed from %s', result.path, this.constructor.name))
27
+            .catch(err => logger.error('Cannot remove %s from cache %s.', result.path, this.constructor.name, { err }))
28
+        }
25 29
       }
26 30
     })
27 31
   }

+ 7
- 2
server/lib/files-cache/videos-caption-cache.ts View File

@@ -4,6 +4,7 @@ import { VideoModel } from '../../models/video/video'
4 4
 import { VideoCaptionModel } from '../../models/video/video-caption'
5 5
 import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
6 6
 import { CONFIG } from '../../initializers/config'
7
+import { logger } from '../../helpers/logger'
7 8
 
8 9
 type GetPathParam = { videoId: string, language: string }
9 10
 
@@ -24,13 +25,15 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
24 25
     const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language)
25 26
     if (!videoCaption) return undefined
26 27
 
27
-    if (videoCaption.isOwned()) return join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName())
28
+    if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) }
28 29
 
29 30
     const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language
30 31
     return this.loadRemoteFile(key)
31 32
   }
32 33
 
33 34
   protected async loadRemoteFile (key: string) {
35
+    logger.debug('Loading remote caption file %s.', key)
36
+
34 37
     const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER)
35 38
 
36 39
     const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language)
@@ -46,7 +49,9 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
46 49
     const remoteStaticPath = videoCaption.getCaptionStaticPath()
47 50
     const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName())
48 51
 
49
-    return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
52
+    const path = await this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
53
+
54
+    return { isOwned: false, path }
50 55
   }
51 56
 }
52 57
 

+ 4
- 2
server/lib/files-cache/videos-preview-cache.ts View File

@@ -20,7 +20,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
20 20
     const video = await VideoModel.loadByUUIDWithFile(videoUUID)
21 21
     if (!video) return undefined
22 22
 
23
-    if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename)
23
+    if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) }
24 24
 
25 25
     return this.loadRemoteFile(videoUUID)
26 26
   }
@@ -35,7 +35,9 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
35 35
     const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)
36 36
     const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename)
37 37
 
38
-    return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
38
+    const path = await this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath)
39
+
40
+    return { isOwned: false, path }
39 41
   }
40 42
 }
41 43
 

+ 7
- 13
server/lib/job-queue/handlers/video-import.ts View File

@@ -18,7 +18,7 @@ import { Notifier } from '../../notifier'
18 18
 import { CONFIG } from '../../../initializers/config'
19 19
 import { sequelizeTypescript } from '../../../initializers/database'
20 20
 import { ThumbnailModel } from '../../../models/video/thumbnail'
21
-import { createVideoThumbnailFromUrl, generateVideoThumbnail } from '../../thumbnail'
21
+import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail'
22 22
 import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
23 23
 
24 24
 type VideoImportYoutubeDLPayload = {
@@ -150,17 +150,17 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
150 150
     // Process thumbnail
151 151
     let thumbnailModel: ThumbnailModel
152 152
     if (options.downloadThumbnail && options.thumbnailUrl) {
153
-      thumbnailModel = await createVideoThumbnailFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.THUMBNAIL)
153
+      thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE)
154 154
     } else if (options.generateThumbnail || options.downloadThumbnail) {
155
-      thumbnailModel = await generateVideoThumbnail(videoImport.Video, videoFile, ThumbnailType.THUMBNAIL)
155
+      thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE)
156 156
     }
157 157
 
158 158
     // Process preview
159 159
     let previewModel: ThumbnailModel
160 160
     if (options.downloadPreview && options.thumbnailUrl) {
161
-      previewModel = await createVideoThumbnailFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW)
161
+      previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW)
162 162
     } else if (options.generatePreview || options.downloadPreview) {
163
-      previewModel = await generateVideoThumbnail(videoImport.Video, videoFile, ThumbnailType.PREVIEW)
163
+      previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW)
164 164
     }
165 165
 
166 166
     // Create torrent
@@ -180,14 +180,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
180 180
       video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED
181 181
       await video.save({ transaction: t })
182 182
 
183
-      if (thumbnailModel) {
184
-        thumbnailModel.videoId = video.id
185
-        video.addThumbnail(await thumbnailModel.save({ transaction: t }))
186
-      }
187
-      if (previewModel) {
188
-        previewModel.videoId = video.id
189
-        video.addThumbnail(await previewModel.save({ transaction: t }))
190
-      }
183
+      if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t)
184
+      if (previewModel) await video.addAndSaveThumbnail(previewModel, t)
191 185
 
192 186
       // Now we can federate the video (reload from database, we need more attributes)
193 187
       const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)

+ 2
- 0
server/lib/oauth-model.ts View File

@@ -39,6 +39,8 @@ function clearCacheByToken (token: string) {
39 39
 function getAccessToken (bearerToken: string) {
40 40
   logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
41 41
 
42
+  if (!bearerToken) return Bluebird.resolve(undefined)
43
+
42 44
   if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken])
43 45
 
44 46
   return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)

+ 13
- 13
server/lib/thumbnail.ts View File

@@ -12,37 +12,37 @@ import { VideoPlaylistModel } from '../models/video/video-playlist'
12 12
 
13 13
 type ImageSize = { height: number, width: number }
14 14
 
15
-function createPlaylistThumbnailFromExisting (inputPath: string, playlist: VideoPlaylistModel, keepOriginal = false, size?: ImageSize) {
15
+function createPlaylistMiniatureFromExisting (inputPath: string, playlist: VideoPlaylistModel, keepOriginal = false, size?: ImageSize) {
16 16
   const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
17
-  const type = ThumbnailType.THUMBNAIL
17
+  const type = ThumbnailType.MINIATURE
18 18
 
19 19
   const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height }, keepOriginal)
20 20
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
21 21
 }
22 22
 
23
-function createPlaylistThumbnailFromUrl (url: string, playlist: VideoPlaylistModel, size?: ImageSize) {
23
+function createPlaylistMiniatureFromUrl (url: string, playlist: VideoPlaylistModel, size?: ImageSize) {
24 24
   const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
25
-  const type = ThumbnailType.THUMBNAIL
25
+  const type = ThumbnailType.MINIATURE
26 26
 
27 27
   const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
28 28
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url })
29 29
 }
30 30
 
31
-function createVideoThumbnailFromUrl (url: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
31
+function createVideoMiniatureFromUrl (url: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
32 32
   const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
33 33
   const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height })
34 34
 
35 35
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url })
36 36
 }
37 37
 
38
-function createVideoThumbnailFromExisting (inputPath: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
38
+function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
39 39
   const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
40 40
   const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height })
41 41
 
42 42
   return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
43 43
 }
44 44
 
45
-function generateVideoThumbnail (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
45
+function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
46 46
   const input = video.getVideoFilePath(videoFile)
47 47
 
48 48
   const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type)
@@ -68,12 +68,12 @@ function createPlaceholderThumbnail (url: string, video: VideoModel, type: Thumb
68 68
 // ---------------------------------------------------------------------------
69 69
 
70 70
 export {
71
-  generateVideoThumbnail,
72
-  createVideoThumbnailFromUrl,
73
-  createVideoThumbnailFromExisting,
71
+  generateVideoMiniature,
72
+  createVideoMiniatureFromUrl,
73
+  createVideoMiniatureFromExisting,
74 74
   createPlaceholderThumbnail,
75
-  createPlaylistThumbnailFromUrl,
76
-  createPlaylistThumbnailFromExisting
75
+  createPlaylistMiniatureFromUrl,
76
+  createPlaylistMiniatureFromExisting
77 77
 }
78 78
 
79 79
 function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) {
@@ -95,7 +95,7 @@ function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?:
95 95
     ? video.Thumbnails.find(t => t.type === type)
96 96
     : undefined
97 97
 
98
-  if (type === ThumbnailType.THUMBNAIL) {
98
+  if (type === ThumbnailType.MINIATURE) {
99 99
     const filename = video.generateThumbnailName()
100 100
     const basePath = CONFIG.STORAGE.THUMBNAILS_DIR
101 101
 

+ 2
- 0
server/middlewares/oauth.ts View File

@@ -35,6 +35,8 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
35 35
 
36 36
   logger.debug('Checking socket access token %s.', accessToken)
37 37
 
38
+  if (!accessToken) return next(new Error('No access token provided'))
39
+
38 40
   getAccessToken(accessToken)
39 41
     .then(tokenDB => {
40 42
       const now = new Date()

+ 1
- 0
server/middlewares/validators/videos/videos.ts View File

@@ -68,6 +68,7 @@ const videosAddValidator = getCommonVideoEditAttributes().concat([
68 68
     if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
69 69
 
70 70
     const isAble = await user.isAbleToUploadVideo(videoFile)
71
+
71 72
     if (isAble === false) {
72 73
       res.status(403)
73 74
          .json({ error: 'The user video quota is exceeded with this video.' })

+ 5
- 5
server/models/account/account-blocklist.ts View File

@@ -8,22 +8,22 @@ enum ScopeNames {
8 8
   WITH_ACCOUNTS = 'WITH_ACCOUNTS'
9 9
 }
10 10
 
11
-@Scopes({
11
+@Scopes(() => ({
12 12
   [ScopeNames.WITH_ACCOUNTS]: {
13 13
     include: [
14 14
       {
15
-        model: () => AccountModel,
15
+        model: AccountModel,
16 16
         required: true,
17 17
         as: 'ByAccount'
18 18
       },
19 19
       {
20
-        model: () => AccountModel,
20
+        model: AccountModel,
21 21
         required: true,
22 22
         as: 'BlockedAccount'
23 23
       }
24 24
     ]
25 25
   }
26
-})
26
+}))
27 27
 
28 28
 @Table({
29 29
   tableName: 'accountBlocklist',
@@ -83,7 +83,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
83 83
       attributes: [ 'accountId', 'id' ],
84 84
       where: {
85 85
         accountId: {
86
-          [Op.any]: accountIds
86
+          [Op.in]: accountIds // FIXME: sequelize ANY seems broken
87 87
         },
88 88
         targetAccountId
89 89
       },

+ 5
- 5
server/models/account/account.ts View File

@@ -33,15 +33,15 @@ export enum ScopeNames {
33 33
   SUMMARY = 'SUMMARY'
34 34
 }
35 35
 
36
-@DefaultScope({
36
+@DefaultScope(() => ({
37 37
   include: [
38 38
     {
39
-      model: () => ActorModel, // Default scope includes avatar and server
39
+      model: ActorModel, // Default scope includes avatar and server
40 40
       required: true
41 41
     }
42 42
   ]
43
-})
44
-@Scopes({
43
+}))
44
+@Scopes(() => ({
45 45
   [ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => {
46 46
     return {
47 47
       attributes: [ 'id', 'name' ],
@@ -66,7 +66,7 @@ export enum ScopeNames {
66 66
       ]
67 67
     }
68 68
   }
69
-})
69
+}))
70 70
 @Table({
71 71
   tableName: 'account',
72 72
   indexes: [

+ 22
- 22
server/models/account/user-notification.ts View File

@@ -6,7 +6,7 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use
6 6
 import { UserModel } from './user'
7 7
 import { VideoModel } from '../video/video'
8 8
 import { VideoCommentModel } from '../video/video-comment'
9
-import { FindOptions, Op } from 'sequelize'
9
+import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
10 10
 import { VideoChannelModel } from '../video/video-channel'
11 11
 import { AccountModel } from './account'
12 12
 import { VideoAbuseModel } from '../video/video-abuse'
@@ -24,17 +24,17 @@ enum ScopeNames {
24 24
 function buildActorWithAvatarInclude () {
25 25
   return {
26 26
     attributes: [ 'preferredUsername' ],
27
-    model: () => ActorModel.unscoped(),
27
+    model: ActorModel.unscoped(),
28 28
     required: true,
29 29
     include: [
30 30
       {
31 31
         attributes: [ 'filename' ],
32
-        model: () => AvatarModel.unscoped(),
32
+        model: AvatarModel.unscoped(),
33 33
         required: false
34 34
       },
35 35
       {
36 36
         attributes: [ 'host' ],
37
-        model: () => ServerModel.unscoped(),
37
+        model: ServerModel.unscoped(),
38 38
         required: false
39 39
       }
40 40
     ]
@@ -44,7 +44,7 @@ function buildActorWithAvatarInclude () {
44 44
 function buildVideoInclude (required: boolean) {
45 45
   return {
46 46
     attributes: [ 'id', 'uuid', 'name' ],
47
-    model: () => VideoModel.unscoped(),
47
+    model: VideoModel.unscoped(),
48 48
     required
49 49
   }
50 50
 }
@@ -53,7 +53,7 @@ function buildChannelInclude (required: boolean, withActor = false) {
53 53
   return {
54 54
     required,
55 55
     attributes: [ 'id', 'name' ],
56
-    model: () => VideoChannelModel.unscoped(),
56
+    model: VideoChannelModel.unscoped(),
57 57
     include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
58 58
   }
59 59
 }
@@ -62,12 +62,12 @@ function buildAccountInclude (required: boolean, withActor = false) {
62 62
   return {
63 63
     required,
64 64
     attributes: [ 'id', 'name' ],
65
-    model: () => AccountModel.unscoped(),
65
+    model: AccountModel.unscoped(),
66 66
     include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
67 67
   }
68 68
 }
69 69
 
70
-@Scopes({
70
+@Scopes(() => ({
71 71
   [ScopeNames.WITH_ALL]: {
72 72
     include: [
73 73
       Object.assign(buildVideoInclude(false), {
@@ -76,7 +76,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
76 76
 
77 77
       {
78 78
         attributes: [ 'id', 'originCommentId' ],
79
-        model: () => VideoCommentModel.unscoped(),
79
+        model: VideoCommentModel.unscoped(),
80 80
         required: false,
81 81
         include: [
82 82
           buildAccountInclude(true, true),
@@ -86,56 +86,56 @@ function buildAccountInclude (required: boolean, withActor = false) {
86 86
 
87 87
       {
88 88
         attributes: [ 'id' ],
89
-        model: () => VideoAbuseModel.unscoped(),
89
+        model: VideoAbuseModel.unscoped(),
90 90
         required: false,
91 91
         include: [ buildVideoInclude(true) ]
92 92
       },
93 93
 
94 94
       {
95 95
         attributes: [ 'id' ],
96
-        model: () => VideoBlacklistModel.unscoped(),
96
+        model: VideoBlacklistModel.unscoped(),
97 97
         required: false,
98 98
         include: [ buildVideoInclude(true) ]
99 99
       },
100 100
 
101 101
       {
102 102
         attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
103
-        model: () => VideoImportModel.unscoped(),
103
+        model: VideoImportModel.unscoped(),
104 104
         required: false,
105 105
         include: [ buildVideoInclude(false) ]
106 106
       },
107 107
 
108 108
       {
109 109
         attributes: [ 'id', 'state' ],
110
-        model: () => ActorFollowModel.unscoped(),
110
+        model: ActorFollowModel.unscoped(),
111 111
         required: false,
112 112
         include: [
113 113
           {
114 114
             attributes: [ 'preferredUsername' ],
115
-            model: () => ActorModel.unscoped(),
115
+            model: ActorModel.unscoped(),
116 116
             required: true,
117 117
             as: 'ActorFollower',
118 118
             include: [
119 119
               {
120 120
                 attributes: [ 'id', 'name' ],
121
-                model: () => AccountModel.unscoped(),
121
+                model: AccountModel.unscoped(),
122 122
                 required: true
123 123
               },
124 124
               {
125 125
                 attributes: [ 'filename' ],
126
-                model: () => AvatarModel.unscoped(),
126
+                model: AvatarModel.unscoped(),
127 127
                 required: false
128 128
               },
129 129
               {
130 130
                 attributes: [ 'host' ],
131
-                model: () => ServerModel.unscoped(),
131
+                model: ServerModel.unscoped(),
132 132
                 required: false
133 133
               }
134 134
             ]
135 135
           },
136 136
           {
137 137
             attributes: [ 'preferredUsername' ],
138
-            model: () => ActorModel.unscoped(),
138
+            model: ActorModel.unscoped(),
139 139
             required: true,
140 140
             as: 'ActorFollowing',
141 141
             include: [
@@ -147,9 +147,9 @@ function buildAccountInclude (required: boolean, withActor = false) {
147 147
       },
148 148
 
149 149
       buildAccountInclude(false, true)
150
-    ] as any // FIXME: sequelize typings
150
+    ]
151 151
   }
152
-})
152
+}))
153 153
 @Table({
154 154
   tableName: 'userNotification',
155 155
   indexes: [
@@ -212,7 +212,7 @@ function buildAccountInclude (required: boolean, withActor = false) {
212 212
         }
213 213
       }
214 214
     }
215
-  ] as any // FIXME: sequelize typings
215
+  ] as (ModelIndexesOptions & { where?: WhereOptions })[]
216 216
 })
217 217
 export class UserNotificationModel extends Model<UserNotificationModel> {
218 218
 
@@ -357,7 +357,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
357 357
       where: {
358 358
         userId,
359 359
         id: {
360
-          [Op.any]: notificationIds
360
+          [Op.in]: notificationIds // FIXME: sequelize ANY seems broken
361 361
         }
362 362
       }
363 363
     }

+ 27
- 31
server/models/account/user.ts View File

@@ -1,4 +1,4 @@
1
-import * as Sequelize from 'sequelize'
1
+import { FindOptions, literal, Op, QueryTypes } from 'sequelize'
2 2
 import {
3 3
   AfterDestroy,
4 4
   AfterUpdate,
@@ -56,33 +56,33 @@ enum ScopeNames {
56 56
   WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
57 57
 }
58 58
 
59
-@DefaultScope({
59
+@DefaultScope(() => ({
60 60
   include: [
61 61
     {
62
-      model: () => AccountModel,
62
+      model: AccountModel,
63 63
       required: true
64 64
     },
65 65
     {
66
-      model: () => UserNotificationSettingModel,
66
+      model: UserNotificationSettingModel,
67 67
       required: true
68 68
     }
69 69
   ]
70
-})
71
-@Scopes({
70
+}))
71
+@Scopes(() => ({
72 72
   [ScopeNames.WITH_VIDEO_CHANNEL]: {
73 73
     include: [
74 74
       {
75
-        model: () => AccountModel,
75
+        model: AccountModel,
76 76
         required: true,
77
-        include: [ () => VideoChannelModel ]
77
+        include: [ VideoChannelModel ]
78 78
       },
79 79
       {
80
-        model: () => UserNotificationSettingModel,
80
+        model: UserNotificationSettingModel,
81 81
         required: true
82 82
       }
83
-    ] as any // FIXME: sequelize typings
83
+    ]
84 84
   }
85
-})
85
+}))
86 86
 @Table({
87 87
   tableName: 'user',
88 88
   indexes: [
@@ -233,26 +233,26 @@ export class UserModel extends Model<UserModel> {
233 233
     let where = undefined
234 234
     if (search) {
235 235
       where = {
236
-        [Sequelize.Op.or]: [
236
+        [Op.or]: [
237 237
           {
238 238
             email: {
239
-              [Sequelize.Op.iLike]: '%' + search + '%'
239
+              [Op.iLike]: '%' + search + '%'
240 240
             }
241 241
           },
242 242
           {
243 243
             username: {
244
-              [ Sequelize.Op.iLike ]: '%' + search + '%'
244
+              [ Op.iLike ]: '%' + search + '%'
245 245
             }
246 246
           }
247 247
         ]
248 248
       }
249 249
     }
250 250
 
251
-    const query = {
251
+    const query: FindOptions = {
252 252
       attributes: {
253 253
         include: [
254 254
           [
255
-            Sequelize.literal(
255
+            literal(
256 256
               '(' +
257 257
                 'SELECT COALESCE(SUM("size"), 0) ' +
258 258
                 'FROM (' +
@@ -265,7 +265,7 @@ export class UserModel extends Model<UserModel> {
265 265
               ')'
266 266
             ),
267 267
             'videoQuotaUsed'
268
-          ] as any // FIXME: typings
268
+          ]
269 269
         ]
270 270
       },
271 271
       offset: start,
@@ -291,7 +291,7 @@ export class UserModel extends Model<UserModel> {
291 291
     const query = {
292 292
       where: {
293 293
         role: {
294
-          [Sequelize.Op.in]: roles
294
+          [Op.in]: roles
295 295
         }
296 296
       }
297 297
     }
@@ -387,7 +387,7 @@ export class UserModel extends Model<UserModel> {
387 387
 
388 388
     const query = {
389 389
       where: {
390
-        [ Sequelize.Op.or ]: [ { username }, { email } ]
390
+        [ Op.or ]: [ { username }, { email } ]
391 391
       }
392 392
     }
393 393
 
@@ -510,7 +510,7 @@ export class UserModel extends Model<UserModel> {
510 510
     const query = {
511 511
       where: {
512 512
         username: {
513
-          [ Sequelize.Op.like ]: `%${search}%`
513
+          [ Op.like ]: `%${search}%`
514 514
         }
515 515
       },
516 516
       limit: 10
@@ -591,15 +591,11 @@ export class UserModel extends Model<UserModel> {
591 591
 
592 592
     const uploadedTotal = videoFile.size + totalBytes
593 593
     const uploadedDaily = videoFile.size + totalBytesDaily
594
-    if (this.videoQuotaDaily === -1) {
595
-      return uploadedTotal < this.videoQuota
596
-    }
597
-    if (this.videoQuota === -1) {
598
-      return uploadedDaily < this.videoQuotaDaily
599
-    }
600 594
 
601
-    return (uploadedTotal < this.videoQuota) &&
602
-        (uploadedDaily < this.videoQuotaDaily)
595
+    if (this.videoQuotaDaily === -1) return uploadedTotal < this.videoQuota
596
+    if (this.videoQuota === -1) return uploadedDaily < this.videoQuotaDaily
597
+
598
+    return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily
603 599
   }
604 600
 
605 601
   private static generateUserQuotaBaseSQL (where?: string) {
@@ -619,14 +615,14 @@ export class UserModel extends Model<UserModel> {
619 615
   private static getTotalRawQuery (query: string, userId: number) {
620 616
     const options = {
621 617
       bind: { userId },
622
-      type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT
618
+      type: QueryTypes.SELECT as QueryTypes.SELECT
623 619
     }
624 620
 
625
-    return UserModel.sequelize.query<{ total: number }>(query, options)
621
+    return UserModel.sequelize.query<{ total: string }>(query, options)
626 622
                     .then(([ { total } ]) => {
627 623
                       if (total === null) return 0
628 624
 
629
-                      return parseInt(total + '', 10)
625
+                      return parseInt(total, 10)
630 626
                     })
631 627
   }
632 628
 }

+ 24
- 23
server/models/activitypub/actor.ts View File

@@ -56,46 +56,46 @@ export const unusedActorAttributesForAPI = [
56 56
   'updatedAt'
57 57
 ]
58 58
 
59
-@DefaultScope({
59
+@DefaultScope(() => ({
60 60
   include: [
61 61
     {
62
-      model: () => ServerModel,
62
+      model: ServerModel,
63 63
       required: false
64 64
     },
65 65
     {
66
-      model: () => AvatarModel,
66
+      model: AvatarModel,
67 67
       required: false
68 68
     }
69 69
   ]
70
-})
71
-@Scopes({
70
+}))
71
+@Scopes(() => ({
72 72
   [ScopeNames.FULL]: {
73 73
     include: [
74 74
       {
75
-        model: () => AccountModel.unscoped(),
75
+        model: AccountModel.unscoped(),
76 76
         required: false
77 77
       },
78 78
       {
79
-        model: () => VideoChannelModel.unscoped(),
79
+        model: VideoChannelModel.unscoped(),
80 80
         required: false,
81 81
         include: [
82 82
           {
83
-            model: () => AccountModel,
83
+            model: AccountModel,
84 84
             required: true
85 85
           }
86 86
         ]
87 87
       },
88 88
       {
89
-        model: () => ServerModel,
89
+        model: ServerModel,
90 90
         required: false
91 91
       },
92 92
       {
93
-        model: () => AvatarModel,
93
+        model: AvatarModel,
94 94
         required: false
95 95
       }
96
-    ] as any // FIXME: sequelize typings
96
+    ]
97 97
   }
98
-})
98
+}))
99 99
 @Table({
100 100
   tableName: 'actor',
101 101
   indexes: [
@@ -131,7 +131,7 @@ export const unusedActorAttributesForAPI = [
131 131
 export class ActorModel extends Model<ActorModel> {
132 132
 
133 133
   @AllowNull(false)
134
-  @Column({ type: DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)) }) // FIXME: sequelize typings
134
+  @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES)))
135 135
   type: ActivityPubActorType
136 136
 
137 137
   @AllowNull(false)
@@ -280,14 +280,16 @@ export class ActorModel extends Model<ActorModel> {
280 280
               attributes: [ 'id' ],
281 281
               model: VideoChannelModel.unscoped(),
282 282
               required: true,
283
-              include: {
284
-                attributes: [ 'id' ],
285
-                model: VideoModel.unscoped(),
286
-                required: true,
287
-                where: {
288
-                  id: videoId
283
+              include: [
284
+                {
285
+                  attributes: [ 'id' ],
286
+                  model: VideoModel.unscoped(),
287
+                  required: true,
288
+                  where: {
289
+                    id: videoId
290
+                  }
289 291
                 }
290
-              }
292
+              ]
291 293
             }
292 294
           ]
293 295
         }
@@ -295,7 +297,7 @@ export class ActorModel extends Model<ActorModel> {
295 297
       transaction
296 298
     }
297 299
 
298
-    return ActorModel.unscoped().findOne(query as any) // FIXME: typings
300
+    return ActorModel.unscoped().findOne(query)
299 301
   }
300 302
 
301 303
   static isActorUrlExist (url: string) {
@@ -389,8 +391,7 @@ export class ActorModel extends Model<ActorModel> {
389 391
   }
390 392
 
391 393
   static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) {
392
-    // FIXME: typings
393
-    return (ActorModel as any).increment(column, {
394
+    return ActorModel.increment(column, {
394 395
       by,
395 396
       where: {
396 397
         id

+ 3
- 3
server/models/application/application.ts View File

@@ -1,14 +1,14 @@
1 1
 import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
2 2
 import { AccountModel } from '../account/account'
3 3
 
4
-@DefaultScope({
4
+@DefaultScope(() => ({
5 5
   include: [
6 6
     {
7
-      model: () => AccountModel,
7
+      model: AccountModel,
8 8
       required: true
9 9
     }
10 10
   ]
11
-})
11
+}))
12 12
 @Table({
13 13
   tableName: 'application'
14 14
 })

+ 2
- 2
server/models/oauth/oauth-client.ts View File

@@ -24,10 +24,10 @@ export class OAuthClientModel extends Model<OAuthClientModel> {
24 24
   @Column
25 25
   clientSecret: string
26 26
 
27
-  @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings
27
+  @Column(DataType.ARRAY(DataType.STRING))
28 28
   grants: string[]
29 29
 
30
-  @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings
30
+  @Column(DataType.ARRAY(DataType.STRING))
31 31
   redirectUris: string[]
32 32
 
33 33
   @CreatedAt

+ 12
- 10
server/models/oauth/oauth-token.ts View File

@@ -34,30 +34,30 @@ enum ScopeNames {
34 34
   WITH_USER = 'WITH_USER'
35 35
 }
36 36
 
37
-@Scopes({
37
+@Scopes(() => ({
38 38
   [ScopeNames.WITH_USER]: {
39 39
     include: [
40 40
       {
41
-        model: () => UserModel.unscoped(),
41
+        model: UserModel.unscoped(),
42 42
         required: true,
43 43
         include: [
44 44
           {
45 45
             attributes: [ 'id' ],
46
-            model: () => AccountModel.unscoped(),
46
+            model: AccountModel.unscoped(),
47 47
             required: true,
48 48
             include: [
49 49
               {
50 50
                 attributes: [ 'id', 'url' ],
51
-                model: () => ActorModel.unscoped(),
51
+                model: ActorModel.unscoped(),
52 52
                 required: true
53 53
               }
54 54
             ]
55 55
           }
56 56
         ]
57 57
       }
58
-    ] as any // FIXME: sequelize typings
58
+    ]
59 59
   }
60
-})
60
+}))
61 61
 @Table({
62 62
   tableName: 'oAuthToken',
63 63
   indexes: [
@@ -167,11 +167,13 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
167 167
       }
168 168
     }
169 169
 
170
-    return OAuthTokenModel.scope(ScopeNames.WITH_USER).findOne(query).then(token => {
171
-      if (token) token['user'] = token.User
170
+    return OAuthTokenModel.scope(ScopeNames.WITH_USER)
171
+                          .findOne(query)
172
+                          .then(token => {
173
+                            if (token) token[ 'user' ] = token.User
172 174
 
173
-      return token
174
-    })
175
+                            return token
176
+                          })
175 177
   }
176 178
 
177 179
   static getByRefreshTokenAndPopulateUser (refreshToken: string) {

+ 26
- 30
server/models/redundancy/video-redundancy.ts View File

@@ -13,7 +13,7 @@ import {
13 13
   UpdatedAt
14 14
 } from 'sequelize-typescript'
15 15
 import { ActorModel } from '../activitypub/actor'
16
-import { getVideoSort, throwIfNotValid } from '../utils'
16
+import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils'
17 17
 import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
18 18
 import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
19 19
 import { VideoFileModel } from '../video/video-file'
@@ -27,7 +27,7 @@ import { ServerModel } from '../server/server'
27 27
 import { sample } from 'lodash'
28 28
 import { isTestInstance } from '../../helpers/core-utils'
29 29
 import * as Bluebird from 'bluebird'
30
-import * as Sequelize from 'sequelize'
30
+import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize'
31 31
 import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
32 32
 import { CONFIG } from '../../initializers/config'
33 33
 
@@ -35,32 +35,32 @@ export enum ScopeNames {
35 35
   WITH_VIDEO = 'WITH_VIDEO'
36 36
 }
37 37
 
38
-@Scopes({
38
+@Scopes(() => ({
39 39
   [ ScopeNames.WITH_VIDEO ]: {
40 40
     include: [
41 41
       {
42
-        model: () => VideoFileModel,
42
+        model: VideoFileModel,
43 43
         required: false,
44 44
         include: [
45 45
           {
46
-            model: () => VideoModel,
46
+            model: VideoModel,
47 47
             required: true
48 48
           }
49 49
         ]
50 50
       },
51 51
       {
52
-        model: () => VideoStreamingPlaylistModel,
52
+        model: VideoStreamingPlaylistModel,
53 53
         required: false,
54 54
         include: [
55 55
           {
56
-            model: () => VideoModel,
56
+            model: VideoModel,
57 57
             required: true
58 58
           }
59 59
         ]
60 60
       }
61
-    ] as any // FIXME: sequelize typings
61
+    ]
62 62
   }
63
-})
63
+}))
64 64
 
65 65
 @Table({
66 66
   tableName: 'videoRedundancy',
@@ -192,7 +192,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
192 192
     return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
193 193
   }
194 194
 
195
-  static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
195
+  static loadByUrl (url: string, transaction?: Transaction) {
196 196
     const query = {
197 197
       where: {
198 198
         url
@@ -292,7 +292,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
292 292
       where: {
293 293
         privacy: VideoPrivacy.PUBLIC,
294 294
         views: {
295
-          [ Sequelize.Op.gte ]: minViews
295
+          [ Op.gte ]: minViews
296 296
         }
297 297
       },
298 298
       include: [
@@ -315,7 +315,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
315 315
         actorId: actor.id,
316 316
         strategy,
317 317
         createdAt: {
318
-          [ Sequelize.Op.lt ]: expiredDate
318
+          [ Op.lt ]: expiredDate
319 319
         }
320 320
       }
321 321
     }
@@ -326,7 +326,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
326 326
   static async getTotalDuplicated (strategy: VideoRedundancyStrategy) {
327 327
     const actor = await getServerActor()
328 328
 
329
-    const options = {
329
+    const query: FindOptions = {
330 330
       include: [
331 331
         {
332 332
           attributes: [],
@@ -340,12 +340,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
340 340
       ]
341 341
     }
342 342
 
343
-    return VideoFileModel.sum('size', options as any) // FIXME: typings
344
-      .then(v => {
345
-        if (!v || isNaN(v)) return 0
346
-
347
-        return v
348
-      })
343
+    return VideoFileModel.aggregate('size', 'SUM', query)
344
+      .then(result => parseAggregateResult(result))
349 345
   }
350 346
 
351 347
   static async listLocalExpired () {
@@ -355,7 +351,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
355 351
       where: {
356 352
         actorId: actor.id,
357 353
         expiresOn: {
358
-          [ Sequelize.Op.lt ]: new Date()
354
+          [ Op.lt ]: new Date()
359 355
         }
360 356
       }
361 357
     }
@@ -369,10 +365,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
369 365
     const query = {
370 366
       where: {
371 367
         actorId: {
372
-          [Sequelize.Op.ne]: actor.id
368
+          [Op.ne]: actor.id
373 369
         },
374 370
         expiresOn: {
375
-          [ Sequelize.Op.lt ]: new Date()
371
+          [ Op.lt ]: new Date()
376 372
         }
377 373
       }
378 374
     }
@@ -428,12 +424,12 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
428 424
   static async getStats (strategy: VideoRedundancyStrategy) {
429 425
     const actor = await getServerActor()
430 426
 
431
-    const query = {
427
+    const query: FindOptions = {
432 428
       raw: true,
433 429
       attributes: [
434
-        [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ],
435
-        [ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', Sequelize.col('videoId'))), 'totalVideos' ],
436
-        [ Sequelize.fn('COUNT', Sequelize.col('videoFileId')), 'totalVideoFiles' ]
430
+        [ fn('COALESCE', fn('SUM', col('VideoFile.size')), '0'), 'totalUsed' ],
431
+        [ fn('COUNT', fn('DISTINCT', col('videoId'))), 'totalVideos' ],
432
+        [ fn('COUNT', col('videoFileId')), 'totalVideoFiles' ]
437 433
       ],
438 434
       where: {
439 435
         strategy,
@@ -448,9 +444,9 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
448 444
       ]
449 445
     }
450 446
 
451
-    return VideoRedundancyModel.findOne(query as any) // FIXME: typings
447
+    return VideoRedundancyModel.findOne(query)
452 448
       .then((r: any) => ({
453
-        totalUsed: parseInt(r.totalUsed.toString(), 10),
449
+        totalUsed: parseAggregateResult(r.totalUsed),
454 450
         totalVideos: r.totalVideos,
455 451
         totalVideoFiles: r.totalVideoFiles
456 452
       }))
@@ -503,7 +499,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
503 499
   private static async buildVideoFileForDuplication () {
504 500
     const actor = await getServerActor()
505 501
 
506
-    const notIn = Sequelize.literal(
502
+    const notIn = literal(
507 503
       '(' +
508 504
         `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` +
509 505
       ')'
@@ -515,7 +511,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
515 511
       required: true,
516 512
       where: {
517 513
         id: {
518
-          [ Sequelize.Op.notIn ]: notIn
514
+          [ Op.notIn ]: notIn
519 515
         }
520 516
       }
521 517
     }

+ 4
- 4
server/models/server/server-blocklist.ts View File

@@ -9,11 +9,11 @@ enum ScopeNames {
9 9
   WITH_SERVER = 'WITH_SERVER'
10 10
 }
11 11
 
12
-@Scopes({
12
+@Scopes(() => ({
13 13
   [ScopeNames.WITH_ACCOUNT]: {
14 14
     include: [
15 15
       {
16
-        model: () => AccountModel,
16
+        model: AccountModel,
17 17
         required: true
18 18
       }
19 19
     ]
@@ -21,12 +21,12 @@ enum ScopeNames {
21 21
   [ScopeNames.WITH_SERVER]: {
22 22
     include: [
23 23
       {
24
-        model: () => ServerModel,
24
+        model: ServerModel,
25 25
         required: true
26 26
       }
27 27
     ]
28 28
   }
29
-})
29
+}))
30 30
 
31 31
 @Table({
32 32
   tableName: 'serverBlocklist',

+ 11
- 1
server/models/utils.ts View File

@@ -118,6 +118,15 @@ function buildWhereIdOrUUID (id: number | string) {
118 118
   return validator.isInt('' + id) ? { id } : { uuid: id }
119 119
 }
120 120
 
121
+function parseAggregateResult (result: any) {
122
+  if (!result) return 0
123
+
124
+  const total = parseInt(result + '', 10)
125
+  if (isNaN(total)) return 0
126
+
127
+  return total
128
+}
129
+
121 130
 // ---------------------------------------------------------------------------
122 131
 
123 132
 export {
@@ -131,7 +140,8 @@ export {
131 140
   buildServerIdsFollowedBy,
132 141
   buildTrigramSearchIndex,
133 142
   buildWhereIdOrUUID,
134
-  isOutdated
143
+  isOutdated,
144
+  parseAggregateResult
135 145
 }
136 146
 
137 147
 // ---------------------------------------------------------------------------

+ 1
- 1
server/models/video/tag.ts View File

@@ -75,7 +75,7 @@ export class TagModel extends Model<TagModel> {
75 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 79
                     .then(data => data.map(d => d.name))
80 80
   }
81 81
 }

+ 2
- 2
server/models/video/thumbnail.ts View File

@@ -75,8 +75,8 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
75 75
   updatedAt: Date
76 76
 
77 77
   private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
78
-    [ThumbnailType.THUMBNAIL]: {
79
-      label: 'thumbnail',
78
+    [ThumbnailType.MINIATURE]: {
79
+      label: 'miniature',
80 80
       directory: CONFIG.STORAGE.THUMBNAILS_DIR,
81 81
       staticPath: STATIC_PATHS.THUMBNAILS
82 82
     },

+ 5
- 8
server/models/video/video-caption.ts View File

@@ -12,7 +12,7 @@ import {
12 12
   Table,
13 13
   UpdatedAt
14 14
 } from 'sequelize-typescript'
15
-import { throwIfNotValid } from '../utils'
15
+import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
16 16
 import { VideoModel } from './video'
17 17
 import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
18 18
 import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
@@ -26,17 +26,17 @@ export enum ScopeNames {
26 26
   WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
27 27
 }
28 28
 
29
-@Scopes({
29
+@Scopes(() => ({
30 30
   [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
31 31
     include: [
32 32
       {
33 33
         attributes: [ 'uuid', 'remote' ],
34
-        model: () => VideoModel.unscoped(),
34
+        model: VideoModel.unscoped(),
35 35
         required: true
36 36
       }
37 37
     ]
38 38
   }
39
-})
39
+}))
40 40
 
41 41
 @Table({
42 42
   tableName: 'videoCaption',
@@ -97,12 +97,9 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
97 97
     const videoInclude = {
98 98
       model: VideoModel.unscoped(),
99 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 103
     const query = {
107 104
       where: {
108 105
         language

+ 7
- 7
server/models/video/video-change-ownership.ts View File

@@ -23,29 +23,29 @@ enum ScopeNames {
23 23
     }
24 24
   ]
25 25
 })
26
-@Scopes({
26
+@Scopes(() => ({
27 27
   [ScopeNames.FULL]: {
28 28
     include: [
29 29
       {
30
-        model: () => AccountModel,
30
+        model: AccountModel,
31 31
         as: 'Initiator',
32 32
         required: true
33 33
       },
34 34
       {
35
-        model: () => AccountModel,
35
+        model: AccountModel,
36 36
         as: 'NextOwner',
37 37
         required: true
38 38
       },
39 39
       {
40
-        model: () => VideoModel,
40
+        model: VideoModel,
41 41
         required: true,
42 42
         include: [
43
-          { model: () => VideoFileModel }
43
+          { model: VideoFileModel }
44 44
         ]
45 45
       }
46
-    ] as any // FIXME: sequelize typings
46
+    ]
47 47
   }
48
-})
48
+}))
49 49
 export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> {
50 50
   @CreatedAt
51 51
   createdAt: Date

+ 8
- 8
server/models/video/video-channel.ts View File

@@ -58,15 +58,15 @@ type AvailableForListOptions = {
58 58
   actorId: number
59 59
 }
60 60
 
61
-@DefaultScope({
61
+@DefaultScope(() => ({
62 62
   include: [
63 63
     {
64
-      model: () => ActorModel,
64
+      model: ActorModel,
65 65
       required: true
66 66
     }
67 67
   ]
68
-})
69
-@Scopes({
68
+}))
69
+@Scopes(() => ({
70 70
   [ScopeNames.SUMMARY]: (withAccount = false) => {
71 71
     const base: FindOptions = {
72 72
       attributes: [ 'name', 'description', 'id', 'actorId' ],
@@ -142,22 +142,22 @@ type AvailableForListOptions = {
142 142
   [ScopeNames.WITH_ACCOUNT]: {
143 143
     include: [
144 144
       {
145
-        model: () => AccountModel,
145
+        model: AccountModel,
146 146
         required: true
147 147
       }
148 148
     ]
149 149
   },
150 150
   [ScopeNames.WITH_VIDEOS]: {
151 151
     include: [
152
-      () => VideoModel
152
+      VideoModel
153 153
     ]
154 154
   },
155 155
   [ScopeNames.WITH_ACTOR]: {
156 156
     include: [
157
-      () => ActorModel
157
+      ActorModel
158 158
     ]
159 159
   }
160
-})
160
+}))
161 161
 @Table({
162 162
   tableName: 'videoChannel',
163 163
   indexes

+ 16
- 17
server/models/video/video-comment.ts View File

@@ -30,7 +30,7 @@ import { UserModel } from '../account/user'
30 30
 import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
31 31
 import { regexpCapture } from '../../helpers/regexp'
32 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 35
 enum ScopeNames {
36 36
   WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -39,7 +39,7 @@ enum ScopeNames {
39 39
   ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API'
40 40
 }
41 41
 
42
-@Scopes({
42
+@Scopes(() => ({
43 43
   [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => {
44 44
     return {
45 45
       attributes: {
@@ -63,34 +63,34 @@ enum ScopeNames {
63 63
           ]
64 64
         ]
65 65
       }
66
-    }
66
+    } as FindOptions
67 67
   },
68 68
   [ScopeNames.WITH_ACCOUNT]: {
69 69
     include: [
70 70
       {
71
-        model: () => AccountModel,
71
+        model: AccountModel,
72 72
         include: [
73 73
           {
74
-            model: () => ActorModel,
74
+            model: ActorModel,
75 75
             include: [
76 76
               {
77
-                model: () => ServerModel,
77
+                model: ServerModel,
78 78
                 required: false
79 79
               },
80 80
               {
81
-                model: () => AvatarModel,
81
+                model: AvatarModel,
82 82
                 required: false
83 83
               }
84 84
             ]
85 85
           }
86 86
         ]
87 87
       }
88
-    ] as any // FIXME: sequelize typings
88
+    ]
89 89
   },
90 90
   [ScopeNames.WITH_IN_REPLY_TO]: {
91 91
     include: [
92 92
       {
93
-        model: () => VideoCommentModel,
93
+        model: VideoCommentModel,
94 94
         as: 'InReplyToVideoComment'
95 95
       }
96 96
     ]
@@ -98,19 +98,19 @@ enum ScopeNames {
98 98
   [ScopeNames.WITH_VIDEO]: {
99 99
     include: [
100 100
       {
101
-        model: () => VideoModel,
101
+        model: VideoModel,
102 102
         required: true,
103 103
         include: [
104 104
           {
105
-            model: () => VideoChannelModel.unscoped(),
105
+            model: VideoChannelModel.unscoped(),
106 106
             required: true,
107 107
             include: [
108 108
               {
109
-                model: () => AccountModel,
109
+                model: AccountModel,
110 110
                 required: true,
111 111
                 include: [
112 112
                   {
113
-                    model: () => ActorModel,
113
+                    model: ActorModel,
114 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 125
 @Table({
126 126
   tableName: 'videoComment',
127 127
   indexes: [
@@ -313,8 +313,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
313 313
       }
314 314
     }
315 315
 
316
-    // FIXME: typings
317
-    const scopes: any[] = [
316
+    const scopes: (string | ScopeOptions)[] = [
318 317
       ScopeNames.WITH_ACCOUNT,
319 318
       {
320 319
         method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ]

+ 12
- 15
server/models/video/video-file.ts View File

@@ -19,11 +19,11 @@ import {
19 19
   isVideoFileSizeValid,
20 20
   isVideoFPSResolutionValid
21 21
 } from '../../helpers/custom-validators/videos'
22
-import { throwIfNotValid } from '../utils'
22
+import { parseAggregateResult, throwIfNotValid } from '../utils'
23 23
 import { VideoModel } from './video'
24
-import * as Sequelize from 'sequelize'
25 24
 import { VideoRedundancyModel } from '../redundancy/video-redundancy'
26 25
 import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
26
+import { FindOptions, QueryTypes, Transaction } from 'sequelize'
27 27
 
28 28
 @Table({
29 29
   tableName: 'videoFile',
@@ -97,15 +97,13 @@ export class VideoFileModel extends Model<VideoFileModel> {
97 97
   static doesInfohashExist (infoHash: string) {
98 98
     const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1'
99 99
     const options = {
100
-      type: Sequelize.QueryTypes.SELECT,
100
+      type: QueryTypes.SELECT,
101 101
       bind: { infoHash },
102 102
       raw: true
103 103
     }
104 104
 
105 105
     return VideoModel.sequelize.query(query, options)
106
-              .then(results => {
107
-                return results.length === 1
108
-              })
106
+              .then(results => results.length === 1)
109 107
   }
110 108
 
111 109
   static loadWithVideo (id: number) {
@@ -121,7 +119,7 @@ export class VideoFileModel extends Model<VideoFileModel> {
121 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 123
     const query = {
126 124
       include: [
127 125
         {
@@ -144,8 +142,8 @@ export class VideoFileModel extends Model<VideoFileModel> {
144 142
     return VideoFileModel.findAll(query)
145 143
   }
146 144
 
147
-  static async getStats () {
148
-    let totalLocalVideoFilesSize = await VideoFileModel.sum('size', {
145
+  static getStats () {
146
+    const query: FindOptions = {
149 147
       include: [
150 148
         {
151 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 164
   hasSameUniqueKeysThan (other: VideoFileModel) {

+ 6
- 4
server/models/video/video-format-utils.ts View File

@@ -59,7 +59,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
59 59
     views: video.views,
60 60
     likes: video.likes,
61 61
     dislikes: video.dislikes,
62
-    thumbnailPath: video.getThumbnailStaticPath(),
62
+    thumbnailPath: video.getMiniatureStaticPath(),
63 63
     previewPath: video.getPreviewStaticPath(),
64 64
     embedPath: video.getEmbedStaticPath(),
65 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 306
   return {
305 307
     type: 'Video' as 'Video',
306 308
     id: video.url,
@@ -326,10 +328,10 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
326 328
     subtitleLanguage,
327 329
     icon: {
328 330
       type: 'Image',
329
-      url: video.getThumbnail().getUrl(),
331
+      url: miniature.getUrl(),
330 332
       mediaType: 'image/jpeg',
331
-      width: video.getThumbnail().width,
332
-      height: video.getThumbnail().height
333
+      width: miniature.width,
334
+      height: miniature.height
333 335
     },
334 336
     url,
335 337
     likes: getVideoLikesActivityPubUrl(video),

+ 4
- 4
server/models/video/video-import.ts View File

@@ -21,18 +21,18 @@ import { VideoImport, VideoImportState } from '../../../shared'
21 21
 import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
22 22
 import { UserModel } from '../account/user'
23 23
 
24
-@DefaultScope({
24
+@DefaultScope(() => ({
25 25
   include: [
26 26
     {
27
-      model: () => UserModel.unscoped(),
27
+      model: UserModel.unscoped(),
28 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 32
       required: false
33 33
     }
34 34
   ]
35
-})
35
+}))
36 36
 
37 37
 @Table({
38 38
   tableName: 'videoImport',

+ 21
- 26
server/models/video/video-playlist.ts View File

@@ -42,7 +42,7 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub'
42 42
 import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model'
43 43
 import { ThumbnailModel } from './thumbnail'
44 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 47
 enum ScopeNames {
48 48
   AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
@@ -61,11 +61,11 @@ type AvailableForListOptions = {
61 61
   privateAndUnlisted?: boolean
62 62
 }
63 63
 
64
-@Scopes({
64
+@Scopes(() => ({
65 65
   [ ScopeNames.WITH_THUMBNAIL ]: {
66 66
     include: [
67 67
       {
68
-        model: () => ThumbnailModel,
68
+        model: ThumbnailModel,
69 69
         required: false
70 70
       }
71 71
     ]
@@ -73,21 +73,17 @@ type AvailableForListOptions = {
73 73
   [ ScopeNames.WITH_VIDEOS_LENGTH ]: {
74 74
     attributes: {
75 75
       include: [
76
-        [
77
-          fn('COUNT', 'toto'),
78
-          'coucou'
79
-        ],
80 76
         [
81 77
           literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'),
82 78
           'videosLength'
83 79
         ]
84 80
       ]
85 81
     }
86
-  },
82
+  } as FindOptions,
87 83
   [ ScopeNames.WITH_ACCOUNT ]: {
88 84
     include: [
89 85
       {
90
-        model: () => AccountModel,
86
+        model: AccountModel,
91 87
         required: true
92 88
       }
93 89
     ]
@@ -95,11 +91,11 @@ type AvailableForListOptions = {
95 91
   [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: {
96 92
     include: [
97 93
       {
98
-        model: () => AccountModel.scope(AccountScopeNames.SUMMARY),
94
+        model: AccountModel.scope(AccountScopeNames.SUMMARY),
99 95
         required: true
100 96
       },
101 97
       {
102
-        model: () => VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
98
+        model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY),
103 99
         required: false
104 100
       }
105 101
     ]
@@ -107,11 +103,11 @@ type AvailableForListOptions = {
107 103
   [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: {
108 104
     include: [
109 105
       {
110
-        model: () => AccountModel,
106
+        model: AccountModel,
111 107
         required: true
112 108
       },
113 109
       {
114
-        model: () => VideoChannelModel,
110
+        model: VideoChannelModel,
115 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 133
     if (options.privateAndUnlisted !== true) {
138 134
       whereAnd.push({
@@ -178,9 +174,9 @@ type AvailableForListOptions = {
178 174
           required: false
179 175
         }
180 176
       ]
181
-    }
177
+    } as FindOptions
182 178
   }
183
-})
179
+}))
184 180
 
185 181
 @Table({
186 182
   tableName: 'videoPlaylist',
@@ -269,6 +265,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
269 265
   VideoPlaylistElements: VideoPlaylistElementModel[]
270 266
 
271 267
   @HasOne(() => ThumbnailModel, {
268
+
272 269
     foreignKey: {
273 270
       name: 'videoPlaylistId',
274 271
       allowNull: true
@@ -294,7 +291,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
294 291
       order: getSort(options.sort)
295 292
     }
296 293
 
297
-    const scopes = [
294
+    const scopes: (string | ScopeOptions)[] = [
298 295
       {
299 296
         method: [
300 297
           ScopeNames.AVAILABLE_FOR_LIST,
@@ -306,7 +303,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
306 303
             privateAndUnlisted: options.privateAndUnlisted
307 304
           } as AvailableForListOptions
308 305
         ]
309
-      } as any, // FIXME: typings
306
+      },
310 307
       ScopeNames.WITH_VIDEOS_LENGTH,
311 308
       ScopeNames.WITH_THUMBNAIL
312 309
     ]
@@ -348,7 +345,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
348 345
           model: VideoPlaylistElementModel.unscoped(),
349 346
           where: {
350 347
             videoId: {
351
-              [Op.any]: videoIds
348
+              [Op.in]: videoIds // FIXME: sequelize ANY seems broken
352 349
             }
353 350
           },
354 351
           required: true
@@ -427,12 +424,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
427 424
     return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
428 425
   }
429 426
 
430
-  setThumbnail (thumbnail: ThumbnailModel) {
431
-    this.Thumbnail = thumbnail
432
-  }
427
+  async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) {
428
+    thumbnail.videoPlaylistId = this.id
433 429
 
434
-  getThumbnail () {
435
-    return this.Thumbnail
430
+    this.Thumbnail = await thumbnail.save({ transaction: t })
436 431
   }
437 432
 
438 433
   hasThumbnail () {
@@ -448,13 +443,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
448 443
   getThumbnailUrl () {
449 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 449
   getThumbnailStaticPath () {
455 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 455
   setAsRefreshed () {

+ 5
- 5
server/models/video/video-share.ts View File

@@ -14,15 +14,15 @@ enum ScopeNames {
14 14
   WITH_ACTOR = 'WITH_ACTOR'
15 15
 }
16 16
 
17
-@Scopes({
17
+@Scopes(() => ({
18 18
   [ScopeNames.FULL]: {
19 19
     include: [
20 20
       {
21
-        model: () => ActorModel,
21
+        model: ActorModel,
22 22
         required: true
23 23
       },
24 24
       {
25
-        model: () => VideoModel,
25
+        model: VideoModel,
26 26
         required: true
27 27
       }
28 28
     ]
@@ -30,12 +30,12 @@ enum ScopeNames {
30 30
   [ScopeNames.WITH_ACTOR]: {
31 31
     include: [
32 32
       {
33
-        model: () => ActorModel,
33
+        model: ActorModel,
34 34
         required: true
35 35
       }
36 36
     ]
37 37
   }
38
-})
38
+}))
39 39
 @Table({
40 40
   tableName: 'videoShare',
41 41
   indexes: [

+ 3
- 3
server/models/video/video-streaming-playlist.ts View File

@@ -26,7 +26,7 @@ import { QueryTypes, Op } from 'sequelize'
26 26
       fields: [ 'p2pMediaLoaderInfohashes' ],
27 27
       using: 'gin'
28 28
     }
29
-  ] as any // FIXME: sequelize typings
29
+  ]
30 30
 })
31 31
 export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> {
32 32
   @CreatedAt
@@ -46,7 +46,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
46 46
 
47 47
   @AllowNull(false)
48 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 50
   p2pMediaLoaderInfohashes: string[]
51 51
 
52 52
   @AllowNull(false)
@@ -87,7 +87,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
87 87
       raw: true
88 88
     }
89 89
 
90
-    return VideoModel.sequelize.query<any>(query, options)
90
+    return VideoModel.sequelize.query<object>(query, options)
91 91
               .then(results => results.length === 1)
92 92
   }
93 93
 

+ 91
- 88
server/models/video/video.ts View File

@@ -227,12 +227,12 @@ type AvailableForListIDsOptions = {
227 227
   historyOfUser?: UserModel
228 228
 }
229 229
 
230
-@Scopes({
230
+@Scopes(() => ({
231 231
   [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => {
232 232
     const query: FindOptions = {
233 233
       where: {
234 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 238
       include: [
@@ -486,7 +486,7 @@ type AvailableForListIDsOptions = {
486 486
   [ ScopeNames.WITH_THUMBNAILS ]: {
487 487
     include: [
488 488
       {
489
-        model: () => ThumbnailModel,
489
+        model: ThumbnailModel,
490 490
         required: false
491 491
       }
492 492
     ]
@@ -495,48 +495,48 @@ type AvailableForListIDsOptions = {
495 495
     include: [
496 496
       {
497 497
         attributes: [ 'accountId' ],
498
-        model: () => VideoChannelModel.unscoped(),
498
+        model: VideoChannelModel.unscoped(),
499 499
         required: true,
500 500
         include: [
501 501
           {
502 502
             attributes: [ 'userId' ],
503
-            model: () => AccountModel.unscoped(),
503
+            model: AccountModel.unscoped(),
504 504
             required: true
505 505
           }
506 506
         ]
507 507
       }
508
-    ] as any // FIXME: sequelize typings
508
+    ]
509 509
   },
510 510
   [ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
511 511
     include: [
512 512
       {
513
-        model: () => VideoChannelModel.unscoped(),
513
+        model: VideoChannelModel.unscoped(),
514 514
         required: true,
515 515
         include: [
516 516
           {
517 517
             attributes: {
518 518
               exclude: [ 'privateKey', 'publicKey' ]
519 519
             },
520
-            model: () => ActorModel.unscoped(),
520
+            model: ActorModel.unscoped(),
521 521
             required: true,
522 522
             include: [
523 523
               {
524 524
                 attributes: [ 'host' ],
525
-                model: () => ServerModel.unscoped(),
525
+                model: ServerModel.unscoped(),
526 526
                 required: false
527 527
               },
528 528
               {
529
-                model: () => AvatarModel.unscoped(),
529
+                model: AvatarModel.unscoped(),
530 530
                 required: false
531 531
               }
532 532
             ]
533 533
           },
534 534
           {
535
-            model: () => AccountModel.unscoped(),
535
+            model: AccountModel.unscoped(),
536 536
             required: true,
537 537
             include: [
538 538
               {
539
-                model: () => ActorModel.unscoped(),
539
+                model: ActorModel.unscoped(),
540 540
                 attributes: {
541 541
                   exclude: [ 'privateKey', 'publicKey' ]
542 542
                 },
@@ -544,11 +544,11 @@ type AvailableForListIDsOptions = {
544 544
                 include: [
545 545
                   {
546 546
                     attributes: [ 'host' ],
547
-                    model: () => ServerModel.unscoped(),
547
+                    model: ServerModel.unscoped(),
548 548
                     required: false
549 549
                   },
550 550
                   {
551
-                    model: () => AvatarModel.unscoped(),
551
+                    model: AvatarModel.unscoped(),
552 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 562
   [ ScopeNames.WITH_TAGS ]: {
563
-    include: [ () => TagModel ]
563
+    include: [ TagModel ]
564 564
   },
565 565
   [ ScopeNames.WITH_BLACKLISTED ]: {
566 566
     include: [
567 567
       {
568 568
         attributes: [ 'id', 'reason' ],
569
-        model: () => VideoBlacklistModel,
569
+        model: VideoBlacklistModel,
570 570
         required: false
571 571
       }
572 572
     ]
@@ -588,8 +588,7 @@ type AvailableForListIDsOptions = {
588 588
       include: [
589 589
         {
590 590
           model: VideoFileModel.unscoped(),
591
-          // FIXME: typings
592
-          [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join
591
+          separate: true, // We may have multiple files, having multiple redundancies so let's separate this join
593 592
           required: false,
594 593
           include: subInclude
595 594
         }
@@ -613,8 +612,7 @@ type AvailableForListIDsOptions = {
613 612
       include: [
614 613
         {
615 614
           model: VideoStreamingPlaylistModel.unscoped(),
616
-          // FIXME: typings
617
-          [ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
615
+          separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join
618 616
           required: false,
619 617
           include: subInclude
620 618
         }
@@ -624,7 +622,7 @@ type AvailableForListIDsOptions = {
624 622
   [ ScopeNames.WITH_SCHEDULED_UPDATE ]: {
625 623
     include: [
626 624
       {
627
-        model: () => ScheduleVideoUpdateModel.unscoped(),
625
+        model: ScheduleVideoUpdateModel.unscoped(),
628 626
         required: false
629 627
       }
630 628
     ]
@@ -643,7 +641,7 @@ type AvailableForListIDsOptions = {
643 641
       ]
644 642
     }
645 643
   }
646
-})
644
+}))
647 645
 @Table({
648 646
   tableName: 'video',
649 647
   indexes
@@ -1075,15 +1073,14 @@ export class VideoModel extends Model<VideoModel> {
1075 1073
     }
1076 1074
 
1077 1075
     return Bluebird.all([
1078
-      // FIXME: typing issue
1079
-      VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any),
1080
-      VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT })
1076
+      VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query),
1077
+      VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT })
1081 1078
     ]).then(([ rows, totals ]) => {
1082 1079
       // totals: totalVideos + totalVideoShares
1083 1080
       let totalVideos = 0
1084 1081
       let totalVideoShares = 0
1085
-      if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10)
1086
-      if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10)
1082
+      if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10)
1083
+      if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10)
1087 1084
 
1088 1085
       const total = totalVideos + totalVideoShares
1089 1086
       return {
@@ -1094,50 +1091,58 @@ export class VideoModel extends Model<VideoModel> {
1094 1091
   }
1095 1092
 
1096 1093
   static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) {
1097
-    const query: FindOptions = {
1098
-      offset: start,
1099
-      limit: count,
1100
-      order: getVideoSort(sort),
1101
-      include: [
1102
-        {
1103
-          model: VideoChannelModel,
1104
-          required: true,
1105
-          include: [
1106
-            {
1107
-              model: AccountModel,
1108
-              where: {
1109
-                id: accountId
1110
-              },
1111
-              required: true
1112
-            }
1113
-          ]
1114
-        },
1115
-        {
1116
-          model: ScheduleVideoUpdateModel,
1117
-          required: false
1118
-        },
1119
-        {
1120
-          model: VideoBlacklistModel,
1121
-          required: false
1122
-        }
1123
-      ]
1094
+    function buildBaseQuery (): FindOptions {
1095
+      return {
1096
+        offset: start,
1097
+        limit: count,
1098
+        order: getVideoSort(sort),
1099
+        include: [
1100
+          {
1101
+            model: VideoChannelModel,
1102
+            required: true,
1103
+            include: [
1104
+              {
1105
+                model: AccountModel,
1106
+                where: {
1107
+                  id: accountId
1108
+                },
1109
+                required: true
1110
+              }
1111
+            ]
1112
+          }
1113
+        ]
1114
+      }
1124 1115
     }
1125 1116
 
1117
+    const countQuery = buildBaseQ