X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo.ts;h=199ea9ea49c866cfce2b991d72e3ae7aa9f0e447;hb=8b9a525a180cc9f3a98c334cc052dcfc8f36dcd4;hp=4f3f75613d101b803aaa355a471a40f973defce6;hpb=71e318b4fe66175d03c7c82357d60062eb68af81;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 4f3f75613..199ea9ea4 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -27,7 +27,7 @@ import { Table, UpdatedAt } from 'sequelize-typescript' -import { VideoPrivacy, VideoState } from '../../../shared' +import { UserRight, VideoPrivacy, VideoState } from '../../../shared' import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' import { VideoFilter } from '../../../shared/models/videos/video-query.type' @@ -70,7 +70,7 @@ import { AccountVideoRateModel } from '../account/account-video-rate' import { ActorModel } from '../activitypub/actor' import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' -import { buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' +import { buildBlockedAccountSQL, buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' import { TagModel } from './tag' import { VideoAbuseModel } from './video-abuse' import { VideoChannelModel } from './video-channel' @@ -93,6 +93,7 @@ import { } from './video-format-utils' import * as validator from 'validator' import { UserVideoHistoryModel } from '../account/user-video-history' +import { UserModel } from '../account/user' // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation const indexes: Sequelize.DefineIndexesOptions[] = [ @@ -138,7 +139,8 @@ type ForAPIOptions = { } type AvailableForListIDsOptions = { - actorId: number + serverAccountId: number + followerActorId: number includeLocalVideos: boolean filter?: VideoFilter categoryOneOf?: number[] @@ -151,6 +153,8 @@ type AvailableForListIDsOptions = { accountId?: number videoChannelId?: number trendingDays?: number + user?: UserModel, + historyOfUser?: UserModel } @Scopes({ @@ -235,6 +239,15 @@ type AvailableForListIDsOptions = { ) } ] + }, + channelId: { + [ Sequelize.Op.notIn ]: Sequelize.literal( + '(' + + 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + + buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + + ')' + + ')' + ) } }, include: [] @@ -303,7 +316,7 @@ type AvailableForListIDsOptions = { query.include.push(videoChannelInclude) } - if (options.actorId) { + if (options.followerActorId) { let localVideosReq = '' if (options.includeLocalVideos === true) { localVideosReq = ' UNION ALL ' + @@ -315,7 +328,7 @@ type AvailableForListIDsOptions = { } // Force actorId to be a number to avoid SQL injections - const actorIdNumber = parseInt(options.actorId.toString(), 10) + const actorIdNumber = parseInt(options.followerActorId.toString(), 10) query.where[ 'id' ][ Sequelize.Op.and ].push({ [ Sequelize.Op.in ]: Sequelize.literal( '(' + @@ -404,6 +417,16 @@ type AvailableForListIDsOptions = { query.subQuery = false } + if (options.historyOfUser) { + query.include.push({ + model: UserVideoHistoryModel, + required: true, + where: { + userId: options.historyOfUser.id + } + }) + } + return query }, [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { @@ -973,12 +996,13 @@ export class VideoModel extends Model { filter?: VideoFilter, accountId?: number, videoChannelId?: number, - actorId?: number + followerActorId?: number trendingDays?: number, - userId?: number + user?: UserModel, + historyOfUser?: UserModel }, countVideos = true) { - if (options.filter && options.filter === 'all-local' && !options.userId) { - throw new Error('Try to filter all-local but no userId is provided') + if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { + throw new Error('Try to filter all-local but no user has not the see all videos right') } const query: IFindOptions = { @@ -994,11 +1018,14 @@ export class VideoModel extends Model { query.group = 'VideoModel.id' } - // actorId === null has a meaning, so just check undefined - const actorId = options.actorId !== undefined ? options.actorId : (await getServerActor()).id + const serverActor = await getServerActor() + + // followerActorId === null has a meaning, so just check undefined + const followerActorId = options.followerActorId !== undefined ? options.followerActorId : serverActor.id const queryOptions = { - actorId, + followerActorId, + serverAccountId: serverActor.Account.id, nsfw: options.nsfw, categoryOneOf: options.categoryOneOf, licenceOneOf: options.licenceOneOf, @@ -1010,7 +1037,8 @@ export class VideoModel extends Model { accountId: options.accountId, videoChannelId: options.videoChannelId, includeLocalVideos: options.includeLocalVideos, - userId: options.userId, + user: options.user, + historyOfUser: options.historyOfUser, trendingDays } @@ -1033,7 +1061,7 @@ export class VideoModel extends Model { tagsAllOf?: string[] durationMin?: number // seconds durationMax?: number // seconds - userId?: number, + user?: UserModel, filter?: VideoFilter }) { const whereAnd = [] @@ -1103,7 +1131,8 @@ export class VideoModel extends Model { const serverActor = await getServerActor() const queryOptions = { - actorId: serverActor.id, + followerActorId: serverActor.id, + serverAccountId: serverActor.Account.id, includeLocalVideos: options.includeLocalVideos, nsfw: options.nsfw, categoryOneOf: options.categoryOneOf, @@ -1111,7 +1140,7 @@ export class VideoModel extends Model { languageOneOf: options.languageOneOf, tagsOneOf: options.tagsOneOf, tagsAllOf: options.tagsAllOf, - userId: options.userId, + user: options.user, filter: options.filter } @@ -1237,12 +1266,31 @@ export class VideoModel extends Model { }) } + static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { + // Instances only share videos + const query = 'SELECT 1 FROM "videoShare" ' + + 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + + 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + + 'LIMIT 1' + + const options = { + type: Sequelize.QueryTypes.SELECT, + bind: { followerActorId, videoId }, + raw: true + } + + return VideoModel.sequelize.query(query, options) + .then(results => results.length === 1) + } + // threshold corresponds to how many video the field should have to be returned static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) { - const actorId = (await getServerActor()).id + const serverActor = await getServerActor() + const followerActorId = serverActor.id - const scopeOptions = { - actorId, + const scopeOptions: AvailableForListIDsOptions = { + serverAccountId: serverActor.Account.id, + followerActorId, includeLocalVideos: true } @@ -1287,7 +1335,7 @@ export class VideoModel extends Model { private static async getAvailableForApi ( query: IFindOptions, - options: AvailableForListIDsOptions & { userId?: number}, + options: AvailableForListIDsOptions, countVideos = true ) { const idsScope = { @@ -1306,7 +1354,7 @@ export class VideoModel extends Model { } const [ count, rowsId ] = await Promise.all([ - countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve(undefined), + countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve(undefined), VideoModel.scope(idsScope).findAll(query) ]) const ids = rowsId.map(r => r.id) @@ -1320,8 +1368,8 @@ export class VideoModel extends Model { } ] - if (options.userId) { - apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.userId ] }) + if (options.user) { + apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) } const secondQuery = { @@ -1503,8 +1551,10 @@ export class VideoModel extends Model { .catch(err => logger.warn('Cannot delete preview %s.', previewPath, { err })) } - removeFile (videoFile: VideoFileModel) { - const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) + removeFile (videoFile: VideoFileModel, isRedundancy = false) { + const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR + + const filePath = join(baseDir, this.getVideoFilename(videoFile)) return remove(filePath) .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) } @@ -1526,6 +1576,12 @@ export class VideoModel extends Model { (now - updatedAtTime) > ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL } + setAsRefreshed () { + this.changed('updatedAt', true) + + return this.save() + } + getBaseUrls () { let baseUrlHttp let baseUrlWs @@ -1576,6 +1632,10 @@ export class VideoModel extends Model { return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) } + getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) { + return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile) + } + getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) }