diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-06-22 13:00:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-22 13:00:39 +0200 |
commit | 1ebddadd0704812a4600c39cabe2268321e88331 (patch) | |
tree | 1cc8560e5b63e9976aa5411ba800a62cfe7b8ea9 /server/models | |
parent | 07aea1a2642fc9868cb01e30c322514029d5b95a (diff) | |
download | PeerTube-1ebddadd0704812a4600c39cabe2268321e88331.tar.gz PeerTube-1ebddadd0704812a4600c39cabe2268321e88331.tar.zst PeerTube-1ebddadd0704812a4600c39cabe2268321e88331.zip |
predefined report reasons & improved reporter UI (#2842)
- added `startAt` and `endAt` optional timestamps to help pin down reported sections of a video
- added predefined report reasons
- added video player with report modal
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/video/video-abuse.ts | 64 |
1 files changed, 61 insertions, 3 deletions
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index b2f111337..1319332f0 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -15,7 +15,13 @@ import { | |||
15 | UpdatedAt | 15 | UpdatedAt |
16 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
17 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' | 17 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' |
18 | import { VideoAbuseState, VideoDetails } from '../../../shared' | 18 | import { |
19 | VideoAbuseState, | ||
20 | VideoDetails, | ||
21 | VideoAbusePredefinedReasons, | ||
22 | VideoAbusePredefinedReasonsString, | ||
23 | videoAbusePredefinedReasonsMap | ||
24 | } from '../../../shared' | ||
19 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' | 25 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' |
20 | import { VideoAbuse } from '../../../shared/models/videos' | 26 | import { VideoAbuse } from '../../../shared/models/videos' |
21 | import { | 27 | import { |
@@ -31,6 +37,7 @@ import { ThumbnailModel } from './thumbnail' | |||
31 | import { VideoModel } from './video' | 37 | import { VideoModel } from './video' |
32 | import { VideoBlacklistModel } from './video-blacklist' | 38 | import { VideoBlacklistModel } from './video-blacklist' |
33 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | 39 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' |
40 | import { invert } from 'lodash' | ||
34 | 41 | ||
35 | export enum ScopeNames { | 42 | export enum ScopeNames { |
36 | FOR_API = 'FOR_API' | 43 | FOR_API = 'FOR_API' |
@@ -47,6 +54,7 @@ export enum ScopeNames { | |||
47 | 54 | ||
48 | // filters | 55 | // filters |
49 | id?: number | 56 | id?: number |
57 | predefinedReasonId?: number | ||
50 | 58 | ||
51 | state?: VideoAbuseState | 59 | state?: VideoAbuseState |
52 | videoIs?: VideoAbuseVideoIs | 60 | videoIs?: VideoAbuseVideoIs |
@@ -104,6 +112,14 @@ export enum ScopeNames { | |||
104 | }) | 112 | }) |
105 | } | 113 | } |
106 | 114 | ||
115 | if (options.predefinedReasonId) { | ||
116 | Object.assign(where, { | ||
117 | predefinedReasons: { | ||
118 | [Op.contains]: [ options.predefinedReasonId ] | ||
119 | } | ||
120 | }) | ||
121 | } | ||
122 | |||
107 | const onlyBlacklisted = options.videoIs === 'blacklisted' | 123 | const onlyBlacklisted = options.videoIs === 'blacklisted' |
108 | 124 | ||
109 | return { | 125 | return { |
@@ -258,6 +274,21 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
258 | @Column(DataType.JSONB) | 274 | @Column(DataType.JSONB) |
259 | deletedVideo: VideoDetails | 275 | deletedVideo: VideoDetails |
260 | 276 | ||
277 | @AllowNull(true) | ||
278 | @Default(null) | ||
279 | @Column(DataType.ARRAY(DataType.INTEGER)) | ||
280 | predefinedReasons: VideoAbusePredefinedReasons[] | ||
281 | |||
282 | @AllowNull(true) | ||
283 | @Default(null) | ||
284 | @Column | ||
285 | startAt: number | ||
286 | |||
287 | @AllowNull(true) | ||
288 | @Default(null) | ||
289 | @Column | ||
290 | endAt: number | ||
291 | |||
261 | @CreatedAt | 292 | @CreatedAt |
262 | createdAt: Date | 293 | createdAt: Date |
263 | 294 | ||
@@ -311,6 +342,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
311 | user?: MUserAccountId | 342 | user?: MUserAccountId |
312 | 343 | ||
313 | id?: number | 344 | id?: number |
345 | predefinedReason?: VideoAbusePredefinedReasonsString | ||
314 | state?: VideoAbuseState | 346 | state?: VideoAbuseState |
315 | videoIs?: VideoAbuseVideoIs | 347 | videoIs?: VideoAbuseVideoIs |
316 | 348 | ||
@@ -329,6 +361,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
329 | serverAccountId, | 361 | serverAccountId, |
330 | state, | 362 | state, |
331 | videoIs, | 363 | videoIs, |
364 | predefinedReason, | ||
332 | searchReportee, | 365 | searchReportee, |
333 | searchVideo, | 366 | searchVideo, |
334 | searchVideoChannel, | 367 | searchVideoChannel, |
@@ -337,6 +370,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
337 | } = parameters | 370 | } = parameters |
338 | 371 | ||
339 | const userAccountId = user ? user.Account.id : undefined | 372 | const userAccountId = user ? user.Account.id : undefined |
373 | const predefinedReasonId = predefinedReason ? videoAbusePredefinedReasonsMap[predefinedReason] : undefined | ||
340 | 374 | ||
341 | const query = { | 375 | const query = { |
342 | offset: start, | 376 | offset: start, |
@@ -348,6 +382,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
348 | 382 | ||
349 | const filters = { | 383 | const filters = { |
350 | id, | 384 | id, |
385 | predefinedReasonId, | ||
351 | search, | 386 | search, |
352 | state, | 387 | state, |
353 | videoIs, | 388 | videoIs, |
@@ -360,7 +395,9 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
360 | } | 395 | } |
361 | 396 | ||
362 | return VideoAbuseModel | 397 | return VideoAbuseModel |
363 | .scope({ method: [ ScopeNames.FOR_API, filters ] }) | 398 | .scope([ |
399 | { method: [ ScopeNames.FOR_API, filters ] } | ||
400 | ]) | ||
364 | .findAndCountAll(query) | 401 | .findAndCountAll(query) |
365 | .then(({ rows, count }) => { | 402 | .then(({ rows, count }) => { |
366 | return { total: count, data: rows } | 403 | return { total: count, data: rows } |
@@ -368,6 +405,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
368 | } | 405 | } |
369 | 406 | ||
370 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { | 407 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { |
408 | const predefinedReasons = VideoAbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) | ||
371 | const countReportsForVideo = this.get('countReportsForVideo') as number | 409 | const countReportsForVideo = this.get('countReportsForVideo') as number |
372 | const nthReportForVideo = this.get('nthReportForVideo') as number | 410 | const nthReportForVideo = this.get('nthReportForVideo') as number |
373 | const countReportsForReporterVideo = this.get('countReportsForReporter__video') as number | 411 | const countReportsForReporterVideo = this.get('countReportsForReporter__video') as number |
@@ -382,6 +420,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
382 | return { | 420 | return { |
383 | id: this.id, | 421 | id: this.id, |
384 | reason: this.reason, | 422 | reason: this.reason, |
423 | predefinedReasons, | ||
385 | reporterAccount: this.Account.toFormattedJSON(), | 424 | reporterAccount: this.Account.toFormattedJSON(), |
386 | state: { | 425 | state: { |
387 | id: this.state, | 426 | id: this.state, |
@@ -400,6 +439,8 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
400 | }, | 439 | }, |
401 | createdAt: this.createdAt, | 440 | createdAt: this.createdAt, |
402 | updatedAt: this.updatedAt, | 441 | updatedAt: this.updatedAt, |
442 | startAt: this.startAt, | ||
443 | endAt: this.endAt, | ||
403 | count: countReportsForVideo || 0, | 444 | count: countReportsForVideo || 0, |
404 | nth: nthReportForVideo || 0, | 445 | nth: nthReportForVideo || 0, |
405 | countReportsForReporter: (countReportsForReporterVideo || 0) + (countReportsForReporterDeletedVideo || 0), | 446 | countReportsForReporter: (countReportsForReporterVideo || 0) + (countReportsForReporterDeletedVideo || 0), |
@@ -408,14 +449,31 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
408 | } | 449 | } |
409 | 450 | ||
410 | toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject { | 451 | toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject { |
452 | const predefinedReasons = VideoAbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) | ||
453 | |||
454 | const startAt = this.startAt | ||
455 | const endAt = this.endAt | ||
456 | |||
411 | return { | 457 | return { |
412 | type: 'Flag' as 'Flag', | 458 | type: 'Flag' as 'Flag', |
413 | content: this.reason, | 459 | content: this.reason, |
414 | object: this.Video.url | 460 | object: this.Video.url, |
461 | tag: predefinedReasons.map(r => ({ | ||
462 | type: 'Hashtag' as 'Hashtag', | ||
463 | name: r | ||
464 | })), | ||
465 | startAt, | ||
466 | endAt | ||
415 | } | 467 | } |
416 | } | 468 | } |
417 | 469 | ||
418 | private static getStateLabel (id: number) { | 470 | private static getStateLabel (id: number) { |
419 | return VIDEO_ABUSE_STATES[id] || 'Unknown' | 471 | return VIDEO_ABUSE_STATES[id] || 'Unknown' |
420 | } | 472 | } |
473 | |||
474 | private static getPredefinedReasonsStrings (predefinedReasons: VideoAbusePredefinedReasons[]): VideoAbusePredefinedReasonsString[] { | ||
475 | return (predefinedReasons || []) | ||
476 | .filter(r => r in VideoAbusePredefinedReasons) | ||
477 | .map(r => invert(videoAbusePredefinedReasonsMap)[r] as VideoAbusePredefinedReasonsString) | ||
478 | } | ||
421 | } | 479 | } |