X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Fvideo%2Fvideo-comment.ts;h=b33c33d5efa002bc52f618584cefcfe508fa70a8;hb=3d527ba173a37bd61ec8ad742642bb320d12995c;hp=28e5818cd3acf846e0f87ae05685f28104b00634;hpb=970ceac0a6bf4990b8924738591df4949491ec9b;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 28e5818cd..b33c33d5e 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -1,36 +1,32 @@ -import { - AllowNull, - BeforeDestroy, - BelongsTo, - Column, - CreatedAt, - DataType, - ForeignKey, - Is, - Model, - Scopes, - Table, - UpdatedAt -} from 'sequelize-typescript' -import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' +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 { sendDeleteVideoComment } from '../../lib/activitypub/send' import { AccountModel } from '../account/account' import { ActorModel } from '../activitypub/actor' -import { AvatarModel } from '../avatar/avatar' -import { ServerModel } from '../server/server' -import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' +import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' import { VideoModel } from './video' import { VideoChannelModel } from './video-channel' import { getServerActor } from '../../helpers/utils' -import { UserModel } from '../account/user' import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' import { regexpCapture } from '../../helpers/regexp' import { uniq } from 'lodash' import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' +import * as Bluebird from 'bluebird' +import { + MComment, + MCommentAP, + MCommentFormattable, + MCommentId, + MCommentOwner, + MCommentOwnerReplyVideoLight, + MCommentOwnerVideo, + MCommentOwnerVideoFeed, + MCommentOwnerVideoReply +} from '../../typings/models/video' +import { MUserAccountId } from '@server/typings/models' enum ScopeNames { WITH_ACCOUNT = 'WITH_ACCOUNT', @@ -60,6 +56,19 @@ enum ScopeNames { ')' ), 'totalReplies' + ], + [ + Sequelize.literal( + '(' + + 'SELECT COUNT("replies"."id") ' + + 'FROM "videoComment" AS "replies" ' + + 'INNER JOIN "video" ON "video"."id" = "replies"."videoId" ' + + 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + + 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id" ' + + 'AND "replies"."accountId" = "videoChannel"."accountId"' + + ')' + ), + 'totalRepliesFromVideoAuthor' ] ] } @@ -68,22 +77,7 @@ enum ScopeNames { [ScopeNames.WITH_ACCOUNT]: { include: [ { - model: AccountModel, - include: [ - { - model: ActorModel, - include: [ - { - model: ServerModel, - required: false - }, - { - model: AvatarModel, - required: false - } - ] - } - ] + model: AccountModel } ] }, @@ -102,18 +96,12 @@ enum ScopeNames { required: true, include: [ { - model: VideoChannelModel.unscoped(), + model: VideoChannelModel, required: true, include: [ { model: AccountModel, - required: true, - include: [ - { - model: ActorModel, - required: true - } - ] + required: true } ] } @@ -147,6 +135,10 @@ export class VideoCommentModel extends Model { @UpdatedAt updatedAt: Date + @AllowNull(true) + @Column(DataType.DATE) + deletedAt: Date + @AllowNull(false) @Is('VideoCommentUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) @@ -202,48 +194,13 @@ export class VideoCommentModel extends Model { @BelongsTo(() => AccountModel, { foreignKey: { - allowNull: false + allowNull: true }, onDelete: 'CASCADE' }) Account: AccountModel - @BeforeDestroy - static async sendDeleteIfOwned (instance: VideoCommentModel, options) { - if (!instance.Account || !instance.Account.Actor) { - instance.Account = await instance.$get('Account', { - include: [ ActorModel ], - transaction: options.transaction - }) as AccountModel - } - - if (!instance.Video) { - instance.Video = await instance.$get('Video', { - include: [ - { - model: VideoChannelModel, - include: [ - { - model: AccountModel, - include: [ - { - model: ActorModel - } - ] - } - ] - } - ], - transaction: options.transaction - }) as VideoModel - } - - if (instance.isOwned()) { - await sendDeleteVideoComment(instance, options.transaction) - } - } - - static loadById (id: number, t?: Transaction) { + static loadById (id: number, t?: Transaction): Bluebird { const query: FindOptions = { where: { id @@ -255,7 +212,7 @@ export class VideoCommentModel extends Model { return VideoCommentModel.findOne(query) } - static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) { + static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Bluebird { const query: FindOptions = { where: { id @@ -269,7 +226,7 @@ export class VideoCommentModel extends Model { .findOne(query) } - static loadByUrlAndPopulateAccount (url: string, t?: Transaction) { + static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Bluebird { const query: FindOptions = { where: { url @@ -278,27 +235,33 @@ export class VideoCommentModel extends Model { if (t !== undefined) query.transaction = t - return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT ]).findOne(query) + return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) } - static loadByUrlAndPopulateReplyAndVideo (url: string, t?: Transaction) { + static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Bluebird { const query: FindOptions = { where: { url - } + }, + include: [ + { + attributes: [ 'id', 'url' ], + model: VideoModel.unscoped() + } + ] } if (t !== undefined) query.transaction = t - return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_VIDEO ]).findOne(query) + return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_ACCOUNT ]).findOne(query) } static async listThreadsForApi (parameters: { - videoId: number, - start: number, - count: number, - sort: string, - user?: UserModel + videoId: number + start: number + count: number + sort: string + user?: MUserAccountId }) { const { videoId, start, count, sort, user } = parameters @@ -309,7 +272,7 @@ export class VideoCommentModel extends Model { const query = { offset: start, limit: count, - order: getSort(sort), + order: getCommentSort(sort), where: { videoId, inReplyToCommentId: null, @@ -337,9 +300,9 @@ export class VideoCommentModel extends Model { } static async listThreadCommentsForApi (parameters: { - videoId: number, - threadId: number, - user?: UserModel + videoId: number + threadId: number + user?: MUserAccountId }) { const { videoId, threadId, user } = parameters @@ -351,7 +314,7 @@ export class VideoCommentModel extends Model { order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, where: { videoId, - [ Op.or ]: [ + [Op.or]: [ { id: threadId }, { originCommentId: threadId } ], @@ -378,12 +341,12 @@ export class VideoCommentModel extends Model { }) } - static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') { + static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Bluebird { const query = { order: [ [ 'createdAt', order ] ] as Order, where: { id: { - [ Op.in ]: Sequelize.literal('(' + + [Op.in]: Sequelize.literal('(' + 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + 'UNION ' + @@ -392,7 +355,7 @@ export class VideoCommentModel extends Model { ') ' + 'SELECT id FROM children' + ')'), - [ Op.ne ]: comment.id + [Op.ne]: comment.id } }, transaction: t @@ -414,10 +377,10 @@ export class VideoCommentModel extends Model { transaction: t } - return VideoCommentModel.findAndCountAll(query) + return VideoCommentModel.findAndCountAll(query) } - static listForFeed (start: number, count: number, videoId?: number) { + static listForFeed (start: number, count: number, videoId?: number): Bluebird { const query = { order: [ [ 'createdAt', 'DESC' ] ] as Order, offset: start, @@ -471,25 +434,11 @@ export class VideoCommentModel extends Model { updatedAt: { [Op.lt]: beforeUpdatedAt }, - videoId - }, - include: [ - { - required: true, - model: AccountModel.unscoped(), - include: [ - { - required: true, - model: ActorModel.unscoped(), - where: { - serverId: { - [Op.ne]: null - } - } - } - ] + videoId, + accountId: { + [Op.notIn]: buildLocalAccountIdsIn() } - ] + } } return VideoCommentModel.destroy(query) @@ -504,9 +453,17 @@ export class VideoCommentModel extends Model { } isOwned () { + if (!this.Account) { + return false + } + return this.Account.isOwned() } + isDeleted () { + return this.deletedAt !== null + } + extractMentions () { let result: string[] = [] @@ -545,7 +502,7 @@ export class VideoCommentModel extends Model { return uniq(result) } - toFormattedJSON () { + toFormattedJSON (this: MCommentFormattable) { return { id: this.id, url: this.url, @@ -555,12 +512,15 @@ export class VideoCommentModel extends Model { videoId: this.videoId, createdAt: this.createdAt, updatedAt: this.updatedAt, + deletedAt: this.deletedAt, + isDeleted: this.isDeleted(), + totalRepliesFromVideoAuthor: this.get('totalRepliesFromVideoAuthor') || 0, totalReplies: this.get('totalReplies') || 0, - account: this.Account.toFormattedJSON() + account: this.Account ? this.Account.toFormattedJSON() : null } as VideoComment } - toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject { + toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject | ActivityTombstoneObject { let inReplyTo: string // New thread, so in AS we reply to the video if (this.inReplyToCommentId === null) { @@ -569,8 +529,22 @@ export class VideoCommentModel extends Model { inReplyTo = this.InReplyToVideoComment.url } + if (this.isDeleted()) { + return { + id: this.url, + type: 'Tombstone', + formerType: 'Note', + inReplyTo, + published: this.createdAt.toISOString(), + updated: this.updatedAt.toISOString(), + deleted: this.deletedAt.toISOString() + } + } + const tag: ActivityTagObject[] = [] for (const parentComment of threadParentComments) { + if (!parentComment.Account) continue + const actor = parentComment.Account.Actor tag.push({