X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo-comment.ts;h=90625d987a9f6436ecf125f3360ee9a8fe544a1b;hb=e234debc4d62e1f58b34f8af5a6139074fb7724d;hp=b33c33d5efa002bc52f618584cefcfe508fa70a8;hpb=c2777c1dfe688c8fab1ef2fed50e360100fa9198;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index b33c33d5e..90625d987 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -1,20 +1,17 @@ +import * as Bluebird from 'bluebird' +import { uniq } from 'lodash' +import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' +import { getServerActor } from '@server/models/application/application' +import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' +import { VideoPrivacy } from '@shared/models' import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects' import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' import { VideoComment } from '../../../shared/models/videos/video-comment.model' -import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' -import { AccountModel } from '../account/account' -import { ActorModel } from '../activitypub/actor' -import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' -import { VideoModel } from './video' -import { VideoChannelModel } from './video-channel' -import { getServerActor } from '../../helpers/utils' import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' +import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { regexpCapture } from '../../helpers/regexp' -import { uniq } from 'lodash' -import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' -import * as Bluebird from 'bluebird' +import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' import { MComment, MCommentAP, @@ -24,26 +21,32 @@ import { MCommentOwnerReplyVideoLight, MCommentOwnerVideo, MCommentOwnerVideoFeed, - MCommentOwnerVideoReply -} from '../../typings/models/video' -import { MUserAccountId } from '@server/typings/models' + MCommentOwnerVideoReply, + MVideoImmutable +} from '../../types/models/video' +import { AccountModel } from '../account/account' +import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' +import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' +import { VideoModel } from './video' +import { VideoChannelModel } from './video-channel' enum ScopeNames { WITH_ACCOUNT = 'WITH_ACCOUNT', + WITH_ACCOUNT_FOR_API = 'WITH_ACCOUNT_FOR_API', WITH_IN_REPLY_TO = 'WITH_IN_REPLY_TO', WITH_VIDEO = 'WITH_VIDEO', ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' } @Scopes(() => ({ - [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { + [ScopeNames.ATTRIBUTES_FOR_API]: (blockerAccountIds: number[]) => { return { attributes: { include: [ [ Sequelize.literal( '(' + - 'WITH "blocklist" AS (' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' + + 'WITH "blocklist" AS (' + buildBlockedAccountSQL(blockerAccountIds) + ')' + 'SELECT COUNT("replies"."id") - (' + 'SELECT COUNT("replies"."id") ' + 'FROM "videoComment" AS "replies" ' + @@ -81,6 +84,22 @@ enum ScopeNames { } ] }, + [ScopeNames.WITH_ACCOUNT_FOR_API]: { + include: [ + { + model: AccountModel.unscoped(), + include: [ + { + attributes: { + exclude: unusedActorAttributesForAPI + }, + model: ActorModel, // Default scope includes avatar and server + required: true + } + ] + } + ] + }, [ScopeNames.WITH_IN_REPLY_TO]: { include: [ { @@ -125,6 +144,11 @@ enum ScopeNames { }, { fields: [ 'accountId' ] + }, + { + fields: [ + { name: 'createdAt', order: 'DESC' } + ] } ] }) @@ -258,36 +282,50 @@ export class VideoCommentModel extends Model { static async listThreadsForApi (parameters: { videoId: number + isVideoOwned: boolean start: number count: number sort: string user?: MUserAccountId }) { - const { videoId, start, count, sort, user } = parameters + const { videoId, isVideoOwned, start, count, sort, user } = parameters - const serverActor = await getServerActor() - const serverAccountId = serverActor.Account.id - const userAccountId = user ? user.Account.id : undefined + const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({ videoId, user, isVideoOwned }) const query = { offset: start, limit: count, order: getCommentSort(sort), where: { - videoId, - inReplyToCommentId: null, - accountId: { - [Op.notIn]: Sequelize.literal( - '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' - ) - } + [Op.and]: [ + { + videoId + }, + { + inReplyToCommentId: null + }, + { + [Op.or]: [ + { + accountId: { + [Op.notIn]: Sequelize.literal( + '(' + buildBlockedAccountSQL(blockerAccountIds) + ')' + ) + } + }, + { + accountId: null + } + ] + } + ] } } const scopes: (string | ScopeOptions)[] = [ - ScopeNames.WITH_ACCOUNT, + ScopeNames.WITH_ACCOUNT_FOR_API, { - method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] + method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ] } ] @@ -301,14 +339,13 @@ export class VideoCommentModel extends Model { static async listThreadCommentsForApi (parameters: { videoId: number + isVideoOwned: boolean threadId: number user?: MUserAccountId }) { - const { videoId, threadId, user } = parameters + const { videoId, threadId, user, isVideoOwned } = parameters - const serverActor = await getServerActor() - const serverAccountId = serverActor.Account.id - const userAccountId = user ? user.Account.id : undefined + const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({ videoId, user, isVideoOwned }) const query = { order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, @@ -320,16 +357,16 @@ export class VideoCommentModel extends Model { ], accountId: { [Op.notIn]: Sequelize.literal( - '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' + '(' + buildBlockedAccountSQL(blockerAccountIds) + ')' ) } } } const scopes: any[] = [ - ScopeNames.WITH_ACCOUNT, + ScopeNames.WITH_ACCOUNT_FOR_API, { - method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] + method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ] } ] @@ -366,13 +403,23 @@ export class VideoCommentModel extends Model { .findAll(query) } - static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction, order: 'ASC' | 'DESC' = 'ASC') { + static async listAndCountByVideoForAP (video: MVideoImmutable, start: number, count: number, t?: Transaction) { + const blockerAccountIds = await VideoCommentModel.buildBlockerAccountIds({ + videoId: video.id, + isVideoOwned: video.isOwned() + }) + const query = { - order: [ [ 'createdAt', order ] ] as Order, + order: [ [ 'createdAt', 'ASC' ] ] as Order, offset: start, limit: count, where: { - videoId + videoId: video.id, + accountId: { + [Op.notIn]: Sequelize.literal( + '(' + buildBlockedAccountSQL(blockerAccountIds) + ')' + ) + } }, transaction: t } @@ -380,17 +427,56 @@ export class VideoCommentModel extends Model { return VideoCommentModel.findAndCountAll(query) } - static listForFeed (start: number, count: number, videoId?: number): Bluebird { + static async listForFeed (parameters: { + start: number + count: number + videoId?: number + accountId?: number + videoChannelId?: number + }): Promise { + const serverActor = await getServerActor() + const { start, count, videoId, accountId, videoChannelId } = parameters + + const accountExclusion = { + [Op.notIn]: Sequelize.literal( + '(' + buildBlockedAccountSQL([ serverActor.Account.id, '"Video->VideoChannel"."accountId"' ]) + ')' + ) + } + const accountWhere = accountId + ? { + [Op.and]: { + ...accountExclusion, + [Op.eq]: accountId + } + } + : accountExclusion + + const videoChannelWhere = videoChannelId ? { id: videoChannelId } : undefined + const query = { order: [ [ 'createdAt', 'DESC' ] ] as Order, offset: start, limit: count, - where: {}, + where: { + deletedAt: null, + accountId: accountWhere + }, include: [ { attributes: [ 'name', 'uuid' ], model: VideoModel.unscoped(), - required: true + required: true, + where: { + privacy: VideoPrivacy.PUBLIC + }, + include: [ + { + attributes: [ 'accountId' ], + model: VideoChannelModel.unscoped(), + required: true, + where: videoChannelWhere + } + ] } ] } @@ -402,6 +488,43 @@ export class VideoCommentModel extends Model { .findAll(query) } + static listForBulkDelete (ofAccount: MAccount, filter: { onVideosOfAccount?: MAccountId } = {}) { + const accountWhere = filter.onVideosOfAccount + ? { id: filter.onVideosOfAccount.id } + : {} + + const query = { + limit: 1000, + where: { + deletedAt: null, + accountId: ofAccount.id + }, + include: [ + { + model: VideoModel, + required: true, + include: [ + { + model: VideoChannelModel, + required: true, + include: [ + { + model: AccountModel, + required: true, + where: accountWhere + } + ] + } + ] + } + ] + } + + return VideoCommentModel + .scope([ ScopeNames.WITH_ACCOUNT ]) + .findAll(query) + } + static async getStats () { const totalLocalVideoComments = await VideoCommentModel.count({ include: [ @@ -437,7 +560,9 @@ export class VideoCommentModel extends Model { videoId, accountId: { [Op.notIn]: buildLocalAccountIdsIn() - } + }, + // Do not delete Tombstones + deletedAt: null } } @@ -566,4 +691,24 @@ export class VideoCommentModel extends Model { tag } } + + private static async buildBlockerAccountIds (options: { + videoId: number + isVideoOwned: boolean + user?: MUserAccountId + }) { + const { videoId, user, isVideoOwned } = options + + const serverActor = await getServerActor() + const blockerAccountIds = [ serverActor.Account.id ] + + if (user) blockerAccountIds.push(user.Account.id) + + if (isVideoOwned) { + const videoOwnerAccount = await AccountModel.loadAccountIdFromVideo(videoId) + blockerAccountIds.push(videoOwnerAccount.id) + } + + return blockerAccountIds + } }