aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/abuse
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/abuse')
-rw-r--r--server/models/abuse/abuse-message.ts103
-rw-r--r--server/models/abuse/abuse-query-builder.ts15
-rw-r--r--server/models/abuse/abuse.ts182
3 files changed, 247 insertions, 53 deletions
diff --git a/server/models/abuse/abuse-message.ts b/server/models/abuse/abuse-message.ts
new file mode 100644
index 000000000..f7721c87d
--- /dev/null
+++ b/server/models/abuse/abuse-message.ts
@@ -0,0 +1,103 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses'
3import { AbuseMessage } from '@shared/models'
4import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
5import { throwIfNotValid, getSort } from '../utils'
6import { AbuseModel } from './abuse'
7import { MAbuseMessageFormattable, MAbuseMessage } from '@server/types/models'
8
9@Table({
10 tableName: 'abuseMessage',
11 indexes: [
12 {
13 fields: [ 'abuseId' ]
14 },
15 {
16 fields: [ 'accountId' ]
17 }
18 ]
19})
20export class AbuseMessageModel extends Model<AbuseMessageModel> {
21
22 @AllowNull(false)
23 @Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message'))
24 @Column(DataType.TEXT)
25 message: string
26
27 @AllowNull(false)
28 @Column
29 byModerator: boolean
30
31 @CreatedAt
32 createdAt: Date
33
34 @UpdatedAt
35 updatedAt: Date
36
37 @ForeignKey(() => AccountModel)
38 @Column
39 accountId: number
40
41 @BelongsTo(() => AccountModel, {
42 foreignKey: {
43 name: 'accountId',
44 allowNull: true
45 },
46 onDelete: 'set null'
47 })
48 Account: AccountModel
49
50 @ForeignKey(() => AbuseModel)
51 @Column
52 abuseId: number
53
54 @BelongsTo(() => AbuseModel, {
55 foreignKey: {
56 name: 'abuseId',
57 allowNull: false
58 },
59 onDelete: 'cascade'
60 })
61 Abuse: AbuseModel
62
63 static listForApi (abuseId: number) {
64 const options = {
65 where: { abuseId },
66
67 order: getSort('createdAt'),
68
69 include: [
70 {
71 model: AccountModel.scope(AccountScopeNames.SUMMARY),
72 required: false
73 }
74 ]
75 }
76
77 return AbuseMessageModel.findAndCountAll(options)
78 .then(({ rows, count }) => ({ data: rows, total: count }))
79 }
80
81 static loadByIdAndAbuseId (messageId: number, abuseId: number): Promise<MAbuseMessage> {
82 return AbuseMessageModel.findOne({
83 where: {
84 id: messageId,
85 abuseId
86 }
87 })
88 }
89
90 toFormattedJSON (this: MAbuseMessageFormattable): AbuseMessage {
91 const account = this.Account
92 ? this.Account.toFormattedSummaryJSON()
93 : null
94
95 return {
96 id: this.id,
97 byModerator: this.byModerator,
98 message: this.message,
99
100 account
101 }
102 }
103}
diff --git a/server/models/abuse/abuse-query-builder.ts b/server/models/abuse/abuse-query-builder.ts
index 5fddcf3c4..9d7cb75aa 100644
--- a/server/models/abuse/abuse-query-builder.ts
+++ b/server/models/abuse/abuse-query-builder.ts
@@ -26,8 +26,10 @@ export type BuildAbusesQueryOptions = {
26 state?: AbuseState 26 state?: AbuseState
27 27
28 // accountIds 28 // accountIds
29 serverAccountId: number 29 serverAccountId?: number
30 userAccountId: number 30 userAccountId?: number
31
32 reporterAccountId?: number
31} 33}
32 34
33function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | 'id') { 35function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | 'id') {
@@ -45,7 +47,14 @@ function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' |
45 'LEFT JOIN "videoComment" ON "commentAbuse"."videoCommentId" = "videoComment"."id"' 47 'LEFT JOIN "videoComment" ON "commentAbuse"."videoCommentId" = "videoComment"."id"'
46 ] 48 ]
47 49
48 whereAnd.push('"abuse"."reporterAccountId" NOT IN (' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')') 50 if (options.serverAccountId || options.userAccountId) {
51 whereAnd.push('"abuse"."reporterAccountId" NOT IN (' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')')
52 }
53
54 if (options.reporterAccountId) {
55 whereAnd.push('"abuse"."reporterAccountId" = :reporterAccountId')
56 replacements.reporterAccountId = options.reporterAccountId
57 }
49 58
50 if (options.search) { 59 if (options.search) {
51 const searchWhereOr = [ 60 const searchWhereOr = [
diff --git a/server/models/abuse/abuse.ts b/server/models/abuse/abuse.ts
index bd96cf79c..7002502d5 100644
--- a/server/models/abuse/abuse.ts
+++ b/server/models/abuse/abuse.ts
@@ -18,7 +18,6 @@ import {
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' 19import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
20import { 20import {
21 Abuse,
22 AbuseFilter, 21 AbuseFilter,
23 AbuseObject, 22 AbuseObject,
24 AbusePredefinedReasons, 23 AbusePredefinedReasons,
@@ -26,11 +25,14 @@ import {
26 AbusePredefinedReasonsString, 25 AbusePredefinedReasonsString,
27 AbuseState, 26 AbuseState,
28 AbuseVideoIs, 27 AbuseVideoIs,
29 VideoAbuse, 28 AdminVideoAbuse,
30 VideoCommentAbuse 29 AdminAbuse,
30 AdminVideoCommentAbuse,
31 UserAbuse,
32 UserVideoAbuse
31} from '@shared/models' 33} from '@shared/models'
32import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' 34import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
33import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models' 35import { MAbuse, MAbuseAdminFormattable, MAbuseAP, MUserAccountId, MAbuseUserFormattable } from '../../types/models'
34import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' 36import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
35import { getSort, throwIfNotValid } from '../utils' 37import { getSort, throwIfNotValid } from '../utils'
36import { ThumbnailModel } from '../video/thumbnail' 38import { ThumbnailModel } from '../video/thumbnail'
@@ -52,6 +54,16 @@ export enum ScopeNames {
52 attributes: { 54 attributes: {
53 include: [ 55 include: [
54 [ 56 [
57 literal(
58 '(' +
59 'SELECT count(*) ' +
60 'FROM "abuseMessage" ' +
61 'WHERE "abuseId" = "AbuseModel"."id"' +
62 ')'
63 ),
64 'countMessages'
65 ],
66 [
55 // we don't care about this count for deleted videos, so there are not included 67 // we don't care about this count for deleted videos, so there are not included
56 literal( 68 literal(
57 '(' + 69 '(' +
@@ -285,7 +297,7 @@ export class AbuseModel extends Model<AbuseModel> {
285 return AbuseModel.findOne(query) 297 return AbuseModel.findOne(query)
286 } 298 }
287 299
288 static async listForApi (parameters: { 300 static async listForAdminApi (parameters: {
289 start: number 301 start: number
290 count: number 302 count: number
291 sort: string 303 sort: string
@@ -353,71 +365,98 @@ export class AbuseModel extends Model<AbuseModel> {
353 return { total, data } 365 return { total, data }
354 } 366 }
355 367
356 toFormattedJSON (this: MAbuseFormattable): Abuse { 368 static async listForUserApi (parameters: {
357 const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) 369 user: MUserAccountId
358 370
359 const countReportsForVideo = this.get('countReportsForVideo') as number 371 start: number
360 const nthReportForVideo = this.get('nthReportForVideo') as number 372 count: number
373 sort: string
361 374
362 const countReportsForReporter = this.get('countReportsForReporter') as number 375 id?: number
363 const countReportsForReportee = this.get('countReportsForReportee') as number 376 search?: string
377 state?: AbuseState
378 }) {
379 const {
380 start,
381 count,
382 sort,
383 search,
384 user,
385 state,
386 id
387 } = parameters
364 388
365 let video: VideoAbuse = null 389 const queryOptions: BuildAbusesQueryOptions = {
366 let comment: VideoCommentAbuse = null 390 start,
391 count,
392 sort,
393 id,
394 search,
395 state,
396 reporterAccountId: user.Account.id
397 }
398
399 const [ total, data ] = await Promise.all([
400 AbuseModel.internalCountForApi(queryOptions),
401 AbuseModel.internalListForApi(queryOptions)
402 ])
403
404 return { total, data }
405 }
367 406
368 if (this.VideoAbuse) { 407 buildBaseVideoCommentAbuse (this: MAbuseUserFormattable) {
369 const abuseModel = this.VideoAbuse 408 if (!this.VideoCommentAbuse) return null
370 const entity = abuseModel.Video || abuseModel.deletedVideo
371 409
372 video = { 410 const abuseModel = this.VideoCommentAbuse
373 id: entity.id, 411 const entity = abuseModel.VideoComment
374 uuid: entity.uuid,
375 name: entity.name,
376 nsfw: entity.nsfw,
377 412
378 startAt: abuseModel.startAt, 413 return {
379 endAt: abuseModel.endAt, 414 id: entity.id,
415 threadId: entity.getThreadId(),
380 416
381 deleted: !abuseModel.Video, 417 text: entity.text ?? '',
382 blacklisted: abuseModel.Video?.isBlacklisted() || false,
383 thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
384 418
385 channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel, 419 deleted: entity.isDeleted(),
386 420
387 countReports: countReportsForVideo, 421 video: {
388 nthReport: nthReportForVideo 422 id: entity.Video.id,
423 name: entity.Video.name,
424 uuid: entity.Video.uuid
389 } 425 }
390 } 426 }
427 }
391 428
392 if (this.VideoCommentAbuse) { 429 buildBaseVideoAbuse (this: MAbuseUserFormattable): UserVideoAbuse {
393 const abuseModel = this.VideoCommentAbuse 430 if (!this.VideoAbuse) return null
394 const entity = abuseModel.VideoComment
395 431
396 comment = { 432 const abuseModel = this.VideoAbuse
397 id: entity.id, 433 const entity = abuseModel.Video || abuseModel.deletedVideo
398 threadId: entity.getThreadId(),
399 434
400 text: entity.text ?? '', 435 return {
436 id: entity.id,
437 uuid: entity.uuid,
438 name: entity.name,
439 nsfw: entity.nsfw,
401 440
402 deleted: entity.isDeleted(), 441 startAt: abuseModel.startAt,
442 endAt: abuseModel.endAt,
403 443
404 video: { 444 deleted: !abuseModel.Video,
405 id: entity.Video.id, 445 blacklisted: abuseModel.Video?.isBlacklisted() || false,
406 name: entity.Video.name, 446 thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
407 uuid: entity.Video.uuid 447
408 } 448 channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel,
409 }
410 } 449 }
450 }
451
452 buildBaseAbuse (this: MAbuseUserFormattable, countMessages: number): UserAbuse {
453 const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons)
411 454
412 return { 455 return {
413 id: this.id, 456 id: this.id,
414 reason: this.reason, 457 reason: this.reason,
415 predefinedReasons, 458 predefinedReasons,
416 459
417 reporterAccount: this.ReporterAccount
418 ? this.ReporterAccount.toFormattedJSON()
419 : null,
420
421 flaggedAccount: this.FlaggedAccount 460 flaggedAccount: this.FlaggedAccount
422 ? this.FlaggedAccount.toFormattedJSON() 461 ? this.FlaggedAccount.toFormattedJSON()
423 : null, 462 : null,
@@ -429,11 +468,41 @@ export class AbuseModel extends Model<AbuseModel> {
429 468
430 moderationComment: this.moderationComment, 469 moderationComment: this.moderationComment,
431 470
471 countMessages,
472
473 createdAt: this.createdAt,
474 updatedAt: this.updatedAt
475 }
476 }
477
478 toFormattedAdminJSON (this: MAbuseAdminFormattable): AdminAbuse {
479 const countReportsForVideo = this.get('countReportsForVideo') as number
480 const nthReportForVideo = this.get('nthReportForVideo') as number
481
482 const countReportsForReporter = this.get('countReportsForReporter') as number
483 const countReportsForReportee = this.get('countReportsForReportee') as number
484
485 const countMessages = this.get('countMessages') as number
486
487 const baseVideo = this.buildBaseVideoAbuse()
488 const video: AdminVideoAbuse = baseVideo
489 ? Object.assign(baseVideo, {
490 countReports: countReportsForVideo,
491 nthReport: nthReportForVideo
492 })
493 : null
494
495 const comment: AdminVideoCommentAbuse = this.buildBaseVideoCommentAbuse()
496
497 const abuse = this.buildBaseAbuse(countMessages || 0)
498
499 return Object.assign(abuse, {
432 video, 500 video,
433 comment, 501 comment,
434 502
435 createdAt: this.createdAt, 503 reporterAccount: this.ReporterAccount
436 updatedAt: this.updatedAt, 504 ? this.ReporterAccount.toFormattedJSON()
505 : null,
437 506
438 countReportsForReporter: (countReportsForReporter || 0), 507 countReportsForReporter: (countReportsForReporter || 0),
439 countReportsForReportee: (countReportsForReportee || 0), 508 countReportsForReportee: (countReportsForReportee || 0),
@@ -443,7 +512,20 @@ export class AbuseModel extends Model<AbuseModel> {
443 endAt: null, 512 endAt: null,
444 count: countReportsForVideo || 0, 513 count: countReportsForVideo || 0,
445 nth: nthReportForVideo || 0 514 nth: nthReportForVideo || 0
446 } 515 })
516 }
517
518 toFormattedUserJSON (this: MAbuseUserFormattable): UserAbuse {
519 const countMessages = this.get('countMessages') as number
520
521 const video = this.buildBaseVideoAbuse()
522 const comment: AdminVideoCommentAbuse = this.buildBaseVideoCommentAbuse()
523 const abuse = this.buildBaseAbuse(countMessages || 0)
524
525 return Object.assign(abuse, {
526 video,
527 comment
528 })
447 } 529 }
448 530
449 toActivityPubObject (this: MAbuseAP): AbuseObject { 531 toActivityPubObject (this: MAbuseAP): AbuseObject {