aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/video/video-comment.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/video/video-comment.ts')
-rw-r--r--server/models/video/video-comment.ts140
1 files changed, 117 insertions, 23 deletions
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index f84c1880c..cf6278da7 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -1,21 +1,37 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { 2import {
3 AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table, 3 AllowNull,
4 BeforeDestroy,
5 BelongsTo,
6 Column,
7 CreatedAt,
8 DataType,
9 ForeignKey,
10 IFindOptions,
11 Is,
12 Model,
13 Scopes,
14 Table,
4 UpdatedAt 15 UpdatedAt
5} from 'sequelize-typescript' 16} from 'sequelize-typescript'
6import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' 17import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects'
7import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' 18import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
8import { VideoComment } from '../../../shared/models/videos/video-comment.model' 19import { VideoComment } from '../../../shared/models/videos/video-comment.model'
9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 20import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
10import { CONSTRAINTS_FIELDS } from '../../initializers' 21import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
11import { sendDeleteVideoComment } from '../../lib/activitypub/send' 22import { sendDeleteVideoComment } from '../../lib/activitypub/send'
12import { AccountModel } from '../account/account' 23import { AccountModel } from '../account/account'
13import { ActorModel } from '../activitypub/actor' 24import { ActorModel } from '../activitypub/actor'
14import { AvatarModel } from '../avatar/avatar' 25import { AvatarModel } from '../avatar/avatar'
15import { ServerModel } from '../server/server' 26import { ServerModel } from '../server/server'
16import { getSort, throwIfNotValid } from '../utils' 27import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils'
17import { VideoModel } from './video' 28import { VideoModel } from './video'
18import { VideoChannelModel } from './video-channel' 29import { VideoChannelModel } from './video-channel'
30import { getServerActor } from '../../helpers/utils'
31import { UserModel } from '../account/user'
32import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
33import { regexpCapture } from '../../helpers/regexp'
34import { uniq } from 'lodash'
19 35
20enum ScopeNames { 36enum ScopeNames {
21 WITH_ACCOUNT = 'WITH_ACCOUNT', 37 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -25,18 +41,29 @@ enum ScopeNames {
25} 41}
26 42
27@Scopes({ 43@Scopes({
28 [ScopeNames.ATTRIBUTES_FOR_API]: { 44 [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => {
29 attributes: { 45 return {
30 include: [ 46 attributes: {
31 [ 47 include: [
32 Sequelize.literal( 48 [
33 '(SELECT COUNT("replies"."id") ' + 49 Sequelize.literal(
34 'FROM "videoComment" AS "replies" ' + 50 '(' +
35 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id")' 51 'WITH "blocklist" AS (' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' +
36 ), 52 'SELECT COUNT("replies"."id") - (' +
37 'totalReplies' 53 'SELECT COUNT("replies"."id") ' +
54 'FROM "videoComment" AS "replies" ' +
55 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id" ' +
56 'AND "accountId" IN (SELECT "id" FROM "blocklist")' +
57 ')' +
58 'FROM "videoComment" AS "replies" ' +
59 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id" ' +
60 'AND "accountId" NOT IN (SELECT "id" FROM "blocklist")' +
61 ')'
62 ),
63 'totalReplies'
64 ]
38 ] 65 ]
39 ] 66 }
40 } 67 }
41 }, 68 },
42 [ScopeNames.WITH_ACCOUNT]: { 69 [ScopeNames.WITH_ACCOUNT]: {
@@ -267,26 +294,47 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
267 return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_VIDEO ]).findOne(query) 294 return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_VIDEO ]).findOne(query)
268 } 295 }
269 296
270 static listThreadsForApi (videoId: number, start: number, count: number, sort: string) { 297 static async listThreadsForApi (videoId: number, start: number, count: number, sort: string, user?: UserModel) {
298 const serverActor = await getServerActor()
299 const serverAccountId = serverActor.Account.id
300 const userAccountId = user ? user.Account.id : undefined
301
271 const query = { 302 const query = {
272 offset: start, 303 offset: start,
273 limit: count, 304 limit: count,
274 order: getSort(sort), 305 order: getSort(sort),
275 where: { 306 where: {
276 videoId, 307 videoId,
277 inReplyToCommentId: null 308 inReplyToCommentId: null,
309 accountId: {
310 [Sequelize.Op.notIn]: Sequelize.literal(
311 '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')'
312 )
313 }
278 } 314 }
279 } 315 }
280 316
317 // FIXME: typings
318 const scopes: any[] = [
319 ScopeNames.WITH_ACCOUNT,
320 {
321 method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ]
322 }
323 ]
324
281 return VideoCommentModel 325 return VideoCommentModel
282 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ]) 326 .scope(scopes)
283 .findAndCountAll(query) 327 .findAndCountAll(query)
284 .then(({ rows, count }) => { 328 .then(({ rows, count }) => {
285 return { total: count, data: rows } 329 return { total: count, data: rows }
286 }) 330 })
287 } 331 }
288 332
289 static listThreadCommentsForApi (videoId: number, threadId: number) { 333 static async listThreadCommentsForApi (videoId: number, threadId: number, user?: UserModel) {
334 const serverActor = await getServerActor()
335 const serverAccountId = serverActor.Account.id
336 const userAccountId = user ? user.Account.id : undefined
337
290 const query = { 338 const query = {
291 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ], 339 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ],
292 where: { 340 where: {
@@ -294,12 +342,24 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
294 [ Sequelize.Op.or ]: [ 342 [ Sequelize.Op.or ]: [
295 { id: threadId }, 343 { id: threadId },
296 { originCommentId: threadId } 344 { originCommentId: threadId }
297 ] 345 ],
346 accountId: {
347 [Sequelize.Op.notIn]: Sequelize.literal(
348 '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')'
349 )
350 }
298 } 351 }
299 } 352 }
300 353
354 const scopes: any[] = [
355 ScopeNames.WITH_ACCOUNT,
356 {
357 method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ]
358 }
359 ]
360
301 return VideoCommentModel 361 return VideoCommentModel
302 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ]) 362 .scope(scopes)
303 .findAndCountAll(query) 363 .findAndCountAll(query)
304 .then(({ rows, count }) => { 364 .then(({ rows, count }) => {
305 return { total: count, data: rows } 365 return { total: count, data: rows }
@@ -313,9 +373,11 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
313 id: { 373 id: {
314 [ Sequelize.Op.in ]: Sequelize.literal('(' + 374 [ Sequelize.Op.in ]: Sequelize.literal('(' +
315 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + 375 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' +
316 'SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ' + comment.id + ' UNION ' + 376 `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` +
317 'SELECT p.id, p."inReplyToCommentId" from "videoComment" p ' + 377 'UNION ' +
318 'INNER JOIN children c ON c."inReplyToCommentId" = p.id) ' + 378 'SELECT "parent"."id", "parent"."inReplyToCommentId" FROM "videoComment" "parent" ' +
379 'INNER JOIN "children" ON "children"."inReplyToCommentId" = "parent"."id"' +
380 ') ' +
319 'SELECT id FROM children' + 381 'SELECT id FROM children' +
320 ')'), 382 ')'),
321 [ Sequelize.Op.ne ]: comment.id 383 [ Sequelize.Op.ne ]: comment.id
@@ -391,6 +453,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
391 } 453 }
392 } 454 }
393 455
456 getCommentStaticPath () {
457 return this.Video.getWatchStaticPath() + ';threadId=' + this.getThreadId()
458 }
459
394 getThreadId (): number { 460 getThreadId (): number {
395 return this.originCommentId || this.id 461 return this.originCommentId || this.id
396 } 462 }
@@ -399,6 +465,34 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
399 return this.Account.isOwned() 465 return this.Account.isOwned()
400 } 466 }
401 467
468 extractMentions () {
469 if (!this.text) return []
470
471 const localMention = `@(${actorNameAlphabet}+)`
472 const remoteMention = `${localMention}@${CONFIG.WEBSERVER.HOST}`
473
474 const remoteMentionsRegex = new RegExp(' ' + remoteMention + ' ', 'g')
475 const localMentionsRegex = new RegExp(' ' + localMention + ' ', 'g')
476 const firstMentionRegex = new RegExp('^(?:(?:' + remoteMention + ')|(?:' + localMention + ')) ', 'g')
477 const endMentionRegex = new RegExp(' (?:(?:' + remoteMention + ')|(?:' + localMention + '))$', 'g')
478
479 return uniq(
480 [].concat(
481 regexpCapture(this.text, remoteMentionsRegex)
482 .map(([ , username ]) => username),
483
484 regexpCapture(this.text, localMentionsRegex)
485 .map(([ , username ]) => username),
486
487 regexpCapture(this.text, firstMentionRegex)
488 .map(([ , username1, username2 ]) => username1 || username2),
489
490 regexpCapture(this.text, endMentionRegex)
491 .map(([ , username1, username2 ]) => username1 || username2)
492 )
493 )
494 }
495
402 toFormattedJSON () { 496 toFormattedJSON () {
403 return { 497 return {
404 id: this.id, 498 id: this.id,