X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;ds=sidebyside;f=server%2Fmodels%2Fvideo%2Fvideo-comment.ts;h=e79aff20939467746736f4dcbb8556a6958ab13b;hb=8cd72bd37724054f8942f2fefc7aa2e60eca74cf;hp=66fca2484484084b4ad3107dbdf9f7a4e84fb83b;hpb=4cb6d4578893db310297d7e118ce2fb7ecb952a3;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 66fca2484..e79aff209 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts @@ -1,8 +1,9 @@ import * as Sequelize from 'sequelize' import { - AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table, + AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' +import { ActivityTagObject } 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' @@ -103,6 +104,13 @@ enum ScopeNames { }, { fields: [ 'videoId', 'originCommentId' ] + }, + { + fields: [ 'url' ], + unique: true + }, + { + fields: [ 'accountId' ] } ] }) @@ -174,10 +182,38 @@ export class VideoCommentModel extends Model { }) Account: AccountModel - @AfterDestroy - static async sendDeleteIfOwned (instance: VideoCommentModel) { + @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, undefined) + await sendDeleteVideoComment(instance, options.transaction) } } @@ -207,7 +243,7 @@ export class VideoCommentModel extends Model { .findOne(query) } - static loadByUrl (url: string, t?: Sequelize.Transaction) { + static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { const query: IFindOptions = { where: { url @@ -216,10 +252,10 @@ export class VideoCommentModel extends Model { if (t !== undefined) query.transaction = t - return VideoCommentModel.findOne(query) + return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT ]).findOne(query) } - static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { + static loadByUrlAndPopulateReplyAndVideo (url: string, t?: Sequelize.Transaction) { const query: IFindOptions = { where: { url @@ -228,14 +264,14 @@ export class VideoCommentModel extends Model { if (t !== undefined) query.transaction = t - return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT ]).findOne(query) + return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_VIDEO ]).findOne(query) } static listThreadsForApi (videoId: number, start: number, count: number, sort: string) { const query = { offset: start, limit: count, - order: [ getSort(sort) ], + order: getSort(sort), where: { videoId, inReplyToCommentId: null @@ -252,7 +288,7 @@ export class VideoCommentModel extends Model { static listThreadCommentsForApi (videoId: number, threadId: number) { const query = { - order: [ [ 'createdAt', 'ASC' ] ], + order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ], where: { videoId, [ Sequelize.Op.or ]: [ @@ -270,6 +306,95 @@ export class VideoCommentModel extends Model { }) } + static listThreadParentComments (comment: VideoCommentModel, t: Sequelize.Transaction, order: 'ASC' | 'DESC' = 'ASC') { + const query = { + order: [ [ 'createdAt', order ] ], + where: { + id: { + [ Sequelize.Op.in ]: Sequelize.literal('(' + + 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + + 'SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ' + comment.id + ' UNION ' + + 'SELECT p.id, p."inReplyToCommentId" from "videoComment" p ' + + 'INNER JOIN children c ON c."inReplyToCommentId" = p.id) ' + + 'SELECT id FROM children' + + ')'), + [ Sequelize.Op.ne ]: comment.id + } + }, + transaction: t + } + + return VideoCommentModel + .scope([ ScopeNames.WITH_ACCOUNT ]) + .findAll(query) + } + + static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Sequelize.Transaction, order: 'ASC' | 'DESC' = 'ASC') { + const query = { + order: [ [ 'createdAt', order ] ], + offset: start, + limit: count, + where: { + videoId + }, + transaction: t + } + + return VideoCommentModel.findAndCountAll(query) + } + + static listForFeed (start: number, count: number, videoId?: number) { + const query = { + order: [ [ 'createdAt', 'DESC' ] ], + offset: start, + limit: count, + where: {}, + include: [ + { + attributes: [ 'name', 'uuid' ], + model: VideoModel.unscoped(), + required: true + } + ] + } + + if (videoId) query.where['videoId'] = videoId + + return VideoCommentModel + .scope([ ScopeNames.WITH_ACCOUNT ]) + .findAll(query) + } + + static async getStats () { + const totalLocalVideoComments = await VideoCommentModel.count({ + include: [ + { + model: AccountModel, + required: true, + include: [ + { + model: ActorModel, + required: true, + where: { + serverId: null + } + } + ] + } + ] + }) + const totalVideoComments = await VideoCommentModel.count() + + return { + totalLocalVideoComments, + totalVideoComments + } + } + + getThreadId (): number { + return this.originCommentId || this.id + } + isOwned () { return this.Account.isOwned() } @@ -289,7 +414,7 @@ export class VideoCommentModel extends Model { } as VideoComment } - toActivityPubObject (): VideoCommentObject { + toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject { let inReplyTo: string // New thread, so in AS we reply to the video if (this.inReplyToCommentId === null) { @@ -298,6 +423,17 @@ export class VideoCommentModel extends Model { inReplyTo = this.InReplyToVideoComment.url } + const tag: ActivityTagObject[] = [] + for (const parentComment of threadParentComments) { + const actor = parentComment.Account.Actor + + tag.push({ + type: 'Mention', + href: actor.url, + name: `@${actor.preferredUsername}@${actor.getHost()}` + }) + } + return { type: 'Note' as 'Note', id: this.url, @@ -306,7 +442,8 @@ export class VideoCommentModel extends Model { updated: this.updatedAt.toISOString(), published: this.createdAt.toISOString(), url: this.url, - attributedTo: this.Account.Actor.url + attributedTo: this.Account.Actor.url, + tag } } }