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'
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'
} 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[] = [
}
type AvailableForListIDsOptions = {
- actorId: number
+ serverAccountId: number
+ followerActorId: number
includeLocalVideos: boolean
filter?: VideoFilter
categoryOneOf?: number[]
accountId?: number
videoChannelId?: number
trendingDays?: number
+ user?: UserModel,
+ historyOfUser?: UserModel
}
@Scopes({
}
]
},
+ channelId: {
+ [ Sequelize.Op.notIn ]: Sequelize.literal(
+ '(' +
+ 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' +
+ buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) +
+ ')' +
+ ')'
+ )
+ }
+ },
+ include: []
+ }
+
+ // Only list public/published videos
+ if (!options.filter || options.filter !== 'all-local') {
+ const privacyWhere = {
// Always list public videos
privacy: VideoPrivacy.PUBLIC,
// Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding
}
}
]
- },
- include: []
+ }
+
+ Object.assign(query.where, privacyWhere)
}
if (options.filter || options.accountId || options.videoChannelId) {
query.include.push(videoChannelInclude)
}
- if (options.actorId) {
+ if (options.followerActorId) {
let localVideosReq = ''
if (options.includeLocalVideos === true) {
localVideosReq = ' UNION ALL ' +
}
// 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(
'(' +
query.subQuery = false
}
+ if (options.historyOfUser) {
+ query.include.push({
+ model: UserVideoHistoryModel,
+ required: true,
+ where: {
+ userId: options.historyOfUser.id
+ }
+ })
+ }
+
return query
},
[ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
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.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<VideoModel> = {
offset: options.start,
limit: options.count,
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,
accountId: options.accountId,
videoChannelId: options.videoChannelId,
includeLocalVideos: options.includeLocalVideos,
- userId: options.userId,
+ user: options.user,
+ historyOfUser: options.historyOfUser,
trendingDays
}
tagsAllOf?: string[]
durationMin?: number // seconds
durationMax?: number // seconds
- userId?: number
+ user?: UserModel,
+ filter?: VideoFilter
}) {
const whereAnd = []
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,
languageOneOf: options.languageOneOf,
tagsOneOf: options.tagsOneOf,
tagsAllOf: options.tagsAllOf,
- userId: options.userId
+ user: options.user,
+ filter: options.filter
}
return VideoModel.getAvailableForApi(query, queryOptions)
})
}
+ 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
}
}
private static buildActorWhereWithFilter (filter?: VideoFilter) {
- if (filter && filter === 'local') {
+ if (filter && (filter === 'local' || filter === 'all-local')) {
return {
serverId: null
}
private static async getAvailableForApi (
query: IFindOptions<VideoModel>,
- options: AvailableForListIDsOptions & { userId?: number},
+ options: AvailableForListIDsOptions,
countVideos = true
) {
const idsScope = {
}
const [ count, rowsId ] = await Promise.all([
- countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve(undefined),
+ countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve<number>(undefined),
VideoModel.scope(idsScope).findAll(query)
])
const ids = rowsId.map(r => r.id)
}
]
- 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 = {
.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 }))
}
(now - updatedAtTime) > ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL
}
+ setAsRefreshed () {
+ this.changed('updatedAt', true)
+
+ return this.save()
+ }
+
getBaseUrls () {
let baseUrlHttp
let baseUrlWs
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)
}