X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo.ts;h=f8a099d9cda1614918a134be77b9b9cd2804f6e5;hb=9a320a06b663a2e02c3156a07135f75f9e987b11;hp=9e67ca0f4d55a2174b6d57fd44a586f5ff8ba39c;hpb=d9a2a03196275065c28f4a0b7d4d7bc9992d77a1;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 9e67ca0f4..f8a099d9c 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -24,7 +24,7 @@ import { Table, UpdatedAt } from 'sequelize-typescript' -import { v4 as uuidv4 } from 'uuid' +import { setAsUpdated } from '@server/helpers/database-utils' import { buildNSFWFilter } from '@server/helpers/express-utils' import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video' import { LiveManager } from '@server/lib/live-manager' @@ -34,7 +34,7 @@ import { ModelCache } from '@server/models/model-cache' import { VideoFile } from '@shared/models/videos/video-file.model' import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared' import { VideoObject } from '../../../shared/models/activitypub/objects' -import { Video, VideoDetails } from '../../../shared/models/videos' +import { Video, VideoDetails, VideoRateType } from '../../../shared/models/videos' import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' import { VideoFilter } from '../../../shared/models/videos/video-query.type' import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' @@ -100,14 +100,14 @@ import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models import { VideoAbuseModel } from '../abuse/video-abuse' import { AccountModel } from '../account/account' import { AccountVideoRateModel } from '../account/account-video-rate' -import { UserModel } from '../account/user' -import { UserVideoHistoryModel } from '../account/user-video-history' -import { ActorModel } from '../activitypub/actor' -import { AvatarModel } from '../avatar/avatar' +import { ActorModel } from '../actor/actor' +import { ActorImageModel } from '../actor/actor-image' import { VideoRedundancyModel } from '../redundancy/video-redundancy' import { ServerModel } from '../server/server' import { TrackerModel } from '../server/tracker' import { VideoTrackerModel } from '../server/video-tracker' +import { UserModel } from '../user/user' +import { UserVideoHistoryModel } from '../user/user-video-history' import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' import { ScheduleVideoUpdateModel } from './schedule-video-update' import { TagModel } from './tag' @@ -286,7 +286,8 @@ export type AvailableForListIDsOptions = { required: false }, { - model: AvatarModel.unscoped(), + model: ActorImageModel.unscoped(), + as: 'Avatar', required: false } ] @@ -308,7 +309,8 @@ export type AvailableForListIDsOptions = { required: false }, { - model: AvatarModel.unscoped(), + model: ActorImageModel.unscoped(), + as: 'Avatar', required: false } ] @@ -356,6 +358,7 @@ export type AvailableForListIDsOptions = { include: [ { model: VideoFileModel, + separate: true, required: false, include: subInclude } @@ -383,6 +386,7 @@ export type AvailableForListIDsOptions = { { model: VideoStreamingPlaylistModel.unscoped(), required: false, + separate: true, include: subInclude } ] @@ -424,7 +428,12 @@ export type AvailableForListIDsOptions = { ] }, { fields: [ 'duration' ] }, - { fields: [ 'views' ] }, + { + fields: [ + { name: 'views', order: 'DESC' }, + { name: 'id', order: 'ASC' } + ] + }, { fields: [ 'channelId' ] }, { fields: [ 'originallyPublishedAt' ], @@ -776,21 +785,20 @@ export class VideoModel extends Model { @BeforeDestroy static async sendDelete (instance: MVideoAccountLight, options) { - if (instance.isOwned()) { - if (!instance.VideoChannel) { - instance.VideoChannel = await instance.$get('VideoChannel', { - include: [ - ActorModel, - AccountModel - ], - transaction: options.transaction - }) as MChannelAccountDefault - } + if (!instance.isOwned()) return undefined - return sendDeleteVideo(instance, options.transaction) + // Lazy load channels + if (!instance.VideoChannel) { + instance.VideoChannel = await instance.$get('VideoChannel', { + include: [ + ActorModel, + AccountModel + ], + transaction: options.transaction + }) as MChannelAccountDefault } - return undefined + return sendDeleteVideo(instance, options.transaction) } @BeforeDestroy @@ -855,6 +863,7 @@ export class VideoModel extends Model { logger.info('Saving video abuses details of video %s.', instance.url) + if (!instance.Trackers) instance.Trackers = await instance.$get('Trackers', { transaction: options.transaction }) const details = instance.toFormattedDetailsJSON() for (const abuse of instance.VideoAbuses) { @@ -909,7 +918,7 @@ export class VideoModel extends Model { }, include: [ { - attributes: [ 'language', 'fileUrl' ], + attributes: [ 'filename', 'language', 'fileUrl' ], model: VideoCaptionModel.unscoped(), required: false }, @@ -1013,14 +1022,28 @@ export class VideoModel extends Model { start: number count: number sort: string + isLive?: boolean search?: string }) { - const { accountId, start, count, sort, search } = options + const { accountId, start, count, sort, search, isLive } = options function buildBaseQuery (): FindOptions { - let baseQuery = { + const where: WhereOptions = {} + + if (search) { + where.name = { + [Op.iLike]: '%' + search + '%' + } + } + + if (isLive) { + where.isLive = isLive + } + + const baseQuery = { offset: start, limit: count, + where, order: getVideoSort(sort), include: [ { @@ -1039,16 +1062,6 @@ export class VideoModel extends Model { ] } - if (search) { - baseQuery = Object.assign(baseQuery, { - where: { - name: { - [Op.iLike]: '%' + search + '%' - } - } - }) - } - return baseQuery } @@ -1076,23 +1089,34 @@ export class VideoModel extends Model { start: number count: number sort: string + nsfw: boolean + filter?: VideoFilter + isLive?: boolean + includeLocalVideos: boolean withFiles: boolean + categoryOneOf?: number[] licenceOneOf?: number[] languageOneOf?: string[] tagsOneOf?: string[] tagsAllOf?: string[] - filter?: VideoFilter + accountId?: number videoChannelId?: number + followerActorId?: number + videoPlaylistId?: number + trendingDays?: number + user?: MUserAccountId historyOfUser?: MUserId + countVideos?: boolean + search?: string }) { if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { @@ -1120,6 +1144,7 @@ export class VideoModel extends Model { followerActorId, serverAccountId: serverActor.Account.id, nsfw: options.nsfw, + isLive: options.isLive, categoryOneOf: options.categoryOneOf, licenceOneOf: options.licenceOneOf, languageOneOf: options.languageOneOf, @@ -1152,6 +1177,7 @@ export class VideoModel extends Model { originallyPublishedStartDate?: string originallyPublishedEndDate?: string nsfw?: boolean + isLive?: boolean categoryOneOf?: number[] licenceOneOf?: number[] languageOneOf?: string[] @@ -1163,23 +1189,32 @@ export class VideoModel extends Model { filter?: VideoFilter }) { const serverActor = await getServerActor() + const queryOptions = { followerActorId: serverActor.id, serverAccountId: serverActor.Account.id, + includeLocalVideos: options.includeLocalVideos, nsfw: options.nsfw, + isLive: options.isLive, + categoryOneOf: options.categoryOneOf, licenceOneOf: options.licenceOneOf, languageOneOf: options.languageOneOf, + tagsOneOf: options.tagsOneOf, tagsAllOf: options.tagsAllOf, + user: options.user, filter: options.filter, + start: options.start, count: options.count, sort: options.sort, + startDate: options.startDate, endDate: options.endDate, + originallyPublishedStartDate: options.originallyPublishedStartDate, originallyPublishedEndDate: options.originallyPublishedEndDate, @@ -1312,8 +1347,7 @@ export class VideoModel extends Model { return VideoModel.scope([ ScopeNames.WITH_BLACKLISTED, - ScopeNames.WITH_USER_ID, - ScopeNames.WITH_THUMBNAILS + ScopeNames.WITH_USER_ID ]).findOne(options) } @@ -1508,6 +1542,24 @@ export class VideoModel extends Model { }) } + static updateRatesOf (videoId: number, type: VideoRateType, t: Transaction) { + const field = type === 'like' + ? 'likes' + : 'dislikes' + + const rawQuery = `UPDATE "video" SET "${field}" = ` + + '(' + + 'SELECT COUNT(id) FROM "accountVideoRate" WHERE "accountVideoRate"."videoId" = "video"."id" AND type = :rateType' + + ') ' + + 'WHERE "video"."id" = :videoId' + + return AccountVideoRateModel.sequelize.query(rawQuery, { + transaction: t, + replacements: { videoId, rateType: type }, + type: QueryTypes.UPDATE + }) + } + static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { // Instances only share videos const query = 'SELECT 1 FROM "videoShare" ' + @@ -1679,7 +1731,7 @@ export class VideoModel extends Model { function buildActor (rowActor: any) { const avatarModel = rowActor.Avatar.id !== null - ? new AvatarModel(pick(rowActor.Avatar, avatarKeys), buildOpts) + ? new ActorImageModel(pick(rowActor.Avatar, avatarKeys), buildOpts) : null const serverModel = rowActor.Server.id !== null @@ -1845,20 +1897,12 @@ export class VideoModel extends Model { this.Thumbnails.push(savedThumbnail) } - generateThumbnailName () { - return uuidv4() + '.jpg' - } - getMiniature () { if (Array.isArray(this.Thumbnails) === false) return undefined return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE) } - generatePreviewName () { - return uuidv4() + '.jpg' - } - hasPreview () { return !!this.getPreview() } @@ -1904,16 +1948,16 @@ export class VideoModel extends Model { return videoModelToFormattedDetailsJSON(this) } - getFormattedVideoFilesJSON (): VideoFile[] { + getFormattedVideoFilesJSON (includeMagnet = true): VideoFile[] { let files: VideoFile[] = [] if (Array.isArray(this.VideoFiles)) { - const result = videoFilesModelToFormattedJSON(this, this.VideoFiles) + const result = videoFilesModelToFormattedJSON(this, this.VideoFiles, includeMagnet) files = files.concat(result) } for (const p of (this.VideoStreamingPlaylists || [])) { - const result = videoFilesModelToFormattedJSON(this, p.VideoFiles) + const result = videoFilesModelToFormattedJSON(this, p.VideoFiles, includeMagnet) files = files.concat(result) } @@ -2010,9 +2054,7 @@ export class VideoModel extends Model { } setAsRefreshed () { - this.changed('updatedAt', true) - - return this.save() + return setAsUpdated('video', this.id) } requiresAuth () {