const video = await Hooks.wrapPromiseFun(
VideoModel.loadForGetAPI,
- { id: res.locals.onlyVideoWithRights.id, userId },
+ { id: _req.params.id, userId },
'filter:api.video.get.result'
)
+import validator from 'validator'
import { AbstractVideosQueryBuilder } from './abstract-videos-query-builder'
import { VideoAttributes } from './video-attributes'
-import { VideoModelBuilder } from './video-model-builder'
+
+/**
+ *
+ * Abstract builder to create SQL query and fetch video models
+ *
+ */
export class AbstractVideosModelQueryBuilder extends AbstractVideosQueryBuilder {
protected attributes: { [key: string]: string } = {}
protected joins: string[] = []
+ protected where: string
protected videoAttributes: VideoAttributes
- protected videoModelBuilder: VideoModelBuilder
- constructor (private readonly mode: 'list' | 'get') {
+ constructor (protected readonly mode: 'list' | 'get') {
super()
this.videoAttributes = new VideoAttributes(this.mode)
- this.videoModelBuilder = new VideoModelBuilder(this.mode, this.videoAttributes)
}
protected buildSelect () {
}
}
- protected includeFiles () {
- this.joins.push(
- 'LEFT JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"',
+ protected includeWebtorrentFiles (required: boolean) {
+ const joinType = required ? 'INNER' : 'LEFT'
+ this.joins.push(joinType + ' JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"')
+
+ this.attributes = {
+ ...this.attributes,
+
+ ...this.buildAttributesObject('VideoFiles', this.videoAttributes.getFileAttributes())
+ }
+ }
+
+ protected includeStreamingPlaylistFiles (required: boolean) {
+ const joinType = required ? 'INNER' : 'LEFT'
- 'LEFT JOIN "videoStreamingPlaylist" AS "VideoStreamingPlaylists" ON "VideoStreamingPlaylists"."videoId" = "video"."id"',
+ this.joins.push(
+ joinType + ' JOIN "videoStreamingPlaylist" AS "VideoStreamingPlaylists" ON "VideoStreamingPlaylists"."videoId" = "video"."id"',
- 'LEFT JOIN "videoFile" AS "VideoStreamingPlaylists->VideoFiles" ' +
+ joinType + ' JOIN "videoFile" AS "VideoStreamingPlaylists->VideoFiles" ' +
'ON "VideoStreamingPlaylists->VideoFiles"."videoStreamingPlaylistId" = "VideoStreamingPlaylists"."id"'
)
this.attributes = {
...this.attributes,
- ...this.buildAttributesObject('VideoFiles', this.videoAttributes.getFileAttributes()),
-
...this.buildAttributesObject('VideoStreamingPlaylists', this.videoAttributes.getStreamingPlaylistAttributes()),
...this.buildAttributesObject('VideoStreamingPlaylists->VideoFiles', this.videoAttributes.getFileAttributes())
}
}
}
- protected includeRedundancies () {
+ protected includeWebTorrentRedundancies () {
this.joins.push(
- 'LEFT OUTER JOIN "videoRedundancy" AS "VideoStreamingPlaylists->RedundancyVideos" ' +
- 'ON "VideoStreamingPlaylists"."id" = "VideoStreamingPlaylists->RedundancyVideos"."videoStreamingPlaylistId"',
-
'LEFT OUTER JOIN "videoRedundancy" AS "VideoFiles->RedundancyVideos" ON ' +
'"VideoFiles"."id" = "VideoFiles->RedundancyVideos"."videoFileId"'
)
this.attributes = {
...this.attributes,
- ...this.buildAttributesObject('VideoFiles->RedundancyVideos', this.videoAttributes.getRedundancyAttributes()),
+ ...this.buildAttributesObject('VideoFiles->RedundancyVideos', this.videoAttributes.getRedundancyAttributes())
+ }
+ }
+
+ protected includeStreamingPlaylistRedundancies () {
+ this.joins.push(
+ 'LEFT OUTER JOIN "videoRedundancy" AS "VideoStreamingPlaylists->RedundancyVideos" ' +
+ 'ON "VideoStreamingPlaylists"."id" = "VideoStreamingPlaylists->RedundancyVideos"."videoStreamingPlaylistId"'
+ )
+
+ this.attributes = {
+ ...this.attributes,
+
...this.buildAttributesObject('VideoStreamingPlaylists->RedundancyVideos', this.videoAttributes.getRedundancyAttributes())
}
}
return result
}
+
+ protected whereId (id: string | number) {
+ if (validator.isInt('' + id)) {
+ this.where = 'WHERE "video".id = :videoId'
+ } else {
+ this.where = 'WHERE uuid = :videoId'
+ }
+
+ this.replacements.videoId = id
+ }
}
import { QueryTypes, Sequelize, Transaction } from 'sequelize'
import { logger } from '@server/helpers/logger'
+/**
+ *
+ * Abstact builder to run video SQL queries
+ *
+ */
+
export class AbstractVideosQueryBuilder {
protected sequelize: Sequelize
+
+/**
+ *
+ * Class to build video attributes we want to fetch from the database
+ *
+ */
export class VideoAttributes {
constructor (readonly mode: 'get' | 'list') {
--- /dev/null
+import { Sequelize } from 'sequelize'
+import { BuildVideoGetQueryOptions } from '../video-model-get-query-builder'
+import { AbstractVideosModelQueryBuilder } from './abstract-videos-model-query-builder'
+
+/**
+ *
+ * Fetch files (webtorrent and streaming playlist) according to a video
+ *
+ */
+
+export class VideoFileQueryBuilder extends AbstractVideosModelQueryBuilder {
+ protected attributes: { [key: string]: string }
+ protected joins: string[] = []
+
+ constructor (protected readonly sequelize: Sequelize) {
+ super('get')
+ }
+
+ queryWebTorrentVideos (options: BuildVideoGetQueryOptions) {
+ this.buildWebtorrentFilesQuery(options)
+
+ return this.runQuery(options.transaction, true)
+ }
+
+ queryStreamingPlaylistVideos (options: BuildVideoGetQueryOptions) {
+ this.buildVideoStreamingPlaylistFilesQuery(options)
+
+ return this.runQuery(options.transaction, true)
+ }
+
+ private buildWebtorrentFilesQuery (options: BuildVideoGetQueryOptions) {
+ this.attributes = {
+ '"video"."id"': ''
+ }
+
+ this.includeWebtorrentFiles(true)
+
+ if (options.forGetAPI === true) {
+ this.includeWebTorrentRedundancies()
+ }
+
+ this.whereId(options.id)
+
+ this.query = this.buildQuery()
+ }
+
+ private buildVideoStreamingPlaylistFilesQuery (options: BuildVideoGetQueryOptions) {
+ this.attributes = {
+ '"video"."id"': ''
+ }
+
+ this.includeStreamingPlaylistFiles(true)
+
+ if (options.forGetAPI === true) {
+ this.includeStreamingPlaylistRedundancies()
+ }
+
+ this.whereId(options.id)
+
+ this.query = this.buildQuery()
+ }
+
+ private buildQuery () {
+ return `${this.buildSelect()} FROM "video" ${this.joins.join(' ')} ${this.where}`
+ }
+}
import { VideoStreamingPlaylistModel } from '../../video-streaming-playlist'
import { VideoAttributes } from './video-attributes'
+/**
+ *
+ * Build video models from SQL rows
+ *
+ */
+
export class VideoModelBuilder {
private videosMemo: { [ id: number ]: VideoModel }
private videoStreamingPlaylistMemo: { [ id: number ]: VideoStreamingPlaylistModel }
}
- buildVideosFromRows (rows: any[]) {
+ buildVideosFromRows (rows: any[], rowsWebtorrentFiles?: any[], rowsStreamingPlaylist?: any[]) {
this.reinit()
for (const row of rows) {
this.setUserHistory(row, videoModel)
this.addThumbnail(row, videoModel)
- this.addWebTorrentFile(row, videoModel)
- this.addStreamingPlaylist(row, videoModel)
- this.addStreamingPlaylistFile(row)
+ if (!rowsWebtorrentFiles) {
+ this.addWebTorrentFile(row, videoModel)
+ }
+
+ if (!rowsStreamingPlaylist) {
+ this.addStreamingPlaylist(row, videoModel)
+ this.addStreamingPlaylistFile(row)
+ }
if (this.mode === 'get') {
this.addTag(row, videoModel)
this.setScheduleVideoUpdate(row, videoModel)
this.setLive(row, videoModel)
- if (row.VideoFiles.id) {
+ if (!rowsWebtorrentFiles && row.VideoFiles.id) {
this.addRedundancy(row.VideoFiles.RedundancyVideos, this.videoFileMemo[row.VideoFiles.id])
}
- if (row.VideoStreamingPlaylists.id) {
+ if (!rowsStreamingPlaylist && row.VideoStreamingPlaylists.id) {
this.addRedundancy(row.VideoStreamingPlaylists.RedundancyVideos, this.videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id])
}
}
}
+ for (const row of rowsWebtorrentFiles || []) {
+ const videoModel = this.videosMemo[row.id]
+ this.addWebTorrentFile(row, videoModel)
+ this.addRedundancy(row.VideoFiles.RedundancyVideos, this.videoFileMemo[row.VideoFiles.id])
+ }
+
+ for (const row of rowsStreamingPlaylist || []) {
+ const videoModel = this.videosMemo[row.id]
+
+ this.addStreamingPlaylist(row, videoModel)
+ this.addStreamingPlaylistFile(row)
+ this.addRedundancy(row.VideoStreamingPlaylists.RedundancyVideos, this.videoStreamingPlaylistMemo[row.VideoStreamingPlaylists.id])
+ }
+
return this.videos
}
import { Sequelize, Transaction } from 'sequelize'
-import validator from 'validator'
import { AbstractVideosModelQueryBuilder } from './shared/abstract-videos-model-query-builder'
+import { VideoAttributes } from './shared/video-attributes'
+import { VideoFileQueryBuilder } from './shared/video-file-query-builder'
+import { VideoModelBuilder } from './shared/video-model-builder'
+
+/**
+ *
+ * Build a GET SQL query, fetch rows and create the video model
+ *
+ */
export type BuildVideoGetQueryOptions = {
id: number | string
forGetAPI?: boolean
}
-export class VideosModelGetQueryBuilder extends AbstractVideosModelQueryBuilder {
+export class VideosModelGetQueryBuilder {
+ videoQueryBuilder: VideosModelGetQuerySubBuilder
+ webtorrentFilesQueryBuilder: VideoFileQueryBuilder
+ streamingPlaylistFilesQueryBuilder: VideoFileQueryBuilder
+
+ private readonly videoModelBuilder: VideoModelBuilder
+
+ constructor (protected readonly sequelize: Sequelize) {
+ this.videoQueryBuilder = new VideosModelGetQuerySubBuilder(sequelize)
+ this.webtorrentFilesQueryBuilder = new VideoFileQueryBuilder(sequelize)
+ this.streamingPlaylistFilesQueryBuilder = new VideoFileQueryBuilder(sequelize)
+
+ this.videoModelBuilder = new VideoModelBuilder('get', new VideoAttributes('get'))
+ }
+
+ async queryVideos (options: BuildVideoGetQueryOptions) {
+ const [ videoRows, webtorrentFilesRows, streamingPlaylistFilesRows ] = await Promise.all([
+ this.videoQueryBuilder.queryVideos(options),
+ this.webtorrentFilesQueryBuilder.queryWebTorrentVideos(options),
+ this.streamingPlaylistFilesQueryBuilder.queryStreamingPlaylistVideos(options)
+ ])
+
+ const videos = this.videoModelBuilder.buildVideosFromRows(videoRows, webtorrentFilesRows, streamingPlaylistFilesRows)
+
+ if (videos.length > 1) {
+ throw new Error('Video results is more than ')
+ }
+
+ if (videos.length === 0) return null
+ return videos[0]
+ }
+}
+
+export class VideosModelGetQuerySubBuilder extends AbstractVideosModelQueryBuilder {
protected attributes: { [key: string]: string }
protected joins: string[] = []
- protected where: string
+
+ protected webtorrentFilesQuery: string
+ protected streamingPlaylistFilesQuery: string
constructor (protected readonly sequelize: Sequelize) {
super('get')
}
queryVideos (options: BuildVideoGetQueryOptions) {
- this.buildGetQuery(options)
-
- return this.runQuery(options.transaction, true).then(rows => {
- const videos = this.videoModelBuilder.buildVideosFromRows(rows)
-
- if (videos.length > 1) {
- throw new Error('Video results is more than ')
- }
+ this.buildMainGetQuery(options)
- if (videos.length === 0) return null
- return videos[0]
- })
+ return this.runQuery(options.transaction, true)
}
- private buildGetQuery (options: BuildVideoGetQueryOptions) {
+ private buildMainGetQuery (options: BuildVideoGetQueryOptions) {
this.attributes = {
'"video".*': ''
}
this.includeThumbnails()
- this.includeFiles()
-
this.includeBlacklisted()
this.includeScheduleUpdate()
if (options.forGetAPI === true) {
this.includeTrackers()
- this.includeRedundancies()
}
this.whereId(options.id)
- const select = this.buildSelect()
- const order = this.buildOrder()
-
- this.query = `${select} FROM "video" ${this.joins.join(' ')} ${this.where} ${order}`
+ this.query = this.buildQuery()
}
- private whereId (id: string | number) {
- if (validator.isInt('' + id)) {
- this.where = 'WHERE "video".id = :videoId'
- } else {
- this.where = 'WHERE uuid = :videoId'
- }
-
- this.replacements.videoId = id
- }
+ private buildQuery () {
+ const order = 'ORDER BY "Tags"."name" ASC'
+ const from = `SELECT * FROM "video" ${this.where} LIMIT 1`
- private buildOrder () {
- return 'ORDER BY "Tags"."name" ASC'
+ return `${this.buildSelect()} FROM (${from}) AS "video" ${this.joins.join(' ')} ${this.where} ${order}`
}
}
import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models'
import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-builder'
+/**
+ *
+ * Build videos list SQL query to fetch rows
+ *
+ */
+
export type BuildVideosListQueryOptions = {
attributes?: string[]
import { Sequelize } from 'sequelize'
import { AbstractVideosModelQueryBuilder } from './shared/abstract-videos-model-query-builder'
+import { VideoModelBuilder } from './shared/video-model-builder'
import { BuildVideosListQueryOptions, VideosIdListQueryBuilder } from './videos-id-list-query-builder'
+/**
+ *
+ * Build videos list SQL query and create video models
+ *
+ */
+
export class VideosModelListQueryBuilder extends AbstractVideosModelQueryBuilder {
protected attributes: { [key: string]: string }
protected joins: string[] = []
private innerQuery: string
private innerSort: string
+ private readonly videoModelBuilder: VideoModelBuilder
+
constructor (protected readonly sequelize: Sequelize) {
super('list')
+
+ this.videoModelBuilder = new VideoModelBuilder(this.mode, this.videoAttributes)
}
queryVideos (options: BuildVideosListQueryOptions) {
this.includeThumbnails()
if (options.withFiles) {
- this.includeFiles()
+ this.includeWebtorrentFiles(false)
+ this.includeStreamingPlaylistFiles(false)
}
if (options.user) {