X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo.ts;h=06c63e87c42a0a372e3c372230d9f4bdde1e98b8;hb=9c6ca37fc1512a99d420ea90707cebcd06cdc970;hp=215e26d7dd2cec92bffe285473ce81826a87b550;hpb=7519127b5cb44095f78f6bf4c51d4ebf2b7d5e88;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 215e26d7d..06c63e87c 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -40,7 +40,7 @@ import { isVideoDurationValid, isVideoLanguageValid, isVideoLicenceValid, - isVideoNameValid, isVideoOriginallyPublishedAtValid, + isVideoNameValid, isVideoPrivacyValid, isVideoStateValid, isVideoSupportValid @@ -52,7 +52,9 @@ import { ACTIVITY_PUB, API_VERSION, CONFIG, - CONSTRAINTS_FIELDS, HLS_PLAYLIST_DIRECTORY, HLS_REDUNDANCY_DIRECTORY, + CONSTRAINTS_FIELDS, + HLS_STREAMING_PLAYLIST_DIRECTORY, + HLS_REDUNDANCY_DIRECTORY, PREVIEWS_SIZE, REMOTE_SCHEME, STATIC_DOWNLOAD_PATHS, @@ -70,10 +72,17 @@ import { AccountVideoRateModel } from '../account/account-video-rate' import { ActorModel } from '../activitypub/actor' import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' -import { buildBlockedAccountSQL, buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' +import { + buildBlockedAccountSQL, + buildTrigramSearchIndex, + buildWhereIdOrUUID, + createSimilarityAttribute, + getVideoSort, + throwIfNotValid +} from '../utils' import { TagModel } from './tag' import { VideoAbuseModel } from './video-abuse' -import { VideoChannelModel } from './video-channel' +import { VideoChannelModel, ScopeNames as VideoChannelScopeNames } from './video-channel' import { VideoCommentModel } from './video-comment' import { VideoFileModel } from './video-file' import { VideoShareModel } from './video-share' @@ -91,11 +100,11 @@ import { videoModelToFormattedDetailsJSON, videoModelToFormattedJSON } from './video-format-utils' -import * as validator from 'validator' import { UserVideoHistoryModel } from '../account/user-video-history' import { UserModel } from '../account/user' import { VideoImportModel } from './video-import' import { VideoStreamingPlaylistModel } from './video-streaming-playlist' +import { VideoPlaylistElementModel } from './video-playlist-element' // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation const indexes: Sequelize.DefineIndexesOptions[] = [ @@ -175,6 +184,9 @@ export enum ScopeNames { type ForAPIOptions = { ids: number[] + + videoPlaylistId?: number + withFiles?: boolean } @@ -182,6 +194,7 @@ type AvailableForListIDsOptions = { serverAccountId: number followerActorId: number includeLocalVideos: boolean + filter?: VideoFilter categoryOneOf?: number[] nsfw?: boolean @@ -189,9 +202,14 @@ type AvailableForListIDsOptions = { languageOneOf?: string[] tagsOneOf?: string[] tagsAllOf?: string[] + withFiles?: boolean + accountId?: number videoChannelId?: number + + videoPlaylistId?: number + trendingDays?: number user?: UserModel, historyOfUser?: UserModel @@ -199,62 +217,17 @@ type AvailableForListIDsOptions = { @Scopes({ [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { - const accountInclude = { - attributes: [ 'id', 'name' ], - model: AccountModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'id', 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], - model: ActorModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - }, - { - model: AvatarModel.unscoped(), - required: false - } - ] - } - ] - } - - const videoChannelInclude = { - attributes: [ 'name', 'description', 'id' ], - model: VideoChannelModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], - model: ActorModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - }, - { - model: AvatarModel.unscoped(), - required: false - } - ] - }, - accountInclude - ] - } - const query: IFindOptions = { where: { id: { [ Sequelize.Op.any ]: options.ids } }, - include: [ videoChannelInclude ] + include: [ + { + model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }) + } + ] } if (options.withFiles === true) { @@ -264,6 +237,13 @@ type AvailableForListIDsOptions = { }) } + if (options.videoPlaylistId) { + query.include.push({ + model: VideoPlaylistElementModel.unscoped(), + required: true + }) + } + return query }, [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { @@ -315,6 +295,17 @@ type AvailableForListIDsOptions = { Object.assign(query.where, privacyWhere) } + if (options.videoPlaylistId) { + query.include.push({ + attributes: [], + model: VideoPlaylistElementModel.unscoped(), + required: true, + where: { + videoPlaylistId: options.videoPlaylistId + } + }) + } + if (options.filter || options.accountId || options.videoChannelId) { const videoChannelInclude: IIncludeOptions = { attributes: [], @@ -772,6 +763,15 @@ export class VideoModel extends Model { }) Tags: TagModel[] + @HasMany(() => VideoPlaylistElementModel, { + foreignKey: { + name: 'videoId', + allowNull: false + }, + onDelete: 'cascade' + }) + VideoPlaylistElements: VideoPlaylistElementModel[] + @HasMany(() => VideoAbuseModel, { foreignKey: { name: 'videoId', @@ -1118,6 +1118,7 @@ export class VideoModel extends Model { accountId?: number, videoChannelId?: number, followerActorId?: number + videoPlaylistId?: number, trendingDays?: number, user?: UserModel, historyOfUser?: UserModel @@ -1157,6 +1158,7 @@ export class VideoModel extends Model { withFiles: options.withFiles, accountId: options.accountId, videoChannelId: options.videoChannelId, + videoPlaylistId: options.videoPlaylistId, includeLocalVideos: options.includeLocalVideos, user: options.user, historyOfUser: options.historyOfUser, @@ -1174,6 +1176,8 @@ export class VideoModel extends Model { sort?: string startDate?: string // ISO 8601 endDate?: string // ISO 8601 + originallyPublishedStartDate?: string + originallyPublishedEndDate?: string nsfw?: boolean categoryOneOf?: number[] licenceOneOf?: number[] @@ -1196,6 +1200,15 @@ export class VideoModel extends Model { whereAnd.push({ publishedAt: publishedAtRange }) } + if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { + const originallyPublishedAtRange = {} + + if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Sequelize.Op.gte ] = options.originallyPublishedStartDate + if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Sequelize.Op.lte ] = options.originallyPublishedEndDate + + whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) + } + if (options.durationMin || options.durationMax) { const durationRange = {} @@ -1269,7 +1282,7 @@ export class VideoModel extends Model { } static load (id: number | string, t?: Sequelize.Transaction) { - const where = VideoModel.buildWhereIdOrUUID(id) + const where = buildWhereIdOrUUID(id) const options = { where, transaction: t @@ -1279,7 +1292,7 @@ export class VideoModel extends Model { } static loadWithRights (id: number | string, t?: Sequelize.Transaction) { - const where = VideoModel.buildWhereIdOrUUID(id) + const where = buildWhereIdOrUUID(id) const options = { where, transaction: t @@ -1289,7 +1302,7 @@ export class VideoModel extends Model { } static loadOnlyId (id: number | string, t?: Sequelize.Transaction) { - const where = VideoModel.buildWhereIdOrUUID(id) + const where = buildWhereIdOrUUID(id) const options = { attributes: [ 'id' ], @@ -1302,7 +1315,7 @@ export class VideoModel extends Model { static loadWithFiles (id: number, t?: Sequelize.Transaction, logging?: boolean) { return VideoModel.scope([ ScopeNames.WITH_FILES, ScopeNames.WITH_STREAMING_PLAYLISTS ]) - .findById(id, { transaction: t, logging }) + .findByPk(id, { transaction: t, logging }) } static loadByUUIDWithFile (uuid: string) { @@ -1342,7 +1355,7 @@ export class VideoModel extends Model { } static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) { - const where = VideoModel.buildWhereIdOrUUID(id) + const where = buildWhereIdOrUUID(id) const options = { order: [ [ 'Tags', 'name', 'ASC' ] ], @@ -1369,7 +1382,7 @@ export class VideoModel extends Model { } static loadForGetAPI (id: number | string, t?: Sequelize.Transaction, userId?: number) { - const where = VideoModel.buildWhereIdOrUUID(id) + const where = buildWhereIdOrUUID(id) const options = { order: [ [ 'Tags', 'name', 'ASC' ] ], @@ -1522,18 +1535,7 @@ export class VideoModel extends Model { if (ids.length === 0) return { data: [], total: count } - // FIXME: typings - const apiScope: any[] = [ - { - method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ] - } - ] - - if (options.user) { - apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) - } - - const secondQuery = { + const secondQuery: IFindOptions = { offset: 0, limit: query.limit, attributes: query.attributes, @@ -1543,6 +1545,29 @@ export class VideoModel extends Model { ) ] } + + // FIXME: typing + const apiScope: any[] = [] + + if (options.user) { + apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) + + // Even if the relation is n:m, we know that a user only have 0..1 video history + // So we won't have multiple rows for the same video + // A subquery adds some bugs in our query so disable it + secondQuery.subQuery = false + } + + apiScope.push({ + method: [ + ScopeNames.FOR_API, { + ids, withFiles: + options.withFiles, + videoPlaylistId: options.videoPlaylistId + } as ForAPIOptions + ] + }) + const rows = await VideoModel.scope(apiScope).findAll(secondQuery) return { @@ -1571,10 +1596,6 @@ export class VideoModel extends Model { return VIDEO_STATES[ id ] || 'Unknown' } - static buildWhereIdOrUUID (id: number | string) { - return validator.isInt('' + id) ? { id } : { uuid: id } - } - getOriginalFile () { if (Array.isArray(this.VideoFiles) === false) return undefined @@ -1587,7 +1608,6 @@ export class VideoModel extends Model { } getThumbnailName () { - // We always have a copy of the thumbnail const extension = '.jpg' return this.uuid + extension } @@ -1731,7 +1751,7 @@ export class VideoModel extends Model { } removeStreamingPlaylist (isRedundancy = false) { - const baseDir = isRedundancy ? HLS_REDUNDANCY_DIRECTORY : HLS_PLAYLIST_DIRECTORY + const baseDir = isRedundancy ? HLS_REDUNDANCY_DIRECTORY : HLS_STREAMING_PLAYLIST_DIRECTORY const filePath = join(baseDir, this.uuid) return remove(filePath)