diff options
Diffstat (limited to 'server/models/abuse')
-rw-r--r-- | server/models/abuse/abuse-message.ts | 103 | ||||
-rw-r--r-- | server/models/abuse/abuse-query-builder.ts | 15 | ||||
-rw-r--r-- | server/models/abuse/abuse.ts | 182 |
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 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses' | ||
3 | import { AbuseMessage } from '@shared/models' | ||
4 | import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' | ||
5 | import { throwIfNotValid, getSort } from '../utils' | ||
6 | import { AbuseModel } from './abuse' | ||
7 | import { 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 | }) | ||
20 | export 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 | ||
33 | function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | 'id') { | 35 | function 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' |
19 | import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' | 19 | import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' |
20 | import { | 20 | import { |
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' |
32 | import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' | 34 | import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' |
33 | import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models' | 35 | import { MAbuse, MAbuseAdminFormattable, MAbuseAP, MUserAccountId, MAbuseUserFormattable } from '../../types/models' |
34 | import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' | 36 | import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' |
35 | import { getSort, throwIfNotValid } from '../utils' | 37 | import { getSort, throwIfNotValid } from '../utils' |
36 | import { ThumbnailModel } from '../video/thumbnail' | 38 | import { 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 { |