diff options
Diffstat (limited to 'server')
43 files changed, 440 insertions, 448 deletions
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index 99325aa9d..6a1d23529 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -41,7 +41,7 @@ import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/vid | |||
41 | import { JobQueue } from '../../lib/job-queue' | 41 | import { JobQueue } from '../../lib/job-queue' |
42 | import { CONFIG } from '../../initializers/config' | 42 | import { CONFIG } from '../../initializers/config' |
43 | import { sequelizeTypescript } from '../../initializers/database' | 43 | import { sequelizeTypescript } from '../../initializers/database' |
44 | import { createPlaylistThumbnailFromExisting } from '../../lib/thumbnail' | 44 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' |
45 | 45 | ||
46 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) | 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 | const thumbnailField = req.files['thumbnailfile'] | 175 | const thumbnailField = req.files['thumbnailfile'] |
176 | const thumbnailModel = thumbnailField | 176 | const thumbnailModel = thumbnailField |
177 | ? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylist) | 177 | ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist) |
178 | : undefined | 178 | : undefined |
179 | 179 | ||
180 | const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { | 180 | const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { |
181 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) | 181 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) |
182 | 182 | ||
183 | if (thumbnailModel) { | 183 | if (thumbnailModel) await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t) |
184 | thumbnailModel.videoPlaylistId = videoPlaylistCreated.id | ||
185 | videoPlaylistCreated.setThumbnail(await thumbnailModel.save({ transaction: t })) | ||
186 | } | ||
187 | 184 | ||
188 | // We need more attributes for the federation | 185 | // We need more attributes for the federation |
189 | videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t) | 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 | const thumbnailField = req.files['thumbnailfile'] | 208 | const thumbnailField = req.files['thumbnailfile'] |
212 | const thumbnailModel = thumbnailField | 209 | const thumbnailModel = thumbnailField |
213 | ? await createPlaylistThumbnailFromExisting(thumbnailField[0].path, videoPlaylistInstance) | 210 | ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance) |
214 | : undefined | 211 | : undefined |
215 | 212 | ||
216 | try { | 213 | try { |
@@ -239,10 +236,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response) | |||
239 | 236 | ||
240 | const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions) | 237 | const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions) |
241 | 238 | ||
242 | if (thumbnailModel) { | 239 | if (thumbnailModel) await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t) |
243 | thumbnailModel.videoPlaylistId = playlistUpdated.id | ||
244 | playlistUpdated.setThumbnail(await thumbnailModel.save({ transaction: t })) | ||
245 | } | ||
246 | 240 | ||
247 | const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE | 241 | const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE |
248 | 242 | ||
@@ -313,8 +307,8 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response) | |||
313 | if (playlistElement.position === 1 && videoPlaylist.hasThumbnail() === false) { | 307 | if (playlistElement.position === 1 && videoPlaylist.hasThumbnail() === false) { |
314 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) | 308 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) |
315 | 309 | ||
316 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnail().filename) | 310 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) |
317 | const thumbnailModel = await createPlaylistThumbnailFromExisting(inputPath, videoPlaylist, true) | 311 | const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true) |
318 | 312 | ||
319 | thumbnailModel.videoPlaylistId = videoPlaylist.id | 313 | thumbnailModel.videoPlaylistId = videoPlaylist.id |
320 | 314 | ||
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index a4ec41d44..bfb690906 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -23,7 +23,7 @@ import { move, readFile } from 'fs-extra' | |||
23 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | 23 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' |
24 | import { CONFIG } from '../../../initializers/config' | 24 | import { CONFIG } from '../../../initializers/config' |
25 | import { sequelizeTypescript } from '../../../initializers/database' | 25 | import { sequelizeTypescript } from '../../../initializers/database' |
26 | import { createVideoThumbnailFromExisting } from '../../../lib/thumbnail' | 26 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' |
27 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 27 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
28 | import { ThumbnailModel } from '../../../models/video/thumbnail' | 28 | import { ThumbnailModel } from '../../../models/video/thumbnail' |
29 | 29 | ||
@@ -204,7 +204,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) { | |||
204 | if (thumbnailField) { | 204 | if (thumbnailField) { |
205 | const thumbnailPhysicalFile = thumbnailField[ 0 ] | 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 | return undefined | 210 | return undefined |
@@ -215,7 +215,7 @@ async function processPreview (req: express.Request, video: VideoModel) { | |||
215 | if (previewField) { | 215 | if (previewField) { |
216 | const previewPhysicalFile = previewField[0] | 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 | return undefined | 221 | return undefined |
@@ -238,14 +238,8 @@ function insertIntoDB (parameters: { | |||
238 | const videoCreated = await video.save(sequelizeOptions) | 238 | const videoCreated = await video.save(sequelizeOptions) |
239 | videoCreated.VideoChannel = videoChannel | 239 | videoCreated.VideoChannel = videoChannel |
240 | 240 | ||
241 | if (thumbnailModel) { | 241 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
242 | thumbnailModel.videoId = videoCreated.id | 242 | if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t) |
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 | } | ||
249 | 243 | ||
250 | await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t) | 244 | await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t) |
251 | 245 | ||
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index ad2fe958c..5bbce11b4 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -52,7 +52,7 @@ import { Notifier } from '../../../lib/notifier' | |||
52 | import { sendView } from '../../../lib/activitypub/send/send-view' | 52 | import { sendView } from '../../../lib/activitypub/send/send-view' |
53 | import { CONFIG } from '../../../initializers/config' | 53 | import { CONFIG } from '../../../initializers/config' |
54 | import { sequelizeTypescript } from '../../../initializers/database' | 54 | import { sequelizeTypescript } from '../../../initializers/database' |
55 | import { createVideoThumbnailFromExisting, generateVideoThumbnail } from '../../../lib/thumbnail' | 55 | import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail' |
56 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 56 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
57 | 57 | ||
58 | const auditLogger = auditLoggerFactory('videos') | 58 | const auditLogger = auditLoggerFactory('videos') |
@@ -214,14 +214,14 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
214 | // Process thumbnail or create it from the video | 214 | // Process thumbnail or create it from the video |
215 | const thumbnailField = req.files['thumbnailfile'] | 215 | const thumbnailField = req.files['thumbnailfile'] |
216 | const thumbnailModel = thumbnailField | 216 | const thumbnailModel = thumbnailField |
217 | ? await createVideoThumbnailFromExisting(thumbnailField[0].path, video, ThumbnailType.THUMBNAIL) | 217 | ? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE) |
218 | : await generateVideoThumbnail(video, videoFile, ThumbnailType.THUMBNAIL) | 218 | : await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE) |
219 | 219 | ||
220 | // Process preview or create it from the video | 220 | // Process preview or create it from the video |
221 | const previewField = req.files['previewfile'] | 221 | const previewField = req.files['previewfile'] |
222 | const previewModel = previewField | 222 | const previewModel = previewField |
223 | ? await createVideoThumbnailFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW) | 223 | ? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW) |
224 | : await generateVideoThumbnail(video, videoFile, ThumbnailType.PREVIEW) | 224 | : await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW) |
225 | 225 | ||
226 | // Create the torrent file | 226 | // Create the torrent file |
227 | await video.createTorrentAndSetInfoHash(videoFile) | 227 | await video.createTorrentAndSetInfoHash(videoFile) |
@@ -231,11 +231,8 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
231 | 231 | ||
232 | const videoCreated = await video.save(sequelizeOptions) | 232 | const videoCreated = await video.save(sequelizeOptions) |
233 | 233 | ||
234 | thumbnailModel.videoId = videoCreated.id | 234 | await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
235 | previewModel.videoId = videoCreated.id | 235 | await videoCreated.addAndSaveThumbnail(previewModel, t) |
236 | |||
237 | videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t })) | ||
238 | videoCreated.addThumbnail(await previewModel.save({ transaction: t })) | ||
239 | 236 | ||
240 | // Do not forget to add video channel information to the created video | 237 | // Do not forget to add video channel information to the created video |
241 | videoCreated.VideoChannel = res.locals.videoChannel | 238 | videoCreated.VideoChannel = res.locals.videoChannel |
@@ -308,11 +305,11 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
308 | 305 | ||
309 | // Process thumbnail or create it from the video | 306 | // Process thumbnail or create it from the video |
310 | const thumbnailModel = req.files && req.files['thumbnailfile'] | 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 | : undefined | 309 | : undefined |
313 | 310 | ||
314 | const previewModel = req.files && req.files['previewfile'] | 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 | : undefined | 313 | : undefined |
317 | 314 | ||
318 | try { | 315 | try { |
@@ -346,14 +343,8 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
346 | 343 | ||
347 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) | 344 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) |
348 | 345 | ||
349 | if (thumbnailModel) { | 346 | if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) |
350 | thumbnailModel.videoId = videoInstanceUpdated.id | 347 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) |
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 | } | ||
357 | 348 | ||
358 | // Video tags update? | 349 | // Video tags update? |
359 | if (videoInfoToUpdate.tags !== undefined) { | 350 | if (videoInfoToUpdate.tags !== undefined) { |
diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts index 7e8e6eff6..e25d9c21b 100644 --- a/server/controllers/bots.ts +++ b/server/controllers/bots.ts | |||
@@ -85,7 +85,7 @@ async function getSitemapLocalVideoUrls () { | |||
85 | // Sitemap description should be < 2000 characters | 85 | // Sitemap description should be < 2000 characters |
86 | description: truncate(v.description || v.name, { length: 2000, omission: '...' }), | 86 | description: truncate(v.description || v.name, { length: 2000, omission: '...' }), |
87 | player_loc: WEBSERVER.URL + '/videos/embed/' + v.uuid, | 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 | })) |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 5064097cd..d3f581615 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -137,7 +137,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response) { | |||
137 | torrent: torrents, | 137 | torrent: torrents, |
138 | thumbnail: [ | 138 | thumbnail: [ |
139 | { | 139 | { |
140 | url: WEBSERVER.URL + video.getThumbnailStaticPath(), | 140 | url: WEBSERVER.URL + video.getMiniatureStaticPath(), |
141 | height: THUMBNAILS_SIZE.height, | 141 | height: THUMBNAILS_SIZE.height, |
142 | width: THUMBNAILS_SIZE.width | 142 | width: THUMBNAILS_SIZE.width |
143 | } | 143 | } |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index d75b95f52..05019fcc2 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -165,20 +165,20 @@ export { | |||
165 | // --------------------------------------------------------------------------- | 165 | // --------------------------------------------------------------------------- |
166 | 166 | ||
167 | async function getPreview (req: express.Request, res: express.Response) { | 167 | async function getPreview (req: express.Request, res: express.Response) { |
168 | const path = await VideosPreviewCache.Instance.getFilePath(req.params.uuid) | 168 | const result = await VideosPreviewCache.Instance.getFilePath(req.params.uuid) |
169 | if (!path) return res.sendStatus(404) | 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 | async function getVideoCaption (req: express.Request, res: express.Response) { | 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 | videoId: req.params.videoId, | 176 | videoId: req.params.videoId, |
177 | language: req.params.captionLanguage | 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 | async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) { | 184 | async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) { |
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index d1744d21f..d9a265e7a 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -140,15 +140,15 @@ async function checkPostgresExtensions () { | |||
140 | } | 140 | } |
141 | 141 | ||
142 | async function checkPostgresExtension (extension: string) { | 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 | const options = { | 144 | const options = { |
145 | type: QueryTypes.SELECT as QueryTypes.SELECT, | 145 | type: QueryTypes.SELECT as QueryTypes.SELECT, |
146 | raw: true | 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 | // Try to create the extension ourselves | 152 | // Try to create the extension ourselves |
153 | try { | 153 | try { |
154 | await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true }) | 154 | await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true }) |
diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts index 721c19603..36a91faec 100644 --- a/server/lib/activitypub/playlist.ts +++ b/server/lib/activitypub/playlist.ts | |||
@@ -16,7 +16,8 @@ import { VideoPlaylistElementModel } from '../../models/video/video-playlist-ele | |||
16 | import { VideoModel } from '../../models/video/video' | 16 | import { VideoModel } from '../../models/video/video' |
17 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 17 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
18 | import { sequelizeTypescript } from '../../initializers/database' | 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 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) { | 22 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) { |
22 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED | 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 }) |
90 | const [ playlist ] = (await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) as any) | ||
91 | 91 | ||
92 | let accItems: string[] = [] | 92 | let accItems: string[] = [] |
93 | await crawlCollectionPage<string>(playlistObject.id, items => { | 93 | await crawlCollectionPage<string>(playlistObject.id, items => { |
@@ -100,10 +100,8 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc | |||
100 | 100 | ||
101 | if (playlistObject.icon) { | 101 | if (playlistObject.icon) { |
102 | try { | 102 | try { |
103 | const thumbnailModel = await createPlaylistThumbnailFromUrl(playlistObject.icon.url, refreshedPlaylist) | 103 | const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist) |
104 | thumbnailModel.videoPlaylistId = refreshedPlaylist.id | 104 | await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined) |
105 | |||
106 | refreshedPlaylist.setThumbnail(await thumbnailModel.save()) | ||
107 | } catch (err) { | 105 | } catch (err) { |
108 | logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err }) | 106 | logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err }) |
109 | } | 107 | } |
@@ -156,7 +154,7 @@ export { | |||
156 | // --------------------------------------------------------------------------- | 154 | // --------------------------------------------------------------------------- |
157 | 155 | ||
158 | async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { | 156 | async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { |
159 | const elementsToCreate: object[] = [] // FIXME: sequelize typings | 157 | const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] |
160 | 158 | ||
161 | await Bluebird.map(elementUrls, async elementUrl => { | 159 | await Bluebird.map(elementUrls, async elementUrl => { |
162 | try { | 160 | try { |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index cb67bf9a4..18f44d50e 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -73,8 +73,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { | |||
73 | const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) | 73 | const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) |
74 | if (!entry) return { created: false } | 74 | if (!entry) return { created: false } |
75 | 75 | ||
76 | // FIXME: sequelize typings | 76 | const [ comment, created ] = await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true }) |
77 | const [ comment, created ] = (await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true }) as any) | ||
78 | comment.Account = actor.Account | 77 | comment.Account = actor.Account |
79 | comment.Video = videoInstance | 78 | comment.Video = videoInstance |
80 | 79 | ||
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 5a56942a9..63bb07ec1 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -49,10 +49,11 @@ import { AccountVideoRateModel } from '../../models/account/account-video-rate' | |||
49 | import { VideoShareModel } from '../../models/video/video-share' | 49 | import { VideoShareModel } from '../../models/video/video-share' |
50 | import { VideoCommentModel } from '../../models/video/video-comment' | 50 | import { VideoCommentModel } from '../../models/video/video-comment' |
51 | import { sequelizeTypescript } from '../../initializers/database' | 51 | import { sequelizeTypescript } from '../../initializers/database' |
52 | import { createPlaceholderThumbnail, createVideoThumbnailFromUrl } from '../thumbnail' | 52 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' |
53 | import { ThumbnailModel } from '../../models/video/thumbnail' | 53 | import { ThumbnailModel } from '../../models/video/thumbnail' |
54 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 54 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
55 | import { join } from 'path' | 55 | import { join } from 'path' |
56 | import { FilteredModelAttributes } from '../../typings/sequelize' | ||
56 | 57 | ||
57 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 58 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
58 | // If the video is not private and is published, we federate it | 59 | // If the video is not private and is published, we federate it |
@@ -247,7 +248,7 @@ async function updateVideoFromAP (options: { | |||
247 | let thumbnailModel: ThumbnailModel | 248 | let thumbnailModel: ThumbnailModel |
248 | 249 | ||
249 | try { | 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 | } catch (err) { | 252 | } catch (err) { |
252 | logger.warn('Cannot generate thumbnail of %s.', options.videoObject.id, { err }) | 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 | await options.video.save(sequelizeOptions) | 290 | await options.video.save(sequelizeOptions) |
290 | 291 | ||
291 | if (thumbnailModel) { | 292 | if (thumbnailModel) if (thumbnailModel) await options.video.addAndSaveThumbnail(thumbnailModel, t) |
292 | thumbnailModel.videoId = options.video.id | ||
293 | options.video.addThumbnail(await thumbnailModel.save({ transaction: t })) | ||
294 | } | ||
295 | 293 | ||
296 | // FIXME: use icon URL instead | 294 | // FIXME: use icon URL instead |
297 | const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename)) | 295 | const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename)) |
298 | const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 296 | const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
299 | 297 | await options.video.addAndSaveThumbnail(previewModel, t) | |
300 | options.video.addThumbnail(await previewModel.save({ transaction: t })) | ||
301 | 298 | ||
302 | { | 299 | { |
303 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject) | 300 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject) |
@@ -311,7 +308,7 @@ async function updateVideoFromAP (options: { | |||
311 | 308 | ||
312 | // Update or add other one | 309 | // Update or add other one |
313 | const upsertTasks = videoFileAttributes.map(a => { | 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 | .then(([ file ]) => file) | 312 | .then(([ file ]) => file) |
316 | }) | 313 | }) |
317 | 314 | ||
@@ -334,8 +331,7 @@ async function updateVideoFromAP (options: { | |||
334 | 331 | ||
335 | // Update or add other one | 332 | // Update or add other one |
336 | const upsertTasks = streamingPlaylistAttributes.map(a => { | 333 | const upsertTasks = streamingPlaylistAttributes.map(a => { |
337 | // FIXME: sequelize typings | 334 | return VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t }) |
338 | return (VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t }) as any) | ||
339 | .then(([ streamingPlaylist ]) => streamingPlaylist) | 335 | .then(([ streamingPlaylist ]) => streamingPlaylist) |
340 | }) | 336 | }) |
341 | 337 | ||
@@ -464,7 +460,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
464 | const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) | 460 | const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) |
465 | const video = VideoModel.build(videoData) | 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 | let thumbnailModel: ThumbnailModel | 465 | let thumbnailModel: ThumbnailModel |
470 | if (waitThumbnail === true) { | 466 | if (waitThumbnail === true) { |
@@ -477,18 +473,12 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
477 | const videoCreated = await video.save(sequelizeOptions) | 473 | const videoCreated = await video.save(sequelizeOptions) |
478 | videoCreated.VideoChannel = channelActor.VideoChannel | 474 | videoCreated.VideoChannel = channelActor.VideoChannel |
479 | 475 | ||
480 | if (thumbnailModel) { | 476 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
481 | thumbnailModel.videoId = videoCreated.id | ||
482 | |||
483 | videoCreated.addThumbnail(await thumbnailModel.save({ transaction: t })) | ||
484 | } | ||
485 | 477 | ||
486 | // FIXME: use icon URL instead | 478 | // FIXME: use icon URL instead |
487 | const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | 479 | const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) |
488 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 480 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
489 | previewModel.videoId = videoCreated.id | 481 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) |
490 | |||
491 | videoCreated.addThumbnail(await previewModel.save({ transaction: t })) | ||
492 | 482 | ||
493 | // Process files | 483 | // Process files |
494 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject) | 484 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject) |
@@ -594,7 +584,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid | |||
594 | throw new Error('Cannot find video files for ' + video.url) | 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 | for (const fileUrl of fileUrls) { | 588 | for (const fileUrl of fileUrls) { |
599 | // Fetch associated magnet uri | 589 | // Fetch associated magnet uri |
600 | const magnet = videoObject.url.find(u => { | 590 | const magnet = videoObject.url.find(u => { |
@@ -629,7 +619,7 @@ function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObj | |||
629 | const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] | 619 | const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] |
630 | if (playlistUrls.length === 0) return [] | 620 | if (playlistUrls.length === 0) return [] |
631 | 621 | ||
632 | const attributes: object[] = [] // FIXME: add typings | 622 | const attributes: FilteredModelAttributes<VideoStreamingPlaylistModel>[] = [] |
633 | for (const playlistUrlObject of playlistUrls) { | 623 | for (const playlistUrlObject of playlistUrls) { |
634 | const segmentsSha256UrlObject = playlistUrlObject.tag | 624 | const segmentsSha256UrlObject = playlistUrlObject.tag |
635 | .find(t => { | 625 | .find(t => { |
diff --git a/server/lib/files-cache/abstract-video-static-file-cache.ts b/server/lib/files-cache/abstract-video-static-file-cache.ts index 61837e0f8..84ed74c98 100644 --- a/server/lib/files-cache/abstract-video-static-file-cache.ts +++ b/server/lib/files-cache/abstract-video-static-file-cache.ts | |||
@@ -4,24 +4,28 @@ import { VideoModel } from '../../models/video/video' | |||
4 | import { fetchRemoteVideoStaticFile } from '../activitypub' | 4 | import { fetchRemoteVideoStaticFile } from '../activitypub' |
5 | import * as memoizee from 'memoizee' | 5 | import * as memoizee from 'memoizee' |
6 | 6 | ||
7 | type GetFilePathResult = { isOwned: boolean, path: string } | undefined | ||
8 | |||
7 | export abstract class AbstractVideoStaticFileCache <T> { | 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 | // Load and save the remote file, then return the local path from filesystem | 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 | init (max: number, maxAge: number) { | 18 | init (max: number, maxAge: number) { |
17 | this.getFilePath = memoizee(this.getFilePathImpl, { | 19 | this.getFilePath = memoizee(this.getFilePathImpl, { |
18 | maxAge, | 20 | maxAge, |
19 | max, | 21 | max, |
20 | promise: true, | 22 | promise: true, |
21 | dispose: (value: string) => { | 23 | dispose: (result: GetFilePathResult) => { |
22 | remove(value) | 24 | if (result.isOwned !== true) { |
23 | .then(() => logger.debug('%s evicted from %s', value, this.constructor.name)) | 25 | remove(result.path) |
24 | .catch(err => logger.error('Cannot remove %s from cache %s.', value, this.constructor.name, { err })) | 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 | } |
diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts index d4a0a3345..305e39c35 100644 --- a/server/lib/files-cache/videos-caption-cache.ts +++ b/server/lib/files-cache/videos-caption-cache.ts | |||
@@ -4,6 +4,7 @@ import { VideoModel } from '../../models/video/video' | |||
4 | import { VideoCaptionModel } from '../../models/video/video-caption' | 4 | import { VideoCaptionModel } from '../../models/video/video-caption' |
5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
6 | import { CONFIG } from '../../initializers/config' | 6 | import { CONFIG } from '../../initializers/config' |
7 | import { logger } from '../../helpers/logger' | ||
7 | 8 | ||
8 | type GetPathParam = { videoId: string, language: string } | 9 | type GetPathParam = { videoId: string, language: string } |
9 | 10 | ||
@@ -24,13 +25,15 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
24 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) | 25 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) |
25 | if (!videoCaption) return undefined | 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 | const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language | 30 | const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language |
30 | return this.loadRemoteFile(key) | 31 | return this.loadRemoteFile(key) |
31 | } | 32 | } |
32 | 33 | ||
33 | protected async loadRemoteFile (key: string) { | 34 | protected async loadRemoteFile (key: string) { |
35 | logger.debug('Loading remote caption file %s.', key) | ||
36 | |||
34 | const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) | 37 | const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) |
35 | 38 | ||
36 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) | 39 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) |
@@ -46,7 +49,9 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
46 | const remoteStaticPath = videoCaption.getCaptionStaticPath() | 49 | const remoteStaticPath = videoCaption.getCaptionStaticPath() |
47 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) | 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 | ||
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index fc0d92c78..c117ae426 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts | |||
@@ -20,7 +20,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
20 | const video = await VideoModel.loadByUUIDWithFile(videoUUID) | 20 | const video = await VideoModel.loadByUUIDWithFile(videoUUID) |
21 | if (!video) return undefined | 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 | return this.loadRemoteFile(videoUUID) | 25 | return this.loadRemoteFile(videoUUID) |
26 | } | 26 | } |
@@ -35,7 +35,9 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
35 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) | 35 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) |
36 | const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename) | 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 | ||
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 3fa0dd65d..1650916a6 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -18,7 +18,7 @@ import { Notifier } from '../../notifier' | |||
18 | import { CONFIG } from '../../../initializers/config' | 18 | import { CONFIG } from '../../../initializers/config' |
19 | import { sequelizeTypescript } from '../../../initializers/database' | 19 | import { sequelizeTypescript } from '../../../initializers/database' |
20 | import { ThumbnailModel } from '../../../models/video/thumbnail' | 20 | import { ThumbnailModel } from '../../../models/video/thumbnail' |
21 | import { createVideoThumbnailFromUrl, generateVideoThumbnail } from '../../thumbnail' | 21 | import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' |
22 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 22 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
23 | 23 | ||
24 | type VideoImportYoutubeDLPayload = { | 24 | type VideoImportYoutubeDLPayload = { |
@@ -150,17 +150,17 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
150 | // Process thumbnail | 150 | // Process thumbnail |
151 | let thumbnailModel: ThumbnailModel | 151 | let thumbnailModel: ThumbnailModel |
152 | if (options.downloadThumbnail && options.thumbnailUrl) { | 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 | } else if (options.generateThumbnail || options.downloadThumbnail) { | 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 | // Process preview | 158 | // Process preview |
159 | let previewModel: ThumbnailModel | 159 | let previewModel: ThumbnailModel |
160 | if (options.downloadPreview && options.thumbnailUrl) { | 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 | } else if (options.generatePreview || options.downloadPreview) { | 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 | // Create torrent | 166 | // Create torrent |
@@ -180,14 +180,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
180 | video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED | 180 | video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED |
181 | await video.save({ transaction: t }) | 181 | await video.save({ transaction: t }) |
182 | 182 | ||
183 | if (thumbnailModel) { | 183 | if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) |
184 | thumbnailModel.videoId = video.id | 184 | if (previewModel) await video.addAndSaveThumbnail(previewModel, t) |
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 | } | ||
191 | 185 | ||
192 | // Now we can federate the video (reload from database, we need more attributes) | 186 | // Now we can federate the video (reload from database, we need more attributes) |
193 | const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | 187 | const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) |
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index eb0e63bc8..45ac3e7c4 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -39,6 +39,8 @@ function clearCacheByToken (token: string) { | |||
39 | function getAccessToken (bearerToken: string) { | 39 | function getAccessToken (bearerToken: string) { |
40 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') | 40 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
41 | 41 | ||
42 | if (!bearerToken) return Bluebird.resolve(undefined) | ||
43 | |||
42 | if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken]) | 44 | if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken]) |
43 | 45 | ||
44 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) | 46 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index 344c28566..8ad82ee80 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -12,37 +12,37 @@ import { VideoPlaylistModel } from '../models/video/video-playlist' | |||
12 | 12 | ||
13 | type ImageSize = { height: number, width: number } | 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 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | 16 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) |
17 | const type = ThumbnailType.THUMBNAIL | 17 | const type = ThumbnailType.MINIATURE |
18 | 18 | ||
19 | const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height }, keepOriginal) | 19 | const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height }, keepOriginal) |
20 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) | 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 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | 24 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) |
25 | const type = ThumbnailType.THUMBNAIL | 25 | const type = ThumbnailType.MINIATURE |
26 | 26 | ||
27 | const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height }) | 27 | const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height }) |
28 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url }) | 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 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 32 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
33 | const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height }) | 33 | const thumbnailCreator = () => downloadImage(url, basePath, filename, { width, height }) |
34 | 34 | ||
35 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, url }) | 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 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 39 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
40 | const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height }) | 40 | const thumbnailCreator = () => processImage({ path: inputPath }, outputPath, { width, height }) |
41 | 41 | ||
42 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) | 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 | const input = video.getVideoFilePath(videoFile) | 46 | const input = video.getVideoFilePath(videoFile) |
47 | 47 | ||
48 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type) | 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 | export { | 70 | export { |
71 | generateVideoThumbnail, | 71 | generateVideoMiniature, |
72 | createVideoThumbnailFromUrl, | 72 | createVideoMiniatureFromUrl, |
73 | createVideoThumbnailFromExisting, | 73 | createVideoMiniatureFromExisting, |
74 | createPlaceholderThumbnail, | 74 | createPlaceholderThumbnail, |
75 | createPlaylistThumbnailFromUrl, | 75 | createPlaylistMiniatureFromUrl, |
76 | createPlaylistThumbnailFromExisting | 76 | createPlaylistMiniatureFromExisting |
77 | } | 77 | } |
78 | 78 | ||
79 | function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { | 79 | function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { |
@@ -95,7 +95,7 @@ function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: | |||
95 | ? video.Thumbnails.find(t => t.type === type) | 95 | ? video.Thumbnails.find(t => t.type === type) |
96 | : undefined | 96 | : undefined |
97 | 97 | ||
98 | if (type === ThumbnailType.THUMBNAIL) { | 98 | if (type === ThumbnailType.MINIATURE) { |
99 | const filename = video.generateThumbnailName() | 99 | const filename = video.generateThumbnailName() |
100 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | 100 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR |
101 | 101 | ||
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts index de736e593..2b4e300e4 100644 --- a/server/middlewares/oauth.ts +++ b/server/middlewares/oauth.ts | |||
@@ -35,6 +35,8 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) { | |||
35 | 35 | ||
36 | logger.debug('Checking socket access token %s.', accessToken) | 36 | logger.debug('Checking socket access token %s.', accessToken) |
37 | 37 | ||
38 | if (!accessToken) return next(new Error('No access token provided')) | ||
39 | |||
38 | getAccessToken(accessToken) | 40 | getAccessToken(accessToken) |
39 | .then(tokenDB => { | 41 | .then(tokenDB => { |
40 | const now = new Date() | 42 | const now = new Date() |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index e9b036a02..2b01f108d 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -68,6 +68,7 @@ const videosAddValidator = getCommonVideoEditAttributes().concat([ | |||
68 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 68 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
69 | 69 | ||
70 | const isAble = await user.isAbleToUploadVideo(videoFile) | 70 | const isAble = await user.isAbleToUploadVideo(videoFile) |
71 | |||
71 | if (isAble === false) { | 72 | if (isAble === false) { |
72 | res.status(403) | 73 | res.status(403) |
73 | .json({ error: 'The user video quota is exceeded with this video.' }) | 74 | .json({ error: 'The user video quota is exceeded with this video.' }) |
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index efd6ed59e..d5746ad76 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -8,22 +8,22 @@ enum ScopeNames { | |||
8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' | 8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' |
9 | } | 9 | } |
10 | 10 | ||
11 | @Scopes({ | 11 | @Scopes(() => ({ |
12 | [ScopeNames.WITH_ACCOUNTS]: { | 12 | [ScopeNames.WITH_ACCOUNTS]: { |
13 | include: [ | 13 | include: [ |
14 | { | 14 | { |
15 | model: () => AccountModel, | 15 | model: AccountModel, |
16 | required: true, | 16 | required: true, |
17 | as: 'ByAccount' | 17 | as: 'ByAccount' |
18 | }, | 18 | }, |
19 | { | 19 | { |
20 | model: () => AccountModel, | 20 | model: AccountModel, |
21 | required: true, | 21 | required: true, |
22 | as: 'BlockedAccount' | 22 | as: 'BlockedAccount' |
23 | } | 23 | } |
24 | ] | 24 | ] |
25 | } | 25 | } |
26 | }) | 26 | })) |
27 | 27 | ||
28 | @Table({ | 28 | @Table({ |
29 | tableName: 'accountBlocklist', | 29 | tableName: 'accountBlocklist', |
@@ -83,7 +83,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
83 | attributes: [ 'accountId', 'id' ], | 83 | attributes: [ 'accountId', 'id' ], |
84 | where: { | 84 | where: { |
85 | accountId: { | 85 | accountId: { |
86 | [Op.any]: accountIds | 86 | [Op.in]: accountIds // FIXME: sequelize ANY seems broken |
87 | }, | 87 | }, |
88 | targetAccountId | 88 | targetAccountId |
89 | }, | 89 | }, |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index bf2ed0a61..c53312990 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -33,15 +33,15 @@ export enum ScopeNames { | |||
33 | SUMMARY = 'SUMMARY' | 33 | SUMMARY = 'SUMMARY' |
34 | } | 34 | } |
35 | 35 | ||
36 | @DefaultScope({ | 36 | @DefaultScope(() => ({ |
37 | include: [ | 37 | include: [ |
38 | { | 38 | { |
39 | model: () => ActorModel, // Default scope includes avatar and server | 39 | model: ActorModel, // Default scope includes avatar and server |
40 | required: true | 40 | required: true |
41 | } | 41 | } |
42 | ] | 42 | ] |
43 | }) | 43 | })) |
44 | @Scopes({ | 44 | @Scopes(() => ({ |
45 | [ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => { | 45 | [ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => { |
46 | return { | 46 | return { |
47 | attributes: [ 'id', 'name' ], | 47 | attributes: [ 'id', 'name' ], |
@@ -66,7 +66,7 @@ export enum ScopeNames { | |||
66 | ] | 66 | ] |
67 | } | 67 | } |
68 | } | 68 | } |
69 | }) | 69 | })) |
70 | @Table({ | 70 | @Table({ |
71 | tableName: 'account', | 71 | tableName: 'account', |
72 | indexes: [ | 72 | indexes: [ |
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index 08388f268..a4f97037b 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts | |||
@@ -6,7 +6,7 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use | |||
6 | import { UserModel } from './user' | 6 | import { UserModel } from './user' |
7 | import { VideoModel } from '../video/video' | 7 | import { VideoModel } from '../video/video' |
8 | import { VideoCommentModel } from '../video/video-comment' | 8 | import { VideoCommentModel } from '../video/video-comment' |
9 | import { FindOptions, Op } from 'sequelize' | 9 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' |
10 | import { VideoChannelModel } from '../video/video-channel' | 10 | import { VideoChannelModel } from '../video/video-channel' |
11 | import { AccountModel } from './account' | 11 | import { AccountModel } from './account' |
12 | import { VideoAbuseModel } from '../video/video-abuse' | 12 | import { VideoAbuseModel } from '../video/video-abuse' |
@@ -24,17 +24,17 @@ enum ScopeNames { | |||
24 | function buildActorWithAvatarInclude () { | 24 | function buildActorWithAvatarInclude () { |
25 | return { | 25 | return { |
26 | attributes: [ 'preferredUsername' ], | 26 | attributes: [ 'preferredUsername' ], |
27 | model: () => ActorModel.unscoped(), | 27 | model: ActorModel.unscoped(), |
28 | required: true, | 28 | required: true, |
29 | include: [ | 29 | include: [ |
30 | { | 30 | { |
31 | attributes: [ 'filename' ], | 31 | attributes: [ 'filename' ], |
32 | model: () => AvatarModel.unscoped(), | 32 | model: AvatarModel.unscoped(), |
33 | required: false | 33 | required: false |
34 | }, | 34 | }, |
35 | { | 35 | { |
36 | attributes: [ 'host' ], | 36 | attributes: [ 'host' ], |
37 | model: () => ServerModel.unscoped(), | 37 | model: ServerModel.unscoped(), |
38 | required: false | 38 | required: false |
39 | } | 39 | } |
40 | ] | 40 | ] |
@@ -44,7 +44,7 @@ function buildActorWithAvatarInclude () { | |||
44 | function buildVideoInclude (required: boolean) { | 44 | function buildVideoInclude (required: boolean) { |
45 | return { | 45 | return { |
46 | attributes: [ 'id', 'uuid', 'name' ], | 46 | attributes: [ 'id', 'uuid', 'name' ], |
47 | model: () => VideoModel.unscoped(), | 47 | model: VideoModel.unscoped(), |
48 | required | 48 | required |
49 | } | 49 | } |
50 | } | 50 | } |
@@ -53,7 +53,7 @@ function buildChannelInclude (required: boolean, withActor = false) { | |||
53 | return { | 53 | return { |
54 | required, | 54 | required, |
55 | attributes: [ 'id', 'name' ], | 55 | attributes: [ 'id', 'name' ], |
56 | model: () => VideoChannelModel.unscoped(), | 56 | model: VideoChannelModel.unscoped(), |
57 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] | 57 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] |
58 | } | 58 | } |
59 | } | 59 | } |
@@ -62,12 +62,12 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
62 | return { | 62 | return { |
63 | required, | 63 | required, |
64 | attributes: [ 'id', 'name' ], | 64 | attributes: [ 'id', 'name' ], |
65 | model: () => AccountModel.unscoped(), | 65 | model: AccountModel.unscoped(), |
66 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] | 66 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] |
67 | } | 67 | } |
68 | } | 68 | } |
69 | 69 | ||
70 | @Scopes({ | 70 | @Scopes(() => ({ |
71 | [ScopeNames.WITH_ALL]: { | 71 | [ScopeNames.WITH_ALL]: { |
72 | include: [ | 72 | include: [ |
73 | Object.assign(buildVideoInclude(false), { | 73 | Object.assign(buildVideoInclude(false), { |
@@ -76,7 +76,7 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
76 | 76 | ||
77 | { | 77 | { |
78 | attributes: [ 'id', 'originCommentId' ], | 78 | attributes: [ 'id', 'originCommentId' ], |
79 | model: () => VideoCommentModel.unscoped(), | 79 | model: VideoCommentModel.unscoped(), |
80 | required: false, | 80 | required: false, |
81 | include: [ | 81 | include: [ |
82 | buildAccountInclude(true, true), | 82 | buildAccountInclude(true, true), |
@@ -86,56 +86,56 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
86 | 86 | ||
87 | { | 87 | { |
88 | attributes: [ 'id' ], | 88 | attributes: [ 'id' ], |
89 | model: () => VideoAbuseModel.unscoped(), | 89 | model: VideoAbuseModel.unscoped(), |
90 | required: false, | 90 | required: false, |
91 | include: [ buildVideoInclude(true) ] | 91 | include: [ buildVideoInclude(true) ] |
92 | }, | 92 | }, |
93 | 93 | ||
94 | { | 94 | { |
95 | attributes: [ 'id' ], | 95 | attributes: [ 'id' ], |
96 | model: () => VideoBlacklistModel.unscoped(), | 96 | model: VideoBlacklistModel.unscoped(), |
97 | required: false, | 97 | required: false, |
98 | include: [ buildVideoInclude(true) ] | 98 | include: [ buildVideoInclude(true) ] |
99 | }, | 99 | }, |
100 | 100 | ||
101 | { | 101 | { |
102 | attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ], | 102 | attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ], |
103 | model: () => VideoImportModel.unscoped(), | 103 | model: VideoImportModel.unscoped(), |
104 | required: false, | 104 | required: false, |
105 | include: [ buildVideoInclude(false) ] | 105 | include: [ buildVideoInclude(false) ] |
106 | }, | 106 | }, |
107 | 107 | ||
108 | { | 108 | { |
109 | attributes: [ 'id', 'state' ], | 109 | attributes: [ 'id', 'state' ], |
110 | model: () => ActorFollowModel.unscoped(), | 110 | model: ActorFollowModel.unscoped(), |
111 | required: false, | 111 | required: false, |
112 | include: [ | 112 | include: [ |
113 | { | 113 | { |
114 | attributes: [ 'preferredUsername' ], | 114 | attributes: [ 'preferredUsername' ], |
115 | model: () => ActorModel.unscoped(), | 115 | model: ActorModel.unscoped(), |
116 | required: true, | 116 | required: true, |
117 | as: 'ActorFollower', | 117 | as: 'ActorFollower', |
118 | include: [ | 118 | include: [ |
119 | { | 119 | { |
120 | attributes: [ 'id', 'name' ], | 120 | attributes: [ 'id', 'name' ], |
121 | model: () => AccountModel.unscoped(), | 121 | model: AccountModel.unscoped(), |
122 | required: true | 122 | required: true |
123 | }, | 123 | }, |
124 | { | 124 | { |
125 | attributes: [ 'filename' ], | 125 | attributes: [ 'filename' ], |
126 | model: () => AvatarModel.unscoped(), | 126 | model: AvatarModel.unscoped(), |
127 | required: false | 127 | required: false |
128 | }, | 128 | }, |
129 | { | 129 | { |
130 | attributes: [ 'host' ], | 130 | attributes: [ 'host' ], |
131 | model: () => ServerModel.unscoped(), | 131 | model: ServerModel.unscoped(), |
132 | required: false | 132 | required: false |
133 | } | 133 | } |
134 | ] | 134 | ] |
135 | }, | 135 | }, |
136 | { | 136 | { |
137 | attributes: [ 'preferredUsername' ], | 137 | attributes: [ 'preferredUsername' ], |
138 | model: () => ActorModel.unscoped(), | 138 | model: ActorModel.unscoped(), |
139 | required: true, | 139 | required: true, |
140 | as: 'ActorFollowing', | 140 | as: 'ActorFollowing', |
141 | include: [ | 141 | include: [ |
@@ -147,9 +147,9 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
147 | }, | 147 | }, |
148 | 148 | ||
149 | buildAccountInclude(false, true) | 149 | buildAccountInclude(false, true) |
150 | ] as any // FIXME: sequelize typings | 150 | ] |
151 | } | 151 | } |
152 | }) | 152 | })) |
153 | @Table({ | 153 | @Table({ |
154 | tableName: 'userNotification', | 154 | tableName: 'userNotification', |
155 | indexes: [ | 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 | export class UserNotificationModel extends Model<UserNotificationModel> { | 217 | export class UserNotificationModel extends Model<UserNotificationModel> { |
218 | 218 | ||
@@ -357,7 +357,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
357 | where: { | 357 | where: { |
358 | userId, | 358 | userId, |
359 | id: { | 359 | id: { |
360 | [Op.any]: notificationIds | 360 | [Op.in]: notificationIds // FIXME: sequelize ANY seems broken |
361 | } | 361 | } |
362 | } | 362 | } |
363 | } | 363 | } |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 8bd0397dd..4a9acd703 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { FindOptions, literal, Op, QueryTypes } from 'sequelize' |
2 | import { | 2 | import { |
3 | AfterDestroy, | 3 | AfterDestroy, |
4 | AfterUpdate, | 4 | AfterUpdate, |
@@ -56,33 +56,33 @@ enum ScopeNames { | |||
56 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 56 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
57 | } | 57 | } |
58 | 58 | ||
59 | @DefaultScope({ | 59 | @DefaultScope(() => ({ |
60 | include: [ | 60 | include: [ |
61 | { | 61 | { |
62 | model: () => AccountModel, | 62 | model: AccountModel, |
63 | required: true | 63 | required: true |
64 | }, | 64 | }, |
65 | { | 65 | { |
66 | model: () => UserNotificationSettingModel, | 66 | model: UserNotificationSettingModel, |
67 | required: true | 67 | required: true |
68 | } | 68 | } |
69 | ] | 69 | ] |
70 | }) | 70 | })) |
71 | @Scopes({ | 71 | @Scopes(() => ({ |
72 | [ScopeNames.WITH_VIDEO_CHANNEL]: { | 72 | [ScopeNames.WITH_VIDEO_CHANNEL]: { |
73 | include: [ | 73 | include: [ |
74 | { | 74 | { |
75 | model: () => AccountModel, | 75 | model: AccountModel, |
76 | required: true, | 76 | required: true, |
77 | include: [ () => VideoChannelModel ] | 77 | include: [ VideoChannelModel ] |
78 | }, | 78 | }, |
79 | { | 79 | { |
80 | model: () => UserNotificationSettingModel, | 80 | model: UserNotificationSettingModel, |
81 | required: true | 81 | required: true |
82 | } | 82 | } |
83 | ] as any // FIXME: sequelize typings | 83 | ] |
84 | } | 84 | } |
85 | }) | 85 | })) |
86 | @Table({ | 86 | @Table({ |
87 | tableName: 'user', | 87 | tableName: 'user', |
88 | indexes: [ | 88 | indexes: [ |
@@ -233,26 +233,26 @@ export class UserModel extends Model<UserModel> { | |||
233 | let where = undefined | 233 | let where = undefined |
234 | if (search) { | 234 | if (search) { |
235 | where = { | 235 | where = { |
236 | [Sequelize.Op.or]: [ | 236 | [Op.or]: [ |
237 | { | 237 | { |
238 | email: { | 238 | email: { |
239 | [Sequelize.Op.iLike]: '%' + search + '%' | 239 | [Op.iLike]: '%' + search + '%' |
240 | } | 240 | } |
241 | }, | 241 | }, |
242 | { | 242 | { |
243 | username: { | 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 | attributes: { | 252 | attributes: { |
253 | include: [ | 253 | include: [ |
254 | [ | 254 | [ |
255 | Sequelize.literal( | 255 | literal( |
256 | '(' + | 256 | '(' + |
257 | 'SELECT COALESCE(SUM("size"), 0) ' + | 257 | 'SELECT COALESCE(SUM("size"), 0) ' + |
258 | 'FROM (' + | 258 | 'FROM (' + |
@@ -265,7 +265,7 @@ export class UserModel extends Model<UserModel> { | |||
265 | ')' | 265 | ')' |
266 | ), | 266 | ), |
267 | 'videoQuotaUsed' | 267 | 'videoQuotaUsed' |
268 | ] as any // FIXME: typings | 268 | ] |
269 | ] | 269 | ] |
270 | }, | 270 | }, |
271 | offset: start, | 271 | offset: start, |
@@ -291,7 +291,7 @@ export class UserModel extends Model<UserModel> { | |||
291 | const query = { | 291 | const query = { |
292 | where: { | 292 | where: { |
293 | role: { | 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 | const query = { | 388 | const query = { |
389 | where: { | 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 | const query = { | 510 | const query = { |
511 | where: { | 511 | where: { |
512 | username: { | 512 | username: { |
513 | [ Sequelize.Op.like ]: `%${search}%` | 513 | [ Op.like ]: `%${search}%` |
514 | } | 514 | } |
515 | }, | 515 | }, |
516 | limit: 10 | 516 | limit: 10 |
@@ -591,15 +591,11 @@ export class UserModel extends Model<UserModel> { | |||
591 | 591 | ||
592 | const uploadedTotal = videoFile.size + totalBytes | 592 | const uploadedTotal = videoFile.size + totalBytes |
593 | const uploadedDaily = videoFile.size + totalBytesDaily | 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) && | 595 | if (this.videoQuotaDaily === -1) return uploadedTotal < this.videoQuota |
602 | (uploadedDaily < this.videoQuotaDaily) | 596 | if (this.videoQuota === -1) return uploadedDaily < this.videoQuotaDaily |
597 | |||
598 | return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily | ||
603 | } | 599 | } |
604 | 600 | ||
605 | private static generateUserQuotaBaseSQL (where?: string) { | 601 | private static generateUserQuotaBaseSQL (where?: string) { |
@@ -619,14 +615,14 @@ export class UserModel extends Model<UserModel> { | |||
619 | private static getTotalRawQuery (query: string, userId: number) { | 615 | private static getTotalRawQuery (query: string, userId: number) { |
620 | const options = { | 616 | const options = { |
621 | bind: { userId }, | 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 | .then(([ { total } ]) => { | 622 | .then(([ { total } ]) => { |
627 | if (total === null) return 0 | 623 | if (total === null) return 0 |
628 | 624 | ||
629 | return parseInt(total + '', 10) | 625 | return parseInt(total, 10) |
630 | }) | 626 | }) |
631 | } | 627 | } |
632 | } | 628 | } |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 1ebee8df5..4a466441c 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -56,46 +56,46 @@ export const unusedActorAttributesForAPI = [ | |||
56 | 'updatedAt' | 56 | 'updatedAt' |
57 | ] | 57 | ] |
58 | 58 | ||
59 | @DefaultScope({ | 59 | @DefaultScope(() => ({ |
60 | include: [ | 60 | include: [ |
61 | { | 61 | { |
62 | model: () => ServerModel, | 62 | model: ServerModel, |
63 | required: false | 63 | required: false |
64 | }, | 64 | }, |
65 | { | 65 | { |
66 | model: () => AvatarModel, | 66 | model: AvatarModel, |
67 | required: false | 67 | required: false |
68 | } | 68 | } |
69 | ] | 69 | ] |
70 | }) | 70 | })) |
71 | @Scopes({ | 71 | @Scopes(() => ({ |
72 | [ScopeNames.FULL]: { | 72 | [ScopeNames.FULL]: { |
73 | include: [ | 73 | include: [ |
74 | { | 74 | { |
75 | model: () => AccountModel.unscoped(), | 75 | model: AccountModel.unscoped(), |
76 | required: false | 76 | required: false |
77 | }, | 77 | }, |
78 | { | 78 | { |
79 | model: () => VideoChannelModel.unscoped(), | 79 | model: VideoChannelModel.unscoped(), |
80 | required: false, | 80 | required: false, |
81 | include: [ | 81 | include: [ |
82 | { | 82 | { |
83 | model: () => AccountModel, | 83 | model: AccountModel, |
84 | required: true | 84 | required: true |
85 | } | 85 | } |
86 | ] | 86 | ] |
87 | }, | 87 | }, |
88 | { | 88 | { |
89 | model: () => ServerModel, | 89 | model: ServerModel, |
90 | required: false | 90 | required: false |
91 | }, | 91 | }, |
92 | { | 92 | { |
93 | model: () => AvatarModel, | 93 | model: AvatarModel, |
94 | required: false | 94 | required: false |
95 | } | 95 | } |
96 | ] as any // FIXME: sequelize typings | 96 | ] |
97 | } | 97 | } |
98 | }) | 98 | })) |
99 | @Table({ | 99 | @Table({ |
100 | tableName: 'actor', | 100 | tableName: 'actor', |
101 | indexes: [ | 101 | indexes: [ |
@@ -131,7 +131,7 @@ export const unusedActorAttributesForAPI = [ | |||
131 | export class ActorModel extends Model<ActorModel> { | 131 | export class ActorModel extends Model<ActorModel> { |
132 | 132 | ||
133 | @AllowNull(false) | 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 | type: ActivityPubActorType | 135 | type: ActivityPubActorType |
136 | 136 | ||
137 | @AllowNull(false) | 137 | @AllowNull(false) |
@@ -280,14 +280,16 @@ export class ActorModel extends Model<ActorModel> { | |||
280 | attributes: [ 'id' ], | 280 | attributes: [ 'id' ], |
281 | model: VideoChannelModel.unscoped(), | 281 | model: VideoChannelModel.unscoped(), |
282 | required: true, | 282 | required: true, |
283 | include: { | 283 | include: [ |
284 | attributes: [ 'id' ], | 284 | { |
285 | model: VideoModel.unscoped(), | 285 | attributes: [ 'id' ], |
286 | required: true, | 286 | model: VideoModel.unscoped(), |
287 | where: { | 287 | required: true, |
288 | id: videoId | 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 | transaction | 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 | static isActorUrlExist (url: string) { | 303 | static isActorUrlExist (url: string) { |
@@ -389,8 +391,7 @@ export class ActorModel extends Model<ActorModel> { | |||
389 | } | 391 | } |
390 | 392 | ||
391 | static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) { | 393 | static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) { |
392 | // FIXME: typings | 394 | return ActorModel.increment(column, { |
393 | return (ActorModel as any).increment(column, { | ||
394 | by, | 395 | by, |
395 | where: { | 396 | where: { |
396 | id | 397 | id |
diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 854a5fb36..a02208b4e 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts | |||
@@ -1,14 +1,14 @@ | |||
1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' | 1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' |
2 | import { AccountModel } from '../account/account' | 2 | import { AccountModel } from '../account/account' |
3 | 3 | ||
4 | @DefaultScope({ | 4 | @DefaultScope(() => ({ |
5 | include: [ | 5 | include: [ |
6 | { | 6 | { |
7 | model: () => AccountModel, | 7 | model: AccountModel, |
8 | required: true | 8 | required: true |
9 | } | 9 | } |
10 | ] | 10 | ] |
11 | }) | 11 | })) |
12 | @Table({ | 12 | @Table({ |
13 | tableName: 'application' | 13 | tableName: 'application' |
14 | }) | 14 | }) |
diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts index b4a841edd..42c59bb79 100644 --- a/server/models/oauth/oauth-client.ts +++ b/server/models/oauth/oauth-client.ts | |||
@@ -24,10 +24,10 @@ export class OAuthClientModel extends Model<OAuthClientModel> { | |||
24 | @Column | 24 | @Column |
25 | clientSecret: string | 25 | clientSecret: string |
26 | 26 | ||
27 | @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings | 27 | @Column(DataType.ARRAY(DataType.STRING)) |
28 | grants: string[] | 28 | grants: string[] |
29 | 29 | ||
30 | @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: sequelize typings | 30 | @Column(DataType.ARRAY(DataType.STRING)) |
31 | redirectUris: string[] | 31 | redirectUris: string[] |
32 | 32 | ||
33 | @CreatedAt | 33 | @CreatedAt |
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 3f41ee63b..903d551df 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -34,30 +34,30 @@ enum ScopeNames { | |||
34 | WITH_USER = 'WITH_USER' | 34 | WITH_USER = 'WITH_USER' |
35 | } | 35 | } |
36 | 36 | ||
37 | @Scopes({ | 37 | @Scopes(() => ({ |
38 | [ScopeNames.WITH_USER]: { | 38 | [ScopeNames.WITH_USER]: { |
39 | include: [ | 39 | include: [ |
40 | { | 40 | { |
41 | model: () => UserModel.unscoped(), | 41 | model: UserModel.unscoped(), |
42 | required: true, | 42 | required: true, |
43 | include: [ | 43 | include: [ |
44 | { | 44 | { |
45 | attributes: [ 'id' ], | 45 | attributes: [ 'id' ], |
46 | model: () => AccountModel.unscoped(), | 46 | model: AccountModel.unscoped(), |
47 | required: true, | 47 | required: true, |
48 | include: [ | 48 | include: [ |
49 | { | 49 | { |
50 | attributes: [ 'id', 'url' ], | 50 | attributes: [ 'id', 'url' ], |
51 | model: () => ActorModel.unscoped(), | 51 | model: ActorModel.unscoped(), |
52 | required: true | 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 | @Table({ | 61 | @Table({ |
62 | tableName: 'oAuthToken', | 62 | tableName: 'oAuthToken', |
63 | indexes: [ | 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 => { | 170 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
171 | if (token) token['user'] = token.User | 171 | .findOne(query) |
172 | .then(token => { | ||
173 | if (token) token[ 'user' ] = token.User | ||
172 | 174 | ||
173 | return token | 175 | return token |
174 | }) | 176 | }) |
175 | } | 177 | } |
176 | 178 | ||
177 | static getByRefreshTokenAndPopulateUser (refreshToken: string) { | 179 | static getByRefreshTokenAndPopulateUser (refreshToken: string) { |
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index cbeaa662b..eb2222256 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -13,7 +13,7 @@ import { | |||
13 | UpdatedAt | 13 | UpdatedAt |
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { ActorModel } from '../activitypub/actor' | 15 | import { ActorModel } from '../activitypub/actor' |
16 | import { getVideoSort, throwIfNotValid } from '../utils' | 16 | import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' |
17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
18 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' | 18 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' |
19 | import { VideoFileModel } from '../video/video-file' | 19 | import { VideoFileModel } from '../video/video-file' |
@@ -27,7 +27,7 @@ import { ServerModel } from '../server/server' | |||
27 | import { sample } from 'lodash' | 27 | import { sample } from 'lodash' |
28 | import { isTestInstance } from '../../helpers/core-utils' | 28 | import { isTestInstance } from '../../helpers/core-utils' |
29 | import * as Bluebird from 'bluebird' | 29 | import * as Bluebird from 'bluebird' |
30 | import * as Sequelize from 'sequelize' | 30 | import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' |
31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' | 31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' |
32 | import { CONFIG } from '../../initializers/config' | 32 | import { CONFIG } from '../../initializers/config' |
33 | 33 | ||
@@ -35,32 +35,32 @@ export enum ScopeNames { | |||
35 | WITH_VIDEO = 'WITH_VIDEO' | 35 | WITH_VIDEO = 'WITH_VIDEO' |
36 | } | 36 | } |
37 | 37 | ||
38 | @Scopes({ | 38 | @Scopes(() => ({ |
39 | [ ScopeNames.WITH_VIDEO ]: { | 39 | [ ScopeNames.WITH_VIDEO ]: { |
40 | include: [ | 40 | include: [ |
41 | { | 41 | { |
42 | model: () => VideoFileModel, | 42 | model: VideoFileModel, |
43 | required: false, | 43 | required: false, |
44 | include: [ | 44 | include: [ |
45 | { | 45 | { |
46 | model: () => VideoModel, | 46 | model: VideoModel, |
47 | required: true | 47 | required: true |
48 | } | 48 | } |
49 | ] | 49 | ] |
50 | }, | 50 | }, |
51 | { | 51 | { |
52 | model: () => VideoStreamingPlaylistModel, | 52 | model: VideoStreamingPlaylistModel, |
53 | required: false, | 53 | required: false, |
54 | include: [ | 54 | include: [ |
55 | { | 55 | { |
56 | model: () => VideoModel, | 56 | model: VideoModel, |
57 | required: true | 57 | required: true |
58 | } | 58 | } |
59 | ] | 59 | ] |
60 | } | 60 | } |
61 | ] as any // FIXME: sequelize typings | 61 | ] |
62 | } | 62 | } |
63 | }) | 63 | })) |
64 | 64 | ||
65 | @Table({ | 65 | @Table({ |
66 | tableName: 'videoRedundancy', | 66 | tableName: 'videoRedundancy', |
@@ -192,7 +192,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
192 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 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 | const query = { | 196 | const query = { |
197 | where: { | 197 | where: { |
198 | url | 198 | url |
@@ -292,7 +292,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
292 | where: { | 292 | where: { |
293 | privacy: VideoPrivacy.PUBLIC, | 293 | privacy: VideoPrivacy.PUBLIC, |
294 | views: { | 294 | views: { |
295 | [ Sequelize.Op.gte ]: minViews | 295 | [ Op.gte ]: minViews |
296 | } | 296 | } |
297 | }, | 297 | }, |
298 | include: [ | 298 | include: [ |
@@ -315,7 +315,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
315 | actorId: actor.id, | 315 | actorId: actor.id, |
316 | strategy, | 316 | strategy, |
317 | createdAt: { | 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 | static async getTotalDuplicated (strategy: VideoRedundancyStrategy) { | 326 | static async getTotalDuplicated (strategy: VideoRedundancyStrategy) { |
327 | const actor = await getServerActor() | 327 | const actor = await getServerActor() |
328 | 328 | ||
329 | const options = { | 329 | const query: FindOptions = { |
330 | include: [ | 330 | include: [ |
331 | { | 331 | { |
332 | attributes: [], | 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 | 343 | return VideoFileModel.aggregate('size', 'SUM', query) |
344 | .then(v => { | 344 | .then(result => parseAggregateResult(result)) |
345 | if (!v || isNaN(v)) return 0 | ||
346 | |||
347 | return v | ||
348 | }) | ||
349 | } | 345 | } |
350 | 346 | ||
351 | static async listLocalExpired () { | 347 | static async listLocalExpired () { |
@@ -355,7 +351,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
355 | where: { | 351 | where: { |
356 | actorId: actor.id, | 352 | actorId: actor.id, |
357 | expiresOn: { | 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 | const query = { | 365 | const query = { |
370 | where: { | 366 | where: { |
371 | actorId: { | 367 | actorId: { |
372 | [Sequelize.Op.ne]: actor.id | 368 | [Op.ne]: actor.id |
373 | }, | 369 | }, |
374 | expiresOn: { | 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 | static async getStats (strategy: VideoRedundancyStrategy) { | 424 | static async getStats (strategy: VideoRedundancyStrategy) { |
429 | const actor = await getServerActor() | 425 | const actor = await getServerActor() |
430 | 426 | ||
431 | const query = { | 427 | const query: FindOptions = { |
432 | raw: true, | 428 | raw: true, |
433 | attributes: [ | 429 | attributes: [ |
434 | [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ], | 430 | [ fn('COALESCE', fn('SUM', col('VideoFile.size')), '0'), 'totalUsed' ], |
435 | [ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', Sequelize.col('videoId'))), 'totalVideos' ], | 431 | [ fn('COUNT', fn('DISTINCT', col('videoId'))), 'totalVideos' ], |
436 | [ Sequelize.fn('COUNT', Sequelize.col('videoFileId')), 'totalVideoFiles' ] | 432 | [ fn('COUNT', col('videoFileId')), 'totalVideoFiles' ] |
437 | ], | 433 | ], |
438 | where: { | 434 | where: { |
439 | strategy, | 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 | .then((r: any) => ({ | 448 | .then((r: any) => ({ |
453 | totalUsed: parseInt(r.totalUsed.toString(), 10), | 449 | totalUsed: parseAggregateResult(r.totalUsed), |
454 | totalVideos: r.totalVideos, | 450 | totalVideos: r.totalVideos, |
455 | totalVideoFiles: r.totalVideoFiles | 451 | totalVideoFiles: r.totalVideoFiles |
456 | })) | 452 | })) |
@@ -503,7 +499,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
503 | private static async buildVideoFileForDuplication () { | 499 | private static async buildVideoFileForDuplication () { |
504 | const actor = await getServerActor() | 500 | const actor = await getServerActor() |
505 | 501 | ||
506 | const notIn = Sequelize.literal( | 502 | const notIn = literal( |
507 | '(' + | 503 | '(' + |
508 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + | 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 | required: true, | 511 | required: true, |
516 | where: { | 512 | where: { |
517 | id: { | 513 | id: { |
518 | [ Sequelize.Op.notIn ]: notIn | 514 | [ Op.notIn ]: notIn |
519 | } | 515 | } |
520 | } | 516 | } |
521 | } | 517 | } |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 450f27152..92c01f642 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -9,11 +9,11 @@ enum ScopeNames { | |||
9 | WITH_SERVER = 'WITH_SERVER' | 9 | WITH_SERVER = 'WITH_SERVER' |
10 | } | 10 | } |
11 | 11 | ||
12 | @Scopes({ | 12 | @Scopes(() => ({ |
13 | [ScopeNames.WITH_ACCOUNT]: { | 13 | [ScopeNames.WITH_ACCOUNT]: { |
14 | include: [ | 14 | include: [ |
15 | { | 15 | { |
16 | model: () => AccountModel, | 16 | model: AccountModel, |
17 | required: true | 17 | required: true |
18 | } | 18 | } |
19 | ] | 19 | ] |
@@ -21,12 +21,12 @@ enum ScopeNames { | |||
21 | [ScopeNames.WITH_SERVER]: { | 21 | [ScopeNames.WITH_SERVER]: { |
22 | include: [ | 22 | include: [ |
23 | { | 23 | { |
24 | model: () => ServerModel, | 24 | model: ServerModel, |
25 | required: true | 25 | required: true |
26 | } | 26 | } |
27 | ] | 27 | ] |
28 | } | 28 | } |
29 | }) | 29 | })) |
30 | 30 | ||
31 | @Table({ | 31 | @Table({ |
32 | tableName: 'serverBlocklist', | 32 | tableName: 'serverBlocklist', |
diff --git a/server/models/utils.ts b/server/models/utils.ts index 98170a00e..2b172f608 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -118,6 +118,15 @@ function buildWhereIdOrUUID (id: number | string) { | |||
118 | return validator.isInt('' + id) ? { id } : { uuid: id } | 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 | export { | 132 | export { |
@@ -131,7 +140,8 @@ export { | |||
131 | buildServerIdsFollowedBy, | 140 | buildServerIdsFollowedBy, |
132 | buildTrigramSearchIndex, | 141 | buildTrigramSearchIndex, |
133 | buildWhereIdOrUUID, | 142 | buildWhereIdOrUUID, |
134 | isOutdated | 143 | isOutdated, |
144 | parseAggregateResult | ||
135 | } | 145 | } |
136 | 146 | ||
137 | // --------------------------------------------------------------------------- | 147 | // --------------------------------------------------------------------------- |
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 048b47613..0fc3cfd4c 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -75,7 +75,7 @@ export class TagModel extends Model<TagModel> { | |||
75 | type: QueryTypes.SELECT as QueryTypes.SELECT | 75 | type: QueryTypes.SELECT as QueryTypes.SELECT |
76 | } | 76 | } |
77 | 77 | ||
78 | return TagModel.sequelize.query<{ name }>(query, options) | 78 | return TagModel.sequelize.query<{ name: string }>(query, options) |
79 | .then(data => data.map(d => d.name)) | 79 | .then(data => data.map(d => d.name)) |
80 | } | 80 | } |
81 | } | 81 | } |
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index baa5533ac..ec945893f 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -75,8 +75,8 @@ export class ThumbnailModel extends Model<ThumbnailModel> { | |||
75 | updatedAt: Date | 75 | updatedAt: Date |
76 | 76 | ||
77 | private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { | 77 | private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { |
78 | [ThumbnailType.THUMBNAIL]: { | 78 | [ThumbnailType.MINIATURE]: { |
79 | label: 'thumbnail', | 79 | label: 'miniature', |
80 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, | 80 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, |
81 | staticPath: STATIC_PATHS.THUMBNAILS | 81 | staticPath: STATIC_PATHS.THUMBNAILS |
82 | }, | 82 | }, |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index 45c60e26b..76243bf48 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -12,7 +12,7 @@ import { | |||
12 | Table, | 12 | Table, |
13 | UpdatedAt | 13 | UpdatedAt |
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { throwIfNotValid } from '../utils' | 15 | import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' |
16 | import { VideoModel } from './video' | 16 | import { VideoModel } from './video' |
17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
@@ -26,17 +26,17 @@ export enum ScopeNames { | |||
26 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 26 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
27 | } | 27 | } |
28 | 28 | ||
29 | @Scopes({ | 29 | @Scopes(() => ({ |
30 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { | 30 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { |
31 | include: [ | 31 | include: [ |
32 | { | 32 | { |
33 | attributes: [ 'uuid', 'remote' ], | 33 | attributes: [ 'uuid', 'remote' ], |
34 | model: () => VideoModel.unscoped(), | 34 | model: VideoModel.unscoped(), |
35 | required: true | 35 | required: true |
36 | } | 36 | } |
37 | ] | 37 | ] |
38 | } | 38 | } |
39 | }) | 39 | })) |
40 | 40 | ||
41 | @Table({ | 41 | @Table({ |
42 | tableName: 'videoCaption', | 42 | tableName: 'videoCaption', |
@@ -97,12 +97,9 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
97 | const videoInclude = { | 97 | const videoInclude = { |
98 | model: VideoModel.unscoped(), | 98 | model: VideoModel.unscoped(), |
99 | attributes: [ 'id', 'remote', 'uuid' ], | 99 | attributes: [ 'id', 'remote', 'uuid' ], |
100 | where: { } | 100 | where: buildWhereIdOrUUID(videoId) |
101 | } | 101 | } |
102 | 102 | ||
103 | if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId | ||
104 | else videoInclude.where['id'] = videoId | ||
105 | |||
106 | const query = { | 103 | const query = { |
107 | where: { | 104 | where: { |
108 | language | 105 | language |
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index a4f4d53f1..171d4574d 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts | |||
@@ -23,29 +23,29 @@ enum ScopeNames { | |||
23 | } | 23 | } |
24 | ] | 24 | ] |
25 | }) | 25 | }) |
26 | @Scopes({ | 26 | @Scopes(() => ({ |
27 | [ScopeNames.FULL]: { | 27 | [ScopeNames.FULL]: { |
28 | include: [ | 28 | include: [ |
29 | { | 29 | { |
30 | model: () => AccountModel, | 30 | model: AccountModel, |
31 | as: 'Initiator', | 31 | as: 'Initiator', |
32 | required: true | 32 | required: true |
33 | }, | 33 | }, |
34 | { | 34 | { |
35 | model: () => AccountModel, | 35 | model: AccountModel, |
36 | as: 'NextOwner', | 36 | as: 'NextOwner', |
37 | required: true | 37 | required: true |
38 | }, | 38 | }, |
39 | { | 39 | { |
40 | model: () => VideoModel, | 40 | model: VideoModel, |
41 | required: true, | 41 | required: true, |
42 | include: [ | 42 | include: [ |
43 | { model: () => VideoFileModel } | 43 | { model: VideoFileModel } |
44 | ] | 44 | ] |
45 | } | 45 | } |
46 | ] as any // FIXME: sequelize typings | 46 | ] |
47 | } | 47 | } |
48 | }) | 48 | })) |
49 | export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> { | 49 | export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> { |
50 | @CreatedAt | 50 | @CreatedAt |
51 | createdAt: Date | 51 | createdAt: Date |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 901006dea..fb70e6625 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -58,15 +58,15 @@ type AvailableForListOptions = { | |||
58 | actorId: number | 58 | actorId: number |
59 | } | 59 | } |
60 | 60 | ||
61 | @DefaultScope({ | 61 | @DefaultScope(() => ({ |
62 | include: [ | 62 | include: [ |
63 | { | 63 | { |
64 | model: () => ActorModel, | 64 | model: ActorModel, |
65 | required: true | 65 | required: true |
66 | } | 66 | } |
67 | ] | 67 | ] |
68 | }) | 68 | })) |
69 | @Scopes({ | 69 | @Scopes(() => ({ |
70 | [ScopeNames.SUMMARY]: (withAccount = false) => { | 70 | [ScopeNames.SUMMARY]: (withAccount = false) => { |
71 | const base: FindOptions = { | 71 | const base: FindOptions = { |
72 | attributes: [ 'name', 'description', 'id', 'actorId' ], | 72 | attributes: [ 'name', 'description', 'id', 'actorId' ], |
@@ -142,22 +142,22 @@ type AvailableForListOptions = { | |||
142 | [ScopeNames.WITH_ACCOUNT]: { | 142 | [ScopeNames.WITH_ACCOUNT]: { |
143 | include: [ | 143 | include: [ |
144 | { | 144 | { |
145 | model: () => AccountModel, | 145 | model: AccountModel, |
146 | required: true | 146 | required: true |
147 | } | 147 | } |
148 | ] | 148 | ] |
149 | }, | 149 | }, |
150 | [ScopeNames.WITH_VIDEOS]: { | 150 | [ScopeNames.WITH_VIDEOS]: { |
151 | include: [ | 151 | include: [ |
152 | () => VideoModel | 152 | VideoModel |
153 | ] | 153 | ] |
154 | }, | 154 | }, |
155 | [ScopeNames.WITH_ACTOR]: { | 155 | [ScopeNames.WITH_ACTOR]: { |
156 | include: [ | 156 | include: [ |
157 | () => ActorModel | 157 | ActorModel |
158 | ] | 158 | ] |
159 | } | 159 | } |
160 | }) | 160 | })) |
161 | @Table({ | 161 | @Table({ |
162 | tableName: 'videoChannel', | 162 | tableName: 'videoChannel', |
163 | indexes | 163 | indexes |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 5f7cd3671..fee11ec5f 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -30,7 +30,7 @@ import { UserModel } from '../account/user' | |||
30 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | 30 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' |
31 | import { regexpCapture } from '../../helpers/regexp' | 31 | import { regexpCapture } from '../../helpers/regexp' |
32 | import { uniq } from 'lodash' | 32 | import { uniq } from 'lodash' |
33 | import { FindOptions, Op, Order, Sequelize, Transaction } from 'sequelize' | 33 | import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' |
34 | 34 | ||
35 | enum ScopeNames { | 35 | enum ScopeNames { |
36 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 36 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -39,7 +39,7 @@ enum ScopeNames { | |||
39 | ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' | 39 | ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' |
40 | } | 40 | } |
41 | 41 | ||
42 | @Scopes({ | 42 | @Scopes(() => ({ |
43 | [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { | 43 | [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { |
44 | return { | 44 | return { |
45 | attributes: { | 45 | attributes: { |
@@ -63,34 +63,34 @@ enum ScopeNames { | |||
63 | ] | 63 | ] |
64 | ] | 64 | ] |
65 | } | 65 | } |
66 | } | 66 | } as FindOptions |
67 | }, | 67 | }, |
68 | [ScopeNames.WITH_ACCOUNT]: { | 68 | [ScopeNames.WITH_ACCOUNT]: { |
69 | include: [ | 69 | include: [ |
70 | { | 70 | { |
71 | model: () => AccountModel, | 71 | model: AccountModel, |
72 | include: [ | 72 | include: [ |
73 | { | 73 | { |
74 | model: () => ActorModel, | 74 | model: ActorModel, |
75 | include: [ | 75 | include: [ |
76 | { | 76 | { |
77 | model: () => ServerModel, | 77 | model: ServerModel, |
78 | required: false | 78 | required: false |
79 | }, | 79 | }, |
80 | { | 80 | { |
81 | model: () => AvatarModel, | 81 | model: AvatarModel, |
82 | required: false | 82 | required: false |
83 | } | 83 | } |
84 | ] | 84 | ] |
85 | } | 85 | } |
86 | ] | 86 | ] |
87 | } | 87 | } |
88 | ] as any // FIXME: sequelize typings | 88 | ] |
89 | }, | 89 | }, |
90 | [ScopeNames.WITH_IN_REPLY_TO]: { | 90 | [ScopeNames.WITH_IN_REPLY_TO]: { |
91 | include: [ | 91 | include: [ |
92 | { | 92 | { |
93 | model: () => VideoCommentModel, | 93 | model: VideoCommentModel, |
94 | as: 'InReplyToVideoComment' | 94 | as: 'InReplyToVideoComment' |
95 | } | 95 | } |
96 | ] | 96 | ] |
@@ -98,19 +98,19 @@ enum ScopeNames { | |||
98 | [ScopeNames.WITH_VIDEO]: { | 98 | [ScopeNames.WITH_VIDEO]: { |
99 | include: [ | 99 | include: [ |
100 | { | 100 | { |
101 | model: () => VideoModel, | 101 | model: VideoModel, |
102 | required: true, | 102 | required: true, |
103 | include: [ | 103 | include: [ |
104 | { | 104 | { |
105 | model: () => VideoChannelModel.unscoped(), | 105 | model: VideoChannelModel.unscoped(), |
106 | required: true, | 106 | required: true, |
107 | include: [ | 107 | include: [ |
108 | { | 108 | { |
109 | model: () => AccountModel, | 109 | model: AccountModel, |
110 | required: true, | 110 | required: true, |
111 | include: [ | 111 | include: [ |
112 | { | 112 | { |
113 | model: () => ActorModel, | 113 | model: ActorModel, |
114 | required: true | 114 | required: true |
115 | } | 115 | } |
116 | ] | 116 | ] |
@@ -119,9 +119,9 @@ enum ScopeNames { | |||
119 | } | 119 | } |
120 | ] | 120 | ] |
121 | } | 121 | } |
122 | ] as any // FIXME: sequelize typings | 122 | ] |
123 | } | 123 | } |
124 | }) | 124 | })) |
125 | @Table({ | 125 | @Table({ |
126 | tableName: 'videoComment', | 126 | tableName: 'videoComment', |
127 | indexes: [ | 127 | indexes: [ |
@@ -313,8 +313,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
313 | } | 313 | } |
314 | } | 314 | } |
315 | 315 | ||
316 | // FIXME: typings | 316 | const scopes: (string | ScopeOptions)[] = [ |
317 | const scopes: any[] = [ | ||
318 | ScopeNames.WITH_ACCOUNT, | 317 | ScopeNames.WITH_ACCOUNT, |
319 | { | 318 | { |
320 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] | 319 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index c14d96bc5..2203a7aba 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -19,11 +19,11 @@ import { | |||
19 | isVideoFileSizeValid, | 19 | isVideoFileSizeValid, |
20 | isVideoFPSResolutionValid | 20 | isVideoFPSResolutionValid |
21 | } from '../../helpers/custom-validators/videos' | 21 | } from '../../helpers/custom-validators/videos' |
22 | import { throwIfNotValid } from '../utils' | 22 | import { parseAggregateResult, throwIfNotValid } from '../utils' |
23 | import { VideoModel } from './video' | 23 | import { VideoModel } from './video' |
24 | import * as Sequelize from 'sequelize' | ||
25 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 24 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
26 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 25 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
26 | import { FindOptions, QueryTypes, Transaction } from 'sequelize' | ||
27 | 27 | ||
28 | @Table({ | 28 | @Table({ |
29 | tableName: 'videoFile', | 29 | tableName: 'videoFile', |
@@ -97,15 +97,13 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
97 | static doesInfohashExist (infoHash: string) { | 97 | static doesInfohashExist (infoHash: string) { |
98 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' | 98 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' |
99 | const options = { | 99 | const options = { |
100 | type: Sequelize.QueryTypes.SELECT, | 100 | type: QueryTypes.SELECT, |
101 | bind: { infoHash }, | 101 | bind: { infoHash }, |
102 | raw: true | 102 | raw: true |
103 | } | 103 | } |
104 | 104 | ||
105 | return VideoModel.sequelize.query(query, options) | 105 | return VideoModel.sequelize.query(query, options) |
106 | .then(results => { | 106 | .then(results => results.length === 1) |
107 | return results.length === 1 | ||
108 | }) | ||
109 | } | 107 | } |
110 | 108 | ||
111 | static loadWithVideo (id: number) { | 109 | static loadWithVideo (id: number) { |
@@ -121,7 +119,7 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
121 | return VideoFileModel.findByPk(id, options) | 119 | return VideoFileModel.findByPk(id, options) |
122 | } | 120 | } |
123 | 121 | ||
124 | static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Sequelize.Transaction) { | 122 | static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) { |
125 | const query = { | 123 | const query = { |
126 | include: [ | 124 | include: [ |
127 | { | 125 | { |
@@ -144,8 +142,8 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
144 | return VideoFileModel.findAll(query) | 142 | return VideoFileModel.findAll(query) |
145 | } | 143 | } |
146 | 144 | ||
147 | static async getStats () { | 145 | static getStats () { |
148 | let totalLocalVideoFilesSize = await VideoFileModel.sum('size', { | 146 | const query: FindOptions = { |
149 | include: [ | 147 | include: [ |
150 | { | 148 | { |
151 | attributes: [], | 149 | attributes: [], |
@@ -155,13 +153,12 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
155 | } | 153 | } |
156 | } | 154 | } |
157 | ] | 155 | ] |
158 | } as any) | ||
159 | // Sequelize could return null... | ||
160 | if (!totalLocalVideoFilesSize) totalLocalVideoFilesSize = 0 | ||
161 | |||
162 | return { | ||
163 | totalLocalVideoFilesSize | ||
164 | } | 156 | } |
157 | |||
158 | return VideoFileModel.aggregate('size', 'SUM', query) | ||
159 | .then(result => ({ | ||
160 | totalLocalVideoFilesSize: parseAggregateResult(result) | ||
161 | })) | ||
165 | } | 162 | } |
166 | 163 | ||
167 | hasSameUniqueKeysThan (other: VideoFileModel) { | 164 | hasSameUniqueKeysThan (other: VideoFileModel) { |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 89992a5a8..877fcbc57 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -59,7 +59,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
59 | views: video.views, | 59 | views: video.views, |
60 | likes: video.likes, | 60 | likes: video.likes, |
61 | dislikes: video.dislikes, | 61 | dislikes: video.dislikes, |
62 | thumbnailPath: video.getThumbnailStaticPath(), | 62 | thumbnailPath: video.getMiniatureStaticPath(), |
63 | previewPath: video.getPreviewStaticPath(), | 63 | previewPath: video.getPreviewStaticPath(), |
64 | embedPath: video.getEmbedStaticPath(), | 64 | embedPath: video.getEmbedStaticPath(), |
65 | createdAt: video.createdAt, | 65 | createdAt: video.createdAt, |
@@ -301,6 +301,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
301 | }) | 301 | }) |
302 | } | 302 | } |
303 | 303 | ||
304 | const miniature = video.getMiniature() | ||
305 | |||
304 | return { | 306 | return { |
305 | type: 'Video' as 'Video', | 307 | type: 'Video' as 'Video', |
306 | id: video.url, | 308 | id: video.url, |
@@ -326,10 +328,10 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
326 | subtitleLanguage, | 328 | subtitleLanguage, |
327 | icon: { | 329 | icon: { |
328 | type: 'Image', | 330 | type: 'Image', |
329 | url: video.getThumbnail().getUrl(), | 331 | url: miniature.getUrl(), |
330 | mediaType: 'image/jpeg', | 332 | mediaType: 'image/jpeg', |
331 | width: video.getThumbnail().width, | 333 | width: miniature.width, |
332 | height: video.getThumbnail().height | 334 | height: miniature.height |
333 | }, | 335 | }, |
334 | url, | 336 | url, |
335 | likes: getVideoLikesActivityPubUrl(video), | 337 | likes: getVideoLikesActivityPubUrl(video), |
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 588a13a4f..480a671c8 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -21,18 +21,18 @@ import { VideoImport, VideoImportState } from '../../../shared' | |||
21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' | 21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' |
22 | import { UserModel } from '../account/user' | 22 | import { UserModel } from '../account/user' |
23 | 23 | ||
24 | @DefaultScope({ | 24 | @DefaultScope(() => ({ |
25 | include: [ | 25 | include: [ |
26 | { | 26 | { |
27 | model: () => UserModel.unscoped(), | 27 | model: UserModel.unscoped(), |
28 | required: true | 28 | required: true |
29 | }, | 29 | }, |
30 | { | 30 | { |
31 | model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), | 31 | model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), |
32 | required: false | 32 | required: false |
33 | } | 33 | } |
34 | ] | 34 | ] |
35 | }) | 35 | })) |
36 | 36 | ||
37 | @Table({ | 37 | @Table({ |
38 | tableName: 'videoImport', | 38 | tableName: 'videoImport', |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index 3e436acfc..63b4a0715 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -42,7 +42,7 @@ import { activityPubCollectionPagination } from '../../helpers/activitypub' | |||
42 | import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model' | 42 | import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model' |
43 | import { ThumbnailModel } from './thumbnail' | 43 | import { ThumbnailModel } from './thumbnail' |
44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' | 44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' |
45 | import { fn, literal, Op, Transaction } from 'sequelize' | 45 | import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' |
46 | 46 | ||
47 | enum ScopeNames { | 47 | enum ScopeNames { |
48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
@@ -61,11 +61,11 @@ type AvailableForListOptions = { | |||
61 | privateAndUnlisted?: boolean | 61 | privateAndUnlisted?: boolean |
62 | } | 62 | } |
63 | 63 | ||
64 | @Scopes({ | 64 | @Scopes(() => ({ |
65 | [ ScopeNames.WITH_THUMBNAIL ]: { | 65 | [ ScopeNames.WITH_THUMBNAIL ]: { |
66 | include: [ | 66 | include: [ |
67 | { | 67 | { |
68 | model: () => ThumbnailModel, | 68 | model: ThumbnailModel, |
69 | required: false | 69 | required: false |
70 | } | 70 | } |
71 | ] | 71 | ] |
@@ -74,20 +74,16 @@ type AvailableForListOptions = { | |||
74 | attributes: { | 74 | attributes: { |
75 | include: [ | 75 | include: [ |
76 | [ | 76 | [ |
77 | fn('COUNT', 'toto'), | ||
78 | 'coucou' | ||
79 | ], | ||
80 | [ | ||
81 | literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'), | 77 | literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'), |
82 | 'videosLength' | 78 | 'videosLength' |
83 | ] | 79 | ] |
84 | ] | 80 | ] |
85 | } | 81 | } |
86 | }, | 82 | } as FindOptions, |
87 | [ ScopeNames.WITH_ACCOUNT ]: { | 83 | [ ScopeNames.WITH_ACCOUNT ]: { |
88 | include: [ | 84 | include: [ |
89 | { | 85 | { |
90 | model: () => AccountModel, | 86 | model: AccountModel, |
91 | required: true | 87 | required: true |
92 | } | 88 | } |
93 | ] | 89 | ] |
@@ -95,11 +91,11 @@ type AvailableForListOptions = { | |||
95 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { | 91 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { |
96 | include: [ | 92 | include: [ |
97 | { | 93 | { |
98 | model: () => AccountModel.scope(AccountScopeNames.SUMMARY), | 94 | model: AccountModel.scope(AccountScopeNames.SUMMARY), |
99 | required: true | 95 | required: true |
100 | }, | 96 | }, |
101 | { | 97 | { |
102 | model: () => VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), | 98 | model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), |
103 | required: false | 99 | required: false |
104 | } | 100 | } |
105 | ] | 101 | ] |
@@ -107,11 +103,11 @@ type AvailableForListOptions = { | |||
107 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { | 103 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { |
108 | include: [ | 104 | include: [ |
109 | { | 105 | { |
110 | model: () => AccountModel, | 106 | model: AccountModel, |
111 | required: true | 107 | required: true |
112 | }, | 108 | }, |
113 | { | 109 | { |
114 | model: () => VideoChannelModel, | 110 | model: VideoChannelModel, |
115 | required: false | 111 | required: false |
116 | } | 112 | } |
117 | ] | 113 | ] |
@@ -132,7 +128,7 @@ type AvailableForListOptions = { | |||
132 | ] | 128 | ] |
133 | } | 129 | } |
134 | 130 | ||
135 | const whereAnd: any[] = [] | 131 | const whereAnd: WhereOptions[] = [] |
136 | 132 | ||
137 | if (options.privateAndUnlisted !== true) { | 133 | if (options.privateAndUnlisted !== true) { |
138 | whereAnd.push({ | 134 | whereAnd.push({ |
@@ -178,9 +174,9 @@ type AvailableForListOptions = { | |||
178 | required: false | 174 | required: false |
179 | } | 175 | } |
180 | ] | 176 | ] |
181 | } | 177 | } as FindOptions |
182 | } | 178 | } |
183 | }) | 179 | })) |
184 | 180 | ||
185 | @Table({ | 181 | @Table({ |
186 | tableName: 'videoPlaylist', | 182 | tableName: 'videoPlaylist', |
@@ -269,6 +265,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
269 | VideoPlaylistElements: VideoPlaylistElementModel[] | 265 | VideoPlaylistElements: VideoPlaylistElementModel[] |
270 | 266 | ||
271 | @HasOne(() => ThumbnailModel, { | 267 | @HasOne(() => ThumbnailModel, { |
268 | |||
272 | foreignKey: { | 269 | foreignKey: { |
273 | name: 'videoPlaylistId', | 270 | name: 'videoPlaylistId', |
274 | allowNull: true | 271 | allowNull: true |
@@ -294,7 +291,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
294 | order: getSort(options.sort) | 291 | order: getSort(options.sort) |
295 | } | 292 | } |
296 | 293 | ||
297 | const scopes = [ | 294 | const scopes: (string | ScopeOptions)[] = [ |
298 | { | 295 | { |
299 | method: [ | 296 | method: [ |
300 | ScopeNames.AVAILABLE_FOR_LIST, | 297 | ScopeNames.AVAILABLE_FOR_LIST, |
@@ -306,7 +303,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
306 | privateAndUnlisted: options.privateAndUnlisted | 303 | privateAndUnlisted: options.privateAndUnlisted |
307 | } as AvailableForListOptions | 304 | } as AvailableForListOptions |
308 | ] | 305 | ] |
309 | } as any, // FIXME: typings | 306 | }, |
310 | ScopeNames.WITH_VIDEOS_LENGTH, | 307 | ScopeNames.WITH_VIDEOS_LENGTH, |
311 | ScopeNames.WITH_THUMBNAIL | 308 | ScopeNames.WITH_THUMBNAIL |
312 | ] | 309 | ] |
@@ -348,7 +345,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
348 | model: VideoPlaylistElementModel.unscoped(), | 345 | model: VideoPlaylistElementModel.unscoped(), |
349 | where: { | 346 | where: { |
350 | videoId: { | 347 | videoId: { |
351 | [Op.any]: videoIds | 348 | [Op.in]: videoIds // FIXME: sequelize ANY seems broken |
352 | } | 349 | } |
353 | }, | 350 | }, |
354 | required: true | 351 | required: true |
@@ -427,12 +424,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
427 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) | 424 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) |
428 | } | 425 | } |
429 | 426 | ||
430 | setThumbnail (thumbnail: ThumbnailModel) { | 427 | async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { |
431 | this.Thumbnail = thumbnail | 428 | thumbnail.videoPlaylistId = this.id |
432 | } | ||
433 | 429 | ||
434 | getThumbnail () { | 430 | this.Thumbnail = await thumbnail.save({ transaction: t }) |
435 | return this.Thumbnail | ||
436 | } | 431 | } |
437 | 432 | ||
438 | hasThumbnail () { | 433 | hasThumbnail () { |
@@ -448,13 +443,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
448 | getThumbnailUrl () { | 443 | getThumbnailUrl () { |
449 | if (!this.hasThumbnail()) return null | 444 | if (!this.hasThumbnail()) return null |
450 | 445 | ||
451 | return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.getThumbnail().filename | 446 | return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename |
452 | } | 447 | } |
453 | 448 | ||
454 | getThumbnailStaticPath () { | 449 | getThumbnailStaticPath () { |
455 | if (!this.hasThumbnail()) return null | 450 | if (!this.hasThumbnail()) return null |
456 | 451 | ||
457 | return join(STATIC_PATHS.THUMBNAILS, this.getThumbnail().filename) | 452 | return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) |
458 | } | 453 | } |
459 | 454 | ||
460 | setAsRefreshed () { | 455 | setAsRefreshed () { |
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index c83f6c5b0..fda2d7cea 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -14,15 +14,15 @@ enum ScopeNames { | |||
14 | WITH_ACTOR = 'WITH_ACTOR' | 14 | WITH_ACTOR = 'WITH_ACTOR' |
15 | } | 15 | } |
16 | 16 | ||
17 | @Scopes({ | 17 | @Scopes(() => ({ |
18 | [ScopeNames.FULL]: { | 18 | [ScopeNames.FULL]: { |
19 | include: [ | 19 | include: [ |
20 | { | 20 | { |
21 | model: () => ActorModel, | 21 | model: ActorModel, |
22 | required: true | 22 | required: true |
23 | }, | 23 | }, |
24 | { | 24 | { |
25 | model: () => VideoModel, | 25 | model: VideoModel, |
26 | required: true | 26 | required: true |
27 | } | 27 | } |
28 | ] | 28 | ] |
@@ -30,12 +30,12 @@ enum ScopeNames { | |||
30 | [ScopeNames.WITH_ACTOR]: { | 30 | [ScopeNames.WITH_ACTOR]: { |
31 | include: [ | 31 | include: [ |
32 | { | 32 | { |
33 | model: () => ActorModel, | 33 | model: ActorModel, |
34 | required: true | 34 | required: true |
35 | } | 35 | } |
36 | ] | 36 | ] |
37 | } | 37 | } |
38 | }) | 38 | })) |
39 | @Table({ | 39 | @Table({ |
40 | tableName: 'videoShare', | 40 | tableName: 'videoShare', |
41 | indexes: [ | 41 | indexes: [ |
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index b30267e09..31dc82c54 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -26,7 +26,7 @@ import { QueryTypes, Op } from 'sequelize' | |||
26 | fields: [ 'p2pMediaLoaderInfohashes' ], | 26 | fields: [ 'p2pMediaLoaderInfohashes' ], |
27 | using: 'gin' | 27 | using: 'gin' |
28 | } | 28 | } |
29 | ] as any // FIXME: sequelize typings | 29 | ] |
30 | }) | 30 | }) |
31 | export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> { | 31 | export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> { |
32 | @CreatedAt | 32 | @CreatedAt |
@@ -46,7 +46,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
46 | 46 | ||
47 | @AllowNull(false) | 47 | @AllowNull(false) |
48 | @Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes')) | 48 | @Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes')) |
49 | @Column({ type: DataType.ARRAY(DataType.STRING) }) // FIXME: typings | 49 | @Column(DataType.ARRAY(DataType.STRING)) |
50 | p2pMediaLoaderInfohashes: string[] | 50 | p2pMediaLoaderInfohashes: string[] |
51 | 51 | ||
52 | @AllowNull(false) | 52 | @AllowNull(false) |
@@ -87,7 +87,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
87 | raw: true | 87 | raw: true |
88 | } | 88 | } |
89 | 89 | ||
90 | return VideoModel.sequelize.query<any>(query, options) | 90 | return VideoModel.sequelize.query<object>(query, options) |
91 | .then(results => results.length === 1) | 91 | .then(results => results.length === 1) |
92 | } | 92 | } |
93 | 93 | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 329cebd28..18f18795e 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -227,12 +227,12 @@ type AvailableForListIDsOptions = { | |||
227 | historyOfUser?: UserModel | 227 | historyOfUser?: UserModel |
228 | } | 228 | } |
229 | 229 | ||
230 | @Scopes({ | 230 | @Scopes(() => ({ |
231 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { | 231 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { |
232 | const query: FindOptions = { | 232 | const query: FindOptions = { |
233 | where: { | 233 | where: { |
234 | id: { | 234 | id: { |
235 | [ Op.in ]: options.ids // FIXME: sequelize any seems broken | 235 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken |
236 | } | 236 | } |
237 | }, | 237 | }, |
238 | include: [ | 238 | include: [ |
@@ -486,7 +486,7 @@ type AvailableForListIDsOptions = { | |||
486 | [ ScopeNames.WITH_THUMBNAILS ]: { | 486 | [ ScopeNames.WITH_THUMBNAILS ]: { |
487 | include: [ | 487 | include: [ |
488 | { | 488 | { |
489 | model: () => ThumbnailModel, | 489 | model: ThumbnailModel, |
490 | required: false | 490 | required: false |
491 | } | 491 | } |
492 | ] | 492 | ] |
@@ -495,48 +495,48 @@ type AvailableForListIDsOptions = { | |||
495 | include: [ | 495 | include: [ |
496 | { | 496 | { |
497 | attributes: [ 'accountId' ], | 497 | attributes: [ 'accountId' ], |
498 | model: () => VideoChannelModel.unscoped(), | 498 | model: VideoChannelModel.unscoped(), |
499 | required: true, | 499 | required: true, |
500 | include: [ | 500 | include: [ |
501 | { | 501 | { |
502 | attributes: [ 'userId' ], | 502 | attributes: [ 'userId' ], |
503 | model: () => AccountModel.unscoped(), | 503 | model: AccountModel.unscoped(), |
504 | required: true | 504 | required: true |
505 | } | 505 | } |
506 | ] | 506 | ] |
507 | } | 507 | } |
508 | ] as any // FIXME: sequelize typings | 508 | ] |
509 | }, | 509 | }, |
510 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { | 510 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { |
511 | include: [ | 511 | include: [ |
512 | { | 512 | { |
513 | model: () => VideoChannelModel.unscoped(), | 513 | model: VideoChannelModel.unscoped(), |
514 | required: true, | 514 | required: true, |
515 | include: [ | 515 | include: [ |
516 | { | 516 | { |
517 | attributes: { | 517 | attributes: { |
518 | exclude: [ 'privateKey', 'publicKey' ] | 518 | exclude: [ 'privateKey', 'publicKey' ] |
519 | }, | 519 | }, |
520 | model: () => ActorModel.unscoped(), | 520 | model: ActorModel.unscoped(), |
521 | required: true, | 521 | required: true, |
522 | include: [ | 522 | include: [ |
523 | { | 523 | { |
524 | attributes: [ 'host' ], | 524 | attributes: [ 'host' ], |
525 | model: () => ServerModel.unscoped(), | 525 | model: ServerModel.unscoped(), |
526 | required: false | 526 | required: false |
527 | }, | 527 | }, |
528 | { | 528 | { |
529 | model: () => AvatarModel.unscoped(), | 529 | model: AvatarModel.unscoped(), |
530 | required: false | 530 | required: false |
531 | } | 531 | } |
532 | ] | 532 | ] |
533 | }, | 533 | }, |
534 | { | 534 | { |
535 | model: () => AccountModel.unscoped(), | 535 | model: AccountModel.unscoped(), |
536 | required: true, | 536 | required: true, |
537 | include: [ | 537 | include: [ |
538 | { | 538 | { |
539 | model: () => ActorModel.unscoped(), | 539 | model: ActorModel.unscoped(), |
540 | attributes: { | 540 | attributes: { |
541 | exclude: [ 'privateKey', 'publicKey' ] | 541 | exclude: [ 'privateKey', 'publicKey' ] |
542 | }, | 542 | }, |
@@ -544,11 +544,11 @@ type AvailableForListIDsOptions = { | |||
544 | include: [ | 544 | include: [ |
545 | { | 545 | { |
546 | attributes: [ 'host' ], | 546 | attributes: [ 'host' ], |
547 | model: () => ServerModel.unscoped(), | 547 | model: ServerModel.unscoped(), |
548 | required: false | 548 | required: false |
549 | }, | 549 | }, |
550 | { | 550 | { |
551 | model: () => AvatarModel.unscoped(), | 551 | model: AvatarModel.unscoped(), |
552 | required: false | 552 | required: false |
553 | } | 553 | } |
554 | ] | 554 | ] |
@@ -557,16 +557,16 @@ type AvailableForListIDsOptions = { | |||
557 | } | 557 | } |
558 | ] | 558 | ] |
559 | } | 559 | } |
560 | ] as any // FIXME: sequelize typings | 560 | ] |
561 | }, | 561 | }, |
562 | [ ScopeNames.WITH_TAGS ]: { | 562 | [ ScopeNames.WITH_TAGS ]: { |
563 | include: [ () => TagModel ] | 563 | include: [ TagModel ] |
564 | }, | 564 | }, |
565 | [ ScopeNames.WITH_BLACKLISTED ]: { | 565 | [ ScopeNames.WITH_BLACKLISTED ]: { |
566 | include: [ | 566 | include: [ |
567 | { | 567 | { |
568 | attributes: [ 'id', 'reason' ], | 568 | attributes: [ 'id', 'reason' ], |
569 | model: () => VideoBlacklistModel, | 569 | model: VideoBlacklistModel, |
570 | required: false | 570 | required: false |
571 | } | 571 | } |
572 | ] | 572 | ] |
@@ -588,8 +588,7 @@ type AvailableForListIDsOptions = { | |||
588 | include: [ | 588 | include: [ |
589 | { | 589 | { |
590 | model: VideoFileModel.unscoped(), | 590 | model: VideoFileModel.unscoped(), |
591 | // FIXME: typings | 591 | separate: true, // We may have multiple files, having multiple redundancies so let's separate this join |
592 | [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join | ||
593 | required: false, | 592 | required: false, |
594 | include: subInclude | 593 | include: subInclude |
595 | } | 594 | } |
@@ -613,8 +612,7 @@ type AvailableForListIDsOptions = { | |||
613 | include: [ | 612 | include: [ |
614 | { | 613 | { |
615 | model: VideoStreamingPlaylistModel.unscoped(), | 614 | model: VideoStreamingPlaylistModel.unscoped(), |
616 | // FIXME: typings | 615 | separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join |
617 | [ 'separate' as any ]: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join | ||
618 | required: false, | 616 | required: false, |
619 | include: subInclude | 617 | include: subInclude |
620 | } | 618 | } |
@@ -624,7 +622,7 @@ type AvailableForListIDsOptions = { | |||
624 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { | 622 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { |
625 | include: [ | 623 | include: [ |
626 | { | 624 | { |
627 | model: () => ScheduleVideoUpdateModel.unscoped(), | 625 | model: ScheduleVideoUpdateModel.unscoped(), |
628 | required: false | 626 | required: false |
629 | } | 627 | } |
630 | ] | 628 | ] |
@@ -643,7 +641,7 @@ type AvailableForListIDsOptions = { | |||
643 | ] | 641 | ] |
644 | } | 642 | } |
645 | } | 643 | } |
646 | }) | 644 | })) |
647 | @Table({ | 645 | @Table({ |
648 | tableName: 'video', | 646 | tableName: 'video', |
649 | indexes | 647 | indexes |
@@ -1075,15 +1073,14 @@ export class VideoModel extends Model<VideoModel> { | |||
1075 | } | 1073 | } |
1076 | 1074 | ||
1077 | return Bluebird.all([ | 1075 | return Bluebird.all([ |
1078 | // FIXME: typing issue | 1076 | VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query), |
1079 | VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query as any), | 1077 | VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT }) |
1080 | VideoModel.sequelize.query<{ total: number }>(rawCountQuery, { type: QueryTypes.SELECT }) | ||
1081 | ]).then(([ rows, totals ]) => { | 1078 | ]).then(([ rows, totals ]) => { |
1082 | // totals: totalVideos + totalVideoShares | 1079 | // totals: totalVideos + totalVideoShares |
1083 | let totalVideos = 0 | 1080 | let totalVideos = 0 |
1084 | let totalVideoShares = 0 | 1081 | let totalVideoShares = 0 |
1085 | if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total + '', 10) | 1082 | if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) |
1086 | if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total + '', 10) | 1083 | if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) |
1087 | 1084 | ||
1088 | const total = totalVideos + totalVideoShares | 1085 | const total = totalVideos + totalVideoShares |
1089 | return { | 1086 | return { |
@@ -1094,50 +1091,58 @@ export class VideoModel extends Model<VideoModel> { | |||
1094 | } | 1091 | } |
1095 | 1092 | ||
1096 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { | 1093 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { |
1097 | const query: FindOptions = { | 1094 | function buildBaseQuery (): FindOptions { |
1098 | offset: start, | 1095 | return { |
1099 | limit: count, | 1096 | offset: start, |
1100 | order: getVideoSort(sort), | 1097 | limit: count, |
1101 | include: [ | 1098 | order: getVideoSort(sort), |
1102 | { | 1099 | include: [ |
1103 | model: VideoChannelModel, | 1100 | { |
1104 | required: true, | 1101 | model: VideoChannelModel, |
1105 | include: [ | 1102 | required: true, |
1106 | { | 1103 | include: [ |
1107 | model: AccountModel, | 1104 | { |
1108 | where: { | 1105 | model: AccountModel, |
1109 | id: accountId | 1106 | where: { |
1110 | }, | 1107 | id: accountId |
1111 | required: true | 1108 | }, |
1112 | } | 1109 | required: true |
1113 | ] | 1110 | } |
1114 | }, | 1111 | ] |
1115 | { | 1112 | } |
1116 | model: ScheduleVideoUpdateModel, | 1113 | ] |
1117 | required: false | 1114 | } |
1118 | }, | ||
1119 | { | ||
1120 | model: VideoBlacklistModel, | ||
1121 | required: false | ||
1122 | } | ||
1123 | ] | ||
1124 | } | 1115 | } |
1125 | 1116 | ||
1117 | const countQuery = buildBaseQuery() | ||
1118 | const findQuery = buildBaseQuery() | ||
1119 | |||
1120 | findQuery.include.push({ | ||
1121 | model: ScheduleVideoUpdateModel, | ||
1122 | required: false | ||
1123 | }) | ||
1124 | |||
1125 | findQuery.include.push({ | ||
1126 | model: VideoBlacklistModel, | ||
1127 | required: false | ||
1128 | }) | ||
1129 | |||
1126 | if (withFiles === true) { | 1130 | if (withFiles === true) { |
1127 | query.include.push({ | 1131 | findQuery.include.push({ |
1128 | model: VideoFileModel.unscoped(), | 1132 | model: VideoFileModel.unscoped(), |
1129 | required: true | 1133 | required: true |
1130 | }) | 1134 | }) |
1131 | } | 1135 | } |
1132 | 1136 | ||
1133 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS) | 1137 | return Promise.all([ |
1134 | .findAndCountAll(query) | 1138 | VideoModel.count(countQuery), |
1135 | .then(({ rows, count }) => { | 1139 | VideoModel.findAll(findQuery) |
1136 | return { | 1140 | ]).then(([ count, rows ]) => { |
1137 | data: rows, | 1141 | return { |
1138 | total: count | 1142 | data: rows, |
1139 | } | 1143 | total: count |
1140 | }) | 1144 | } |
1145 | }) | ||
1141 | } | 1146 | } |
1142 | 1147 | ||
1143 | static async listForApi (options: { | 1148 | static async listForApi (options: { |
@@ -1404,12 +1409,12 @@ export class VideoModel extends Model<VideoModel> { | |||
1404 | const where = buildWhereIdOrUUID(id) | 1409 | const where = buildWhereIdOrUUID(id) |
1405 | 1410 | ||
1406 | const options = { | 1411 | const options = { |
1407 | order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings | 1412 | order: [ [ 'Tags', 'name', 'ASC' ] ] as any, |
1408 | where, | 1413 | where, |
1409 | transaction: t | 1414 | transaction: t |
1410 | } | 1415 | } |
1411 | 1416 | ||
1412 | const scopes = [ | 1417 | const scopes: (string | ScopeOptions)[] = [ |
1413 | ScopeNames.WITH_TAGS, | 1418 | ScopeNames.WITH_TAGS, |
1414 | ScopeNames.WITH_BLACKLISTED, | 1419 | ScopeNames.WITH_BLACKLISTED, |
1415 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1420 | ScopeNames.WITH_ACCOUNT_DETAILS, |
@@ -1420,7 +1425,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1420 | ] | 1425 | ] |
1421 | 1426 | ||
1422 | if (userId) { | 1427 | if (userId) { |
1423 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings | 1428 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) |
1424 | } | 1429 | } |
1425 | 1430 | ||
1426 | return VideoModel | 1431 | return VideoModel |
@@ -1437,18 +1442,18 @@ export class VideoModel extends Model<VideoModel> { | |||
1437 | transaction: t | 1442 | transaction: t |
1438 | } | 1443 | } |
1439 | 1444 | ||
1440 | const scopes = [ | 1445 | const scopes: (string | ScopeOptions)[] = [ |
1441 | ScopeNames.WITH_TAGS, | 1446 | ScopeNames.WITH_TAGS, |
1442 | ScopeNames.WITH_BLACKLISTED, | 1447 | ScopeNames.WITH_BLACKLISTED, |
1443 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1448 | ScopeNames.WITH_ACCOUNT_DETAILS, |
1444 | ScopeNames.WITH_SCHEDULED_UPDATE, | 1449 | ScopeNames.WITH_SCHEDULED_UPDATE, |
1445 | ScopeNames.WITH_THUMBNAILS, | 1450 | ScopeNames.WITH_THUMBNAILS, |
1446 | { method: [ ScopeNames.WITH_FILES, true ] } as any, // FIXME: typings | 1451 | { method: [ ScopeNames.WITH_FILES, true ] }, |
1447 | { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } as any // FIXME: typings | 1452 | { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } |
1448 | ] | 1453 | ] |
1449 | 1454 | ||
1450 | if (userId) { | 1455 | if (userId) { |
1451 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings | 1456 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) |
1452 | } | 1457 | } |
1453 | 1458 | ||
1454 | return VideoModel | 1459 | return VideoModel |
@@ -1520,9 +1525,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1520 | attributes: [ field ], | 1525 | attributes: [ field ], |
1521 | limit: count, | 1526 | limit: count, |
1522 | group: field, | 1527 | group: field, |
1523 | having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { | 1528 | having: Sequelize.where( |
1524 | [ Op.gte ]: threshold | 1529 | Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } |
1525 | }) as any, // FIXME: typings | 1530 | ), |
1526 | order: [ (this.sequelize as any).random() ] | 1531 | order: [ (this.sequelize as any).random() ] |
1527 | } | 1532 | } |
1528 | 1533 | ||
@@ -1594,16 +1599,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1594 | ] | 1599 | ] |
1595 | } | 1600 | } |
1596 | 1601 | ||
1597 | // FIXME: typing | 1602 | const apiScope: (string | ScopeOptions)[] = [ ScopeNames.WITH_THUMBNAILS ] |
1598 | const apiScope: any[] = [ ScopeNames.WITH_THUMBNAILS ] | ||
1599 | 1603 | ||
1600 | if (options.user) { | 1604 | if (options.user) { |
1601 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) | 1605 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) |
1602 | |||
1603 | // Even if the relation is n:m, we know that a user only have 0..1 video history | ||
1604 | // So we won't have multiple rows for the same video | ||
1605 | // A subquery adds some bugs in our query so disable it | ||
1606 | secondQuery.subQuery = false | ||
1607 | } | 1606 | } |
1608 | 1607 | ||
1609 | apiScope.push({ | 1608 | apiScope.push({ |
@@ -1651,13 +1650,17 @@ export class VideoModel extends Model<VideoModel> { | |||
1651 | return maxBy(this.VideoFiles, file => file.resolution) | 1650 | return maxBy(this.VideoFiles, file => file.resolution) |
1652 | } | 1651 | } |
1653 | 1652 | ||
1654 | addThumbnail (thumbnail: ThumbnailModel) { | 1653 | async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { |
1654 | thumbnail.videoId = this.id | ||
1655 | |||
1656 | const savedThumbnail = await thumbnail.save({ transaction }) | ||
1657 | |||
1655 | if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = [] | 1658 | if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = [] |
1656 | 1659 | ||
1657 | // Already have this thumbnail, skip | 1660 | // Already have this thumbnail, skip |
1658 | if (this.Thumbnails.find(t => t.id === thumbnail.id)) return | 1661 | if (this.Thumbnails.find(t => t.id === savedThumbnail.id)) return |
1659 | 1662 | ||
1660 | this.Thumbnails.push(thumbnail) | 1663 | this.Thumbnails.push(savedThumbnail) |
1661 | } | 1664 | } |
1662 | 1665 | ||
1663 | getVideoFilename (videoFile: VideoFileModel) { | 1666 | getVideoFilename (videoFile: VideoFileModel) { |
@@ -1668,10 +1671,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1668 | return this.uuid + '.jpg' | 1671 | return this.uuid + '.jpg' |
1669 | } | 1672 | } |
1670 | 1673 | ||
1671 | getThumbnail () { | 1674 | getMiniature () { |
1672 | if (Array.isArray(this.Thumbnails) === false) return undefined | 1675 | if (Array.isArray(this.Thumbnails) === false) return undefined |
1673 | 1676 | ||
1674 | return this.Thumbnails.find(t => t.type === ThumbnailType.THUMBNAIL) | 1677 | return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE) |
1675 | } | 1678 | } |
1676 | 1679 | ||
1677 | generatePreviewName () { | 1680 | generatePreviewName () { |
@@ -1732,8 +1735,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1732 | return '/videos/embed/' + this.uuid | 1735 | return '/videos/embed/' + this.uuid |
1733 | } | 1736 | } |
1734 | 1737 | ||
1735 | getThumbnailStaticPath () { | 1738 | getMiniatureStaticPath () { |
1736 | const thumbnail = this.getThumbnail() | 1739 | const thumbnail = this.getMiniature() |
1737 | if (!thumbnail) return null | 1740 | if (!thumbnail) return null |
1738 | 1741 | ||
1739 | return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) | 1742 | return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) |
diff --git a/server/typings/sequelize.ts b/server/typings/sequelize.ts new file mode 100644 index 000000000..9cd83612d --- /dev/null +++ b/server/typings/sequelize.ts | |||
@@ -0,0 +1,18 @@ | |||
1 | import { Model } from 'sequelize-typescript' | ||
2 | |||
3 | // Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript | ||
4 | |||
5 | export type Diff<T extends string | symbol | number, U extends string | symbol | number> = | ||
6 | ({ [P in T]: P } & { [P in U]: never } & { [ x: string ]: never })[T] | ||
7 | |||
8 | export type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] } | ||
9 | |||
10 | export type RecursivePartial<T> = { [P in keyof T]?: RecursivePartial<T[P]> } | ||
11 | |||
12 | export type FilteredModelAttributes<T extends Model<T>> = RecursivePartial<Omit<T, keyof Model<any>>> & { | ||
13 | id?: number | any | ||
14 | createdAt?: Date | any | ||
15 | updatedAt?: Date | any | ||
16 | deletedAt?: Date | any | ||
17 | version?: number | any | ||
18 | } | ||