aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-06-11 14:09:33 +0200
committerChocobozzz <me@florianbigard.com>2021-06-11 14:09:52 +0200
commit71d4af1efc810f853e1a0d986bf758c201692594 (patch)
tree2066053638baefb6430772c2e0a0aa1774019a51
parent3c79c2ce86eaf9e151ab6c2c9d1f646968a16744 (diff)
downloadPeerTube-71d4af1efc810f853e1a0d986bf758c201692594.tar.gz
PeerTube-71d4af1efc810f853e1a0d986bf758c201692594.tar.zst
PeerTube-71d4af1efc810f853e1a0d986bf758c201692594.zip
Use raw SQL for most of video queries
-rw-r--r--scripts/create-import-video-file-job.ts4
-rwxr-xr-xscripts/prune-storage.ts2
-rw-r--r--server/controllers/activitypub/client.ts7
-rw-r--r--server/helpers/video.ts2
-rw-r--r--server/lib/model-loaders/video.ts13
-rw-r--r--server/middlewares/validators/shared/videos.ts8
-rw-r--r--server/middlewares/validators/videos/videos.ts6
-rw-r--r--server/models/video/sql/shared/abstract-videos-model-query-builder.ts24
-rw-r--r--server/models/video/sql/shared/abstract-videos-query-builder.ts9
-rw-r--r--server/models/video/sql/shared/video-file-query-builder.ts16
-rw-r--r--server/models/video/sql/shared/video-model-builder.ts36
-rw-r--r--server/models/video/sql/shared/video-tables.ts8
-rw-r--r--server/models/video/sql/video-model-get-query-builder.ts110
-rw-r--r--server/models/video/sql/videos-model-list-query-builder.ts3
-rw-r--r--server/models/video/video.ts150
-rw-r--r--server/typings/express/index.d.ts4
16 files changed, 206 insertions, 196 deletions
diff --git a/scripts/create-import-video-file-job.ts b/scripts/create-import-video-file-job.ts
index 5d38af066..094544e05 100644
--- a/scripts/create-import-video-file-job.ts
+++ b/scripts/create-import-video-file-job.ts
@@ -36,7 +36,7 @@ async function run () {
36 return 36 return
37 } 37 }
38 38
39 const video = await VideoModel.loadByUUID(options.video) 39 const video = await VideoModel.load(options.video)
40 if (!video) throw new Error('Video not found.') 40 if (!video) throw new Error('Video not found.')
41 if (video.isOwned() === false) throw new Error('Cannot import files of a non owned video.') 41 if (video.isOwned() === false) throw new Error('Cannot import files of a non owned video.')
42 42
@@ -45,7 +45,7 @@ async function run () {
45 filePath: resolve(options.import) 45 filePath: resolve(options.import)
46 } 46 }
47 47
48 await JobQueue.Instance.init() 48 JobQueue.Instance.init()
49 await JobQueue.Instance.createJobWithPromise({ type: 'video-file-import', payload: dataInput }) 49 await JobQueue.Instance.createJobWithPromise({ type: 'video-file-import', payload: dataInput })
50 console.log('Import job for video %s created.', video.uuid) 50 console.log('Import job for video %s created.', video.uuid)
51} 51}
diff --git a/scripts/prune-storage.ts b/scripts/prune-storage.ts
index 0f2d1320e..58d24816e 100755
--- a/scripts/prune-storage.ts
+++ b/scripts/prune-storage.ts
@@ -89,7 +89,7 @@ async function pruneDirectory (directory: string, existFun: ExistFun) {
89function doesVideoExist (keepOnlyOwned: boolean) { 89function doesVideoExist (keepOnlyOwned: boolean) {
90 return async (file: string) => { 90 return async (file: string) => {
91 const uuid = getUUIDFromFilename(file) 91 const uuid = getUUIDFromFilename(file)
92 const video = await VideoModel.loadByUUID(uuid) 92 const video = await VideoModel.load(uuid)
93 93
94 return video && (keepOnlyOwned === false || video.isOwned()) 94 return video && (keepOnlyOwned === false || video.isOwned())
95 } 95 }
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 1982e171d..444a8abaa 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -78,12 +78,12 @@ activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
78activityPubClientRouter.get('/videos/watch/:id', 78activityPubClientRouter.get('/videos/watch/:id',
79 executeIfActivityPub, 79 executeIfActivityPub,
80 asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS)), 80 asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS)),
81 asyncMiddleware(videosCustomGetValidator('only-video-with-rights')), 81 asyncMiddleware(videosCustomGetValidator('all')),
82 asyncMiddleware(videoController) 82 asyncMiddleware(videoController)
83) 83)
84activityPubClientRouter.get('/videos/watch/:id/activity', 84activityPubClientRouter.get('/videos/watch/:id/activity',
85 executeIfActivityPub, 85 executeIfActivityPub,
86 asyncMiddleware(videosCustomGetValidator('only-video-with-rights')), 86 asyncMiddleware(videosCustomGetValidator('all')),
87 asyncMiddleware(videoController) 87 asyncMiddleware(videoController)
88) 88)
89activityPubClientRouter.get('/videos/watch/:id/announces', 89activityPubClientRouter.get('/videos/watch/:id/announces',
@@ -222,8 +222,7 @@ function getAccountVideoRateFactory (rateType: VideoRateType) {
222} 222}
223 223
224async function videoController (req: express.Request, res: express.Response) { 224async function videoController (req: express.Request, res: express.Response) {
225 // We need more attributes 225 const video = res.locals.videoAll
226 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(res.locals.onlyVideoWithRights.id)
227 226
228 if (redirectIfNotOwned(video.url, res)) return 227 if (redirectIfNotOwned(video.url, res)) return
229 228
diff --git a/server/helpers/video.ts b/server/helpers/video.ts
index c2e15a705..f5f645d3e 100644
--- a/server/helpers/video.ts
+++ b/server/helpers/video.ts
@@ -4,7 +4,7 @@ import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/ty
4import { VideoPrivacy, VideoState } from '@shared/models' 4import { VideoPrivacy, VideoState } from '@shared/models'
5 5
6function getVideoWithAttributes (res: Response) { 6function getVideoWithAttributes (res: Response) {
7 return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights 7 return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
8} 8}
9 9
10function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) { 10function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
diff --git a/server/lib/model-loaders/video.ts b/server/lib/model-loaders/video.ts
index 07b373ed3..e2bf96f62 100644
--- a/server/lib/model-loaders/video.ts
+++ b/server/lib/model-loaders/video.ts
@@ -3,31 +3,30 @@ import {
3 MVideoAccountLightBlacklistAllFiles, 3 MVideoAccountLightBlacklistAllFiles,
4 MVideoFormattableDetails, 4 MVideoFormattableDetails,
5 MVideoFullLight, 5 MVideoFullLight,
6 MVideoIdThumbnail, 6 MVideoId,
7 MVideoImmutable, 7 MVideoImmutable,
8 MVideoThumbnail, 8 MVideoThumbnail,
9 MVideoWithRights 9 MVideoWithRights
10} from '@server/types/models' 10} from '@server/types/models'
11import { Hooks } from '../plugins/hooks' 11import { Hooks } from '../plugins/hooks'
12 12
13type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 'only-immutable-attributes' 13type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'id' | 'none' | 'only-immutable-attributes'
14 14
15function loadVideo (id: number | string, fetchType: 'for-api', userId?: number): Promise<MVideoFormattableDetails> 15function loadVideo (id: number | string, fetchType: 'for-api', userId?: number): Promise<MVideoFormattableDetails>
16function loadVideo (id: number | string, fetchType: 'all', userId?: number): Promise<MVideoFullLight> 16function loadVideo (id: number | string, fetchType: 'all', userId?: number): Promise<MVideoFullLight>
17function loadVideo (id: number | string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable> 17function loadVideo (id: number | string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable>
18function loadVideo (id: number | string, fetchType: 'only-video', userId?: number): Promise<MVideoThumbnail> 18function loadVideo (id: number | string, fetchType: 'only-video', userId?: number): Promise<MVideoThumbnail>
19function loadVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Promise<MVideoWithRights> 19function loadVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Promise<MVideoId>
20function loadVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Promise<MVideoIdThumbnail>
21function loadVideo ( 20function loadVideo (
22 id: number | string, 21 id: number | string,
23 fetchType: VideoLoadType, 22 fetchType: VideoLoadType,
24 userId?: number 23 userId?: number
25): Promise<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> 24): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable>
26function loadVideo ( 25function loadVideo (
27 id: number | string, 26 id: number | string,
28 fetchType: VideoLoadType, 27 fetchType: VideoLoadType,
29 userId?: number 28 userId?: number
30): Promise<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> { 29): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable> {
31 30
32 if (fetchType === 'for-api') { 31 if (fetchType === 'for-api') {
33 return Hooks.wrapPromiseFun( 32 return Hooks.wrapPromiseFun(
@@ -41,8 +40,6 @@ function loadVideo (
41 40
42 if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id) 41 if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
43 42
44 if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id)
45
46 if (fetchType === 'only-video') return VideoModel.load(id) 43 if (fetchType === 'only-video') return VideoModel.load(id)
47 44
48 if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) 45 if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id)
diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts
index 1a22d6513..6131379ce 100644
--- a/server/middlewares/validators/shared/videos.ts
+++ b/server/middlewares/validators/shared/videos.ts
@@ -8,7 +8,7 @@ import {
8 MVideoAccountLight, 8 MVideoAccountLight,
9 MVideoFormattableDetails, 9 MVideoFormattableDetails,
10 MVideoFullLight, 10 MVideoFullLight,
11 MVideoIdThumbnail, 11 MVideoId,
12 MVideoImmutable, 12 MVideoImmutable,
13 MVideoThumbnail, 13 MVideoThumbnail,
14 MVideoWithRights 14 MVideoWithRights
@@ -43,16 +43,12 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi
43 break 43 break
44 44
45 case 'id': 45 case 'id':
46 res.locals.videoId = video as MVideoIdThumbnail 46 res.locals.videoId = video as MVideoId
47 break 47 break
48 48
49 case 'only-video': 49 case 'only-video':
50 res.locals.onlyVideo = video as MVideoThumbnail 50 res.locals.onlyVideo = video as MVideoThumbnail
51 break 51 break
52
53 case 'only-video-with-rights':
54 res.locals.onlyVideoWithRights = video as MVideoWithRights
55 break
56 } 52 }
57 53
58 return true 54 return true
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index a707fd086..2bed5f181 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -4,7 +4,7 @@ import { getResumableUploadPath } from '@server/helpers/upload'
4import { isAbleToUploadVideo } from '@server/lib/user' 4import { isAbleToUploadVideo } from '@server/lib/user'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { ExpressPromiseHandler } from '@server/types/express' 6import { ExpressPromiseHandler } from '@server/types/express'
7import { MUserAccountId, MVideoWithRights } from '@server/types/models' 7import { MUserAccountId, MVideoFullLight } from '@server/types/models'
8import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared' 8import { ServerErrorCode, UserRight, VideoChangeOwnershipStatus, VideoPrivacy } from '../../../../shared'
9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes' 9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
10import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/change-ownership/video-change-ownership-accept.model' 10import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/change-ownership/video-change-ownership-accept.model'
@@ -258,7 +258,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
258} 258}
259 259
260const videosCustomGetValidator = ( 260const videosCustomGetValidator = (
261 fetchType: 'for-api' | 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes', 261 fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes',
262 authenticateInQuery = false 262 authenticateInQuery = false
263) => { 263) => {
264 return [ 264 return [
@@ -273,7 +273,7 @@ const videosCustomGetValidator = (
273 // Controllers does not need to check video rights 273 // Controllers does not need to check video rights
274 if (fetchType === 'only-immutable-attributes') return next() 274 if (fetchType === 'only-immutable-attributes') return next()
275 275
276 const video = getVideoWithAttributes(res) as MVideoWithRights 276 const video = getVideoWithAttributes(res) as MVideoFullLight
277 277
278 // Video private or blacklisted 278 // Video private or blacklisted
279 if (video.requiresAuth()) { 279 if (video.requiresAuth()) {
diff --git a/server/models/video/sql/shared/abstract-videos-model-query-builder.ts b/server/models/video/sql/shared/abstract-videos-model-query-builder.ts
index 65df8d914..d959cb5d0 100644
--- a/server/models/video/sql/shared/abstract-videos-model-query-builder.ts
+++ b/server/models/video/sql/shared/abstract-videos-model-query-builder.ts
@@ -80,6 +80,18 @@ export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder
80 } 80 }
81 } 81 }
82 82
83 protected includeOwnerUser () {
84 this.addJoin('INNER JOIN "videoChannel" AS "VideoChannel" ON "video"."channelId" = "VideoChannel"."id"')
85 this.addJoin('INNER JOIN "account" AS "VideoChannel->Account" ON "VideoChannel"."accountId" = "VideoChannel->Account"."id"')
86
87 this.attributes = {
88 ...this.attributes,
89
90 ...this.buildAttributesObject('VideoChannel', this.tables.getChannelAttributes()),
91 ...this.buildAttributesObject('VideoChannel->Account', this.tables.getUserAccountAttributes())
92 }
93 }
94
83 protected includeThumbnails () { 95 protected includeThumbnails () {
84 this.addJoin('LEFT OUTER JOIN "thumbnail" AS "Thumbnails" ON "video"."id" = "Thumbnails"."videoId"') 96 this.addJoin('LEFT OUTER JOIN "thumbnail" AS "Thumbnails" ON "video"."id" = "Thumbnails"."videoId"')
85 97
@@ -269,14 +281,20 @@ export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder
269 return result 281 return result
270 } 282 }
271 283
272 protected whereId (id: string | number) { 284 protected whereId (options: { id?: string | number, url?: string }) {
273 if (validator.isInt('' + id)) { 285 if (options.url) {
286 this.where = 'WHERE "video"."url" = :videoUrl'
287 this.replacements.videoUrl = options.url
288 return
289 }
290
291 if (validator.isInt('' + options.id)) {
274 this.where = 'WHERE "video".id = :videoId' 292 this.where = 'WHERE "video".id = :videoId'
275 } else { 293 } else {
276 this.where = 'WHERE uuid = :videoId' 294 this.where = 'WHERE uuid = :videoId'
277 } 295 }
278 296
279 this.replacements.videoId = id 297 this.replacements.videoId = options.id
280 } 298 }
281 299
282 protected addJoin (join: string) { 300 protected addJoin (join: string) {
diff --git a/server/models/video/sql/shared/abstract-videos-query-builder.ts b/server/models/video/sql/shared/abstract-videos-query-builder.ts
index 7e67fa34f..10699317a 100644
--- a/server/models/video/sql/shared/abstract-videos-query-builder.ts
+++ b/server/models/video/sql/shared/abstract-videos-query-builder.ts
@@ -13,16 +13,17 @@ export class AbstractVideosQueryBuilder {
13 protected query: string 13 protected query: string
14 protected replacements: any = {} 14 protected replacements: any = {}
15 15
16 protected runQuery (transaction?: Transaction) { 16 protected runQuery (options: { transaction?: Transaction, logging?: boolean } = {}) {
17 logger.debug('Running videos query.', { query: this.query, replacements: this.replacements }) 17 logger.debug('Running videos query.', { query: this.query, replacements: this.replacements })
18 18
19 const options = { 19 const queryOptions = {
20 transaction, 20 transaction: options.transaction,
21 logging: options.logging,
21 replacements: this.replacements, 22 replacements: this.replacements,
22 type: QueryTypes.SELECT as QueryTypes.SELECT, 23 type: QueryTypes.SELECT as QueryTypes.SELECT,
23 next: false 24 next: false
24 } 25 }
25 26
26 return this.sequelize.query<any>(this.query, options) 27 return this.sequelize.query<any>(this.query, queryOptions)
27 } 28 }
28} 29}
diff --git a/server/models/video/sql/shared/video-file-query-builder.ts b/server/models/video/sql/shared/video-file-query-builder.ts
index 7d822f8fa..a62fa64f8 100644
--- a/server/models/video/sql/shared/video-file-query-builder.ts
+++ b/server/models/video/sql/shared/video-file-query-builder.ts
@@ -18,13 +18,13 @@ export class VideoFileQueryBuilder extends AbstractVideosModelQueryBuilder {
18 queryWebTorrentVideos (options: BuildVideoGetQueryOptions) { 18 queryWebTorrentVideos (options: BuildVideoGetQueryOptions) {
19 this.buildWebtorrentFilesQuery(options) 19 this.buildWebtorrentFilesQuery(options)
20 20
21 return this.runQuery(options.transaction) 21 return this.runQuery(options)
22 } 22 }
23 23
24 queryStreamingPlaylistVideos (options: BuildVideoGetQueryOptions) { 24 queryStreamingPlaylistVideos (options: BuildVideoGetQueryOptions) {
25 this.buildVideoStreamingPlaylistFilesQuery(options) 25 this.buildVideoStreamingPlaylistFilesQuery(options)
26 26
27 return this.runQuery(options.transaction) 27 return this.runQuery(options)
28 } 28 }
29 29
30 private buildWebtorrentFilesQuery (options: BuildVideoGetQueryOptions) { 30 private buildWebtorrentFilesQuery (options: BuildVideoGetQueryOptions) {
@@ -34,11 +34,11 @@ export class VideoFileQueryBuilder extends AbstractVideosModelQueryBuilder {
34 34
35 this.includeWebtorrentFiles(true) 35 this.includeWebtorrentFiles(true)
36 36
37 if (options.forGetAPI === true) { 37 if (this.shouldIncludeRedundancies(options)) {
38 this.includeWebTorrentRedundancies() 38 this.includeWebTorrentRedundancies()
39 } 39 }
40 40
41 this.whereId(options.id) 41 this.whereId(options)
42 42
43 this.query = this.buildQuery() 43 this.query = this.buildQuery()
44 } 44 }
@@ -50,11 +50,11 @@ export class VideoFileQueryBuilder extends AbstractVideosModelQueryBuilder {
50 50
51 this.includeStreamingPlaylistFiles(true) 51 this.includeStreamingPlaylistFiles(true)
52 52
53 if (options.forGetAPI === true) { 53 if (this.shouldIncludeRedundancies(options)) {
54 this.includeStreamingPlaylistRedundancies() 54 this.includeStreamingPlaylistRedundancies()
55 } 55 }
56 56
57 this.whereId(options.id) 57 this.whereId(options)
58 58
59 this.query = this.buildQuery() 59 this.query = this.buildQuery()
60 } 60 }
@@ -62,4 +62,8 @@ export class VideoFileQueryBuilder extends AbstractVideosModelQueryBuilder {
62 private buildQuery () { 62 private buildQuery () {
63 return `${this.buildSelect()} FROM "video" ${this.joins} ${this.where}` 63 return `${this.buildSelect()} FROM "video" ${this.joins} ${this.where}`
64 } 64 }
65
66 private shouldIncludeRedundancies (options: BuildVideoGetQueryOptions) {
67 return options.type === 'api'
68 }
65} 69}
diff --git a/server/models/video/sql/shared/video-model-builder.ts b/server/models/video/sql/shared/video-model-builder.ts
index 2a60dab04..467a9378a 100644
--- a/server/models/video/sql/shared/video-model-builder.ts
+++ b/server/models/video/sql/shared/video-model-builder.ts
@@ -1,5 +1,4 @@
1 1
2import { logger } from '@server/helpers/logger'
3import { AccountModel } from '@server/models/account/account' 2import { AccountModel } from '@server/models/account/account'
4import { ActorModel } from '@server/models/actor/actor' 3import { ActorModel } from '@server/models/actor/actor'
5import { ActorImageModel } from '@server/models/actor/actor-image' 4import { ActorImageModel } from '@server/models/actor/actor-image'
@@ -56,7 +55,7 @@ export class VideoModelBuilder {
56 this.reinit() 55 this.reinit()
57 56
58 for (const row of rows) { 57 for (const row of rows) {
59 this.buildVideo(row) 58 this.buildVideoAndAccount(row)
60 59
61 const videoModel = this.videosMemo[row.id] 60 const videoModel = this.videosMemo[row.id]
62 61
@@ -131,22 +130,10 @@ export class VideoModelBuilder {
131 } 130 }
132 } 131 }
133 132
134 private buildVideo (row: SQLRow) { 133 private buildVideoAndAccount (row: SQLRow) {
135 if (this.videosMemo[row.id]) return 134 if (this.videosMemo[row.id]) return
136 135
137 // Build Channel
138 const channelModel = new VideoChannelModel(this.grab(row, this.tables.getChannelAttributes(), 'VideoChannel'), this.buildOpts)
139 channelModel.Actor = this.buildActor(row, 'VideoChannel')
140
141 const accountModel = new AccountModel(this.grab(row, this.tables.getAccountAttributes(), 'VideoChannel.Account'), this.buildOpts)
142 accountModel.Actor = this.buildActor(row, 'VideoChannel.Account')
143
144 channelModel.Account = accountModel
145
146 const videoModel = new VideoModel(this.grab(row, this.tables.getVideoAttributes(), ''), this.buildOpts) 136 const videoModel = new VideoModel(this.grab(row, this.tables.getVideoAttributes(), ''), this.buildOpts)
147 videoModel.VideoChannel = channelModel
148
149 this.videosMemo[row.id] = videoModel
150 137
151 videoModel.UserVideoHistories = [] 138 videoModel.UserVideoHistories = []
152 videoModel.Thumbnails = [] 139 videoModel.Thumbnails = []
@@ -155,10 +142,29 @@ export class VideoModelBuilder {
155 videoModel.Tags = [] 142 videoModel.Tags = []
156 videoModel.Trackers = [] 143 videoModel.Trackers = []
157 144
145 this.buildAccount(row, videoModel)
146
147 this.videosMemo[row.id] = videoModel
148
158 // Keep rows order 149 // Keep rows order
159 this.videos.push(videoModel) 150 this.videos.push(videoModel)
160 } 151 }
161 152
153 private buildAccount (row: SQLRow, videoModel: VideoModel) {
154 const id = row['VideoChannel.Account.id']
155 if (!id) return
156
157 const channelModel = new VideoChannelModel(this.grab(row, this.tables.getChannelAttributes(), 'VideoChannel'), this.buildOpts)
158 channelModel.Actor = this.buildActor(row, 'VideoChannel')
159
160 const accountModel = new AccountModel(this.grab(row, this.tables.getAccountAttributes(), 'VideoChannel.Account'), this.buildOpts)
161 accountModel.Actor = this.buildActor(row, 'VideoChannel.Account')
162
163 channelModel.Account = accountModel
164
165 videoModel.VideoChannel = channelModel
166 }
167
162 private buildActor (row: SQLRow, prefix: string) { 168 private buildActor (row: SQLRow, prefix: string) {
163 const actorPrefix = `${prefix}.Actor` 169 const actorPrefix = `${prefix}.Actor`
164 const avatarPrefix = `${actorPrefix}.Avatar` 170 const avatarPrefix = `${actorPrefix}.Avatar`
diff --git a/server/models/video/sql/shared/video-tables.ts b/server/models/video/sql/shared/video-tables.ts
index fddf1210c..52929fa5e 100644
--- a/server/models/video/sql/shared/video-tables.ts
+++ b/server/models/video/sql/shared/video-tables.ts
@@ -10,6 +10,10 @@ export class VideoTables {
10 10
11 } 11 }
12 12
13 getChannelAttributesForUser () {
14 return [ 'id', 'accountId' ]
15 }
16
13 getChannelAttributes () { 17 getChannelAttributes () {
14 let attributeKeys = [ 18 let attributeKeys = [
15 'id', 19 'id',
@@ -29,6 +33,10 @@ export class VideoTables {
29 return attributeKeys 33 return attributeKeys
30 } 34 }
31 35
36 getUserAccountAttributes () {
37 return [ 'id', 'userId' ]
38 }
39
32 getAccountAttributes () { 40 getAccountAttributes () {
33 let attributeKeys = [ 'id', 'name', 'actorId' ] 41 let attributeKeys = [ 'id', 'name', 'actorId' ]
34 42
diff --git a/server/models/video/sql/video-model-get-query-builder.ts b/server/models/video/sql/video-model-get-query-builder.ts
index 4aab9ff1d..f56fdd474 100644
--- a/server/models/video/sql/video-model-get-query-builder.ts
+++ b/server/models/video/sql/video-model-get-query-builder.ts
@@ -11,10 +11,15 @@ import { VideoTables } from './shared/video-tables'
11 */ 11 */
12 12
13export type BuildVideoGetQueryOptions = { 13export type BuildVideoGetQueryOptions = {
14 id: number | string 14 id?: number | string
15 transaction?: Transaction 15 url?: string
16
17 type: 'api' | 'full-light' | 'account-blacklist-files' | 'all-files' | 'thumbnails' | 'thumbnails-blacklist' | 'id' | 'blacklist-rights'
18
16 userId?: number 19 userId?: number
17 forGetAPI?: boolean 20 transaction?: Transaction
21
22 logging?: boolean
18} 23}
19 24
20export class VideosModelGetQueryBuilder { 25export class VideosModelGetQueryBuilder {
@@ -32,11 +37,17 @@ export class VideosModelGetQueryBuilder {
32 this.videoModelBuilder = new VideoModelBuilder('get', new VideoTables('get')) 37 this.videoModelBuilder = new VideoModelBuilder('get', new VideoTables('get'))
33 } 38 }
34 39
35 async queryVideos (options: BuildVideoGetQueryOptions) { 40 async queryVideo (options: BuildVideoGetQueryOptions) {
36 const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([ 41 const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([
37 this.videoQueryBuilder.queryVideos(options), 42 this.videoQueryBuilder.queryVideos(options),
38 this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(options), 43
39 this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(options) 44 this.shouldQueryVideoFiles(options)
45 ? this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(options)
46 : Promise.resolve(undefined),
47
48 this.shouldQueryVideoFiles(options)
49 ? this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(options)
50 : Promise.resolve(undefined)
40 ]) 51 ])
41 52
42 const videos = this.videoModelBuilder.buildVideosFromRows(videoRows, webtorrentFilesRows, streamingPlaylistFilesRows) 53 const videos = this.videoModelBuilder.buildVideosFromRows(videoRows, webtorrentFilesRows, streamingPlaylistFilesRows)
@@ -48,6 +59,10 @@ export class VideosModelGetQueryBuilder {
48 if (videos.length === 0) return null 59 if (videos.length === 0) return null
49 return videos[0] 60 return videos[0]
50 } 61 }
62
63 private shouldQueryVideoFiles (options: BuildVideoGetQueryOptions) {
64 return [ 'api', 'full-light', 'account-blacklist-files', 'all-files' ].includes(options.type)
65 }
51} 66}
52 67
53export class VideosModelGetQuerySubBuilder extends AbstractVideosModelQueryBuilder { 68export class VideosModelGetQuerySubBuilder extends AbstractVideosModelQueryBuilder {
@@ -63,7 +78,7 @@ export class VideosModelGetQuerySubBuilder extends AbstractVideosModelQueryBuild
63 queryVideos (options: BuildVideoGetQueryOptions) { 78 queryVideos (options: BuildVideoGetQueryOptions) {
64 this.buildMainGetQuery(options) 79 this.buildMainGetQuery(options)
65 80
66 return this.runQuery(options.transaction) 81 return this.runQuery(options)
67 } 82 }
68 83
69 private buildMainGetQuery (options: BuildVideoGetQueryOptions) { 84 private buildMainGetQuery (options: BuildVideoGetQueryOptions) {
@@ -71,36 +86,91 @@ export class VideosModelGetQuerySubBuilder extends AbstractVideosModelQueryBuild
71 '"video".*': '' 86 '"video".*': ''
72 } 87 }
73 88
74 this.includeChannels() 89 if (this.shouldIncludeThumbnails(options)) {
75 this.includeAccounts() 90 this.includeThumbnails()
91 }
76 92
77 this.includeTags() 93 if (this.shouldIncludeBlacklisted(options)) {
94 this.includeBlacklisted()
95 }
78 96
79 this.includeThumbnails() 97 if (this.shouldIncludeAccount(options)) {
98 this.includeChannels()
99 this.includeAccounts()
100 }
80 101
81 this.includeBlacklisted() 102 if (this.shouldIncludeTags(options)) {
103 this.includeTags()
104 }
82 105
83 this.includeScheduleUpdate() 106 if (this.shouldIncludeScheduleUpdate(options)) {
107 this.includeScheduleUpdate()
108 }
84 109
85 this.includeLive() 110 if (this.shouldIncludeLive(options)) {
111 this.includeLive()
112 }
86 113
87 if (options.userId) { 114 if (options.userId && this.shouldIncludeUserHistory(options)) {
88 this.includeUserHistory(options.userId) 115 this.includeUserHistory(options.userId)
89 } 116 }
90 117
91 if (options.forGetAPI === true) { 118 if (this.shouldIncludeOwnerUser(options)) {
119 this.includeOwnerUser()
120 }
121
122 if (this.shouldIncludeTrackers(options)) {
92 this.includeTrackers() 123 this.includeTrackers()
93 } 124 }
94 125
95 this.whereId(options.id) 126 this.whereId(options)
96 127
97 this.query = this.buildQuery() 128 this.query = this.buildQuery(options)
98 } 129 }
99 130
100 private buildQuery () { 131 private buildQuery (options: BuildVideoGetQueryOptions) {
101 const order = 'ORDER BY "Tags"."name" ASC' 132 const order = this.shouldIncludeTags(options)
133 ? 'ORDER BY "Tags"."name" ASC'
134 : ''
135
102 const from = `SELECT * FROM "video" ${this.where} LIMIT 1` 136 const from = `SELECT * FROM "video" ${this.where} LIMIT 1`
103 137
104 return `${this.buildSelect()} FROM (${from}) AS "video" ${this.joins} ${order}` 138 return `${this.buildSelect()} FROM (${from}) AS "video" ${this.joins} ${order}`
105 } 139 }
140
141 private shouldIncludeTrackers (options: BuildVideoGetQueryOptions) {
142 return options.type === 'api'
143 }
144
145 private shouldIncludeLive (options: BuildVideoGetQueryOptions) {
146 return [ 'api', 'full-light' ].includes(options.type)
147 }
148
149 private shouldIncludeScheduleUpdate (options: BuildVideoGetQueryOptions) {
150 return [ 'api', 'full-light' ].includes(options.type)
151 }
152
153 private shouldIncludeTags (options: BuildVideoGetQueryOptions) {
154 return [ 'api', 'full-light' ].includes(options.type)
155 }
156
157 private shouldIncludeUserHistory (options: BuildVideoGetQueryOptions) {
158 return [ 'api', 'full-light' ].includes(options.type)
159 }
160
161 private shouldIncludeAccount (options: BuildVideoGetQueryOptions) {
162 return [ 'api', 'full-light', 'account-blacklist-files' ].includes(options.type)
163 }
164
165 private shouldIncludeBlacklisted (options: BuildVideoGetQueryOptions) {
166 return [ 'api', 'full-light', 'account-blacklist-files', 'thumbnails-blacklist', 'blacklist-rights' ].includes(options.type)
167 }
168
169 private shouldIncludeOwnerUser (options: BuildVideoGetQueryOptions) {
170 return options.type === 'blacklist-rights'
171 }
172
173 private shouldIncludeThumbnails (options: BuildVideoGetQueryOptions) {
174 return [ 'api', 'full-light', 'account-blacklist-files', 'thumbnails', 'thumbnails-blacklist' ].includes(options.type)
175 }
106} 176}
diff --git a/server/models/video/sql/videos-model-list-query-builder.ts b/server/models/video/sql/videos-model-list-query-builder.ts
index d3a9a9466..43040fc5e 100644
--- a/server/models/video/sql/videos-model-list-query-builder.ts
+++ b/server/models/video/sql/videos-model-list-query-builder.ts
@@ -27,7 +27,8 @@ export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder
27 this.buildInnerQuery(options) 27 this.buildInnerQuery(options)
28 this.buildListQueryFromIdsQuery(options) 28 this.buildListQueryFromIdsQuery(options)
29 29
30 return this.runQuery(undefined).then(rows => this.videoModelBuilder.buildVideosFromRows(rows)) 30 return this.runQuery()
31 .then(rows => this.videoModelBuilder.buildVideosFromRows(rows))
31 } 32 }
32 33
33 private buildInnerQuery (options: BuildVideosListQueryOptions) { 34 private buildInnerQuery (options: BuildVideosListQueryOptions) {
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 00fbb18f6..2d8b7b653 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -88,13 +88,12 @@ import {
88 MVideoFormattableDetails, 88 MVideoFormattableDetails,
89 MVideoForUser, 89 MVideoForUser,
90 MVideoFullLight, 90 MVideoFullLight,
91 MVideoIdThumbnail, 91 MVideoId,
92 MVideoImmutable, 92 MVideoImmutable,
93 MVideoThumbnail, 93 MVideoThumbnail,
94 MVideoThumbnailBlacklist, 94 MVideoThumbnailBlacklist,
95 MVideoWithAllFiles, 95 MVideoWithAllFiles,
96 MVideoWithFile, 96 MVideoWithFile
97 MVideoWithRights
98} from '../../types/models' 97} from '../../types/models'
99import { MThumbnail } from '../../types/models/video/thumbnail' 98import { MThumbnail } from '../../types/models/video/thumbnail'
100import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file' 99import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file'
@@ -1301,27 +1300,16 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1301 return VideoModel.count(options) 1300 return VideoModel.count(options)
1302 } 1301 }
1303 1302
1304 static load (id: number | string, t?: Transaction): Promise<MVideoThumbnail> { 1303 static load (id: number | string, transaction?: Transaction): Promise<MVideoThumbnail> {
1305 const where = buildWhereIdOrUUID(id) 1304 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1306 const options = {
1307 where,
1308 transaction: t
1309 }
1310 1305
1311 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1306 return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails' })
1312 } 1307 }
1313 1308
1314 static loadWithBlacklist (id: number | string, t?: Transaction): Promise<MVideoThumbnailBlacklist> { 1309 static loadWithBlacklist (id: number | string, transaction?: Transaction): Promise<MVideoThumbnailBlacklist> {
1315 const where = buildWhereIdOrUUID(id) 1310 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1316 const options = {
1317 where,
1318 transaction: t
1319 }
1320 1311
1321 return VideoModel.scope([ 1312 return queryBuilder.queryVideo({ id, transaction, type: 'thumbnails-blacklist' })
1322 ScopeNames.WITH_THUMBNAILS,
1323 ScopeNames.WITH_BLACKLISTED
1324 ]).findOne(options)
1325 } 1313 }
1326 1314
1327 static loadImmutableAttributes (id: number | string, t?: Transaction): Promise<MVideoImmutable> { 1315 static loadImmutableAttributes (id: number | string, t?: Transaction): Promise<MVideoImmutable> {
@@ -1342,68 +1330,6 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1342 }) 1330 })
1343 } 1331 }
1344 1332
1345 static loadWithRights (id: number | string, t?: Transaction): Promise<MVideoWithRights> {
1346 const where = buildWhereIdOrUUID(id)
1347 const options = {
1348 where,
1349 transaction: t
1350 }
1351
1352 return VideoModel.scope([
1353 ScopeNames.WITH_BLACKLISTED,
1354 ScopeNames.WITH_USER_ID
1355 ]).findOne(options)
1356 }
1357
1358 static loadOnlyId (id: number | string, t?: Transaction): Promise<MVideoIdThumbnail> {
1359 const where = buildWhereIdOrUUID(id)
1360
1361 const options = {
1362 attributes: [ 'id' ],
1363 where,
1364 transaction: t
1365 }
1366
1367 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1368 }
1369
1370 static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Promise<MVideoWithAllFiles> {
1371 const where = buildWhereIdOrUUID(id)
1372
1373 const query = {
1374 where,
1375 transaction: t,
1376 logging
1377 }
1378
1379 return VideoModel.scope([
1380 ScopeNames.WITH_WEBTORRENT_FILES,
1381 ScopeNames.WITH_STREAMING_PLAYLISTS,
1382 ScopeNames.WITH_THUMBNAILS
1383 ]).findOne(query)
1384 }
1385
1386 static loadByUUID (uuid: string): Promise<MVideoThumbnail> {
1387 const options = {
1388 where: {
1389 uuid
1390 }
1391 }
1392
1393 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1394 }
1395
1396 static loadByUrl (url: string, transaction?: Transaction): Promise<MVideoThumbnail> {
1397 const query: FindOptions = {
1398 where: {
1399 url
1400 },
1401 transaction
1402 }
1403
1404 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
1405 }
1406
1407 static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Promise<MVideoImmutable> { 1333 static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Promise<MVideoImmutable> {
1408 const fun = () => { 1334 const fun = () => {
1409 const query: FindOptions = { 1335 const query: FindOptions = {
@@ -1424,50 +1350,34 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1424 }) 1350 })
1425 } 1351 }
1426 1352
1427 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLightBlacklistAllFiles> { 1353 static loadOnlyId (id: number | string, transaction?: Transaction): Promise<MVideoId> {
1428 const query: FindOptions = { 1354 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1429 where: {
1430 url
1431 },
1432 transaction
1433 }
1434 1355
1435 return VideoModel.scope([ 1356 return queryBuilder.queryVideo({ id, transaction, type: 'id' })
1436 ScopeNames.WITH_ACCOUNT_DETAILS,
1437 ScopeNames.WITH_WEBTORRENT_FILES,
1438 ScopeNames.WITH_STREAMING_PLAYLISTS,
1439 ScopeNames.WITH_THUMBNAILS,
1440 ScopeNames.WITH_BLACKLISTED
1441 ]).findOne(query)
1442 } 1357 }
1443 1358
1444 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Promise<MVideoFullLight> { 1359 static loadWithFiles (id: number | string, transaction?: Transaction, logging?: boolean): Promise<MVideoWithAllFiles> {
1445 const where = buildWhereIdOrUUID(id) 1360 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1446 1361
1447 const options = { 1362 return queryBuilder.queryVideo({ id, transaction, type: 'all-files', logging })
1448 order: [ [ 'Tags', 'name', 'ASC' ] ] as any, 1363 }
1449 where,
1450 transaction: t
1451 }
1452 1364
1453 const scopes: (string | ScopeOptions)[] = [ 1365 static loadByUrl (url: string, transaction?: Transaction): Promise<MVideoThumbnail> {
1454 ScopeNames.WITH_TAGS, 1366 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1455 ScopeNames.WITH_BLACKLISTED,
1456 ScopeNames.WITH_ACCOUNT_DETAILS,
1457 ScopeNames.WITH_SCHEDULED_UPDATE,
1458 ScopeNames.WITH_WEBTORRENT_FILES,
1459 ScopeNames.WITH_STREAMING_PLAYLISTS,
1460 ScopeNames.WITH_THUMBNAILS,
1461 ScopeNames.WITH_LIVE
1462 ]
1463 1367
1464 if (userId) { 1368 return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails' })
1465 scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) 1369 }
1466 } 1370
1371 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLightBlacklistAllFiles> {
1372 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1373
1374 return queryBuilder.queryVideo({ url, transaction, type: 'account-blacklist-files' })
1375 }
1376
1377 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Promise<MVideoFullLight> {
1378 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1467 1379
1468 return VideoModel 1380 return queryBuilder.queryVideo({ id, transaction: t, type: 'full-light', userId })
1469 .scope(scopes)
1470 .findOne(options)
1471 } 1381 }
1472 1382
1473 static loadForGetAPI (parameters: { 1383 static loadForGetAPI (parameters: {
@@ -1478,7 +1388,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1478 const { id, transaction, userId } = parameters 1388 const { id, transaction, userId } = parameters
1479 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize) 1389 const queryBuilder = new VideosModelGetQueryBuilder(VideoModel.sequelize)
1480 1390
1481 return queryBuilder.queryVideos({ id, transaction, forGetAPI: true, userId }) 1391 return queryBuilder.queryVideo({ id, transaction, type: 'api', userId })
1482 } 1392 }
1483 1393
1484 static async getStats () { 1394 static async getStats () {
diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts
index 00ff68943..de673f4fc 100644
--- a/server/typings/express/index.d.ts
+++ b/server/typings/express/index.d.ts
@@ -11,6 +11,7 @@ import {
11 MVideoChangeOwnershipFull, 11 MVideoChangeOwnershipFull,
12 MVideoFile, 12 MVideoFile,
13 MVideoFormattableDetails, 13 MVideoFormattableDetails,
14 MVideoId,
14 MVideoImmutable, 15 MVideoImmutable,
15 MVideoLive, 16 MVideoLive,
16 MVideoPlaylistFull, 17 MVideoPlaylistFull,
@@ -106,8 +107,7 @@ declare module 'express' {
106 videoAll?: MVideoFullLight 107 videoAll?: MVideoFullLight
107 onlyImmutableVideo?: MVideoImmutable 108 onlyImmutableVideo?: MVideoImmutable
108 onlyVideo?: MVideoThumbnail 109 onlyVideo?: MVideoThumbnail
109 onlyVideoWithRights?: MVideoWithRights 110 videoId?: MVideoId
110 videoId?: MVideoIdThumbnail
111 111
112 videoLive?: MVideoLive 112 videoLive?: MVideoLive
113 113