diff options
103 files changed, 2137 insertions, 1162 deletions
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts index 6f340884f..1e137e63e 100644 --- a/client/src/app/+admin/admin.component.ts +++ b/client/src/app/+admin/admin.component.ts | |||
@@ -91,7 +91,7 @@ export class AdminComponent implements OnInit { | |||
91 | } | 91 | } |
92 | 92 | ||
93 | hasVideoAbusesRight () { | 93 | hasVideoAbusesRight () { |
94 | return this.auth.getUser().hasRight(UserRight.MANAGE_VIDEO_ABUSES) | 94 | return this.auth.getUser().hasRight(UserRight.MANAGE_ABUSES) |
95 | } | 95 | } |
96 | 96 | ||
97 | hasVideoBlocklistRight () { | 97 | hasVideoBlocklistRight () { |
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts index 728227a84..c59bd2927 100644 --- a/client/src/app/+admin/admin.module.ts +++ b/client/src/app/+admin/admin.module.ts | |||
@@ -14,10 +14,10 @@ import { FollowersListComponent, FollowsComponent, VideoRedundanciesListComponen | |||
14 | import { FollowingListComponent } from './follows/following-list/following-list.component' | 14 | import { FollowingListComponent } from './follows/following-list/following-list.component' |
15 | import { RedundancyCheckboxComponent } from './follows/shared/redundancy-checkbox.component' | 15 | import { RedundancyCheckboxComponent } from './follows/shared/redundancy-checkbox.component' |
16 | import { VideoRedundancyInformationComponent } from './follows/video-redundancies-list/video-redundancy-information.component' | 16 | import { VideoRedundancyInformationComponent } from './follows/video-redundancies-list/video-redundancy-information.component' |
17 | import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlockListComponent } from './moderation' | 17 | import { ModerationCommentModalComponent, AbuseListComponent, VideoBlockListComponent } from './moderation' |
18 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist' | 18 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist' |
19 | import { ModerationComponent } from './moderation/moderation.component' | 19 | import { ModerationComponent } from './moderation/moderation.component' |
20 | import { VideoAbuseDetailsComponent } from './moderation/video-abuse-list/video-abuse-details.component' | 20 | import { AbuseDetailsComponent } from './moderation/abuse-list/abuse-details.component' |
21 | import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component' | 21 | import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component' |
22 | import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component' | 22 | import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component' |
23 | import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component' | 23 | import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component' |
@@ -60,8 +60,10 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom | |||
60 | 60 | ||
61 | ModerationComponent, | 61 | ModerationComponent, |
62 | VideoBlockListComponent, | 62 | VideoBlockListComponent, |
63 | VideoAbuseListComponent, | 63 | |
64 | VideoAbuseDetailsComponent, | 64 | AbuseListComponent, |
65 | AbuseDetailsComponent, | ||
66 | |||
65 | ModerationCommentModalComponent, | 67 | ModerationCommentModalComponent, |
66 | InstanceServerBlocklistComponent, | 68 | InstanceServerBlocklistComponent, |
67 | InstanceAccountBlocklistComponent, | 69 | InstanceAccountBlocklistComponent, |
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html new file mode 100644 index 000000000..d031ea8ed --- /dev/null +++ b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.html | |||
@@ -0,0 +1,93 @@ | |||
1 | <div class="d-flex moderation-expanded"> | ||
2 | <!-- report left part (report details) --> | ||
3 | <div class="col-8"> | ||
4 | |||
5 | <!-- report metadata --> | ||
6 | <div class="d-flex"> | ||
7 | <span class="col-3 moderation-expanded-label" i18n>Reporter</span> | ||
8 | <span class="col-9 moderation-expanded-text"> | ||
9 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" class="chip"> | ||
10 | <img | ||
11 | class="avatar" | ||
12 | [src]="abuse.reporterAccount.avatar?.path" | ||
13 | (error)="switchToDefaultAvatar($event)" | ||
14 | alt="Avatar" | ||
15 | > | ||
16 | <div> | ||
17 | <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span> | ||
18 | </div> | ||
19 | </a> | ||
20 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | ||
21 | {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1Â glyphicon glyphicon-flag"></span> | ||
22 | </a> | ||
23 | </span> | ||
24 | </div> | ||
25 | |||
26 | <div class="d-flex"> | ||
27 | <span class="col-3 moderation-expanded-label" i18n>Reportee</span> | ||
28 | <span class="col-9 moderation-expanded-text"> | ||
29 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.video.channel.ownerAccount.displayName + '"' }" class="chip"> | ||
30 | <img | ||
31 | class="avatar" | ||
32 | [src]="abuse.video.channel.ownerAccount?.avatar?.path" | ||
33 | (error)="switchToDefaultAvatar($event)" | ||
34 | alt="Avatar" | ||
35 | > | ||
36 | <div> | ||
37 | <span class="text-muted">{{ abuse.video.channel.ownerAccount ? abuse.video.channel.ownerAccount.nameWithHost : '' }}</span> | ||
38 | </div> | ||
39 | </a> | ||
40 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.video.channel.ownerAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | ||
41 | {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1Â glyphicon glyphicon-flag"></span> | ||
42 | </a> | ||
43 | </span> | ||
44 | </div> | ||
45 | |||
46 | <div class="d-flex" *ngIf="abuse.updatedAt"> | ||
47 | <span class="col-3 moderation-expanded-label" i18n>Updated</span> | ||
48 | <time class="col-9 moderation-expanded-text video-details-date-updated">{{ abuse.updatedAt | date: 'medium' }}</time> | ||
49 | </div> | ||
50 | |||
51 | <!-- report text --> | ||
52 | <div class="mt-3 d-flex"> | ||
53 | <span class="col-3 moderation-expanded-label"> | ||
54 | <ng-container i18n>Report</ng-container> | ||
55 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a> | ||
56 | </span> | ||
57 | <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span> | ||
58 | </div> | ||
59 | |||
60 | <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex"> | ||
61 | <span class="col-3"></span> | ||
62 | <span class="col-9"> | ||
63 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" *ngFor="let reason of getPredefinedReasons()"> | ||
64 | <div>{{ reason.label }}</div> | ||
65 | </a> | ||
66 | </span> | ||
67 | </div> | ||
68 | |||
69 | <div *ngIf="abuse.startAt" class="mt-2 d-flex"> | ||
70 | <span class="col-3 moderation-expanded-label" i18n>Reported part</span> | ||
71 | <span class="col-9"> | ||
72 | {{ startAt }}<ng-container *ngIf="abuse.endAt"> - {{ endAt }}</ng-container> | ||
73 | </span> | ||
74 | </div> | ||
75 | |||
76 | <div class="mt-3 d-flex" *ngIf="abuse.moderationComment"> | ||
77 | <span class="col-3 moderation-expanded-label" i18n>Note</span> | ||
78 | <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.moderationCommentHtml"></span> | ||
79 | </div> | ||
80 | |||
81 | </div> | ||
82 | |||
83 | <!-- report right part (video details) --> | ||
84 | <div class="col-4"> | ||
85 | <div class="screenratio"> | ||
86 | <div *ngIf="abuse.video.deleted || abuse.video.blacklisted"> | ||
87 | <span i18n *ngIf="abuse.video.deleted">The video was deleted</span> | ||
88 | <span i18n *ngIf="!abuse.video.deleted">The video was blocked</span> | ||
89 | </div> | ||
90 | <div *ngIf="!abuse.video.deleted && !abuse.video.blacklisted" [innerHTML]="abuse.embedHtml"></div> | ||
91 | </div> | ||
92 | </div> | ||
93 | </div> | ||
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts index 5db2887fa..8f87630b8 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts +++ b/client/src/app/+admin/moderation/abuse-list/abuse-details.component.ts | |||
@@ -1,19 +1,19 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { Actor } from '@app/shared/shared-main' | 2 | import { Actor } from '@app/shared/shared-main' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { VideoAbusePredefinedReasonsString } from '../../../../../../shared/models/videos/abuse/video-abuse-reason.model' | 4 | import { AbusePredefinedReasonsString } from '@shared/models' |
5 | import { ProcessedVideoAbuse } from './video-abuse-list.component' | 5 | import { ProcessedAbuse } from './abuse-list.component' |
6 | import { durationToString } from '@app/helpers' | 6 | import { durationToString } from '@app/helpers' |
7 | 7 | ||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-video-abuse-details', | 9 | selector: 'my-abuse-details', |
10 | templateUrl: './video-abuse-details.component.html', | 10 | templateUrl: './abuse-details.component.html', |
11 | styleUrls: [ '../moderation.component.scss' ] | 11 | styleUrls: [ '../moderation.component.scss' ] |
12 | }) | 12 | }) |
13 | export class VideoAbuseDetailsComponent { | 13 | export class AbuseDetailsComponent { |
14 | @Input() videoAbuse: ProcessedVideoAbuse | 14 | @Input() abuse: ProcessedAbuse |
15 | 15 | ||
16 | private predefinedReasonsTranslations: { [key in VideoAbusePredefinedReasonsString]: string } | 16 | private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string } |
17 | 17 | ||
18 | constructor ( | 18 | constructor ( |
19 | private i18n: I18n | 19 | private i18n: I18n |
@@ -31,16 +31,16 @@ export class VideoAbuseDetailsComponent { | |||
31 | } | 31 | } |
32 | 32 | ||
33 | get startAt () { | 33 | get startAt () { |
34 | return durationToString(this.videoAbuse.startAt) | 34 | return durationToString(this.abuse.startAt) |
35 | } | 35 | } |
36 | 36 | ||
37 | get endAt () { | 37 | get endAt () { |
38 | return durationToString(this.videoAbuse.endAt) | 38 | return durationToString(this.abuse.endAt) |
39 | } | 39 | } |
40 | 40 | ||
41 | getPredefinedReasons () { | 41 | getPredefinedReasons () { |
42 | if (!this.videoAbuse.predefinedReasons) return [] | 42 | if (!this.abuse.predefinedReasons) return [] |
43 | return this.videoAbuse.predefinedReasons.map(r => ({ | 43 | return this.abuse.predefinedReasons.map(r => ({ |
44 | id: r, | 44 | id: r, |
45 | label: this.predefinedReasonsTranslations[r] | 45 | label: this.predefinedReasonsTranslations[r] |
46 | })) | 46 | })) |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html index 64641b28a..167f32fe6 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html | |||
@@ -1,5 +1,5 @@ | |||
1 | <p-table | 1 | <p-table |
2 | [value]="videoAbuses" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" | 2 | [value]="abuses" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" |
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" |
4 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate | 4 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate |
5 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" | 5 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" |
@@ -16,11 +16,11 @@ | |||
16 | 16 | ||
17 | <div role="menu" ngbDropdownMenu> | 17 | <div role="menu" ngbDropdownMenu> |
18 | <h6 class="dropdown-header" i18n>Advanced report filters</h6> | 18 | <h6 class="dropdown-header" i18n>Advanced report filters</h6> |
19 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a> | 19 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a> |
20 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a> | 20 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a> |
21 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a> | 21 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a> |
22 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a> | 22 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a> |
23 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a> | 23 | <a [routerLink]="[ '/admin/moderation/abuses/list' ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a> |
24 | </div> | 24 | </div> |
25 | </div> | 25 | </div> |
26 | <input | 26 | <input |
@@ -45,91 +45,91 @@ | |||
45 | </tr> | 45 | </tr> |
46 | </ng-template> | 46 | </ng-template> |
47 | 47 | ||
48 | <ng-template pTemplate="body" let-expanded="expanded" let-videoAbuse> | 48 | <ng-template pTemplate="body" let-expanded="expanded" let-abuse> |
49 | <tr> | 49 | <tr> |
50 | <td class="c-hand" [pRowToggler]="videoAbuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body"> | 50 | <td class="c-hand" [pRowToggler]="abuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body"> |
51 | <span class="expander"> | 51 | <span class="expander"> |
52 | <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i> | 52 | <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i> |
53 | </span> | 53 | </span> |
54 | </td> | 54 | </td> |
55 | 55 | ||
56 | <td> | 56 | <td> |
57 | <a [href]="videoAbuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> | 57 | <a [href]="abuse.reporterAccount.url" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer"> |
58 | <div class="chip two-lines"> | 58 | <div class="chip two-lines"> |
59 | <img | 59 | <img |
60 | class="avatar" | 60 | class="avatar" |
61 | [src]="videoAbuse.reporterAccount.avatar?.path" | 61 | [src]="abuse.reporterAccount.avatar?.path" |
62 | (error)="switchToDefaultAvatar($event)" | 62 | (error)="switchToDefaultAvatar($event)" |
63 | alt="Avatar" | 63 | alt="Avatar" |
64 | > | 64 | > |
65 | <div> | 65 | <div> |
66 | {{ videoAbuse.reporterAccount.displayName }} | 66 | {{ abuse.reporterAccount.displayName }} |
67 | <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span> | 67 | <span class="text-muted">{{ abuse.reporterAccount.nameWithHost }}</span> |
68 | </div> | 68 | </div> |
69 | </div> | 69 | </div> |
70 | </a> | 70 | </a> |
71 | </td> | 71 | </td> |
72 | 72 | ||
73 | <td *ngIf="!videoAbuse.video.deleted"> | 73 | <td *ngIf="!abuse.video.deleted"> |
74 | <a [href]="getVideoUrl(videoAbuse)" class="video-table-video-link" [title]="videoAbuse.video.name" target="_blank" rel="noopener noreferrer"> | 74 | <a [href]="getVideoUrl(abuse)" class="video-table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer"> |
75 | <div class="video-table-video"> | 75 | <div class="video-table-video"> |
76 | <div class="video-table-video-image"> | 76 | <div class="video-table-video-image"> |
77 | <img [src]="videoAbuse.video.thumbnailPath"> | 77 | <img [src]="abuse.video.thumbnailPath"> |
78 | <span | 78 | <span |
79 | class="video-table-video-image-label" *ngIf="videoAbuse.count > 1" | 79 | class="video-table-video-image-label" *ngIf="abuse.count > 1" |
80 | i18n-title title="This video has been reported multiple times." | 80 | i18n-title title="This video has been reported multiple times." |
81 | > | 81 | > |
82 | {{ videoAbuse.nth }}/{{ videoAbuse.count }} | 82 | {{ abuse.nth }}/{{ abuse.count }} |
83 | </span> | 83 | </span> |
84 | </div> | 84 | </div> |
85 | <div class="video-table-video-text"> | 85 | <div class="video-table-video-text"> |
86 | <div> | 86 | <div> |
87 | <span *ngIf="!videoAbuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span> | 87 | <span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span> |
88 | <span *ngIf="videoAbuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span> | 88 | <span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span> |
89 | {{ videoAbuse.video.name }} | 89 | {{ abuse.video.name }} |
90 | </div> | 90 | </div> |
91 | <div class="text-muted" i18n>by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }} </div> | 91 | <div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> |
92 | </div> | 92 | </div> |
93 | </div> | 93 | </div> |
94 | </a> | 94 | </a> |
95 | </td> | 95 | </td> |
96 | 96 | ||
97 | <td *ngIf="videoAbuse.video.deleted" class="c-hand" [pRowToggler]="videoAbuse"> | 97 | <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse"> |
98 | <div class="video-table-video" i18n-title title="Video was deleted"> | 98 | <div class="video-table-video" i18n-title title="Video was deleted"> |
99 | <div class="video-table-video-image"> | 99 | <div class="video-table-video-image"> |
100 | <span i18n>Deleted</span> | 100 | <span i18n>Deleted</span> |
101 | </div> | 101 | </div> |
102 | <div class="video-table-video-text"> | 102 | <div class="video-table-video-text"> |
103 | <div> | 103 | <div> |
104 | {{ videoAbuse.video.name }} | 104 | {{ abuse.video.name }} |
105 | <span class="glyphicon glyphicon-trash"></span> | 105 | <span class="glyphicon glyphicon-trash"></span> |
106 | </div> | 106 | </div> |
107 | <div class="text-muted" i18n>by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }} </div> | 107 | <div class="text-muted" i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div> |
108 | </div> | 108 | </div> |
109 | </div> | 109 | </div> |
110 | </td> | 110 | </td> |
111 | 111 | ||
112 | <td class="c-hand" [pRowToggler]="videoAbuse">{{ videoAbuse.createdAt | date: 'short' }}</td> | 112 | <td class="c-hand" [pRowToggler]="abuse">{{ abuse.createdAt | date: 'short' }}</td> |
113 | 113 | ||
114 | <td class="c-hand video-abuse-states" [pRowToggler]="videoAbuse"> | 114 | <td class="c-hand video-abuse-states" [pRowToggler]="abuse"> |
115 | <span *ngIf="isVideoAbuseAccepted(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-ok"></span> | 115 | <span *ngIf="isAbuseAccepted(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-ok"></span> |
116 | <span *ngIf="isVideoAbuseRejected(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-remove"></span> | 116 | <span *ngIf="isAbuseRejected(abuse)" [title]="abuse.state.label" class="glyphicon glyphicon-remove"></span> |
117 | <span *ngIf="videoAbuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="videoAbuse.moderationComment" class="glyphicon glyphicon-comment"></span> | 117 | <span *ngIf="abuse.moderationComment" container="body" placement="left auto" [ngbTooltip]="abuse.moderationComment" class="glyphicon glyphicon-comment"></span> |
118 | </td> | 118 | </td> |
119 | 119 | ||
120 | <td class="action-cell"> | 120 | <td class="action-cell"> |
121 | <my-action-dropdown | 121 | <my-action-dropdown |
122 | [ngClass]="{ 'show': expanded }" placement="bottom-right top-right left auto" container="body" | 122 | [ngClass]="{ 'show': expanded }" placement="bottom-right top-right left auto" container="body" |
123 | i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse" | 123 | i18n-label label="Actions" [actions]="abuseActions" [entry]="abuse" |
124 | ></my-action-dropdown> | 124 | ></my-action-dropdown> |
125 | </td> | 125 | </td> |
126 | </tr> | 126 | </tr> |
127 | </ng-template> | 127 | </ng-template> |
128 | 128 | ||
129 | <ng-template pTemplate="rowexpansion" let-videoAbuse> | 129 | <ng-template pTemplate="rowexpansion" let-abuse> |
130 | <tr> | 130 | <tr> |
131 | <td class="expand-cell" colspan="6"> | 131 | <td class="expand-cell" colspan="6"> |
132 | <my-video-abuse-details [videoAbuse]="videoAbuse"></my-video-abuse-details> | 132 | <my-abuse-details [abuse]="abuse"></my-abuse-details> |
133 | </td> | 133 | </td> |
134 | </tr> | 134 | </tr> |
135 | </ng-template> | 135 | </ng-template> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss index 8eee15b64..8eee15b64 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss +++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.scss | |||
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts index 409dd42c7..427ec4d5d 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { filter } from 'rxjs/operators' | ||
3 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' | 2 | import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' |
4 | import { environment } from 'src/environments/environment' | 3 | import { environment } from 'src/environments/environment' |
5 | import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' | 4 | import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' |
@@ -7,43 +6,45 @@ import { DomSanitizer } from '@angular/platform-browser' | |||
7 | import { ActivatedRoute, Params, Router } from '@angular/router' | 6 | import { ActivatedRoute, Params, Router } from '@angular/router' |
8 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' | 7 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' |
9 | import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 8 | import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
10 | import { BlocklistService, VideoAbuseService, VideoBlockService } from '@app/shared/shared-moderation' | 9 | import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' |
11 | import { I18n } from '@ngx-translate/i18n-polyfill' | 10 | import { I18n } from '@ngx-translate/i18n-polyfill' |
12 | import { VideoAbuse, VideoAbuseState } from '@shared/models' | 11 | import { Abuse, AbuseState } from '@shared/models' |
13 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' | 12 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' |
14 | 13 | ||
15 | export type ProcessedVideoAbuse = VideoAbuse & { | 14 | export type ProcessedAbuse = Abuse & { |
16 | moderationCommentHtml?: string, | 15 | moderationCommentHtml?: string, |
17 | reasonHtml?: string | 16 | reasonHtml?: string |
18 | embedHtml?: string | 17 | embedHtml?: string |
19 | updatedAt?: Date | 18 | updatedAt?: Date |
19 | |||
20 | // override bare server-side definitions with rich client-side definitions | 20 | // override bare server-side definitions with rich client-side definitions |
21 | reporterAccount: Account | 21 | reporterAccount: Account |
22 | video: VideoAbuse['video'] & { | 22 | |
23 | channel: VideoAbuse['video']['channel'] & { | 23 | video: Abuse['video'] & { |
24 | channel: Abuse['video']['channel'] & { | ||
24 | ownerAccount: Account | 25 | ownerAccount: Account |
25 | } | 26 | } |
26 | } | 27 | } |
27 | } | 28 | } |
28 | 29 | ||
29 | @Component({ | 30 | @Component({ |
30 | selector: 'my-video-abuse-list', | 31 | selector: 'my-abuse-list', |
31 | templateUrl: './video-abuse-list.component.html', | 32 | templateUrl: './abuse-list.component.html', |
32 | styleUrls: [ '../moderation.component.scss', './video-abuse-list.component.scss' ] | 33 | styleUrls: [ '../moderation.component.scss', './abuse-list.component.scss' ] |
33 | }) | 34 | }) |
34 | export class VideoAbuseListComponent extends RestTable implements OnInit, AfterViewInit { | 35 | export class AbuseListComponent extends RestTable implements OnInit, AfterViewInit { |
35 | @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent | 36 | @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent |
36 | 37 | ||
37 | videoAbuses: ProcessedVideoAbuse[] = [] | 38 | abuses: ProcessedAbuse[] = [] |
38 | totalRecords = 0 | 39 | totalRecords = 0 |
39 | sort: SortMeta = { field: 'createdAt', order: 1 } | 40 | sort: SortMeta = { field: 'createdAt', order: 1 } |
40 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 41 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
41 | 42 | ||
42 | videoAbuseActions: DropdownAction<VideoAbuse>[][] = [] | 43 | abuseActions: DropdownAction<Abuse>[][] = [] |
43 | 44 | ||
44 | constructor ( | 45 | constructor ( |
45 | private notifier: Notifier, | 46 | private notifier: Notifier, |
46 | private videoAbuseService: VideoAbuseService, | 47 | private abuseService: AbuseService, |
47 | private blocklistService: BlocklistService, | 48 | private blocklistService: BlocklistService, |
48 | private videoService: VideoService, | 49 | private videoService: VideoService, |
49 | private videoBlocklistService: VideoBlockService, | 50 | private videoBlocklistService: VideoBlockService, |
@@ -56,7 +57,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
56 | ) { | 57 | ) { |
57 | super() | 58 | super() |
58 | 59 | ||
59 | this.videoAbuseActions = [ | 60 | this.abuseActions = [ |
60 | [ | 61 | [ |
61 | { | 62 | { |
62 | label: this.i18n('Internal actions'), | 63 | label: this.i18n('Internal actions'), |
@@ -64,45 +65,45 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
64 | }, | 65 | }, |
65 | { | 66 | { |
66 | label: this.i18n('Delete report'), | 67 | label: this.i18n('Delete report'), |
67 | handler: videoAbuse => this.removeVideoAbuse(videoAbuse) | 68 | handler: abuse => this.removeAbuse(abuse) |
68 | }, | 69 | }, |
69 | { | 70 | { |
70 | label: this.i18n('Add note'), | 71 | label: this.i18n('Add note'), |
71 | handler: videoAbuse => this.openModerationCommentModal(videoAbuse), | 72 | handler: abuse => this.openModerationCommentModal(abuse), |
72 | isDisplayed: videoAbuse => !videoAbuse.moderationComment | 73 | isDisplayed: abuse => !abuse.moderationComment |
73 | }, | 74 | }, |
74 | { | 75 | { |
75 | label: this.i18n('Update note'), | 76 | label: this.i18n('Update note'), |
76 | handler: videoAbuse => this.openModerationCommentModal(videoAbuse), | 77 | handler: abuse => this.openModerationCommentModal(abuse), |
77 | isDisplayed: videoAbuse => !!videoAbuse.moderationComment | 78 | isDisplayed: abuse => !!abuse.moderationComment |
78 | }, | 79 | }, |
79 | { | 80 | { |
80 | label: this.i18n('Mark as accepted'), | 81 | label: this.i18n('Mark as accepted'), |
81 | handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED), | 82 | handler: abuse => this.updateAbuseState(abuse, AbuseState.ACCEPTED), |
82 | isDisplayed: videoAbuse => !this.isVideoAbuseAccepted(videoAbuse) | 83 | isDisplayed: abuse => !this.isAbuseAccepted(abuse) |
83 | }, | 84 | }, |
84 | { | 85 | { |
85 | label: this.i18n('Mark as rejected'), | 86 | label: this.i18n('Mark as rejected'), |
86 | handler: videoAbuse => this.updateVideoAbuseState(videoAbuse, VideoAbuseState.REJECTED), | 87 | handler: abuse => this.updateAbuseState(abuse, AbuseState.REJECTED), |
87 | isDisplayed: videoAbuse => !this.isVideoAbuseRejected(videoAbuse) | 88 | isDisplayed: abuse => !this.isAbuseRejected(abuse) |
88 | } | 89 | } |
89 | ], | 90 | ], |
90 | [ | 91 | [ |
91 | { | 92 | { |
92 | label: this.i18n('Actions for the video'), | 93 | label: this.i18n('Actions for the video'), |
93 | isHeader: true, | 94 | isHeader: true, |
94 | isDisplayed: videoAbuse => !videoAbuse.video.deleted | 95 | isDisplayed: abuse => !abuse.video.deleted |
95 | }, | 96 | }, |
96 | { | 97 | { |
97 | label: this.i18n('Block video'), | 98 | label: this.i18n('Block video'), |
98 | isDisplayed: videoAbuse => !videoAbuse.video.deleted && !videoAbuse.video.blacklisted, | 99 | isDisplayed: abuse => !abuse.video.deleted && !abuse.video.blacklisted, |
99 | handler: videoAbuse => { | 100 | handler: abuse => { |
100 | this.videoBlocklistService.blockVideo(videoAbuse.video.id, undefined, true) | 101 | this.videoBlocklistService.blockVideo(abuse.video.id, undefined, true) |
101 | .subscribe( | 102 | .subscribe( |
102 | () => { | 103 | () => { |
103 | this.notifier.success(this.i18n('Video blocked.')) | 104 | this.notifier.success(this.i18n('Video blocked.')) |
104 | 105 | ||
105 | this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) | 106 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) |
106 | }, | 107 | }, |
107 | 108 | ||
108 | err => this.notifier.error(err.message) | 109 | err => this.notifier.error(err.message) |
@@ -111,14 +112,14 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
111 | }, | 112 | }, |
112 | { | 113 | { |
113 | label: this.i18n('Unblock video'), | 114 | label: this.i18n('Unblock video'), |
114 | isDisplayed: videoAbuse => !videoAbuse.video.deleted && videoAbuse.video.blacklisted, | 115 | isDisplayed: abuse => !abuse.video.deleted && abuse.video.blacklisted, |
115 | handler: videoAbuse => { | 116 | handler: abuse => { |
116 | this.videoBlocklistService.unblockVideo(videoAbuse.video.id) | 117 | this.videoBlocklistService.unblockVideo(abuse.video.id) |
117 | .subscribe( | 118 | .subscribe( |
118 | () => { | 119 | () => { |
119 | this.notifier.success(this.i18n('Video unblocked.')) | 120 | this.notifier.success(this.i18n('Video unblocked.')) |
120 | 121 | ||
121 | this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) | 122 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) |
122 | }, | 123 | }, |
123 | 124 | ||
124 | err => this.notifier.error(err.message) | 125 | err => this.notifier.error(err.message) |
@@ -127,20 +128,20 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
127 | }, | 128 | }, |
128 | { | 129 | { |
129 | label: this.i18n('Delete video'), | 130 | label: this.i18n('Delete video'), |
130 | isDisplayed: videoAbuse => !videoAbuse.video.deleted, | 131 | isDisplayed: abuse => !abuse.video.deleted, |
131 | handler: async videoAbuse => { | 132 | handler: async abuse => { |
132 | const res = await this.confirmService.confirm( | 133 | const res = await this.confirmService.confirm( |
133 | this.i18n('Do you really want to delete this video?'), | 134 | this.i18n('Do you really want to delete this video?'), |
134 | this.i18n('Delete') | 135 | this.i18n('Delete') |
135 | ) | 136 | ) |
136 | if (res === false) return | 137 | if (res === false) return |
137 | 138 | ||
138 | this.videoService.removeVideo(videoAbuse.video.id) | 139 | this.videoService.removeVideo(abuse.video.id) |
139 | .subscribe( | 140 | .subscribe( |
140 | () => { | 141 | () => { |
141 | this.notifier.success(this.i18n('Video deleted.')) | 142 | this.notifier.success(this.i18n('Video deleted.')) |
142 | 143 | ||
143 | this.updateVideoAbuseState(videoAbuse, VideoAbuseState.ACCEPTED) | 144 | this.updateAbuseState(abuse, AbuseState.ACCEPTED) |
144 | }, | 145 | }, |
145 | 146 | ||
146 | err => this.notifier.error(err.message) | 147 | err => this.notifier.error(err.message) |
@@ -155,8 +156,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
155 | }, | 156 | }, |
156 | { | 157 | { |
157 | label: this.i18n('Mute reporter'), | 158 | label: this.i18n('Mute reporter'), |
158 | handler: async videoAbuse => { | 159 | handler: async abuse => { |
159 | const account = videoAbuse.reporterAccount as Account | 160 | const account = abuse.reporterAccount as Account |
160 | 161 | ||
161 | this.blocklistService.blockAccountByInstance(account) | 162 | this.blocklistService.blockAccountByInstance(account) |
162 | .subscribe( | 163 | .subscribe( |
@@ -174,13 +175,13 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
174 | }, | 175 | }, |
175 | { | 176 | { |
176 | label: this.i18n('Mute server'), | 177 | label: this.i18n('Mute server'), |
177 | isDisplayed: videoAbuse => !videoAbuse.reporterAccount.userId, | 178 | isDisplayed: abuse => !abuse.reporterAccount.userId, |
178 | handler: async videoAbuse => { | 179 | handler: async abuse => { |
179 | this.blocklistService.blockServerByInstance(videoAbuse.reporterAccount.host) | 180 | this.blocklistService.blockServerByInstance(abuse.reporterAccount.host) |
180 | .subscribe( | 181 | .subscribe( |
181 | () => { | 182 | () => { |
182 | this.notifier.success( | 183 | this.notifier.success( |
183 | this.i18n('Server {{host}} muted by the instance.', { host: videoAbuse.reporterAccount.host }) | 184 | this.i18n('Server {{host}} muted by the instance.', { host: abuse.reporterAccount.host }) |
184 | ) | 185 | ) |
185 | }, | 186 | }, |
186 | 187 | ||
@@ -209,11 +210,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
209 | } | 210 | } |
210 | 211 | ||
211 | getIdentifier () { | 212 | getIdentifier () { |
212 | return 'VideoAbuseListComponent' | 213 | return 'AbuseListComponent' |
213 | } | 214 | } |
214 | 215 | ||
215 | openModerationCommentModal (videoAbuse: VideoAbuse) { | 216 | openModerationCommentModal (abuse: Abuse) { |
216 | this.moderationCommentModal.openModal(videoAbuse) | 217 | this.moderationCommentModal.openModal(abuse) |
217 | } | 218 | } |
218 | 219 | ||
219 | onModerationCommentUpdated () { | 220 | onModerationCommentUpdated () { |
@@ -240,26 +241,26 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
240 | } | 241 | } |
241 | /* END Table filter functions */ | 242 | /* END Table filter functions */ |
242 | 243 | ||
243 | isVideoAbuseAccepted (videoAbuse: VideoAbuse) { | 244 | isAbuseAccepted (abuse: Abuse) { |
244 | return videoAbuse.state.id === VideoAbuseState.ACCEPTED | 245 | return abuse.state.id === AbuseState.ACCEPTED |
245 | } | 246 | } |
246 | 247 | ||
247 | isVideoAbuseRejected (videoAbuse: VideoAbuse) { | 248 | isAbuseRejected (abuse: Abuse) { |
248 | return videoAbuse.state.id === VideoAbuseState.REJECTED | 249 | return abuse.state.id === AbuseState.REJECTED |
249 | } | 250 | } |
250 | 251 | ||
251 | getVideoUrl (videoAbuse: VideoAbuse) { | 252 | getVideoUrl (abuse: Abuse) { |
252 | return Video.buildClientUrl(videoAbuse.video.uuid) | 253 | return Video.buildClientUrl(abuse.video.uuid) |
253 | } | 254 | } |
254 | 255 | ||
255 | getVideoEmbed (videoAbuse: VideoAbuse) { | 256 | getVideoEmbed (abuse: Abuse) { |
256 | return buildVideoEmbed( | 257 | return buildVideoEmbed( |
257 | buildVideoLink({ | 258 | buildVideoLink({ |
258 | baseUrl: `${environment.embedUrl}/videos/embed/${videoAbuse.video.uuid}`, | 259 | baseUrl: `${environment.embedUrl}/videos/embed/${abuse.video.uuid}`, |
259 | title: false, | 260 | title: false, |
260 | warningTitle: false, | 261 | warningTitle: false, |
261 | startTime: videoAbuse.startAt, | 262 | startTime: abuse.startAt, |
262 | stopTime: videoAbuse.endAt | 263 | stopTime: abuse.endAt |
263 | }) | 264 | }) |
264 | ) | 265 | ) |
265 | } | 266 | } |
@@ -268,11 +269,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
268 | ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() | 269 | ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() |
269 | } | 270 | } |
270 | 271 | ||
271 | async removeVideoAbuse (videoAbuse: VideoAbuse) { | 272 | async removeAbuse (abuse: Abuse) { |
272 | const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete')) | 273 | const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this abuse report?'), this.i18n('Delete')) |
273 | if (res === false) return | 274 | if (res === false) return |
274 | 275 | ||
275 | this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe( | 276 | this.abuseService.removeAbuse(abuse).subscribe( |
276 | () => { | 277 | () => { |
277 | this.notifier.success(this.i18n('Abuse deleted.')) | 278 | this.notifier.success(this.i18n('Abuse deleted.')) |
278 | this.loadData() | 279 | this.loadData() |
@@ -282,8 +283,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
282 | ) | 283 | ) |
283 | } | 284 | } |
284 | 285 | ||
285 | updateVideoAbuseState (videoAbuse: VideoAbuse, state: VideoAbuseState) { | 286 | updateAbuseState (abuse: Abuse, state: AbuseState) { |
286 | this.videoAbuseService.updateVideoAbuse(videoAbuse, { state }) | 287 | this.abuseService.updateAbuse(abuse, { state }) |
287 | .subscribe( | 288 | .subscribe( |
288 | () => this.loadData(), | 289 | () => this.loadData(), |
289 | 290 | ||
@@ -292,14 +293,14 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
292 | } | 293 | } |
293 | 294 | ||
294 | protected loadData () { | 295 | protected loadData () { |
295 | return this.videoAbuseService.getVideoAbuses({ | 296 | return this.abuseService.getAbuses({ |
296 | pagination: this.pagination, | 297 | pagination: this.pagination, |
297 | sort: this.sort, | 298 | sort: this.sort, |
298 | search: this.search | 299 | search: this.search |
299 | }).subscribe( | 300 | }).subscribe( |
300 | async resultList => { | 301 | async resultList => { |
301 | this.totalRecords = resultList.total | 302 | this.totalRecords = resultList.total |
302 | const videoAbuses = [] | 303 | const abuses = [] |
303 | 304 | ||
304 | for (const abuse of resultList.data) { | 305 | for (const abuse of resultList.data) { |
305 | Object.assign(abuse, { | 306 | Object.assign(abuse, { |
@@ -312,10 +313,10 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
312 | if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) | 313 | if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) |
313 | if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt | 314 | if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt |
314 | 315 | ||
315 | videoAbuses.push(abuse as ProcessedVideoAbuse) | 316 | abuses.push(abuse as ProcessedAbuse) |
316 | } | 317 | } |
317 | 318 | ||
318 | this.videoAbuses = videoAbuses | 319 | this.abuses = abuses |
319 | }, | 320 | }, |
320 | 321 | ||
321 | err => this.notifier.error(err.message) | 322 | err => this.notifier.error(err.message) |
diff --git a/client/src/app/+admin/moderation/abuse-list/index.ts b/client/src/app/+admin/moderation/abuse-list/index.ts new file mode 100644 index 000000000..c6037dab4 --- /dev/null +++ b/client/src/app/+admin/moderation/abuse-list/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './abuse-details.component' | ||
2 | export * from './abuse-list.component' | ||
3 | export * from './moderation-comment-modal.component' | ||
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.html index 8082e93f4..8082e93f4 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html +++ b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.html | |||
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.scss b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.scss index afcdb9a16..afcdb9a16 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.scss +++ b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.scss | |||
diff --git a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.ts index 3cd763ca4..23738f9cd 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts +++ b/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, FormValidatorService, VideoAbuseValidatorsService } from '@app/shared/shared-forms' | 3 | import { FormReactive, FormValidatorService, AbuseValidatorsService } from '@app/shared/shared-forms' |
4 | import { VideoAbuseService } from '@app/shared/shared-moderation' | 4 | import { AbuseService } from '@app/shared/shared-moderation' |
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { VideoAbuse } from '@shared/models' | 8 | import { Abuse } from '@shared/models' |
9 | 9 | ||
10 | @Component({ | 10 | @Component({ |
11 | selector: 'my-moderation-comment-modal', | 11 | selector: 'my-moderation-comment-modal', |
@@ -16,15 +16,15 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
16 | @ViewChild('modal', { static: true }) modal: NgbModal | 16 | @ViewChild('modal', { static: true }) modal: NgbModal |
17 | @Output() commentUpdated = new EventEmitter<string>() | 17 | @Output() commentUpdated = new EventEmitter<string>() |
18 | 18 | ||
19 | private abuseToComment: VideoAbuse | 19 | private abuseToComment: Abuse |
20 | private openedModal: NgbModalRef | 20 | private openedModal: NgbModalRef |
21 | 21 | ||
22 | constructor ( | 22 | constructor ( |
23 | protected formValidatorService: FormValidatorService, | 23 | protected formValidatorService: FormValidatorService, |
24 | private modalService: NgbModal, | 24 | private modalService: NgbModal, |
25 | private notifier: Notifier, | 25 | private notifier: Notifier, |
26 | private videoAbuseService: VideoAbuseService, | 26 | private abuseService: AbuseService, |
27 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, | 27 | private abuseValidatorsService: AbuseValidatorsService, |
28 | private i18n: I18n | 28 | private i18n: I18n |
29 | ) { | 29 | ) { |
30 | super() | 30 | super() |
@@ -32,11 +32,11 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
32 | 32 | ||
33 | ngOnInit () { | 33 | ngOnInit () { |
34 | this.buildForm({ | 34 | this.buildForm({ |
35 | moderationComment: this.videoAbuseValidatorsService.VIDEO_ABUSE_MODERATION_COMMENT | 35 | moderationComment: this.abuseValidatorsService.ABUSE_MODERATION_COMMENT |
36 | }) | 36 | }) |
37 | } | 37 | } |
38 | 38 | ||
39 | openModal (abuseToComment: VideoAbuse) { | 39 | openModal (abuseToComment: Abuse) { |
40 | this.abuseToComment = abuseToComment | 40 | this.abuseToComment = abuseToComment |
41 | this.openedModal = this.modalService.open(this.modal, { centered: true }) | 41 | this.openedModal = this.modalService.open(this.modal, { centered: true }) |
42 | 42 | ||
@@ -54,7 +54,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI | |||
54 | async banUser () { | 54 | async banUser () { |
55 | const moderationComment: string = this.form.value[ 'moderationComment' ] | 55 | const moderationComment: string = this.form.value[ 'moderationComment' ] |
56 | 56 | ||
57 | this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment }) | 57 | this.abuseService.updateAbuse(this.abuseToComment, { moderationComment }) |
58 | .subscribe( | 58 | .subscribe( |
59 | () => { | 59 | () => { |
60 | this.notifier.success(this.i18n('Comment updated.')) | 60 | this.notifier.success(this.i18n('Comment updated.')) |
diff --git a/client/src/app/+admin/moderation/index.ts b/client/src/app/+admin/moderation/index.ts index 16249236c..53e4bc991 100644 --- a/client/src/app/+admin/moderation/index.ts +++ b/client/src/app/+admin/moderation/index.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export * from './abuse-list' | ||
1 | export * from './instance-blocklist' | 2 | export * from './instance-blocklist' |
2 | export * from './video-abuse-list' | ||
3 | export * from './video-block-list' | 3 | export * from './video-block-list' |
4 | export * from './moderation.component' | 4 | export * from './moderation.component' |
5 | export * from './moderation.routes' | 5 | export * from './moderation.routes' |
diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts index cd837bcb9..1e207e5e8 100644 --- a/client/src/app/+admin/moderation/moderation.routes.ts +++ b/client/src/app/+admin/moderation/moderation.routes.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Routes } from '@angular/router' | 1 | import { Routes } from '@angular/router' |
2 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' | 2 | import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' |
3 | import { ModerationComponent } from '@app/+admin/moderation/moderation.component' | 3 | import { ModerationComponent } from '@app/+admin/moderation/moderation.component' |
4 | import { VideoAbuseListComponent } from '@app/+admin/moderation/video-abuse-list' | 4 | import { AbuseListComponent } from '@app/+admin/moderation/abuse-list' |
5 | import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list' | 5 | import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list' |
6 | import { UserRightGuard } from '@app/core' | 6 | import { UserRightGuard } from '@app/core' |
7 | import { UserRight } from '@shared/models' | 7 | import { UserRight } from '@shared/models' |
@@ -13,20 +13,25 @@ export const ModerationRoutes: Routes = [ | |||
13 | children: [ | 13 | children: [ |
14 | { | 14 | { |
15 | path: '', | 15 | path: '', |
16 | redirectTo: 'video-abuses/list', | 16 | redirectTo: 'abuses/list', |
17 | pathMatch: 'full' | 17 | pathMatch: 'full' |
18 | }, | 18 | }, |
19 | { | 19 | { |
20 | path: 'video-abuses', | 20 | path: 'video-abuses', |
21 | redirectTo: 'video-abuses/list', | 21 | redirectTo: 'abuses/list', |
22 | pathMatch: 'full' | 22 | pathMatch: 'full' |
23 | }, | 23 | }, |
24 | { | 24 | { |
25 | path: 'video-abuses/list', | 25 | path: 'video-abuses/list', |
26 | component: VideoAbuseListComponent, | 26 | redirectTo: 'abuses/list', |
27 | pathMatch: 'full' | ||
28 | }, | ||
29 | { | ||
30 | path: 'abuses/list', | ||
31 | component: AbuseListComponent, | ||
27 | canActivate: [ UserRightGuard ], | 32 | canActivate: [ UserRightGuard ], |
28 | data: { | 33 | data: { |
29 | userRight: UserRight.MANAGE_VIDEO_ABUSES, | 34 | userRight: UserRight.MANAGE_ABUSES, |
30 | meta: { | 35 | meta: { |
31 | title: 'Video reports' | 36 | title: 'Video reports' |
32 | } | 37 | } |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/index.ts b/client/src/app/+admin/moderation/video-abuse-list/index.ts deleted file mode 100644 index da7176e52..000000000 --- a/client/src/app/+admin/moderation/video-abuse-list/index.ts +++ /dev/null | |||
@@ -1,2 +0,0 @@ | |||
1 | export * from './video-abuse-list.component' | ||
2 | export * from './moderation-comment-modal.component' | ||
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html deleted file mode 100644 index ec808cdb8..000000000 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html +++ /dev/null | |||
@@ -1,93 +0,0 @@ | |||
1 | <div class="d-flex moderation-expanded"> | ||
2 | <!-- report left part (report details) --> | ||
3 | <div class="col-8"> | ||
4 | |||
5 | <!-- report metadata --> | ||
6 | <div class="d-flex"> | ||
7 | <span class="col-3 moderation-expanded-label" i18n>Reporter</span> | ||
8 | <span class="col-9 moderation-expanded-text"> | ||
9 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + videoAbuse.reporterAccount.displayName + '"' }" class="chip"> | ||
10 | <img | ||
11 | class="avatar" | ||
12 | [src]="videoAbuse.reporterAccount.avatar?.path" | ||
13 | (error)="switchToDefaultAvatar($event)" | ||
14 | alt="Avatar" | ||
15 | > | ||
16 | <div> | ||
17 | <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span> | ||
18 | </div> | ||
19 | </a> | ||
20 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reporter:"' + videoAbuse.reporterAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | ||
21 | {videoAbuse.countReportsForReporter, plural, =1 {1 report} other {{{ videoAbuse.countReportsForReporter }} reports}}<span class="ml-1Â glyphicon glyphicon-flag"></span> | ||
22 | </a> | ||
23 | </span> | ||
24 | </div> | ||
25 | |||
26 | <div class="d-flex"> | ||
27 | <span class="col-3 moderation-expanded-label" i18n>Reportee</span> | ||
28 | <span class="col-9 moderation-expanded-text"> | ||
29 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +videoAbuse.video.channel.ownerAccount.displayName + '"' }" class="chip"> | ||
30 | <img | ||
31 | class="avatar" | ||
32 | [src]="videoAbuse.video.channel.ownerAccount?.avatar?.path" | ||
33 | (error)="switchToDefaultAvatar($event)" | ||
34 | alt="Avatar" | ||
35 | > | ||
36 | <div> | ||
37 | <span class="text-muted">{{ videoAbuse.video.channel.ownerAccount ? videoAbuse.video.channel.ownerAccount.nameWithHost : '' }}</span> | ||
38 | </div> | ||
39 | </a> | ||
40 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +videoAbuse.video.channel.ownerAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | ||
41 | {videoAbuse.countReportsForReportee, plural, =1 {1 report} other {{{ videoAbuse.countReportsForReportee }} reports}}<span class="ml-1Â glyphicon glyphicon-flag"></span> | ||
42 | </a> | ||
43 | </span> | ||
44 | </div> | ||
45 | |||
46 | <div class="d-flex" *ngIf="videoAbuse.updatedAt"> | ||
47 | <span class="col-3 moderation-expanded-label" i18n>Updated</span> | ||
48 | <time class="col-9 moderation-expanded-text video-details-date-updated">{{ videoAbuse.updatedAt | date: 'medium' }}</time> | ||
49 | </div> | ||
50 | |||
51 | <!-- report text --> | ||
52 | <div class="mt-3 d-flex"> | ||
53 | <span class="col-3 moderation-expanded-label"> | ||
54 | <ng-container i18n>Report</ng-container> | ||
55 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': '#' + videoAbuse.id }" class="ml-1 text-muted">#{{ videoAbuse.id }}</a> | ||
56 | </span> | ||
57 | <span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.reasonHtml"></span> | ||
58 | </div> | ||
59 | |||
60 | <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex"> | ||
61 | <span class="col-3"></span> | ||
62 | <span class="col-9"> | ||
63 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" *ngFor="let reason of getPredefinedReasons()"> | ||
64 | <div>{{ reason.label }}</div> | ||
65 | </a> | ||
66 | </span> | ||
67 | </div> | ||
68 | |||
69 | <div *ngIf="videoAbuse.startAt" class="mt-2 d-flex"> | ||
70 | <span class="col-3 moderation-expanded-label" i18n>Reported part</span> | ||
71 | <span class="col-9"> | ||
72 | {{ startAt }}<ng-container *ngIf="videoAbuse.endAt"> - {{ endAt }}</ng-container> | ||
73 | </span> | ||
74 | </div> | ||
75 | |||
76 | <div class="mt-3 d-flex" *ngIf="videoAbuse.moderationComment"> | ||
77 | <span class="col-3 moderation-expanded-label" i18n>Note</span> | ||
78 | <span class="col-9 moderation-expanded-text" [innerHTML]="videoAbuse.moderationCommentHtml"></span> | ||
79 | </div> | ||
80 | |||
81 | </div> | ||
82 | |||
83 | <!-- report right part (video details) --> | ||
84 | <div class="col-4"> | ||
85 | <div class="screenratio"> | ||
86 | <div *ngIf="videoAbuse.video.deleted || videoAbuse.video.blacklisted"> | ||
87 | <span i18n *ngIf="videoAbuse.video.deleted">The video was deleted</span> | ||
88 | <span i18n *ngIf="!videoAbuse.video.deleted">The video was blocked</span> | ||
89 | </div> | ||
90 | <div *ngIf="!videoAbuse.video.deleted && !videoAbuse.video.blacklisted" [innerHTML]="videoAbuse.embedHtml"></div> | ||
91 | </div> | ||
92 | </div> | ||
93 | </div> | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts index cfa514b26..adc18b587 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts | |||
@@ -47,7 +47,7 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
47 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] | 47 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] |
48 | 48 | ||
49 | this.rightNotifications = { | 49 | this.rightNotifications = { |
50 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, | 50 | videoAbuseAsModerator: UserRight.MANAGE_ABUSES, |
51 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, | 51 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, |
52 | newUserRegistration: UserRight.MANAGE_USERS, | 52 | newUserRegistration: UserRight.MANAGE_USERS, |
53 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, | 53 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, |
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index 2dbe695c9..0ea251f1c 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts | |||
@@ -28,7 +28,7 @@ export class MenuComponent implements OnInit { | |||
28 | private routesPerRight: { [ role in UserRight ]?: string } = { | 28 | private routesPerRight: { [ role in UserRight ]?: string } = { |
29 | [UserRight.MANAGE_USERS]: '/admin/users', | 29 | [UserRight.MANAGE_USERS]: '/admin/users', |
30 | [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', | 30 | [UserRight.MANAGE_SERVER_FOLLOW]: '/admin/friends', |
31 | [UserRight.MANAGE_VIDEO_ABUSES]: '/admin/moderation/video-abuses', | 31 | [UserRight.MANAGE_ABUSES]: '/admin/moderation/abuses', |
32 | [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/moderation/video-blocks', | 32 | [UserRight.MANAGE_VIDEO_BLACKLIST]: '/admin/moderation/video-blocks', |
33 | [UserRight.MANAGE_JOBS]: '/admin/jobs', | 33 | [UserRight.MANAGE_JOBS]: '/admin/jobs', |
34 | [UserRight.MANAGE_CONFIGURATION]: '/admin/config' | 34 | [UserRight.MANAGE_CONFIGURATION]: '/admin/config' |
@@ -126,7 +126,7 @@ export class MenuComponent implements OnInit { | |||
126 | const adminRights = [ | 126 | const adminRights = [ |
127 | UserRight.MANAGE_USERS, | 127 | UserRight.MANAGE_USERS, |
128 | UserRight.MANAGE_SERVER_FOLLOW, | 128 | UserRight.MANAGE_SERVER_FOLLOW, |
129 | UserRight.MANAGE_VIDEO_ABUSES, | 129 | UserRight.MANAGE_ABUSES, |
130 | UserRight.MANAGE_VIDEO_BLACKLIST, | 130 | UserRight.MANAGE_VIDEO_BLACKLIST, |
131 | UserRight.MANAGE_JOBS, | 131 | UserRight.MANAGE_JOBS, |
132 | UserRight.MANAGE_CONFIGURATION | 132 | UserRight.MANAGE_CONFIGURATION |
diff --git a/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts b/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts index aae56d607..739115e19 100644 --- a/client/src/app/shared/shared-forms/form-validators/video-abuse-validators.service.ts +++ b/client/src/app/shared/shared-forms/form-validators/abuse-validators.service.ts | |||
@@ -4,12 +4,12 @@ import { Injectable } from '@angular/core' | |||
4 | import { BuildFormValidator } from './form-validator.service' | 4 | import { BuildFormValidator } from './form-validator.service' |
5 | 5 | ||
6 | @Injectable() | 6 | @Injectable() |
7 | export class VideoAbuseValidatorsService { | 7 | export class AbuseValidatorsService { |
8 | readonly VIDEO_ABUSE_REASON: BuildFormValidator | 8 | readonly ABUSE_REASON: BuildFormValidator |
9 | readonly VIDEO_ABUSE_MODERATION_COMMENT: BuildFormValidator | 9 | readonly ABUSE_MODERATION_COMMENT: BuildFormValidator |
10 | 10 | ||
11 | constructor (private i18n: I18n) { | 11 | constructor (private i18n: I18n) { |
12 | this.VIDEO_ABUSE_REASON = { | 12 | this.ABUSE_REASON = { |
13 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], | 13 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], |
14 | MESSAGES: { | 14 | MESSAGES: { |
15 | 'required': this.i18n('Report reason is required.'), | 15 | 'required': this.i18n('Report reason is required.'), |
@@ -18,7 +18,7 @@ export class VideoAbuseValidatorsService { | |||
18 | } | 18 | } |
19 | } | 19 | } |
20 | 20 | ||
21 | this.VIDEO_ABUSE_MODERATION_COMMENT = { | 21 | this.ABUSE_MODERATION_COMMENT = { |
22 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], | 22 | VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ], |
23 | MESSAGES: { | 23 | MESSAGES: { |
24 | 'required': this.i18n('Moderation comment is required.'), | 24 | 'required': this.i18n('Moderation comment is required.'), |
diff --git a/client/src/app/shared/shared-forms/form-validators/index.ts b/client/src/app/shared/shared-forms/form-validators/index.ts index 8b71841a9..b06a326ff 100644 --- a/client/src/app/shared/shared-forms/form-validators/index.ts +++ b/client/src/app/shared/shared-forms/form-validators/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './abuse-validators.service' | ||
1 | export * from './batch-domains-validators.service' | 2 | export * from './batch-domains-validators.service' |
2 | export * from './custom-config-validators.service' | 3 | export * from './custom-config-validators.service' |
3 | export * from './form-validator.service' | 4 | export * from './form-validator.service' |
@@ -6,7 +7,6 @@ export * from './instance-validators.service' | |||
6 | export * from './login-validators.service' | 7 | export * from './login-validators.service' |
7 | export * from './reset-password-validators.service' | 8 | export * from './reset-password-validators.service' |
8 | export * from './user-validators.service' | 9 | export * from './user-validators.service' |
9 | export * from './video-abuse-validators.service' | ||
10 | export * from './video-accept-ownership-validators.service' | 10 | export * from './video-accept-ownership-validators.service' |
11 | export * from './video-block-validators.service' | 11 | export * from './video-block-validators.service' |
12 | export * from './video-captions-validators.service' | 12 | export * from './video-captions-validators.service' |
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts index e82fa97d4..ba33704cf 100644 --- a/client/src/app/shared/shared-forms/shared-form.module.ts +++ b/client/src/app/shared/shared-forms/shared-form.module.ts | |||
@@ -11,7 +11,7 @@ import { | |||
11 | LoginValidatorsService, | 11 | LoginValidatorsService, |
12 | ResetPasswordValidatorsService, | 12 | ResetPasswordValidatorsService, |
13 | UserValidatorsService, | 13 | UserValidatorsService, |
14 | VideoAbuseValidatorsService, | 14 | AbuseValidatorsService, |
15 | VideoAcceptOwnershipValidatorsService, | 15 | VideoAcceptOwnershipValidatorsService, |
16 | VideoBlockValidatorsService, | 16 | VideoBlockValidatorsService, |
17 | VideoCaptionsValidatorsService, | 17 | VideoCaptionsValidatorsService, |
@@ -69,7 +69,7 @@ import { TimestampInputComponent } from './timestamp-input.component' | |||
69 | LoginValidatorsService, | 69 | LoginValidatorsService, |
70 | ResetPasswordValidatorsService, | 70 | ResetPasswordValidatorsService, |
71 | UserValidatorsService, | 71 | UserValidatorsService, |
72 | VideoAbuseValidatorsService, | 72 | AbuseValidatorsService, |
73 | VideoAcceptOwnershipValidatorsService, | 73 | VideoAcceptOwnershipValidatorsService, |
74 | VideoBlockValidatorsService, | 74 | VideoBlockValidatorsService, |
75 | VideoCaptionsValidatorsService, | 75 | VideoCaptionsValidatorsService, |
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts index 5fc7989dd..0fa161ce6 100644 --- a/client/src/app/shared/shared-main/account/actor.model.ts +++ b/client/src/app/shared/shared-main/account/actor.model.ts | |||
@@ -14,7 +14,7 @@ export abstract class Actor implements ActorServer { | |||
14 | 14 | ||
15 | avatarUrl: string | 15 | avatarUrl: string |
16 | 16 | ||
17 | static GET_ACTOR_AVATAR_URL (actor: { avatar?: Avatar }) { | 17 | static GET_ACTOR_AVATAR_URL (actor: { avatar?: { url?: string, path: string } }) { |
18 | if (actor?.avatar?.url) return actor.avatar.url | 18 | if (actor?.avatar?.url) return actor.avatar.url |
19 | 19 | ||
20 | if (actor && actor.avatar) { | 20 | if (actor && actor.avatar) { |
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts index de25d3ab9..389a242fd 100644 --- a/client/src/app/shared/shared-main/users/user-notification.model.ts +++ b/client/src/app/shared/shared-main/users/user-notification.model.ts | |||
@@ -25,9 +25,20 @@ export class UserNotification implements UserNotificationServer { | |||
25 | video: VideoInfo | 25 | video: VideoInfo |
26 | } | 26 | } |
27 | 27 | ||
28 | videoAbuse?: { | 28 | abuse?: { |
29 | id: number | 29 | id: number |
30 | video: VideoInfo | 30 | |
31 | video?: VideoInfo | ||
32 | |||
33 | comment?: { | ||
34 | threadId: number | ||
35 | |||
36 | video: { | ||
37 | uuid: string | ||
38 | } | ||
39 | } | ||
40 | |||
41 | account?: ActorInfo | ||
31 | } | 42 | } |
32 | 43 | ||
33 | videoBlacklist?: { | 44 | videoBlacklist?: { |
@@ -55,7 +66,7 @@ export class UserNotification implements UserNotificationServer { | |||
55 | // Additional fields | 66 | // Additional fields |
56 | videoUrl?: string | 67 | videoUrl?: string |
57 | commentUrl?: any[] | 68 | commentUrl?: any[] |
58 | videoAbuseUrl?: string | 69 | abuseUrl?: string |
59 | videoAutoBlacklistUrl?: string | 70 | videoAutoBlacklistUrl?: string |
60 | accountUrl?: string | 71 | accountUrl?: string |
61 | videoImportIdentifier?: string | 72 | videoImportIdentifier?: string |
@@ -78,7 +89,7 @@ export class UserNotification implements UserNotificationServer { | |||
78 | this.comment = hash.comment | 89 | this.comment = hash.comment |
79 | if (this.comment) this.setAvatarUrl(this.comment.account) | 90 | if (this.comment) this.setAvatarUrl(this.comment.account) |
80 | 91 | ||
81 | this.videoAbuse = hash.videoAbuse | 92 | this.abuse = hash.abuse |
82 | 93 | ||
83 | this.videoBlacklist = hash.videoBlacklist | 94 | this.videoBlacklist = hash.videoBlacklist |
84 | 95 | ||
@@ -108,8 +119,9 @@ export class UserNotification implements UserNotificationServer { | |||
108 | break | 119 | break |
109 | 120 | ||
110 | case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS: | 121 | case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS: |
111 | this.videoAbuseUrl = '/admin/moderation/video-abuses/list' | 122 | this.abuseUrl = '/admin/moderation/abuses/list' |
112 | this.videoUrl = this.buildVideoUrl(this.videoAbuse.video) | 123 | |
124 | if (this.abuse.video) this.videoUrl = this.buildVideoUrl(this.abuse.video) | ||
113 | break | 125 | break |
114 | 126 | ||
115 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: | 127 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: |
@@ -178,7 +190,7 @@ export class UserNotification implements UserNotificationServer { | |||
178 | return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName | 190 | return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName |
179 | } | 191 | } |
180 | 192 | ||
181 | private setAvatarUrl (actor: { avatarUrl?: string, avatar?: Avatar }) { | 193 | private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { url?: string, path: string } }) { |
182 | actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) | 194 | actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) |
183 | } | 195 | } |
184 | } | 196 | } |
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html index d5be1470e..8d31eab0d 100644 --- a/client/src/app/shared/shared-main/users/user-notifications.component.html +++ b/client/src/app/shared/shared-main/users/user-notifications.component.html | |||
@@ -19,7 +19,7 @@ | |||
19 | 19 | ||
20 | <ng-template #noVideo> | 20 | <ng-template #noVideo> |
21 | <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> | 21 | <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> |
22 | 22 | ||
23 | <div class="message" i18n> | 23 | <div class="message" i18n> |
24 | The notification concerns a video now unavailable | 24 | The notification concerns a video now unavailable |
25 | </div> | 25 | </div> |
@@ -46,7 +46,7 @@ | |||
46 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> | 46 | <my-global-icon iconName="flag" aria-hidden="true"></my-global-icon> |
47 | 47 | ||
48 | <div class="message" i18n> | 48 | <div class="message" i18n> |
49 | <a (click)="markAsRead(notification)" [routerLink]="notification.videoAbuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoAbuse.video.name }}</a> | 49 | <a (click)="markAsRead(notification)" [routerLink]="notification.abuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.abuse.video.name }}</a> |
50 | </div> | 50 | </div> |
51 | </ng-container> | 51 | </ng-container> |
52 | 52 | ||
@@ -65,7 +65,7 @@ | |||
65 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> | 65 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl"> |
66 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> | 66 | <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" /> |
67 | </a> | 67 | </a> |
68 | 68 | ||
69 | <div class="message" i18n> | 69 | <div class="message" i18n> |
70 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a> | 70 | <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a> |
71 | </div> | 71 | </div> |
@@ -73,7 +73,7 @@ | |||
73 | 73 | ||
74 | <ng-template #noComment> | 74 | <ng-template #noComment> |
75 | <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> | 75 | <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon> |
76 | 76 | ||
77 | <div class="message" i18n> | 77 | <div class="message" i18n> |
78 | The notification concerns a comment now unavailable | 78 | The notification concerns a comment now unavailable |
79 | </div> | 79 | </div> |
diff --git a/client/src/app/shared/shared-moderation/video-abuse.service.ts b/client/src/app/shared/shared-moderation/abuse.service.ts index 44dea44a5..f45018d5c 100644 --- a/client/src/app/shared/shared-moderation/video-abuse.service.ts +++ b/client/src/app/shared/shared-moderation/abuse.service.ts | |||
@@ -5,12 +5,12 @@ import { catchError, map } from 'rxjs/operators' | |||
5 | import { HttpClient, HttpParams } from '@angular/common/http' | 5 | import { HttpClient, HttpParams } from '@angular/common/http' |
6 | import { Injectable } from '@angular/core' | 6 | import { Injectable } from '@angular/core' |
7 | import { RestExtractor, RestPagination, RestService } from '@app/core' | 7 | import { RestExtractor, RestPagination, RestService } from '@app/core' |
8 | import { ResultList, VideoAbuse, VideoAbuseCreate, VideoAbuseState, VideoAbuseUpdate } from '@shared/models' | 8 | import { AbuseUpdate, ResultList, Abuse, AbuseCreate, AbuseState } from '@shared/models' |
9 | import { environment } from '../../../environments/environment' | 9 | import { environment } from '../../../environments/environment' |
10 | 10 | ||
11 | @Injectable() | 11 | @Injectable() |
12 | export class VideoAbuseService { | 12 | export class AbuseService { |
13 | private static BASE_VIDEO_ABUSE_URL = environment.apiUrl + '/api/v1/videos/' | 13 | private static BASE_ABUSE_URL = environment.apiUrl + '/api/v1/abuses' |
14 | 14 | ||
15 | constructor ( | 15 | constructor ( |
16 | private authHttp: HttpClient, | 16 | private authHttp: HttpClient, |
@@ -18,13 +18,13 @@ export class VideoAbuseService { | |||
18 | private restExtractor: RestExtractor | 18 | private restExtractor: RestExtractor |
19 | ) {} | 19 | ) {} |
20 | 20 | ||
21 | getVideoAbuses (options: { | 21 | getAbuses (options: { |
22 | pagination: RestPagination, | 22 | pagination: RestPagination, |
23 | sort: SortMeta, | 23 | sort: SortMeta, |
24 | search?: string | 24 | search?: string |
25 | }): Observable<ResultList<VideoAbuse>> { | 25 | }): Observable<ResultList<Abuse>> { |
26 | const { pagination, sort, search } = options | 26 | const { pagination, sort, search } = options |
27 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse' | 27 | const url = AbuseService.BASE_ABUSE_URL + 'abuse' |
28 | 28 | ||
29 | let params = new HttpParams() | 29 | let params = new HttpParams() |
30 | params = this.restService.addRestGetParams(params, pagination, sort) | 30 | params = this.restService.addRestGetParams(params, pagination, sort) |
@@ -35,9 +35,9 @@ export class VideoAbuseService { | |||
35 | state: { | 35 | state: { |
36 | prefix: 'state:', | 36 | prefix: 'state:', |
37 | handler: v => { | 37 | handler: v => { |
38 | if (v === 'accepted') return VideoAbuseState.ACCEPTED | 38 | if (v === 'accepted') return AbuseState.ACCEPTED |
39 | if (v === 'pending') return VideoAbuseState.PENDING | 39 | if (v === 'pending') return AbuseState.PENDING |
40 | if (v === 'rejected') return VideoAbuseState.REJECTED | 40 | if (v === 'rejected') return AbuseState.REJECTED |
41 | 41 | ||
42 | return undefined | 42 | return undefined |
43 | } | 43 | } |
@@ -59,14 +59,14 @@ export class VideoAbuseService { | |||
59 | params = this.restService.addObjectParams(params, filters) | 59 | params = this.restService.addObjectParams(params, filters) |
60 | } | 60 | } |
61 | 61 | ||
62 | return this.authHttp.get<ResultList<VideoAbuse>>(url, { params }) | 62 | return this.authHttp.get<ResultList<Abuse>>(url, { params }) |
63 | .pipe( | 63 | .pipe( |
64 | catchError(res => this.restExtractor.handleError(res)) | 64 | catchError(res => this.restExtractor.handleError(res)) |
65 | ) | 65 | ) |
66 | } | 66 | } |
67 | 67 | ||
68 | reportVideo (parameters: { id: number } & VideoAbuseCreate) { | 68 | reportVideo (parameters: AbuseCreate) { |
69 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + parameters.id + '/abuse' | 69 | const url = AbuseService.BASE_ABUSE_URL |
70 | 70 | ||
71 | const body = omit(parameters, [ 'id' ]) | 71 | const body = omit(parameters, [ 'id' ]) |
72 | 72 | ||
@@ -77,8 +77,8 @@ export class VideoAbuseService { | |||
77 | ) | 77 | ) |
78 | } | 78 | } |
79 | 79 | ||
80 | updateVideoAbuse (videoAbuse: VideoAbuse, abuseUpdate: VideoAbuseUpdate) { | 80 | updateAbuse (abuse: Abuse, abuseUpdate: AbuseUpdate) { |
81 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id | 81 | const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id |
82 | 82 | ||
83 | return this.authHttp.put(url, abuseUpdate) | 83 | return this.authHttp.put(url, abuseUpdate) |
84 | .pipe( | 84 | .pipe( |
@@ -87,8 +87,8 @@ export class VideoAbuseService { | |||
87 | ) | 87 | ) |
88 | } | 88 | } |
89 | 89 | ||
90 | removeVideoAbuse (videoAbuse: VideoAbuse) { | 90 | removeAbuse (abuse: Abuse) { |
91 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + videoAbuse.video.uuid + '/abuse/' + videoAbuse.id | 91 | const url = AbuseService.BASE_ABUSE_URL + '/' + abuse.id |
92 | 92 | ||
93 | return this.authHttp.delete(url) | 93 | return this.authHttp.delete(url) |
94 | .pipe( | 94 | .pipe( |
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts index 8e74254f6..d6c4a10be 100644 --- a/client/src/app/shared/shared-moderation/index.ts +++ b/client/src/app/shared/shared-moderation/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './abuse.service' | ||
1 | export * from './account-block.model' | 2 | export * from './account-block.model' |
2 | export * from './account-blocklist.component' | 3 | export * from './account-blocklist.component' |
3 | export * from './batch-domains-modal.component' | 4 | export * from './batch-domains-modal.component' |
@@ -6,7 +7,6 @@ export * from './bulk.service' | |||
6 | export * from './server-blocklist.component' | 7 | export * from './server-blocklist.component' |
7 | export * from './user-ban-modal.component' | 8 | export * from './user-ban-modal.component' |
8 | export * from './user-moderation-dropdown.component' | 9 | export * from './user-moderation-dropdown.component' |
9 | export * from './video-abuse.service' | ||
10 | export * from './video-block.component' | 10 | export * from './video-block.component' |
11 | export * from './video-block.service' | 11 | export * from './video-block.service' |
12 | export * from './video-report.component' | 12 | export * from './video-report.component' |
diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts index f7e64dfa3..742193e58 100644 --- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts +++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts | |||
@@ -8,7 +8,7 @@ import { BlocklistService } from './blocklist.service' | |||
8 | import { BulkService } from './bulk.service' | 8 | import { BulkService } from './bulk.service' |
9 | import { UserBanModalComponent } from './user-ban-modal.component' | 9 | import { UserBanModalComponent } from './user-ban-modal.component' |
10 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' | 10 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' |
11 | import { VideoAbuseService } from './video-abuse.service' | 11 | import { AbuseService } from './abuse.service' |
12 | import { VideoBlockComponent } from './video-block.component' | 12 | import { VideoBlockComponent } from './video-block.component' |
13 | import { VideoBlockService } from './video-block.service' | 13 | import { VideoBlockService } from './video-block.service' |
14 | import { VideoReportComponent } from './video-report.component' | 14 | import { VideoReportComponent } from './video-report.component' |
@@ -39,7 +39,7 @@ import { VideoReportComponent } from './video-report.component' | |||
39 | providers: [ | 39 | providers: [ |
40 | BlocklistService, | 40 | BlocklistService, |
41 | BulkService, | 41 | BulkService, |
42 | VideoAbuseService, | 42 | AbuseService, |
43 | VideoBlockService | 43 | VideoBlockService |
44 | ] | 44 | ] |
45 | }) | 45 | }) |
diff --git a/client/src/app/shared/shared-moderation/video-report.component.ts b/client/src/app/shared/shared-moderation/video-report.component.ts index 11c805636..b8d9f8d27 100644 --- a/client/src/app/shared/shared-moderation/video-report.component.ts +++ b/client/src/app/shared/shared-moderation/video-report.component.ts | |||
@@ -3,13 +3,13 @@ import { buildVideoEmbed, buildVideoLink } from 'src/assets/player/utils' | |||
3 | import { Component, Input, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, Input, OnInit, ViewChild } from '@angular/core' |
4 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | 4 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' |
5 | import { Notifier } from '@app/core' | 5 | import { Notifier } from '@app/core' |
6 | import { FormReactive, FormValidatorService, VideoAbuseValidatorsService } from '@app/shared/shared-forms' | 6 | import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 8 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { videoAbusePredefinedReasonsMap, VideoAbusePredefinedReasonsString } from '@shared/models/videos/abuse/video-abuse-reason.model' | 10 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString } from '@shared/models' |
11 | import { Video } from '../shared-main' | 11 | import { Video } from '../shared-main' |
12 | import { VideoAbuseService } from './video-abuse.service' | 12 | import { AbuseService } from './abuse.service' |
13 | 13 | ||
14 | @Component({ | 14 | @Component({ |
15 | selector: 'my-video-report', | 15 | selector: 'my-video-report', |
@@ -22,7 +22,7 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
22 | @ViewChild('modal', { static: true }) modal: NgbModal | 22 | @ViewChild('modal', { static: true }) modal: NgbModal |
23 | 23 | ||
24 | error: string = null | 24 | error: string = null |
25 | predefinedReasons: { id: VideoAbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] | 25 | predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] |
26 | embedHtml: SafeHtml | 26 | embedHtml: SafeHtml |
27 | 27 | ||
28 | private openedModal: NgbModalRef | 28 | private openedModal: NgbModalRef |
@@ -30,8 +30,8 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
30 | constructor ( | 30 | constructor ( |
31 | protected formValidatorService: FormValidatorService, | 31 | protected formValidatorService: FormValidatorService, |
32 | private modalService: NgbModal, | 32 | private modalService: NgbModal, |
33 | private videoAbuseValidatorsService: VideoAbuseValidatorsService, | 33 | private abuseValidatorsService: AbuseValidatorsService, |
34 | private videoAbuseService: VideoAbuseService, | 34 | private abuseService: AbuseService, |
35 | private notifier: Notifier, | 35 | private notifier: Notifier, |
36 | private sanitizer: DomSanitizer, | 36 | private sanitizer: DomSanitizer, |
37 | private i18n: I18n | 37 | private i18n: I18n |
@@ -69,8 +69,8 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
69 | 69 | ||
70 | ngOnInit () { | 70 | ngOnInit () { |
71 | this.buildForm({ | 71 | this.buildForm({ |
72 | reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON, | 72 | reason: this.abuseValidatorsService.ABUSE_REASON, |
73 | predefinedReasons: mapValues(videoAbusePredefinedReasonsMap, r => null), | 73 | predefinedReasons: mapValues(abusePredefinedReasonsMap, r => null), |
74 | timestamp: { | 74 | timestamp: { |
75 | hasStart: null, | 75 | hasStart: null, |
76 | startAt: null, | 76 | startAt: null, |
@@ -136,15 +136,18 @@ export class VideoReportComponent extends FormReactive implements OnInit { | |||
136 | 136 | ||
137 | report () { | 137 | report () { |
138 | const reason = this.form.get('reason').value | 138 | const reason = this.form.get('reason').value |
139 | const predefinedReasons = Object.keys(pickBy(this.form.get('predefinedReasons').value)) as VideoAbusePredefinedReasonsString[] | 139 | const predefinedReasons = Object.keys(pickBy(this.form.get('predefinedReasons').value)) as AbusePredefinedReasonsString[] |
140 | const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value | 140 | const { hasStart, startAt, hasEnd, endAt } = this.form.get('timestamp').value |
141 | 141 | ||
142 | this.videoAbuseService.reportVideo({ | 142 | this.abuseService.reportVideo({ |
143 | id: this.video.id, | 143 | accountId: this.video.account.id, |
144 | reason, | 144 | reason, |
145 | predefinedReasons, | 145 | predefinedReasons, |
146 | startAt: hasStart && startAt ? startAt : undefined, | 146 | video: { |
147 | endAt: hasEnd && endAt ? endAt : undefined | 147 | id: this.video.id, |
148 | startAt: hasStart && startAt ? startAt : undefined, | ||
149 | endAt: hasEnd && endAt ? endAt : undefined | ||
150 | } | ||
148 | }).subscribe( | 151 | }).subscribe( |
149 | () => { | 152 | () => { |
150 | this.notifier.success(this.i18n('Video reported.')) | 153 | this.notifier.success(this.i18n('Video reported.')) |
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts new file mode 100644 index 000000000..ee046cb3a --- /dev/null +++ b/server/controllers/api/abuse.ts | |||
@@ -0,0 +1,168 @@ | |||
1 | import * as express from 'express' | ||
2 | import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' | ||
3 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
4 | import { getServerActor } from '@server/models/application/application' | ||
5 | import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared' | ||
6 | import { getFormattedObjects } from '../../helpers/utils' | ||
7 | import { sequelizeTypescript } from '../../initializers/database' | ||
8 | import { | ||
9 | abuseGetValidator, | ||
10 | abuseListValidator, | ||
11 | abuseReportValidator, | ||
12 | abusesSortValidator, | ||
13 | abuseUpdateValidator, | ||
14 | asyncMiddleware, | ||
15 | asyncRetryTransactionMiddleware, | ||
16 | authenticate, | ||
17 | ensureUserHasRight, | ||
18 | paginationValidator, | ||
19 | setDefaultPagination, | ||
20 | setDefaultSort | ||
21 | } from '../../middlewares' | ||
22 | import { AccountModel } from '../../models/account/account' | ||
23 | |||
24 | const abuseRouter = express.Router() | ||
25 | |||
26 | abuseRouter.get('/abuse', | ||
27 | authenticate, | ||
28 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
29 | paginationValidator, | ||
30 | abusesSortValidator, | ||
31 | setDefaultSort, | ||
32 | setDefaultPagination, | ||
33 | abuseListValidator, | ||
34 | asyncMiddleware(listAbuses) | ||
35 | ) | ||
36 | abuseRouter.put('/:videoId/abuse/:id', | ||
37 | authenticate, | ||
38 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
39 | asyncMiddleware(abuseUpdateValidator), | ||
40 | asyncRetryTransactionMiddleware(updateAbuse) | ||
41 | ) | ||
42 | abuseRouter.post('/:videoId/abuse', | ||
43 | authenticate, | ||
44 | asyncMiddleware(abuseReportValidator), | ||
45 | asyncRetryTransactionMiddleware(reportAbuse) | ||
46 | ) | ||
47 | abuseRouter.delete('/:videoId/abuse/:id', | ||
48 | authenticate, | ||
49 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
50 | asyncMiddleware(abuseGetValidator), | ||
51 | asyncRetryTransactionMiddleware(deleteAbuse) | ||
52 | ) | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | export { | ||
57 | abuseRouter, | ||
58 | |||
59 | // FIXME: deprecated in 2.3. Remove these exports | ||
60 | listAbuses, | ||
61 | updateAbuse, | ||
62 | deleteAbuse, | ||
63 | reportAbuse | ||
64 | } | ||
65 | |||
66 | // --------------------------------------------------------------------------- | ||
67 | |||
68 | async function listAbuses (req: express.Request, res: express.Response) { | ||
69 | const user = res.locals.oauth.token.user | ||
70 | const serverActor = await getServerActor() | ||
71 | |||
72 | const resultList = await AbuseModel.listForApi({ | ||
73 | start: req.query.start, | ||
74 | count: req.query.count, | ||
75 | sort: req.query.sort, | ||
76 | id: req.query.id, | ||
77 | filter: 'video', | ||
78 | predefinedReason: req.query.predefinedReason, | ||
79 | search: req.query.search, | ||
80 | state: req.query.state, | ||
81 | videoIs: req.query.videoIs, | ||
82 | searchReporter: req.query.searchReporter, | ||
83 | searchReportee: req.query.searchReportee, | ||
84 | searchVideo: req.query.searchVideo, | ||
85 | searchVideoChannel: req.query.searchVideoChannel, | ||
86 | serverAccountId: serverActor.Account.id, | ||
87 | user | ||
88 | }) | ||
89 | |||
90 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
91 | } | ||
92 | |||
93 | async function updateAbuse (req: express.Request, res: express.Response) { | ||
94 | const abuse = res.locals.abuse | ||
95 | |||
96 | if (req.body.moderationComment !== undefined) abuse.moderationComment = req.body.moderationComment | ||
97 | if (req.body.state !== undefined) abuse.state = req.body.state | ||
98 | |||
99 | await sequelizeTypescript.transaction(t => { | ||
100 | return abuse.save({ transaction: t }) | ||
101 | }) | ||
102 | |||
103 | // Do not send the delete to other instances, we updated OUR copy of this video abuse | ||
104 | |||
105 | return res.type('json').status(204).end() | ||
106 | } | ||
107 | |||
108 | async function deleteAbuse (req: express.Request, res: express.Response) { | ||
109 | const abuse = res.locals.abuse | ||
110 | |||
111 | await sequelizeTypescript.transaction(t => { | ||
112 | return abuse.destroy({ transaction: t }) | ||
113 | }) | ||
114 | |||
115 | // Do not send the delete to other instances, we delete OUR copy of this video abuse | ||
116 | |||
117 | return res.type('json').status(204).end() | ||
118 | } | ||
119 | |||
120 | async function reportAbuse (req: express.Request, res: express.Response) { | ||
121 | const videoInstance = res.locals.videoAll | ||
122 | const commentInstance = res.locals.videoCommentFull | ||
123 | const accountInstance = res.locals.account | ||
124 | |||
125 | const body: AbuseCreate = req.body | ||
126 | |||
127 | const { id } = await sequelizeTypescript.transaction(async t => { | ||
128 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | ||
129 | const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r]) | ||
130 | |||
131 | const baseAbuse = { | ||
132 | reporterAccountId: reporterAccount.id, | ||
133 | reason: body.reason, | ||
134 | state: AbuseState.PENDING, | ||
135 | predefinedReasons | ||
136 | } | ||
137 | |||
138 | if (body.video) { | ||
139 | return createVideoAbuse({ | ||
140 | baseAbuse, | ||
141 | videoInstance, | ||
142 | reporterAccount, | ||
143 | transaction: t, | ||
144 | startAt: body.video.startAt, | ||
145 | endAt: body.video.endAt | ||
146 | }) | ||
147 | } | ||
148 | |||
149 | if (body.comment) { | ||
150 | return createVideoCommentAbuse({ | ||
151 | baseAbuse, | ||
152 | commentInstance, | ||
153 | reporterAccount, | ||
154 | transaction: t | ||
155 | }) | ||
156 | } | ||
157 | |||
158 | // Account report | ||
159 | return createAccountAbuse({ | ||
160 | baseAbuse, | ||
161 | accountInstance, | ||
162 | reporterAccount, | ||
163 | transaction: t | ||
164 | }) | ||
165 | }) | ||
166 | |||
167 | return res.json({ abuse: { id } }) | ||
168 | } | ||
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index c334a26b4..eda9e04d1 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -3,6 +3,7 @@ import * as express from 'express' | |||
3 | import * as RateLimit from 'express-rate-limit' | 3 | import * as RateLimit from 'express-rate-limit' |
4 | import { badRequest } from '../../helpers/express-utils' | 4 | import { badRequest } from '../../helpers/express-utils' |
5 | import { CONFIG } from '../../initializers/config' | 5 | import { CONFIG } from '../../initializers/config' |
6 | import { abuseRouter } from './abuse' | ||
6 | import { accountsRouter } from './accounts' | 7 | import { accountsRouter } from './accounts' |
7 | import { bulkRouter } from './bulk' | 8 | import { bulkRouter } from './bulk' |
8 | import { configRouter } from './config' | 9 | import { configRouter } from './config' |
@@ -32,6 +33,7 @@ const apiRateLimiter = RateLimit({ | |||
32 | apiRouter.use(apiRateLimiter) | 33 | apiRouter.use(apiRateLimiter) |
33 | 34 | ||
34 | apiRouter.use('/server', serverRouter) | 35 | apiRouter.use('/server', serverRouter) |
36 | apiRouter.use('/abuses', abuseRouter) | ||
35 | apiRouter.use('/bulk', bulkRouter) | 37 | apiRouter.use('/bulk', bulkRouter) |
36 | apiRouter.use('/oauth-clients', oauthClientsRouter) | 38 | apiRouter.use('/oauth-clients', oauthClientsRouter) |
37 | apiRouter.use('/config', configRouter) | 39 | apiRouter.use('/config', configRouter) |
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index ab2074459..b92a66360 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoAbuseCreate, VideoAbuseState, VideoAbuse, videoAbusePredefinedReasonsMap } from '../../../../shared' | 2 | import { AbuseModel } from '@server/models/abuse/abuse' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { getServerActor } from '@server/models/application/application' |
4 | import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared' | ||
4 | import { getFormattedObjects } from '../../../helpers/utils' | 5 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers/database' | ||
6 | import { | 6 | import { |
7 | abusesSortValidator, | ||
7 | asyncMiddleware, | 8 | asyncMiddleware, |
8 | asyncRetryTransactionMiddleware, | 9 | asyncRetryTransactionMiddleware, |
9 | authenticate, | 10 | authenticate, |
@@ -12,28 +13,21 @@ import { | |||
12 | setDefaultPagination, | 13 | setDefaultPagination, |
13 | setDefaultSort, | 14 | setDefaultSort, |
14 | videoAbuseGetValidator, | 15 | videoAbuseGetValidator, |
16 | videoAbuseListValidator, | ||
15 | videoAbuseReportValidator, | 17 | videoAbuseReportValidator, |
16 | videoAbusesSortValidator, | 18 | videoAbuseUpdateValidator |
17 | videoAbuseUpdateValidator, | ||
18 | videoAbuseListValidator | ||
19 | } from '../../../middlewares' | 19 | } from '../../../middlewares' |
20 | import { AccountModel } from '../../../models/account/account' | 20 | import { deleteAbuse, reportAbuse, updateAbuse } from '../abuse' |
21 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 21 | |
22 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | 22 | // FIXME: deprecated in 2.3. Remove this controller |
23 | import { Notifier } from '../../../lib/notifier' | ||
24 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' | ||
25 | import { MVideoAbuseAccountVideo } from '../../../types/models/video' | ||
26 | import { getServerActor } from '@server/models/application/application' | ||
27 | import { MAccountDefault } from '@server/types/models' | ||
28 | 23 | ||
29 | const auditLogger = auditLoggerFactory('abuse') | ||
30 | const abuseVideoRouter = express.Router() | 24 | const abuseVideoRouter = express.Router() |
31 | 25 | ||
32 | abuseVideoRouter.get('/abuse', | 26 | abuseVideoRouter.get('/abuse', |
33 | authenticate, | 27 | authenticate, |
34 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 28 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
35 | paginationValidator, | 29 | paginationValidator, |
36 | videoAbusesSortValidator, | 30 | abusesSortValidator, |
37 | setDefaultSort, | 31 | setDefaultSort, |
38 | setDefaultPagination, | 32 | setDefaultPagination, |
39 | videoAbuseListValidator, | 33 | videoAbuseListValidator, |
@@ -41,7 +35,7 @@ abuseVideoRouter.get('/abuse', | |||
41 | ) | 35 | ) |
42 | abuseVideoRouter.put('/:videoId/abuse/:id', | 36 | abuseVideoRouter.put('/:videoId/abuse/:id', |
43 | authenticate, | 37 | authenticate, |
44 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 38 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
45 | asyncMiddleware(videoAbuseUpdateValidator), | 39 | asyncMiddleware(videoAbuseUpdateValidator), |
46 | asyncRetryTransactionMiddleware(updateVideoAbuse) | 40 | asyncRetryTransactionMiddleware(updateVideoAbuse) |
47 | ) | 41 | ) |
@@ -52,7 +46,7 @@ abuseVideoRouter.post('/:videoId/abuse', | |||
52 | ) | 46 | ) |
53 | abuseVideoRouter.delete('/:videoId/abuse/:id', | 47 | abuseVideoRouter.delete('/:videoId/abuse/:id', |
54 | authenticate, | 48 | authenticate, |
55 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 49 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
56 | asyncMiddleware(videoAbuseGetValidator), | 50 | asyncMiddleware(videoAbuseGetValidator), |
57 | asyncRetryTransactionMiddleware(deleteVideoAbuse) | 51 | asyncRetryTransactionMiddleware(deleteVideoAbuse) |
58 | ) | 52 | ) |
@@ -69,11 +63,12 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
69 | const user = res.locals.oauth.token.user | 63 | const user = res.locals.oauth.token.user |
70 | const serverActor = await getServerActor() | 64 | const serverActor = await getServerActor() |
71 | 65 | ||
72 | const resultList = await VideoAbuseModel.listForApi({ | 66 | const resultList = await AbuseModel.listForApi({ |
73 | start: req.query.start, | 67 | start: req.query.start, |
74 | count: req.query.count, | 68 | count: req.query.count, |
75 | sort: req.query.sort, | 69 | sort: req.query.sort, |
76 | id: req.query.id, | 70 | id: req.query.id, |
71 | filter: 'video', | ||
77 | predefinedReason: req.query.predefinedReason, | 72 | predefinedReason: req.query.predefinedReason, |
78 | search: req.query.search, | 73 | search: req.query.search, |
79 | state: req.query.state, | 74 | state: req.query.state, |
@@ -90,74 +85,28 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
90 | } | 85 | } |
91 | 86 | ||
92 | async function updateVideoAbuse (req: express.Request, res: express.Response) { | 87 | async function updateVideoAbuse (req: express.Request, res: express.Response) { |
93 | const videoAbuse = res.locals.videoAbuse | 88 | return updateAbuse(req, res) |
94 | |||
95 | if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment | ||
96 | if (req.body.state !== undefined) videoAbuse.state = req.body.state | ||
97 | |||
98 | await sequelizeTypescript.transaction(t => { | ||
99 | return videoAbuse.save({ transaction: t }) | ||
100 | }) | ||
101 | |||
102 | // Do not send the delete to other instances, we updated OUR copy of this video abuse | ||
103 | |||
104 | return res.type('json').status(204).end() | ||
105 | } | 89 | } |
106 | 90 | ||
107 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { | 91 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { |
108 | const videoAbuse = res.locals.videoAbuse | 92 | return deleteAbuse(req, res) |
109 | |||
110 | await sequelizeTypescript.transaction(t => { | ||
111 | return videoAbuse.destroy({ transaction: t }) | ||
112 | }) | ||
113 | |||
114 | // Do not send the delete to other instances, we delete OUR copy of this video abuse | ||
115 | |||
116 | return res.type('json').status(204).end() | ||
117 | } | 93 | } |
118 | 94 | ||
119 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 95 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
120 | const videoInstance = res.locals.videoAll | 96 | const oldBody = req.body as VideoAbuseCreate |
121 | const body: VideoAbuseCreate = req.body | ||
122 | let reporterAccount: MAccountDefault | ||
123 | let videoAbuseJSON: VideoAbuse | ||
124 | |||
125 | const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { | ||
126 | reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | ||
127 | const predefinedReasons = body.predefinedReasons?.map(r => videoAbusePredefinedReasonsMap[r]) | ||
128 | |||
129 | const abuseToCreate = { | ||
130 | reporterAccountId: reporterAccount.id, | ||
131 | reason: body.reason, | ||
132 | videoId: videoInstance.id, | ||
133 | state: VideoAbuseState.PENDING, | ||
134 | predefinedReasons, | ||
135 | startAt: body.startAt, | ||
136 | endAt: body.endAt | ||
137 | } | ||
138 | |||
139 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) | ||
140 | videoAbuseInstance.Video = videoInstance | ||
141 | videoAbuseInstance.Account = reporterAccount | ||
142 | |||
143 | // We send the video abuse to the origin server | ||
144 | if (videoInstance.isOwned() === false) { | ||
145 | await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) | ||
146 | } | ||
147 | 97 | ||
148 | videoAbuseJSON = videoAbuseInstance.toFormattedJSON() | 98 | req.body = { |
149 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseJSON)) | 99 | accountId: res.locals.videoAll.VideoChannel.accountId, |
150 | 100 | ||
151 | return videoAbuseInstance | 101 | reason: oldBody.reason, |
152 | }) | 102 | predefinedReasons: oldBody.predefinedReasons, |
153 | 103 | ||
154 | Notifier.Instance.notifyOnNewVideoAbuse({ | 104 | video: { |
155 | videoAbuse: videoAbuseJSON, | 105 | id: res.locals.videoAll.id, |
156 | videoAbuseInstance, | 106 | startAt: oldBody.startAt, |
157 | reporter: reporterAccount.Actor.getIdentifier() | 107 | endAt: oldBody.endAt |
158 | }) | 108 | } |
159 | 109 | } as AbuseCreate | |
160 | logger.info('Abuse report for video "%s" created.', videoInstance.name) | ||
161 | 110 | ||
162 | return res.json({ videoAbuse: videoAbuseJSON }).end() | 111 | return reportAbuse(req, res) |
163 | } | 112 | } |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 0bbfbc753..954b0b69d 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -1,15 +1,15 @@ | |||
1 | import * as path from 'path' | ||
2 | import * as express from 'express' | ||
3 | import { diff } from 'deep-object-diff' | 1 | import { diff } from 'deep-object-diff' |
4 | import { chain } from 'lodash' | 2 | import * as express from 'express' |
5 | import * as flatten from 'flat' | 3 | import * as flatten from 'flat' |
4 | import { chain } from 'lodash' | ||
5 | import * as path from 'path' | ||
6 | import * as winston from 'winston' | 6 | import * as winston from 'winston' |
7 | import { jsonLoggerFormat, labelFormatter } from './logger' | 7 | import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' |
8 | import { User, VideoAbuse, VideoChannel, VideoDetails, VideoImport } from '../../shared' | 8 | import { Abuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' |
9 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | ||
10 | import { CustomConfig } from '../../shared/models/server/custom-config.model' | 9 | import { CustomConfig } from '../../shared/models/server/custom-config.model' |
10 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | ||
11 | import { CONFIG } from '../initializers/config' | 11 | import { CONFIG } from '../initializers/config' |
12 | import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' | 12 | import { jsonLoggerFormat, labelFormatter } from './logger' |
13 | 13 | ||
14 | function getAuditIdFromRes (res: express.Response) { | 14 | function getAuditIdFromRes (res: express.Response) { |
15 | return res.locals.oauth.token.User.username | 15 | return res.locals.oauth.token.User.username |
@@ -212,18 +212,15 @@ class VideoChannelAuditView extends EntityAuditView { | |||
212 | } | 212 | } |
213 | } | 213 | } |
214 | 214 | ||
215 | const videoAbuseKeysToKeep = [ | 215 | const abuseKeysToKeep = [ |
216 | 'id', | 216 | 'id', |
217 | 'reason', | 217 | 'reason', |
218 | 'reporterAccount', | 218 | 'reporterAccount', |
219 | 'video-id', | ||
220 | 'video-name', | ||
221 | 'video-uuid', | ||
222 | 'createdAt' | 219 | 'createdAt' |
223 | ] | 220 | ] |
224 | class VideoAbuseAuditView extends EntityAuditView { | 221 | class AbuseAuditView extends EntityAuditView { |
225 | constructor (private readonly videoAbuse: VideoAbuse) { | 222 | constructor (private readonly abuse: Abuse) { |
226 | super(videoAbuseKeysToKeep, 'abuse', videoAbuse) | 223 | super(abuseKeysToKeep, 'abuse', abuse) |
227 | } | 224 | } |
228 | } | 225 | } |
229 | 226 | ||
@@ -274,6 +271,6 @@ export { | |||
274 | CommentAuditView, | 271 | CommentAuditView, |
275 | UserAuditView, | 272 | UserAuditView, |
276 | VideoAuditView, | 273 | VideoAuditView, |
277 | VideoAbuseAuditView, | 274 | AbuseAuditView, |
278 | CustomConfigAuditView | 275 | CustomConfigAuditView |
279 | } | 276 | } |
diff --git a/server/helpers/custom-validators/abuses.ts b/server/helpers/custom-validators/abuses.ts new file mode 100644 index 000000000..a6a895c65 --- /dev/null +++ b/server/helpers/custom-validators/abuses.ts | |||
@@ -0,0 +1,54 @@ | |||
1 | import validator from 'validator' | ||
2 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString, AbuseVideoIs } from '@shared/models' | ||
3 | import { CONSTRAINTS_FIELDS, ABUSE_STATES } from '../../initializers/constants' | ||
4 | import { exists, isArray } from './misc' | ||
5 | |||
6 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES | ||
7 | |||
8 | function isAbuseReasonValid (value: string) { | ||
9 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
10 | } | ||
11 | |||
12 | function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) { | ||
13 | return exists(value) && value in abusePredefinedReasonsMap | ||
14 | } | ||
15 | |||
16 | function isAbusePredefinedReasonsValid (value: AbusePredefinedReasonsString[]) { | ||
17 | return exists(value) && isArray(value) && value.every(v => v in abusePredefinedReasonsMap) | ||
18 | } | ||
19 | |||
20 | function isAbuseTimestampValid (value: number) { | ||
21 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
22 | } | ||
23 | |||
24 | function isAbuseTimestampCoherent (endAt: number, { req }) { | ||
25 | return exists(req.body.startAt) && endAt > req.body.startAt | ||
26 | } | ||
27 | |||
28 | function isAbuseModerationCommentValid (value: string) { | ||
29 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT) | ||
30 | } | ||
31 | |||
32 | function isAbuseStateValid (value: string) { | ||
33 | return exists(value) && ABUSE_STATES[value] !== undefined | ||
34 | } | ||
35 | |||
36 | function isAbuseVideoIsValid (value: AbuseVideoIs) { | ||
37 | return exists(value) && ( | ||
38 | value === 'deleted' || | ||
39 | value === 'blacklisted' | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | // --------------------------------------------------------------------------- | ||
44 | |||
45 | export { | ||
46 | isAbuseReasonValid, | ||
47 | isAbusePredefinedReasonValid, | ||
48 | isAbusePredefinedReasonsValid, | ||
49 | isAbuseTimestampValid, | ||
50 | isAbuseTimestampCoherent, | ||
51 | isAbuseModerationCommentValid, | ||
52 | isAbuseStateValid, | ||
53 | isAbuseVideoIsValid | ||
54 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/flag.ts b/server/helpers/custom-validators/activitypub/flag.ts index 6452e297c..dc90b3667 100644 --- a/server/helpers/custom-validators/activitypub/flag.ts +++ b/server/helpers/custom-validators/activitypub/flag.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { isActivityPubUrlValid } from './misc' | 1 | import { isActivityPubUrlValid } from './misc' |
2 | import { isVideoAbuseReasonValid } from '../video-abuses' | 2 | import { isAbuseReasonValid } from '../abuses' |
3 | 3 | ||
4 | function isFlagActivityValid (activity: any) { | 4 | function isFlagActivityValid (activity: any) { |
5 | return activity.type === 'Flag' && | 5 | return activity.type === 'Flag' && |
6 | isVideoAbuseReasonValid(activity.content) && | 6 | isAbuseReasonValid(activity.content) && |
7 | isActivityPubUrlValid(activity.object) | 7 | isActivityPubUrlValid(activity.object) |
8 | } | 8 | } |
9 | 9 | ||
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts deleted file mode 100644 index 0c2c34268..000000000 --- a/server/helpers/custom-validators/video-abuses.ts +++ /dev/null | |||
@@ -1,56 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | |||
3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | ||
4 | import { exists, isArray } from './misc' | ||
5 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' | ||
6 | import { VideoAbusePredefinedReasonsString, videoAbusePredefinedReasonsMap } from '@shared/models/videos/abuse/video-abuse-reason.model' | ||
7 | |||
8 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES | ||
9 | |||
10 | function isVideoAbuseReasonValid (value: string) { | ||
11 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
12 | } | ||
13 | |||
14 | function isVideoAbusePredefinedReasonValid (value: VideoAbusePredefinedReasonsString) { | ||
15 | return exists(value) && value in videoAbusePredefinedReasonsMap | ||
16 | } | ||
17 | |||
18 | function isVideoAbusePredefinedReasonsValid (value: VideoAbusePredefinedReasonsString[]) { | ||
19 | return exists(value) && isArray(value) && value.every(v => v in videoAbusePredefinedReasonsMap) | ||
20 | } | ||
21 | |||
22 | function isVideoAbuseTimestampValid (value: number) { | ||
23 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
24 | } | ||
25 | |||
26 | function isVideoAbuseTimestampCoherent (endAt: number, { req }) { | ||
27 | return exists(req.body.startAt) && endAt > req.body.startAt | ||
28 | } | ||
29 | |||
30 | function isVideoAbuseModerationCommentValid (value: string) { | ||
31 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT) | ||
32 | } | ||
33 | |||
34 | function isVideoAbuseStateValid (value: string) { | ||
35 | return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined | ||
36 | } | ||
37 | |||
38 | function isAbuseVideoIsValid (value: VideoAbuseVideoIs) { | ||
39 | return exists(value) && ( | ||
40 | value === 'deleted' || | ||
41 | value === 'blacklisted' | ||
42 | ) | ||
43 | } | ||
44 | |||
45 | // --------------------------------------------------------------------------- | ||
46 | |||
47 | export { | ||
48 | isVideoAbuseReasonValid, | ||
49 | isVideoAbusePredefinedReasonValid, | ||
50 | isVideoAbusePredefinedReasonsValid, | ||
51 | isVideoAbuseTimestampValid, | ||
52 | isVideoAbuseTimestampCoherent, | ||
53 | isVideoAbuseModerationCommentValid, | ||
54 | isVideoAbuseStateValid, | ||
55 | isAbuseVideoIsValid | ||
56 | } | ||
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/abuses.ts index 97a5724b6..3906f6760 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/abuses.ts | |||
@@ -1,19 +1,20 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 2 | import { AbuseModel } from '../../models/abuse/abuse' |
3 | import { fetchVideo } from '../video' | 3 | import { fetchVideo } from '../video' |
4 | 4 | ||
5 | // FIXME: deprecated in 2.3. Remove this function | ||
5 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { | 6 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { |
6 | const abuseId = parseInt(abuseIdArg + '', 10) | 7 | const abuseId = parseInt(abuseIdArg + '', 10) |
7 | let videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, null, videoUUID) | 8 | let abuse = await AbuseModel.loadByIdAndVideoId(abuseId, null, videoUUID) |
8 | 9 | ||
9 | if (!videoAbuse) { | 10 | if (!abuse) { |
10 | const userId = res.locals.oauth?.token.User.id | 11 | const userId = res.locals.oauth?.token.User.id |
11 | const video = await fetchVideo(videoUUID, 'all', userId) | 12 | const video = await fetchVideo(videoUUID, 'all', userId) |
12 | 13 | ||
13 | if (video) videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, video.id) | 14 | if (video) abuse = await AbuseModel.loadByIdAndVideoId(abuseId, video.id) |
14 | } | 15 | } |
15 | 16 | ||
16 | if (videoAbuse === null) { | 17 | if (abuse === null) { |
17 | res.status(404) | 18 | res.status(404) |
18 | .json({ error: 'Video abuse not found' }) | 19 | .json({ error: 'Video abuse not found' }) |
19 | .end() | 20 | .end() |
@@ -21,12 +22,17 @@ async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: stri | |||
21 | return false | 22 | return false |
22 | } | 23 | } |
23 | 24 | ||
24 | res.locals.videoAbuse = videoAbuse | 25 | res.locals.abuse = abuse |
25 | return true | 26 | return true |
26 | } | 27 | } |
27 | 28 | ||
29 | async function doesAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { | ||
30 | |||
31 | } | ||
32 | |||
28 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
29 | 34 | ||
30 | export { | 35 | export { |
36 | doesAbuseExist, | ||
31 | doesVideoAbuseExist | 37 | doesVideoAbuseExist |
32 | } | 38 | } |
diff --git a/server/helpers/middlewares/index.ts b/server/helpers/middlewares/index.ts index f91aeaa12..f57f3ad31 100644 --- a/server/helpers/middlewares/index.ts +++ b/server/helpers/middlewares/index.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export * from './abuses' | ||
1 | export * from './accounts' | 2 | export * from './accounts' |
2 | export * from './video-abuses' | ||
3 | export * from './video-blacklists' | 3 | export * from './video-blacklists' |
4 | export * from './video-captions' | 4 | export * from './video-captions' |
5 | export * from './video-channels' | 5 | export * from './video-channels' |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index e730e3c84..8f86bbbef 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -1,9 +1,17 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { randomBytes } from 'crypto' | 2 | import { randomBytes } from 'crypto' |
3 | import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models' | ||
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 3 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { FollowState } from '../../shared/models/actors' | 4 | import { FollowState } from '../../shared/models/actors' |
6 | import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' | 5 | import { |
6 | AbuseState, | ||
7 | VideoImportState, | ||
8 | VideoPrivacy, | ||
9 | VideoTranscodingFPS, | ||
10 | JobType, | ||
11 | VideoRateType, | ||
12 | VideoResolution, | ||
13 | VideoState | ||
14 | } from '../../shared/models' | ||
7 | // Do not use barrels, remain constants as independent as possible | 15 | // Do not use barrels, remain constants as independent as possible |
8 | import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils' | 16 | import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils' |
9 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 17 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
@@ -51,7 +59,6 @@ const SORTABLE_COLUMNS = { | |||
51 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], | 59 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], |
52 | ACCOUNTS: [ 'createdAt' ], | 60 | ACCOUNTS: [ 'createdAt' ], |
53 | JOBS: [ 'createdAt' ], | 61 | JOBS: [ 'createdAt' ], |
54 | VIDEO_ABUSES: [ 'id', 'createdAt', 'state' ], | ||
55 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], | 62 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], |
56 | VIDEO_IMPORTS: [ 'createdAt' ], | 63 | VIDEO_IMPORTS: [ 'createdAt' ], |
57 | VIDEO_COMMENT_THREADS: [ 'createdAt', 'totalReplies' ], | 64 | VIDEO_COMMENT_THREADS: [ 'createdAt', 'totalReplies' ], |
@@ -66,6 +73,8 @@ const SORTABLE_COLUMNS = { | |||
66 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], | 73 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], |
67 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ], | 74 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ], |
68 | 75 | ||
76 | ABUSES: [ 'id', 'createdAt', 'state' ], | ||
77 | |||
69 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], | 78 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], |
70 | SERVERS_BLOCKLIST: [ 'createdAt' ], | 79 | SERVERS_BLOCKLIST: [ 'createdAt' ], |
71 | 80 | ||
@@ -193,7 +202,7 @@ const CONSTRAINTS_FIELDS = { | |||
193 | VIDEO_LANGUAGES: { max: 500 }, // Array length | 202 | VIDEO_LANGUAGES: { max: 500 }, // Array length |
194 | BLOCKED_REASON: { min: 3, max: 250 } // Length | 203 | BLOCKED_REASON: { min: 3, max: 250 } // Length |
195 | }, | 204 | }, |
196 | VIDEO_ABUSES: { | 205 | ABUSES: { |
197 | REASON: { min: 2, max: 3000 }, // Length | 206 | REASON: { min: 2, max: 3000 }, // Length |
198 | MODERATION_COMMENT: { min: 2, max: 3000 } // Length | 207 | MODERATION_COMMENT: { min: 2, max: 3000 } // Length |
199 | }, | 208 | }, |
@@ -378,10 +387,10 @@ const VIDEO_IMPORT_STATES = { | |||
378 | [VideoImportState.REJECTED]: 'Rejected' | 387 | [VideoImportState.REJECTED]: 'Rejected' |
379 | } | 388 | } |
380 | 389 | ||
381 | const VIDEO_ABUSE_STATES = { | 390 | const ABUSE_STATES = { |
382 | [VideoAbuseState.PENDING]: 'Pending', | 391 | [AbuseState.PENDING]: 'Pending', |
383 | [VideoAbuseState.REJECTED]: 'Rejected', | 392 | [AbuseState.REJECTED]: 'Rejected', |
384 | [VideoAbuseState.ACCEPTED]: 'Accepted' | 393 | [AbuseState.ACCEPTED]: 'Accepted' |
385 | } | 394 | } |
386 | 395 | ||
387 | const VIDEO_PLAYLIST_PRIVACIES = { | 396 | const VIDEO_PLAYLIST_PRIVACIES = { |
@@ -778,7 +787,7 @@ export { | |||
778 | VIDEO_RATE_TYPES, | 787 | VIDEO_RATE_TYPES, |
779 | VIDEO_TRANSCODING_FPS, | 788 | VIDEO_TRANSCODING_FPS, |
780 | FFMPEG_NICE, | 789 | FFMPEG_NICE, |
781 | VIDEO_ABUSE_STATES, | 790 | ABUSE_STATES, |
782 | VIDEO_CHANNELS, | 791 | VIDEO_CHANNELS, |
783 | LRU_CACHE, | 792 | LRU_CACHE, |
784 | JOB_REQUEST_TIMEOUT, | 793 | JOB_REQUEST_TIMEOUT, |
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 633d4f956..0775f1fad 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -1,44 +1,45 @@ | |||
1 | import { QueryTypes, Transaction } from 'sequelize' | ||
1 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' | 2 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' |
3 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
4 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' | ||
5 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
2 | import { isTestInstance } from '../helpers/core-utils' | 6 | import { isTestInstance } from '../helpers/core-utils' |
3 | import { logger } from '../helpers/logger' | 7 | import { logger } from '../helpers/logger' |
4 | |||
5 | import { AccountModel } from '../models/account/account' | 8 | import { AccountModel } from '../models/account/account' |
9 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | ||
6 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | 10 | import { AccountVideoRateModel } from '../models/account/account-video-rate' |
7 | import { UserModel } from '../models/account/user' | 11 | import { UserModel } from '../models/account/user' |
12 | import { UserNotificationModel } from '../models/account/user-notification' | ||
13 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | ||
14 | import { UserVideoHistoryModel } from '../models/account/user-video-history' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | 15 | import { ActorModel } from '../models/activitypub/actor' |
9 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | 16 | import { ActorFollowModel } from '../models/activitypub/actor-follow' |
10 | import { ApplicationModel } from '../models/application/application' | 17 | import { ApplicationModel } from '../models/application/application' |
11 | import { AvatarModel } from '../models/avatar/avatar' | 18 | import { AvatarModel } from '../models/avatar/avatar' |
12 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 19 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
13 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | 20 | import { OAuthTokenModel } from '../models/oauth/oauth-token' |
21 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
22 | import { PluginModel } from '../models/server/plugin' | ||
14 | import { ServerModel } from '../models/server/server' | 23 | import { ServerModel } from '../models/server/server' |
24 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | ||
25 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' | ||
15 | import { TagModel } from '../models/video/tag' | 26 | import { TagModel } from '../models/video/tag' |
27 | import { ThumbnailModel } from '../models/video/thumbnail' | ||
16 | import { VideoModel } from '../models/video/video' | 28 | import { VideoModel } from '../models/video/video' |
17 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
18 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 29 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
30 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
31 | import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' | ||
19 | import { VideoChannelModel } from '../models/video/video-channel' | 32 | import { VideoChannelModel } from '../models/video/video-channel' |
20 | import { VideoCommentModel } from '../models/video/video-comment' | 33 | import { VideoCommentModel } from '../models/video/video-comment' |
21 | import { VideoFileModel } from '../models/video/video-file' | 34 | import { VideoFileModel } from '../models/video/video-file' |
22 | import { VideoShareModel } from '../models/video/video-share' | ||
23 | import { VideoTagModel } from '../models/video/video-tag' | ||
24 | import { CONFIG } from './config' | ||
25 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' | ||
26 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
27 | import { VideoImportModel } from '../models/video/video-import' | 35 | import { VideoImportModel } from '../models/video/video-import' |
28 | import { VideoViewModel } from '../models/video/video-view' | ||
29 | import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' | ||
30 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
31 | import { UserVideoHistoryModel } from '../models/account/user-video-history' | ||
32 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | ||
33 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | ||
34 | import { UserNotificationModel } from '../models/account/user-notification' | ||
35 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | ||
36 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
37 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 36 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
38 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' | 37 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' |
39 | import { ThumbnailModel } from '../models/video/thumbnail' | 38 | import { VideoShareModel } from '../models/video/video-share' |
40 | import { PluginModel } from '../models/server/plugin' | 39 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
41 | import { QueryTypes, Transaction } from 'sequelize' | 40 | import { VideoTagModel } from '../models/video/video-tag' |
41 | import { VideoViewModel } from '../models/video/video-view' | ||
42 | import { CONFIG } from './config' | ||
42 | 43 | ||
43 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string | 44 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string |
44 | 45 | ||
@@ -86,6 +87,8 @@ async function initDatabaseModels (silent: boolean) { | |||
86 | TagModel, | 87 | TagModel, |
87 | AccountVideoRateModel, | 88 | AccountVideoRateModel, |
88 | UserModel, | 89 | UserModel, |
90 | AbuseModel, | ||
91 | VideoCommentAbuseModel, | ||
89 | VideoAbuseModel, | 92 | VideoAbuseModel, |
90 | VideoModel, | 93 | VideoModel, |
91 | VideoChangeOwnershipModel, | 94 | VideoChangeOwnershipModel, |
diff --git a/server/initializers/migrations/0250-video-abuse-state.ts b/server/initializers/migrations/0250-video-abuse-state.ts index 50de25182..e4993c393 100644 --- a/server/initializers/migrations/0250-video-abuse-state.ts +++ b/server/initializers/migrations/0250-video-abuse-state.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { VideoAbuseState } from '../../../shared/models/videos' | 2 | import { AbuseState } from '../../../shared/models' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction | 5 | transaction: Sequelize.Transaction |
@@ -16,7 +16,7 @@ async function up (utils: { | |||
16 | } | 16 | } |
17 | 17 | ||
18 | { | 18 | { |
19 | const query = 'UPDATE "videoAbuse" SET "state" = ' + VideoAbuseState.PENDING | 19 | const query = 'UPDATE "videoAbuse" SET "state" = ' + AbuseState.PENDING |
20 | await utils.sequelize.query(query) | 20 | await utils.sequelize.query(query) |
21 | } | 21 | } |
22 | 22 | ||
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 1d7132a3a..6350cee12 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts | |||
@@ -1,24 +1,19 @@ | |||
1 | import { | 1 | import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' |
2 | ActivityCreate, | 2 | import { AccountModel } from '@server/models/account/account' |
3 | ActivityFlag, | 3 | import { VideoModel } from '@server/models/video/video' |
4 | VideoAbuseState, | 4 | import { VideoCommentModel } from '@server/models/video/video-comment' |
5 | videoAbusePredefinedReasonsMap | 5 | import { AbuseObject, abusePredefinedReasonsMap, AbuseState, ActivityCreate, ActivityFlag } from '../../../../shared' |
6 | } from '../../../../shared' | 6 | import { getAPId } from '../../../helpers/activitypub' |
7 | import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects' | ||
8 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 7 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
9 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
10 | import { sequelizeTypescript } from '../../../initializers/database' | 9 | import { sequelizeTypescript } from '../../../initializers/database' |
11 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
12 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | ||
13 | import { Notifier } from '../../notifier' | ||
14 | import { getAPId } from '../../../helpers/activitypub' | ||
15 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
16 | import { MActorSignature, MVideoAbuseAccountVideo } from '../../../types/models' | 11 | import { MAccountDefault, MActorSignature, MCommentOwnerVideo } from '../../../types/models' |
17 | import { AccountModel } from '@server/models/account/account' | ||
18 | 12 | ||
19 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { | 13 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { |
20 | const { activity, byActor } = options | 14 | const { activity, byActor } = options |
21 | return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor) | 15 | |
16 | return retryTransactionWrapper(processCreateAbuse, activity, byActor) | ||
22 | } | 17 | } |
23 | 18 | ||
24 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
@@ -29,55 +24,79 @@ export { | |||
29 | 24 | ||
30 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
31 | 26 | ||
32 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { | 27 | async function processCreateAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { |
33 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) | 28 | const flag = activity.type === 'Flag' ? activity : (activity.object as AbuseObject) |
34 | 29 | ||
35 | const account = byActor.Account | 30 | const account = byActor.Account |
36 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) | 31 | if (!account) throw new Error('Cannot create abuse with the non account actor ' + byActor.url) |
32 | |||
33 | const reporterAccount = await AccountModel.load(account.id) | ||
37 | 34 | ||
38 | const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] | 35 | const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] |
39 | 36 | ||
37 | const tags = Array.isArray(flag.tag) ? flag.tag : [] | ||
38 | const predefinedReasons = tags.map(tag => abusePredefinedReasonsMap[tag.name]) | ||
39 | .filter(v => !isNaN(v)) | ||
40 | |||
41 | const startAt = flag.startAt | ||
42 | const endAt = flag.endAt | ||
43 | |||
40 | for (const object of objects) { | 44 | for (const object of objects) { |
41 | try { | 45 | try { |
42 | logger.debug('Reporting remote abuse for video %s.', getAPId(object)) | 46 | const uri = getAPId(object) |
43 | |||
44 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) | ||
45 | const reporterAccount = await sequelizeTypescript.transaction(async t => AccountModel.load(account.id, t)) | ||
46 | const tags = Array.isArray(flag.tag) ? flag.tag : [] | ||
47 | const predefinedReasons = tags.map(tag => videoAbusePredefinedReasonsMap[tag.name]) | ||
48 | .filter(v => !isNaN(v)) | ||
49 | const startAt = flag.startAt | ||
50 | const endAt = flag.endAt | ||
51 | |||
52 | const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { | ||
53 | const videoAbuseData = { | ||
54 | reporterAccountId: account.id, | ||
55 | reason: flag.content, | ||
56 | videoId: video.id, | ||
57 | state: VideoAbuseState.PENDING, | ||
58 | predefinedReasons, | ||
59 | startAt, | ||
60 | endAt | ||
61 | } | ||
62 | 47 | ||
63 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) | 48 | logger.debug('Reporting remote abuse for object %s.', uri) |
64 | videoAbuseInstance.Video = video | ||
65 | videoAbuseInstance.Account = reporterAccount | ||
66 | 49 | ||
67 | logger.info('Remote abuse for video uuid %s created', flag.object) | 50 | await sequelizeTypescript.transaction(async t => { |
68 | 51 | ||
69 | return videoAbuseInstance | 52 | const video = await VideoModel.loadByUrlAndPopulateAccount(uri) |
70 | }) | 53 | let videoComment: MCommentOwnerVideo |
54 | let flaggedAccount: MAccountDefault | ||
55 | |||
56 | if (!video) videoComment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(uri) | ||
57 | if (!videoComment) flaggedAccount = await AccountModel.loadByUrl(uri) | ||
58 | |||
59 | if (!video && !videoComment && !flaggedAccount) { | ||
60 | logger.warn('Cannot flag unknown entity %s.', object) | ||
61 | return | ||
62 | } | ||
63 | |||
64 | const baseAbuse = { | ||
65 | reporterAccountId: reporterAccount.id, | ||
66 | reason: flag.content, | ||
67 | state: AbuseState.PENDING, | ||
68 | predefinedReasons | ||
69 | } | ||
71 | 70 | ||
72 | const videoAbuseJSON = videoAbuseInstance.toFormattedJSON() | 71 | if (video) { |
72 | return createVideoAbuse({ | ||
73 | baseAbuse, | ||
74 | startAt, | ||
75 | endAt, | ||
76 | reporterAccount, | ||
77 | transaction: t, | ||
78 | videoInstance: video | ||
79 | }) | ||
80 | } | ||
81 | |||
82 | if (videoComment) { | ||
83 | return createVideoCommentAbuse({ | ||
84 | baseAbuse, | ||
85 | reporterAccount, | ||
86 | transaction: t, | ||
87 | commentInstance: videoComment | ||
88 | }) | ||
89 | } | ||
73 | 90 | ||
74 | Notifier.Instance.notifyOnNewVideoAbuse({ | 91 | return await createAccountAbuse({ |
75 | videoAbuse: videoAbuseJSON, | 92 | baseAbuse, |
76 | videoAbuseInstance, | 93 | reporterAccount, |
77 | reporter: reporterAccount.Actor.getIdentifier() | 94 | transaction: t, |
95 | accountInstance: flaggedAccount | ||
96 | }) | ||
78 | }) | 97 | }) |
79 | } catch (err) { | 98 | } catch (err) { |
80 | logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) | 99 | logger.debug('Cannot process report of %s', getAPId(object), { err }) |
81 | } | 100 | } |
82 | } | 101 | } |
83 | } | 102 | } |
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index 3a1fe0812..821637ec8 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts | |||
@@ -1,32 +1,31 @@ | |||
1 | import { getVideoAbuseActivityPubUrl } from '../url' | 1 | import { Transaction } from 'sequelize' |
2 | import { unicastTo } from './utils' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' |
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { MAbuseAP, MAccountLight, MActor } from '../../../types/models' | ||
5 | import { audiencify, getAudience } from '../audience' | 5 | import { audiencify, getAudience } from '../audience' |
6 | import { Transaction } from 'sequelize' | 6 | import { getAbuseActivityPubUrl } from '../url' |
7 | import { MActor, MVideoFullLight } from '../../../types/models' | 7 | import { unicastTo } from './utils' |
8 | import { MVideoAbuseVideo } from '../../../types/models/video' | ||
9 | 8 | ||
10 | function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { | 9 | function sendAbuse (byActor: MActor, abuse: MAbuseAP, flaggedAccount: MAccountLight, t: Transaction) { |
11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user | 10 | if (!flaggedAccount.Actor.serverId) return // Local user |
12 | 11 | ||
13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | 12 | const url = getAbuseActivityPubUrl(abuse) |
14 | 13 | ||
15 | logger.info('Creating job to send video abuse %s.', url) | 14 | logger.info('Creating job to send abuse %s.', url) |
16 | 15 | ||
17 | // Custom audience, we only send the abuse to the origin instance | 16 | // Custom audience, we only send the abuse to the origin instance |
18 | const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } | 17 | const audience = { to: [ flaggedAccount.Actor.url ], cc: [] } |
19 | const flagActivity = buildFlagActivity(url, byActor, videoAbuse, audience) | 18 | const flagActivity = buildFlagActivity(url, byActor, abuse, audience) |
20 | 19 | ||
21 | t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.getSharedInbox())) | 20 | t.afterCommit(() => unicastTo(flagActivity, byActor, flaggedAccount.Actor.getSharedInbox())) |
22 | } | 21 | } |
23 | 22 | ||
24 | function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag { | 23 | function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag { |
25 | if (!audience) audience = getAudience(byActor) | 24 | if (!audience) audience = getAudience(byActor) |
26 | 25 | ||
27 | const activity = Object.assign( | 26 | const activity = Object.assign( |
28 | { id: url, actor: byActor.url }, | 27 | { id: url, actor: byActor.url }, |
29 | videoAbuse.toActivityPubObject() | 28 | abuse.toActivityPubObject() |
30 | ) | 29 | ) |
31 | 30 | ||
32 | return audiencify(activity, audience) | 31 | return audiencify(activity, audience) |
@@ -35,5 +34,5 @@ function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbus | |||
35 | // --------------------------------------------------------------------------- | 34 | // --------------------------------------------------------------------------- |
36 | 35 | ||
37 | export { | 36 | export { |
38 | sendVideoAbuse | 37 | sendAbuse |
39 | } | 38 | } |
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index 7f98751a1..b54e038a4 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts | |||
@@ -5,10 +5,10 @@ import { | |||
5 | MActorId, | 5 | MActorId, |
6 | MActorUrl, | 6 | MActorUrl, |
7 | MCommentId, | 7 | MCommentId, |
8 | MVideoAbuseId, | ||
9 | MVideoId, | 8 | MVideoId, |
10 | MVideoUrl, | 9 | MVideoUrl, |
11 | MVideoUUID | 10 | MVideoUUID, |
11 | MAbuseId | ||
12 | } from '../../types/models' | 12 | } from '../../types/models' |
13 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' | 13 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' |
14 | import { MVideoFileVideoUUID } from '../../types/models/video/video-file' | 14 | import { MVideoFileVideoUUID } from '../../types/models/video/video-file' |
@@ -48,8 +48,8 @@ function getAccountActivityPubUrl (accountName: string) { | |||
48 | return WEBSERVER.URL + '/accounts/' + accountName | 48 | return WEBSERVER.URL + '/accounts/' + accountName |
49 | } | 49 | } |
50 | 50 | ||
51 | function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) { | 51 | function getAbuseActivityPubUrl (abuse: MAbuseId) { |
52 | return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id | 52 | return WEBSERVER.URL + '/admin/abuses/' + abuse.id |
53 | } | 53 | } |
54 | 54 | ||
55 | function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { | 55 | function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
@@ -118,7 +118,7 @@ export { | |||
118 | getVideoCacheStreamingPlaylistActivityPubUrl, | 118 | getVideoCacheStreamingPlaylistActivityPubUrl, |
119 | getVideoChannelActivityPubUrl, | 119 | getVideoChannelActivityPubUrl, |
120 | getAccountActivityPubUrl, | 120 | getAccountActivityPubUrl, |
121 | getVideoAbuseActivityPubUrl, | 121 | getAbuseActivityPubUrl, |
122 | getActorFollowActivityPubUrl, | 122 | getActorFollowActivityPubUrl, |
123 | getActorFollowAcceptActivityPubUrl, | 123 | getActorFollowAcceptActivityPubUrl, |
124 | getVideoAnnounceActivityPubUrl, | 124 | getVideoAnnounceActivityPubUrl, |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index c08732b48..e821aea5f 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -1,26 +1,20 @@ | |||
1 | import { readFileSync } from 'fs-extra' | ||
2 | import { merge } from 'lodash' | ||
1 | import { createTransport, Transporter } from 'nodemailer' | 3 | import { createTransport, Transporter } from 'nodemailer' |
4 | import { join } from 'path' | ||
5 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
6 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' | ||
7 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' | ||
8 | import { Abuse, EmailPayload } from '@shared/models' | ||
9 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' | ||
2 | import { isTestInstance, root } from '../helpers/core-utils' | 10 | import { isTestInstance, root } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 11 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG, isEmailEnabled } from '../initializers/config' | 12 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
5 | import { JobQueue } from './job-queue' | ||
6 | import { readFileSync } from 'fs-extra' | ||
7 | import { WEBSERVER } from '../initializers/constants' | 13 | import { WEBSERVER } from '../initializers/constants' |
8 | import { | 14 | import { MAbuseFull, MActorFollowActors, MActorFollowFull, MUser } from '../types/models' |
9 | MCommentOwnerVideo, | 15 | import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video' |
10 | MVideo, | 16 | import { JobQueue } from './job-queue' |
11 | MVideoAbuseVideo, | 17 | |
12 | MVideoAccountLight, | ||
13 | MVideoBlacklistLightVideo, | ||
14 | MVideoBlacklistVideo | ||
15 | } from '../types/models/video' | ||
16 | import { MActorFollowActors, MActorFollowFull, MUser } from '../types/models' | ||
17 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' | ||
18 | import { EmailPayload } from '@shared/models' | ||
19 | import { join } from 'path' | ||
20 | import { VideoAbuse } from '../../shared/models/videos' | ||
21 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' | ||
22 | import { merge } from 'lodash' | ||
23 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
24 | const Email = require('email-templates') | 18 | const Email = require('email-templates') |
25 | 19 | ||
26 | class Emailer { | 20 | class Emailer { |
@@ -288,28 +282,70 @@ class Emailer { | |||
288 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 282 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
289 | } | 283 | } |
290 | 284 | ||
291 | addVideoAbuseModeratorsNotification (to: string[], parameters: { | 285 | addAbuseModeratorsNotification (to: string[], parameters: { |
292 | videoAbuse: VideoAbuse | 286 | abuse: Abuse |
293 | videoAbuseInstance: MVideoAbuseVideo | 287 | abuseInstance: MAbuseFull |
294 | reporter: string | 288 | reporter: string |
295 | }) { | 289 | }) { |
296 | const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id | 290 | const { abuse, abuseInstance, reporter } = parameters |
297 | const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath() | ||
298 | 291 | ||
299 | const emailPayload: EmailPayload = { | 292 | const action = { |
300 | template: 'video-abuse-new', | 293 | text: 'View report #' + abuse.id, |
301 | to, | 294 | url: WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id |
302 | subject: `New video abuse report from ${parameters.reporter}`, | 295 | } |
303 | locals: { | 296 | |
304 | videoUrl, | 297 | let emailPayload: EmailPayload |
305 | videoAbuseUrl, | 298 | |
306 | videoCreatedAt: new Date(parameters.videoAbuseInstance.Video.createdAt).toLocaleString(), | 299 | if (abuseInstance.VideoAbuse) { |
307 | videoPublishedAt: new Date(parameters.videoAbuseInstance.Video.publishedAt).toLocaleString(), | 300 | const video = abuseInstance.VideoAbuse.Video |
308 | videoAbuse: parameters.videoAbuse, | 301 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
309 | reporter: parameters.reporter, | 302 | |
310 | action: { | 303 | emailPayload = { |
311 | text: 'View report #' + parameters.videoAbuse.id, | 304 | template: 'video-abuse-new', |
312 | url: videoAbuseUrl | 305 | to, |
306 | subject: `New video abuse report from ${reporter}`, | ||
307 | locals: { | ||
308 | videoUrl, | ||
309 | isLocal: video.remote === false, | ||
310 | videoCreatedAt: new Date(video.createdAt).toLocaleString(), | ||
311 | videoPublishedAt: new Date(video.publishedAt).toLocaleString(), | ||
312 | videoName: video.name, | ||
313 | reason: abuse.reason, | ||
314 | videoChannel: video.VideoChannel, | ||
315 | action | ||
316 | } | ||
317 | } | ||
318 | } else if (abuseInstance.VideoCommentAbuse) { | ||
319 | const comment = abuseInstance.VideoCommentAbuse.VideoComment | ||
320 | const commentUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() + ';threadId=' + comment.getThreadId() | ||
321 | |||
322 | emailPayload = { | ||
323 | template: 'comment-abuse-new', | ||
324 | to, | ||
325 | subject: `New comment abuse report from ${reporter}`, | ||
326 | locals: { | ||
327 | commentUrl, | ||
328 | isLocal: comment.isOwned(), | ||
329 | commentCreatedAt: new Date(comment.createdAt).toLocaleString(), | ||
330 | reason: abuse.reason, | ||
331 | flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(), | ||
332 | action | ||
333 | } | ||
334 | } | ||
335 | } else { | ||
336 | const account = abuseInstance.FlaggedAccount | ||
337 | const accountUrl = account.getClientUrl() | ||
338 | |||
339 | emailPayload = { | ||
340 | template: 'account-abuse-new', | ||
341 | to, | ||
342 | subject: `New account abuse report from ${reporter}`, | ||
343 | locals: { | ||
344 | accountUrl, | ||
345 | accountDisplayName: account.getDisplayName(), | ||
346 | isLocal: account.isOwned(), | ||
347 | reason: abuse.reason, | ||
348 | action | ||
313 | } | 349 | } |
314 | } | 350 | } |
315 | } | 351 | } |
diff --git a/server/lib/emails/account-abuse-new/html.pug b/server/lib/emails/account-abuse-new/html.pug new file mode 100644 index 000000000..06be8025b --- /dev/null +++ b/server/lib/emails/account-abuse-new/html.pug | |||
@@ -0,0 +1,14 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins.pug | ||
3 | |||
4 | block title | ||
5 | | An account is pending moderation | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account " | ||
10 | a(href=accountUrl) #{accountDisplayName} | ||
11 | |||
12 | p The reporter, #{reporter}, cited the following reason(s): | ||
13 | blockquote #{reason} | ||
14 | br(style="display: none;") | ||
diff --git a/server/lib/emails/common/mixins.pug b/server/lib/emails/common/mixins.pug index 76b805a24..831211864 100644 --- a/server/lib/emails/common/mixins.pug +++ b/server/lib/emails/common/mixins.pug | |||
@@ -1,3 +1,7 @@ | |||
1 | mixin channel(channel) | 1 | mixin channel(channel) |
2 | - var handle = `${channel.name}@${channel.host}` | 2 | - var handle = `${channel.name}@${channel.host}` |
3 | | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] \ No newline at end of file | 3 | | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] |
4 | |||
5 | mixin account(account) | ||
6 | - var handle = `${account.name}@${account.host}` | ||
7 | | #[a(href=`${WEBSERVER.URL}/accounts/${handle}` title=handle) #{account.displayName}] | ||
diff --git a/server/lib/emails/video-abuse-new/html.pug b/server/lib/emails/video-abuse-new/html.pug index 999c89d26..a1acdabdc 100644 --- a/server/lib/emails/video-abuse-new/html.pug +++ b/server/lib/emails/video-abuse-new/html.pug | |||
@@ -6,13 +6,13 @@ block title | |||
6 | 6 | ||
7 | block content | 7 | block content |
8 | p | 8 | p |
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{videoAbuse.video.channel.isLocal ? '' : 'remote '}video " | 9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}video " |
10 | a(href=videoUrl) #{videoAbuse.video.name} | 10 | a(href=videoUrl) #{videoName} |
11 | | " by #[+channel(videoAbuse.video.channel)] | 11 | | " by #[+channel(videoChannel)] |
12 | if videoPublishedAt | 12 | if videoPublishedAt |
13 | | , published the #{videoPublishedAt}. | 13 | | , published the #{videoPublishedAt}. |
14 | else | 14 | else |
15 | | , uploaded the #{videoCreatedAt} but not yet published. | 15 | | , uploaded the #{videoCreatedAt} but not yet published. |
16 | p The reporter, #{reporter}, cited the following reason(s): | 16 | p The reporter, #{reporter}, cited the following reason(s): |
17 | blockquote #{videoAbuse.reason} | 17 | blockquote #{reason} |
18 | br(style="display: none;") | 18 | br(style="display: none;") |
diff --git a/server/lib/emails/video-comment-abuse-new/html.pug b/server/lib/emails/video-comment-abuse-new/html.pug new file mode 100644 index 000000000..170b79576 --- /dev/null +++ b/server/lib/emails/video-comment-abuse-new/html.pug | |||
@@ -0,0 +1,15 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins.pug | ||
3 | |||
4 | block title | ||
5 | | A comment is pending moderation | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment " | ||
10 | a(href=commentUrl) of #{flaggedAccount} | ||
11 | | created on #{commentCreatedAt} | ||
12 | |||
13 | p The reporter, #{reporter}, cited the following reason(s): | ||
14 | blockquote #{reason} | ||
15 | br(style="display: none;") | ||
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts index 60d1b4053..4fc9cd747 100644 --- a/server/lib/moderation.ts +++ b/server/lib/moderation.ts | |||
@@ -1,15 +1,33 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { PathLike } from 'fs-extra' |
2 | import { VideoCommentModel } from '../models/video/video-comment' | 2 | import { Transaction } from 'sequelize/types' |
3 | import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' | 3 | import { AbuseAuditView, auditLoggerFactory } from '@server/helpers/audit-logger' |
4 | import { logger } from '@server/helpers/logger' | ||
5 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
6 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' | ||
7 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
8 | import { VideoFileModel } from '@server/models/video/video-file' | ||
9 | import { FilteredModelAttributes } from '@server/types' | ||
10 | import { | ||
11 | MAbuseFull, | ||
12 | MAccountDefault, | ||
13 | MAccountLight, | ||
14 | MCommentAbuseAccountVideo, | ||
15 | MCommentOwnerVideo, | ||
16 | MUser, | ||
17 | MVideoAbuseVideoFull, | ||
18 | MVideoAccountLightBlacklistAllFiles | ||
19 | } from '@server/types/models' | ||
20 | import { ActivityCreate } from '../../shared/models/activitypub' | ||
21 | import { VideoTorrentObject } from '../../shared/models/activitypub/objects' | ||
22 | import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' | ||
4 | import { VideoCreate, VideoImportCreate } from '../../shared/models/videos' | 23 | import { VideoCreate, VideoImportCreate } from '../../shared/models/videos' |
24 | import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' | ||
5 | import { UserModel } from '../models/account/user' | 25 | import { UserModel } from '../models/account/user' |
6 | import { VideoTorrentObject } from '../../shared/models/activitypub/objects' | ||
7 | import { ActivityCreate } from '../../shared/models/activitypub' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | 26 | import { ActorModel } from '../models/activitypub/actor' |
9 | import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' | 27 | import { VideoModel } from '../models/video/video' |
10 | import { VideoFileModel } from '@server/models/video/video-file' | 28 | import { VideoCommentModel } from '../models/video/video-comment' |
11 | import { PathLike } from 'fs-extra' | 29 | import { sendAbuse } from './activitypub/send/send-flag' |
12 | import { MUser } from '@server/types/models' | 30 | import { Notifier } from './notifier' |
13 | 31 | ||
14 | export type AcceptResult = { | 32 | export type AcceptResult = { |
15 | accepted: boolean | 33 | accepted: boolean |
@@ -73,6 +91,89 @@ function isPostImportVideoAccepted (object: { | |||
73 | return { accepted: true } | 91 | return { accepted: true } |
74 | } | 92 | } |
75 | 93 | ||
94 | async function createVideoAbuse (options: { | ||
95 | baseAbuse: FilteredModelAttributes<AbuseModel> | ||
96 | videoInstance: MVideoAccountLightBlacklistAllFiles | ||
97 | startAt: number | ||
98 | endAt: number | ||
99 | transaction: Transaction | ||
100 | reporterAccount: MAccountDefault | ||
101 | }) { | ||
102 | const { baseAbuse, videoInstance, startAt, endAt, transaction, reporterAccount } = options | ||
103 | |||
104 | const associateFun = async (abuseInstance: MAbuseFull) => { | ||
105 | const videoAbuseInstance: MVideoAbuseVideoFull = await VideoAbuseModel.create({ | ||
106 | abuseId: abuseInstance.id, | ||
107 | videoId: videoInstance.id, | ||
108 | startAt: startAt, | ||
109 | endAt: endAt | ||
110 | }, { transaction }) | ||
111 | |||
112 | videoAbuseInstance.Video = videoInstance | ||
113 | abuseInstance.VideoAbuse = videoAbuseInstance | ||
114 | |||
115 | return { isOwned: videoInstance.isOwned() } | ||
116 | } | ||
117 | |||
118 | return createAbuse({ | ||
119 | base: baseAbuse, | ||
120 | reporterAccount, | ||
121 | flaggedAccount: videoInstance.VideoChannel.Account, | ||
122 | transaction, | ||
123 | associateFun | ||
124 | }) | ||
125 | } | ||
126 | |||
127 | function createVideoCommentAbuse (options: { | ||
128 | baseAbuse: FilteredModelAttributes<AbuseModel> | ||
129 | commentInstance: MCommentOwnerVideo | ||
130 | transaction: Transaction | ||
131 | reporterAccount: MAccountDefault | ||
132 | }) { | ||
133 | const { baseAbuse, commentInstance, transaction, reporterAccount } = options | ||
134 | |||
135 | const associateFun = async (abuseInstance: MAbuseFull) => { | ||
136 | const commentAbuseInstance: MCommentAbuseAccountVideo = await VideoCommentAbuseModel.create({ | ||
137 | abuseId: abuseInstance.id, | ||
138 | videoCommentId: commentInstance.id | ||
139 | }, { transaction }) | ||
140 | |||
141 | commentAbuseInstance.VideoComment = commentInstance | ||
142 | abuseInstance.VideoCommentAbuse = commentAbuseInstance | ||
143 | |||
144 | return { isOwned: commentInstance.isOwned() } | ||
145 | } | ||
146 | |||
147 | return createAbuse({ | ||
148 | base: baseAbuse, | ||
149 | reporterAccount, | ||
150 | flaggedAccount: commentInstance.Account, | ||
151 | transaction, | ||
152 | associateFun | ||
153 | }) | ||
154 | } | ||
155 | |||
156 | function createAccountAbuse (options: { | ||
157 | baseAbuse: FilteredModelAttributes<AbuseModel> | ||
158 | accountInstance: MAccountDefault | ||
159 | transaction: Transaction | ||
160 | reporterAccount: MAccountDefault | ||
161 | }) { | ||
162 | const { baseAbuse, accountInstance, transaction, reporterAccount } = options | ||
163 | |||
164 | const associateFun = async () => { | ||
165 | return { isOwned: accountInstance.isOwned() } | ||
166 | } | ||
167 | |||
168 | return createAbuse({ | ||
169 | base: baseAbuse, | ||
170 | reporterAccount, | ||
171 | flaggedAccount: accountInstance, | ||
172 | transaction, | ||
173 | associateFun | ||
174 | }) | ||
175 | } | ||
176 | |||
76 | export { | 177 | export { |
77 | isLocalVideoAccepted, | 178 | isLocalVideoAccepted, |
78 | isLocalVideoThreadAccepted, | 179 | isLocalVideoThreadAccepted, |
@@ -80,5 +181,48 @@ export { | |||
80 | isRemoteVideoCommentAccepted, | 181 | isRemoteVideoCommentAccepted, |
81 | isLocalVideoCommentReplyAccepted, | 182 | isLocalVideoCommentReplyAccepted, |
82 | isPreImportVideoAccepted, | 183 | isPreImportVideoAccepted, |
83 | isPostImportVideoAccepted | 184 | isPostImportVideoAccepted, |
185 | |||
186 | createAbuse, | ||
187 | createVideoAbuse, | ||
188 | createVideoCommentAbuse, | ||
189 | createAccountAbuse | ||
190 | } | ||
191 | |||
192 | // --------------------------------------------------------------------------- | ||
193 | |||
194 | async function createAbuse (options: { | ||
195 | base: FilteredModelAttributes<AbuseModel> | ||
196 | reporterAccount: MAccountDefault | ||
197 | flaggedAccount: MAccountLight | ||
198 | associateFun: (abuseInstance: MAbuseFull) => Promise<{ isOwned: boolean} > | ||
199 | transaction: Transaction | ||
200 | }) { | ||
201 | const { base, reporterAccount, flaggedAccount, associateFun, transaction } = options | ||
202 | const auditLogger = auditLoggerFactory('abuse') | ||
203 | |||
204 | const abuseAttributes = Object.assign({}, base, { flaggedAccountId: flaggedAccount.id }) | ||
205 | const abuseInstance: MAbuseFull = await AbuseModel.create(abuseAttributes, { transaction }) | ||
206 | |||
207 | abuseInstance.ReporterAccount = reporterAccount | ||
208 | abuseInstance.FlaggedAccount = flaggedAccount | ||
209 | |||
210 | const { isOwned } = await associateFun(abuseInstance) | ||
211 | |||
212 | if (isOwned === false) { | ||
213 | await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction) | ||
214 | } | ||
215 | |||
216 | const abuseJSON = abuseInstance.toFormattedJSON() | ||
217 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON)) | ||
218 | |||
219 | Notifier.Instance.notifyOnNewAbuse({ | ||
220 | abuse: abuseJSON, | ||
221 | abuseInstance, | ||
222 | reporter: reporterAccount.Actor.getIdentifier() | ||
223 | }) | ||
224 | |||
225 | logger.info('Abuse report %d created.', abuseInstance.id) | ||
226 | |||
227 | return abuseJSON | ||
84 | } | 228 | } |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 943a087d2..40cff66d2 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -8,23 +8,18 @@ import { | |||
8 | MUserWithNotificationSetting, | 8 | MUserWithNotificationSetting, |
9 | UserNotificationModelForApi | 9 | UserNotificationModelForApi |
10 | } from '@server/types/models/user' | 10 | } from '@server/types/models/user' |
11 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' | ||
11 | import { MVideoImportVideo } from '@server/types/models/video/video-import' | 12 | import { MVideoImportVideo } from '@server/types/models/video/video-import' |
13 | import { Abuse } from '@shared/models' | ||
12 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' | 14 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' |
13 | import { VideoAbuse, VideoPrivacy, VideoState } from '../../shared/models/videos' | 15 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' |
14 | import { logger } from '../helpers/logger' | 16 | import { logger } from '../helpers/logger' |
15 | import { CONFIG } from '../initializers/config' | 17 | import { CONFIG } from '../initializers/config' |
16 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 18 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
17 | import { UserModel } from '../models/account/user' | 19 | import { UserModel } from '../models/account/user' |
18 | import { UserNotificationModel } from '../models/account/user-notification' | 20 | import { UserNotificationModel } from '../models/account/user-notification' |
19 | import { MAccountServer, MActorFollowFull } from '../types/models' | 21 | import { MAbuseFull, MAbuseVideo, MAccountServer, MActorFollowFull } from '../types/models' |
20 | import { | 22 | import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' |
21 | MCommentOwnerVideo, | ||
22 | MVideoAbuseVideo, | ||
23 | MVideoAccountLight, | ||
24 | MVideoBlacklistLightVideo, | ||
25 | MVideoBlacklistVideo, | ||
26 | MVideoFullLight | ||
27 | } from '../types/models/video' | ||
28 | import { isBlockedByServerOrAccount } from './blocklist' | 23 | import { isBlockedByServerOrAccount } from './blocklist' |
29 | import { Emailer } from './emailer' | 24 | import { Emailer } from './emailer' |
30 | import { PeerTubeSocket } from './peertube-socket' | 25 | import { PeerTubeSocket } from './peertube-socket' |
@@ -78,9 +73,9 @@ class Notifier { | |||
78 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) | 73 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) |
79 | } | 74 | } |
80 | 75 | ||
81 | notifyOnNewVideoAbuse (parameters: { videoAbuse: VideoAbuse, videoAbuseInstance: MVideoAbuseVideo, reporter: string }): void { | 76 | notifyOnNewAbuse (parameters: { abuse: Abuse, abuseInstance: MAbuseFull, reporter: string }): void { |
82 | this.notifyModeratorsOfNewVideoAbuse(parameters) | 77 | this.notifyModeratorsOfNewAbuse(parameters) |
83 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', parameters.videoAbuseInstance.Video.url, { err })) | 78 | .catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err })) |
84 | } | 79 | } |
85 | 80 | ||
86 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { | 81 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { |
@@ -354,33 +349,37 @@ class Notifier { | |||
354 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | 349 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) |
355 | } | 350 | } |
356 | 351 | ||
357 | private async notifyModeratorsOfNewVideoAbuse (parameters: { | 352 | private async notifyModeratorsOfNewAbuse (parameters: { |
358 | videoAbuse: VideoAbuse | 353 | abuse: Abuse |
359 | videoAbuseInstance: MVideoAbuseVideo | 354 | abuseInstance: MAbuseFull |
360 | reporter: string | 355 | reporter: string |
361 | }) { | 356 | }) { |
362 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) | 357 | const { abuse, abuseInstance } = parameters |
358 | |||
359 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES) | ||
363 | if (moderators.length === 0) return | 360 | if (moderators.length === 0) return |
364 | 361 | ||
365 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, parameters.videoAbuseInstance.Video.url) | 362 | const url = abuseInstance.VideoAbuse?.Video?.url || abuseInstance.VideoCommentAbuse?.VideoComment?.url |
363 | |||
364 | logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url) | ||
366 | 365 | ||
367 | function settingGetter (user: MUserWithNotificationSetting) { | 366 | function settingGetter (user: MUserWithNotificationSetting) { |
368 | return user.NotificationSetting.videoAbuseAsModerator | 367 | return user.NotificationSetting.videoAbuseAsModerator |
369 | } | 368 | } |
370 | 369 | ||
371 | async function notificationCreator (user: MUserWithNotificationSetting) { | 370 | async function notificationCreator (user: MUserWithNotificationSetting) { |
372 | const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({ | 371 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
373 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, | 372 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, |
374 | userId: user.id, | 373 | userId: user.id, |
375 | videoAbuseId: parameters.videoAbuse.id | 374 | abuseId: abuse.id |
376 | }) | 375 | }) |
377 | notification.VideoAbuse = parameters.videoAbuseInstance | 376 | notification.Abuse = abuseInstance |
378 | 377 | ||
379 | return notification | 378 | return notification |
380 | } | 379 | } |
381 | 380 | ||
382 | function emailSender (emails: string[]) { | 381 | function emailSender (emails: string[]) { |
383 | return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, parameters) | 382 | return Emailer.Instance.addAbuseModeratorsNotification(emails, parameters) |
384 | } | 383 | } |
385 | 384 | ||
386 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 385 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts new file mode 100644 index 000000000..f098e2ff9 --- /dev/null +++ b/server/middlewares/validators/abuse.ts | |||
@@ -0,0 +1,253 @@ | |||
1 | import * as express from 'express' | ||
2 | import { body, param, query } from 'express-validator' | ||
3 | import { | ||
4 | isAbuseModerationCommentValid, | ||
5 | isAbusePredefinedReasonsValid, | ||
6 | isAbusePredefinedReasonValid, | ||
7 | isAbuseReasonValid, | ||
8 | isAbuseStateValid, | ||
9 | isAbuseTimestampCoherent, | ||
10 | isAbuseTimestampValid, | ||
11 | isAbuseVideoIsValid | ||
12 | } from '@server/helpers/custom-validators/abuses' | ||
13 | import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers/custom-validators/misc' | ||
14 | import { logger } from '@server/helpers/logger' | ||
15 | import { doesAbuseExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares' | ||
16 | import { areValidationErrors } from './utils' | ||
17 | |||
18 | const abuseReportValidator = [ | ||
19 | param('videoId') | ||
20 | .custom(isIdOrUUIDValid) | ||
21 | .not() | ||
22 | .isEmpty() | ||
23 | .withMessage('Should have a valid videoId'), | ||
24 | body('reason') | ||
25 | .custom(isAbuseReasonValid) | ||
26 | .withMessage('Should have a valid reason'), | ||
27 | body('predefinedReasons') | ||
28 | .optional() | ||
29 | .custom(isAbusePredefinedReasonsValid) | ||
30 | .withMessage('Should have a valid list of predefined reasons'), | ||
31 | body('startAt') | ||
32 | .optional() | ||
33 | .customSanitizer(toIntOrNull) | ||
34 | .custom(isAbuseTimestampValid) | ||
35 | .withMessage('Should have valid starting time value'), | ||
36 | body('endAt') | ||
37 | .optional() | ||
38 | .customSanitizer(toIntOrNull) | ||
39 | .custom(isAbuseTimestampValid) | ||
40 | .withMessage('Should have valid ending time value') | ||
41 | .bail() | ||
42 | .custom(isAbuseTimestampCoherent) | ||
43 | .withMessage('Should have a startAt timestamp beginning before endAt'), | ||
44 | |||
45 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
46 | logger.debug('Checking abuseReport parameters', { parameters: req.body }) | ||
47 | |||
48 | if (areValidationErrors(req, res)) return | ||
49 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
50 | |||
51 | // TODO: check comment or video (exlusive) | ||
52 | |||
53 | return next() | ||
54 | } | ||
55 | ] | ||
56 | |||
57 | const abuseGetValidator = [ | ||
58 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
59 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
60 | |||
61 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
62 | logger.debug('Checking abuseGetValidator parameters', { parameters: req.body }) | ||
63 | |||
64 | if (areValidationErrors(req, res)) return | ||
65 | // if (!await doesAbuseExist(req.params.id, req.params.videoId, res)) return | ||
66 | |||
67 | return next() | ||
68 | } | ||
69 | ] | ||
70 | |||
71 | const abuseUpdateValidator = [ | ||
72 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
73 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
74 | body('state') | ||
75 | .optional() | ||
76 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
77 | body('moderationComment') | ||
78 | .optional() | ||
79 | .custom(isAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), | ||
80 | |||
81 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
82 | logger.debug('Checking abuseUpdateValidator parameters', { parameters: req.body }) | ||
83 | |||
84 | if (areValidationErrors(req, res)) return | ||
85 | // if (!await doesAbuseExist(req.params.id, req.params.videoId, res)) return | ||
86 | |||
87 | return next() | ||
88 | } | ||
89 | ] | ||
90 | |||
91 | const abuseListValidator = [ | ||
92 | query('id') | ||
93 | .optional() | ||
94 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
95 | query('predefinedReason') | ||
96 | .optional() | ||
97 | .custom(isAbusePredefinedReasonValid) | ||
98 | .withMessage('Should have a valid predefinedReason'), | ||
99 | query('search') | ||
100 | .optional() | ||
101 | .custom(exists).withMessage('Should have a valid search'), | ||
102 | query('state') | ||
103 | .optional() | ||
104 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
105 | query('videoIs') | ||
106 | .optional() | ||
107 | .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), | ||
108 | query('searchReporter') | ||
109 | .optional() | ||
110 | .custom(exists).withMessage('Should have a valid reporter search'), | ||
111 | query('searchReportee') | ||
112 | .optional() | ||
113 | .custom(exists).withMessage('Should have a valid reportee search'), | ||
114 | query('searchVideo') | ||
115 | .optional() | ||
116 | .custom(exists).withMessage('Should have a valid video search'), | ||
117 | query('searchVideoChannel') | ||
118 | .optional() | ||
119 | .custom(exists).withMessage('Should have a valid video channel search'), | ||
120 | |||
121 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
122 | logger.debug('Checking abuseListValidator parameters', { parameters: req.body }) | ||
123 | |||
124 | if (areValidationErrors(req, res)) return | ||
125 | |||
126 | return next() | ||
127 | } | ||
128 | ] | ||
129 | |||
130 | // FIXME: deprecated in 2.3. Remove these validators | ||
131 | |||
132 | const videoAbuseReportValidator = [ | ||
133 | param('videoId') | ||
134 | .custom(isIdOrUUIDValid) | ||
135 | .not() | ||
136 | .isEmpty() | ||
137 | .withMessage('Should have a valid videoId'), | ||
138 | body('reason') | ||
139 | .custom(isAbuseReasonValid) | ||
140 | .withMessage('Should have a valid reason'), | ||
141 | body('predefinedReasons') | ||
142 | .optional() | ||
143 | .custom(isAbusePredefinedReasonsValid) | ||
144 | .withMessage('Should have a valid list of predefined reasons'), | ||
145 | body('startAt') | ||
146 | .optional() | ||
147 | .customSanitizer(toIntOrNull) | ||
148 | .custom(isAbuseTimestampValid) | ||
149 | .withMessage('Should have valid starting time value'), | ||
150 | body('endAt') | ||
151 | .optional() | ||
152 | .customSanitizer(toIntOrNull) | ||
153 | .custom(isAbuseTimestampValid) | ||
154 | .withMessage('Should have valid ending time value') | ||
155 | .bail() | ||
156 | .custom(isAbuseTimestampCoherent) | ||
157 | .withMessage('Should have a startAt timestamp beginning before endAt'), | ||
158 | |||
159 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
160 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | ||
161 | |||
162 | if (areValidationErrors(req, res)) return | ||
163 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
164 | |||
165 | return next() | ||
166 | } | ||
167 | ] | ||
168 | |||
169 | const videoAbuseGetValidator = [ | ||
170 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
171 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
172 | |||
173 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
174 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | ||
175 | |||
176 | if (areValidationErrors(req, res)) return | ||
177 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
178 | |||
179 | return next() | ||
180 | } | ||
181 | ] | ||
182 | |||
183 | const videoAbuseUpdateValidator = [ | ||
184 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
185 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
186 | body('state') | ||
187 | .optional() | ||
188 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
189 | body('moderationComment') | ||
190 | .optional() | ||
191 | .custom(isAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), | ||
192 | |||
193 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
194 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | ||
195 | |||
196 | if (areValidationErrors(req, res)) return | ||
197 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
198 | |||
199 | return next() | ||
200 | } | ||
201 | ] | ||
202 | |||
203 | const videoAbuseListValidator = [ | ||
204 | query('id') | ||
205 | .optional() | ||
206 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
207 | query('predefinedReason') | ||
208 | .optional() | ||
209 | .custom(isAbusePredefinedReasonValid) | ||
210 | .withMessage('Should have a valid predefinedReason'), | ||
211 | query('search') | ||
212 | .optional() | ||
213 | .custom(exists).withMessage('Should have a valid search'), | ||
214 | query('state') | ||
215 | .optional() | ||
216 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
217 | query('videoIs') | ||
218 | .optional() | ||
219 | .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), | ||
220 | query('searchReporter') | ||
221 | .optional() | ||
222 | .custom(exists).withMessage('Should have a valid reporter search'), | ||
223 | query('searchReportee') | ||
224 | .optional() | ||
225 | .custom(exists).withMessage('Should have a valid reportee search'), | ||
226 | query('searchVideo') | ||
227 | .optional() | ||
228 | .custom(exists).withMessage('Should have a valid video search'), | ||
229 | query('searchVideoChannel') | ||
230 | .optional() | ||
231 | .custom(exists).withMessage('Should have a valid video channel search'), | ||
232 | |||
233 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
234 | logger.debug('Checking videoAbuseListValidator parameters', { parameters: req.body }) | ||
235 | |||
236 | if (areValidationErrors(req, res)) return | ||
237 | |||
238 | return next() | ||
239 | } | ||
240 | ] | ||
241 | |||
242 | // --------------------------------------------------------------------------- | ||
243 | |||
244 | export { | ||
245 | abuseListValidator, | ||
246 | abuseReportValidator, | ||
247 | abuseGetValidator, | ||
248 | abuseUpdateValidator, | ||
249 | videoAbuseReportValidator, | ||
250 | videoAbuseGetValidator, | ||
251 | videoAbuseUpdateValidator, | ||
252 | videoAbuseListValidator | ||
253 | } | ||
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 65dd00335..4086d77aa 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './abuse' | ||
1 | export * from './account' | 2 | export * from './account' |
2 | export * from './blocklist' | 3 | export * from './blocklist' |
3 | export * from './oembed' | 4 | export * from './oembed' |
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index b76dab722..29aba0436 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -5,7 +5,7 @@ import { checkSort, createSortableColumns } from './utils' | |||
5 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) | 5 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) |
6 | const SORTABLE_ACCOUNTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS) | 6 | const SORTABLE_ACCOUNTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS) |
7 | const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS) | 7 | const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS) |
8 | const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES) | 8 | const SORTABLE_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ABUSES) |
9 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) | 9 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) |
10 | const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH) | 10 | const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH) |
11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) | 11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) |
@@ -28,7 +28,7 @@ const SORTABLE_VIDEO_REDUNDANCIES_COLUMNS = createSortableColumns(SORTABLE_COLUM | |||
28 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) | 28 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) |
29 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) | 29 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) |
30 | const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS) | 30 | const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS) |
31 | const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS) | 31 | const abusesSortValidator = checkSort(SORTABLE_ABUSES_COLUMNS) |
32 | const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) | 32 | const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) |
33 | const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) | 33 | const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) |
34 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) | 34 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) |
@@ -52,7 +52,7 @@ const videoRedundanciesSortValidator = checkSort(SORTABLE_VIDEO_REDUNDANCIES_COL | |||
52 | 52 | ||
53 | export { | 53 | export { |
54 | usersSortValidator, | 54 | usersSortValidator, |
55 | videoAbusesSortValidator, | 55 | abusesSortValidator, |
56 | videoChannelsSortValidator, | 56 | videoChannelsSortValidator, |
57 | videoImportsSortValidator, | 57 | videoImportsSortValidator, |
58 | videosSearchSortValidator, | 58 | videosSearchSortValidator, |
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts index a0d585b93..1eabada0a 100644 --- a/server/middlewares/validators/videos/index.ts +++ b/server/middlewares/validators/videos/index.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | export * from './video-abuses' | ||
2 | export * from './video-blacklist' | 1 | export * from './video-blacklist' |
3 | export * from './video-captions' | 2 | export * from './video-captions' |
4 | export * from './video-channels' | 3 | export * from './video-channels' |
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts deleted file mode 100644 index 5bbd1e3c6..000000000 --- a/server/middlewares/validators/videos/video-abuses.ts +++ /dev/null | |||
@@ -1,135 +0,0 @@ | |||
1 | import * as express from 'express' | ||
2 | import { body, param, query } from 'express-validator' | ||
3 | import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' | ||
4 | import { | ||
5 | isAbuseVideoIsValid, | ||
6 | isVideoAbuseModerationCommentValid, | ||
7 | isVideoAbuseReasonValid, | ||
8 | isVideoAbuseStateValid, | ||
9 | isVideoAbusePredefinedReasonsValid, | ||
10 | isVideoAbusePredefinedReasonValid, | ||
11 | isVideoAbuseTimestampValid, | ||
12 | isVideoAbuseTimestampCoherent | ||
13 | } from '../../../helpers/custom-validators/video-abuses' | ||
14 | import { logger } from '../../../helpers/logger' | ||
15 | import { doesVideoAbuseExist, doesVideoExist } from '../../../helpers/middlewares' | ||
16 | import { areValidationErrors } from '../utils' | ||
17 | |||
18 | const videoAbuseReportValidator = [ | ||
19 | param('videoId') | ||
20 | .custom(isIdOrUUIDValid) | ||
21 | .not() | ||
22 | .isEmpty() | ||
23 | .withMessage('Should have a valid videoId'), | ||
24 | body('reason') | ||
25 | .custom(isVideoAbuseReasonValid) | ||
26 | .withMessage('Should have a valid reason'), | ||
27 | body('predefinedReasons') | ||
28 | .optional() | ||
29 | .custom(isVideoAbusePredefinedReasonsValid) | ||
30 | .withMessage('Should have a valid list of predefined reasons'), | ||
31 | body('startAt') | ||
32 | .optional() | ||
33 | .customSanitizer(toIntOrNull) | ||
34 | .custom(isVideoAbuseTimestampValid) | ||
35 | .withMessage('Should have valid starting time value'), | ||
36 | body('endAt') | ||
37 | .optional() | ||
38 | .customSanitizer(toIntOrNull) | ||
39 | .custom(isVideoAbuseTimestampValid) | ||
40 | .withMessage('Should have valid ending time value') | ||
41 | .bail() | ||
42 | .custom(isVideoAbuseTimestampCoherent) | ||
43 | .withMessage('Should have a startAt timestamp beginning before endAt'), | ||
44 | |||
45 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
46 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | ||
47 | |||
48 | if (areValidationErrors(req, res)) return | ||
49 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
50 | |||
51 | return next() | ||
52 | } | ||
53 | ] | ||
54 | |||
55 | const videoAbuseGetValidator = [ | ||
56 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
57 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
58 | |||
59 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
60 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | ||
61 | |||
62 | if (areValidationErrors(req, res)) return | ||
63 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
64 | |||
65 | return next() | ||
66 | } | ||
67 | ] | ||
68 | |||
69 | const videoAbuseUpdateValidator = [ | ||
70 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
71 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
72 | body('state') | ||
73 | .optional() | ||
74 | .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
75 | body('moderationComment') | ||
76 | .optional() | ||
77 | .custom(isVideoAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), | ||
78 | |||
79 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
80 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | ||
81 | |||
82 | if (areValidationErrors(req, res)) return | ||
83 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
84 | |||
85 | return next() | ||
86 | } | ||
87 | ] | ||
88 | |||
89 | const videoAbuseListValidator = [ | ||
90 | query('id') | ||
91 | .optional() | ||
92 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
93 | query('predefinedReason') | ||
94 | .optional() | ||
95 | .custom(isVideoAbusePredefinedReasonValid) | ||
96 | .withMessage('Should have a valid predefinedReason'), | ||
97 | query('search') | ||
98 | .optional() | ||
99 | .custom(exists).withMessage('Should have a valid search'), | ||
100 | query('state') | ||
101 | .optional() | ||
102 | .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
103 | query('videoIs') | ||
104 | .optional() | ||
105 | .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), | ||
106 | query('searchReporter') | ||
107 | .optional() | ||
108 | .custom(exists).withMessage('Should have a valid reporter search'), | ||
109 | query('searchReportee') | ||
110 | .optional() | ||
111 | .custom(exists).withMessage('Should have a valid reportee search'), | ||
112 | query('searchVideo') | ||
113 | .optional() | ||
114 | .custom(exists).withMessage('Should have a valid video search'), | ||
115 | query('searchVideoChannel') | ||
116 | .optional() | ||
117 | .custom(exists).withMessage('Should have a valid video channel search'), | ||
118 | |||
119 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
120 | logger.debug('Checking videoAbuseListValidator parameters', { parameters: req.body }) | ||
121 | |||
122 | if (areValidationErrors(req, res)) return | ||
123 | |||
124 | return next() | ||
125 | } | ||
126 | ] | ||
127 | |||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | export { | ||
131 | videoAbuseListValidator, | ||
132 | videoAbuseReportValidator, | ||
133 | videoAbuseGetValidator, | ||
134 | videoAbuseUpdateValidator | ||
135 | } | ||
diff --git a/server/models/video/video-abuse.ts b/server/models/abuse/abuse.ts index 1319332f0..4f99f9c9b 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/abuse/abuse.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { literal, Op } from 'sequelize' | 2 | import { invert } from 'lodash' |
3 | import { literal, Op, WhereOptions } from 'sequelize' | ||
3 | import { | 4 | import { |
4 | AllowNull, | 5 | AllowNull, |
5 | BelongsTo, | 6 | BelongsTo, |
@@ -8,36 +9,35 @@ import { | |||
8 | DataType, | 9 | DataType, |
9 | Default, | 10 | Default, |
10 | ForeignKey, | 11 | ForeignKey, |
12 | HasOne, | ||
11 | Is, | 13 | Is, |
12 | Model, | 14 | Model, |
13 | Scopes, | 15 | Scopes, |
14 | Table, | 16 | Table, |
15 | UpdatedAt | 17 | UpdatedAt |
16 | } from 'sequelize-typescript' | 18 | } from 'sequelize-typescript' |
17 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' | 19 | import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' |
18 | import { | ||
19 | VideoAbuseState, | ||
20 | VideoDetails, | ||
21 | VideoAbusePredefinedReasons, | ||
22 | VideoAbusePredefinedReasonsString, | ||
23 | videoAbusePredefinedReasonsMap | ||
24 | } from '../../../shared' | ||
25 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' | ||
26 | import { VideoAbuse } from '../../../shared/models/videos' | ||
27 | import { | 20 | import { |
28 | isVideoAbuseModerationCommentValid, | 21 | Abuse, |
29 | isVideoAbuseReasonValid, | 22 | AbuseObject, |
30 | isVideoAbuseStateValid | 23 | AbusePredefinedReasons, |
31 | } from '../../helpers/custom-validators/video-abuses' | 24 | abusePredefinedReasonsMap, |
32 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | 25 | AbusePredefinedReasonsString, |
33 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../types/models' | 26 | AbuseState, |
34 | import { AccountModel } from '../account/account' | 27 | AbuseVideoIs, |
28 | VideoAbuse | ||
29 | } from '@shared/models' | ||
30 | import { AbuseFilter } from '@shared/models/moderation/abuse/abuse-filter' | ||
31 | import { CONSTRAINTS_FIELDS, ABUSE_STATES } from '../../initializers/constants' | ||
32 | import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models' | ||
33 | import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' | ||
35 | import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils' | 34 | import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils' |
36 | import { ThumbnailModel } from './thumbnail' | 35 | import { ThumbnailModel } from '../video/thumbnail' |
37 | import { VideoModel } from './video' | 36 | import { VideoModel } from '../video/video' |
38 | import { VideoBlacklistModel } from './video-blacklist' | 37 | import { VideoBlacklistModel } from '../video/video-blacklist' |
39 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | 38 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' |
40 | import { invert } from 'lodash' | 39 | import { VideoAbuseModel } from './video-abuse' |
40 | import { VideoCommentAbuseModel } from './video-comment-abuse' | ||
41 | 41 | ||
42 | export enum ScopeNames { | 42 | export enum ScopeNames { |
43 | FOR_API = 'FOR_API' | 43 | FOR_API = 'FOR_API' |
@@ -49,20 +49,26 @@ export enum ScopeNames { | |||
49 | search?: string | 49 | search?: string |
50 | searchReporter?: string | 50 | searchReporter?: string |
51 | searchReportee?: string | 51 | searchReportee?: string |
52 | |||
53 | // video releated | ||
52 | searchVideo?: string | 54 | searchVideo?: string |
53 | searchVideoChannel?: string | 55 | searchVideoChannel?: string |
56 | videoIs?: AbuseVideoIs | ||
54 | 57 | ||
55 | // filters | 58 | // filters |
56 | id?: number | 59 | id?: number |
57 | predefinedReasonId?: number | 60 | predefinedReasonId?: number |
61 | filter?: AbuseFilter | ||
58 | 62 | ||
59 | state?: VideoAbuseState | 63 | state?: AbuseState |
60 | videoIs?: VideoAbuseVideoIs | ||
61 | 64 | ||
62 | // accountIds | 65 | // accountIds |
63 | serverAccountId: number | 66 | serverAccountId: number |
64 | userAccountId: number | 67 | userAccountId: number |
65 | }) => { | 68 | }) => { |
69 | const onlyBlacklisted = options.videoIs === 'blacklisted' | ||
70 | const videoRequired = !!(onlyBlacklisted || options.searchVideo || options.searchVideoChannel) | ||
71 | |||
66 | const where = { | 72 | const where = { |
67 | reporterAccountId: { | 73 | reporterAccountId: { |
68 | [Op.notIn]: literal('(' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')') | 74 | [Op.notIn]: literal('(' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')') |
@@ -70,33 +76,36 @@ export enum ScopeNames { | |||
70 | } | 76 | } |
71 | 77 | ||
72 | if (options.search) { | 78 | if (options.search) { |
79 | const escapedSearch = AbuseModel.sequelize.escape('%' + options.search + '%') | ||
80 | |||
73 | Object.assign(where, { | 81 | Object.assign(where, { |
74 | [Op.or]: [ | 82 | [Op.or]: [ |
75 | { | 83 | { |
76 | [Op.and]: [ | 84 | [Op.and]: [ |
77 | { videoId: { [Op.not]: null } }, | 85 | { '$VideoAbuse.videoId$': { [Op.not]: null } }, |
78 | searchAttribute(options.search, '$Video.name$') | 86 | searchAttribute(options.search, '$VideoAbuse.Video.name$') |
79 | ] | 87 | ] |
80 | }, | 88 | }, |
81 | { | 89 | { |
82 | [Op.and]: [ | 90 | [Op.and]: [ |
83 | { videoId: { [Op.not]: null } }, | 91 | { '$VideoAbuse.videoId$': { [Op.not]: null } }, |
84 | searchAttribute(options.search, '$Video.VideoChannel.name$') | 92 | searchAttribute(options.search, '$VideoAbuse.Video.VideoChannel.name$') |
85 | ] | 93 | ] |
86 | }, | 94 | }, |
87 | { | 95 | { |
88 | [Op.and]: [ | 96 | [Op.and]: [ |
89 | { deletedVideo: { [Op.not]: null } }, | 97 | { '$VideoAbuse.deletedVideo$': { [Op.not]: null } }, |
90 | { deletedVideo: searchAttribute(options.search, 'name') } | 98 | literal(`"VideoAbuse"."deletedVideo"->>'name' ILIKE ${escapedSearch}`) |
91 | ] | 99 | ] |
92 | }, | 100 | }, |
93 | { | 101 | { |
94 | [Op.and]: [ | 102 | [Op.and]: [ |
95 | { deletedVideo: { [Op.not]: null } }, | 103 | { '$VideoAbuse.deletedVideo$': { [Op.not]: null } }, |
96 | { deletedVideo: { channel: searchAttribute(options.search, 'displayName') } } | 104 | literal(`"VideoAbuse"."deletedVideo"->'channel'->>'displayName' ILIKE ${escapedSearch}`) |
97 | ] | 105 | ] |
98 | }, | 106 | }, |
99 | searchAttribute(options.search, '$Account.name$') | 107 | searchAttribute(options.search, '$ReporterAccount.name$'), |
108 | searchAttribute(options.search, '$FlaggedAccount.name$') | ||
100 | ] | 109 | ] |
101 | }) | 110 | }) |
102 | } | 111 | } |
@@ -106,7 +115,7 @@ export enum ScopeNames { | |||
106 | 115 | ||
107 | if (options.videoIs === 'deleted') { | 116 | if (options.videoIs === 'deleted') { |
108 | Object.assign(where, { | 117 | Object.assign(where, { |
109 | deletedVideo: { | 118 | '$VideoAbuse.deletedVideo$': { |
110 | [Op.not]: null | 119 | [Op.not]: null |
111 | } | 120 | } |
112 | }) | 121 | }) |
@@ -120,8 +129,6 @@ export enum ScopeNames { | |||
120 | }) | 129 | }) |
121 | } | 130 | } |
122 | 131 | ||
123 | const onlyBlacklisted = options.videoIs === 'blacklisted' | ||
124 | |||
125 | return { | 132 | return { |
126 | attributes: { | 133 | attributes: { |
127 | include: [ | 134 | include: [ |
@@ -131,7 +138,7 @@ export enum ScopeNames { | |||
131 | '(' + | 138 | '(' + |
132 | 'SELECT count(*) ' + | 139 | 'SELECT count(*) ' + |
133 | 'FROM "videoAbuse" ' + | 140 | 'FROM "videoAbuse" ' + |
134 | 'WHERE "videoId" = "VideoAbuseModel"."videoId" ' + | 141 | 'WHERE "videoId" = "VideoAbuse"."videoId" ' + |
135 | ')' | 142 | ')' |
136 | ), | 143 | ), |
137 | 'countReportsForVideo' | 144 | 'countReportsForVideo' |
@@ -146,7 +153,7 @@ export enum ScopeNames { | |||
146 | 'row_number() OVER (PARTITION BY "videoId" ORDER BY "createdAt") AS nth ' + | 153 | 'row_number() OVER (PARTITION BY "videoId" ORDER BY "createdAt") AS nth ' + |
147 | 'FROM "videoAbuse" ' + | 154 | 'FROM "videoAbuse" ' + |
148 | ') t ' + | 155 | ') t ' + |
149 | 'WHERE t.id = "VideoAbuseModel".id ' + | 156 | 'WHERE t.id = "VideoAbuse".id' + |
150 | ')' | 157 | ')' |
151 | ), | 158 | ), |
152 | 'nthReportForVideo' | 159 | 'nthReportForVideo' |
@@ -159,7 +166,7 @@ export enum ScopeNames { | |||
159 | 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + | 166 | 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + |
160 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | 167 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + |
161 | 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + | 168 | 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + |
162 | 'WHERE "account"."id" = "VideoAbuseModel"."reporterAccountId" ' + | 169 | 'WHERE "account"."id" = "AbuseModel"."reporterAccountId" ' + |
163 | ')' | 170 | ')' |
164 | ), | 171 | ), |
165 | 'countReportsForReporter__video' | 172 | 'countReportsForReporter__video' |
@@ -169,7 +176,7 @@ export enum ScopeNames { | |||
169 | '(' + | 176 | '(' + |
170 | 'SELECT count(DISTINCT "videoAbuse"."id") ' + | 177 | 'SELECT count(DISTINCT "videoAbuse"."id") ' + |
171 | 'FROM "videoAbuse" ' + | 178 | 'FROM "videoAbuse" ' + |
172 | `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "VideoAbuseModel"."reporterAccountId" ` + | 179 | `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "AbuseModel"."reporterAccountId" ` + |
173 | ')' | 180 | ')' |
174 | ), | 181 | ), |
175 | 'countReportsForReporter__deletedVideo' | 182 | 'countReportsForReporter__deletedVideo' |
@@ -182,8 +189,8 @@ export enum ScopeNames { | |||
182 | 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + | 189 | 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + |
183 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | 190 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + |
184 | 'INNER JOIN "account" ON ' + | 191 | 'INNER JOIN "account" ON ' + |
185 | '"videoChannel"."accountId" = "Video->VideoChannel"."accountId" ' + | 192 | '"videoChannel"."accountId" = "VideoAbuse->Video->VideoChannel"."accountId" ' + |
186 | `OR "videoChannel"."accountId" = CAST("VideoAbuseModel"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + | 193 | `OR "videoChannel"."accountId" = CAST("VideoAbuse"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + |
187 | ')' | 194 | ')' |
188 | ), | 195 | ), |
189 | 'countReportsForReportee__video' | 196 | 'countReportsForReportee__video' |
@@ -193,9 +200,9 @@ export enum ScopeNames { | |||
193 | '(' + | 200 | '(' + |
194 | 'SELECT count(DISTINCT "videoAbuse"."id") ' + | 201 | 'SELECT count(DISTINCT "videoAbuse"."id") ' + |
195 | 'FROM "videoAbuse" ' + | 202 | 'FROM "videoAbuse" ' + |
196 | `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "Video->VideoChannel"."accountId" ` + | 203 | `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "VideoAbuse->Video->VideoChannel"."accountId" ` + |
197 | `OR CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = ` + | 204 | `OR CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = ` + |
198 | `CAST("VideoAbuseModel"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + | 205 | `CAST("VideoAbuse"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + |
199 | ')' | 206 | ')' |
200 | ), | 207 | ), |
201 | 'countReportsForReportee__deletedVideo' | 208 | 'countReportsForReportee__deletedVideo' |
@@ -204,32 +211,47 @@ export enum ScopeNames { | |||
204 | }, | 211 | }, |
205 | include: [ | 212 | include: [ |
206 | { | 213 | { |
207 | model: AccountModel, | 214 | model: AccountModel.scope(AccountScopeNames.SUMMARY), |
215 | as: 'ReporterAccount', | ||
208 | required: true, | 216 | required: true, |
209 | where: searchAttribute(options.searchReporter, 'name') | 217 | where: searchAttribute(options.searchReporter, 'name') |
210 | }, | 218 | }, |
211 | { | 219 | { |
212 | model: VideoModel, | 220 | model: AccountModel.scope(AccountScopeNames.SUMMARY), |
213 | required: !!(onlyBlacklisted || options.searchVideo || options.searchReportee || options.searchVideoChannel), | 221 | as: 'FlaggedAccount', |
214 | where: searchAttribute(options.searchVideo, 'name'), | 222 | required: true, |
223 | where: searchAttribute(options.searchReportee, 'name') | ||
224 | }, | ||
225 | { | ||
226 | model: VideoAbuseModel, | ||
227 | required: options.filter === 'video' || !!options.videoIs || videoRequired, | ||
215 | include: [ | 228 | include: [ |
216 | { | 229 | { |
217 | model: ThumbnailModel | 230 | model: VideoModel, |
218 | }, | 231 | required: videoRequired, |
219 | { | 232 | where: searchAttribute(options.searchVideo, 'name'), |
220 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), | ||
221 | where: searchAttribute(options.searchVideoChannel, 'name'), | ||
222 | include: [ | 233 | include: [ |
223 | { | 234 | { |
224 | model: AccountModel, | 235 | model: ThumbnailModel |
225 | where: searchAttribute(options.searchReportee, 'name') | 236 | }, |
237 | { | ||
238 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: false } as SummaryOptions ] }), | ||
239 | where: searchAttribute(options.searchVideoChannel, 'name'), | ||
240 | required: true, | ||
241 | include: [ | ||
242 | { | ||
243 | model: AccountModel.scope(AccountScopeNames.SUMMARY), | ||
244 | required: true, | ||
245 | where: searchAttribute(options.searchReportee, 'name') | ||
246 | } | ||
247 | ] | ||
248 | }, | ||
249 | { | ||
250 | attributes: [ 'id', 'reason', 'unfederated' ], | ||
251 | model: VideoBlacklistModel, | ||
252 | required: onlyBlacklisted | ||
226 | } | 253 | } |
227 | ] | 254 | ] |
228 | }, | ||
229 | { | ||
230 | attributes: [ 'id', 'reason', 'unfederated' ], | ||
231 | model: VideoBlacklistModel, | ||
232 | required: onlyBlacklisted | ||
233 | } | 255 | } |
234 | ] | 256 | ] |
235 | } | 257 | } |
@@ -239,55 +261,40 @@ export enum ScopeNames { | |||
239 | } | 261 | } |
240 | })) | 262 | })) |
241 | @Table({ | 263 | @Table({ |
242 | tableName: 'videoAbuse', | 264 | tableName: 'abuse', |
243 | indexes: [ | 265 | indexes: [ |
244 | { | 266 | { |
245 | fields: [ 'videoId' ] | 267 | fields: [ 'reporterAccountId' ] |
246 | }, | 268 | }, |
247 | { | 269 | { |
248 | fields: [ 'reporterAccountId' ] | 270 | fields: [ 'flaggedAccountId' ] |
249 | } | 271 | } |
250 | ] | 272 | ] |
251 | }) | 273 | }) |
252 | export class VideoAbuseModel extends Model<VideoAbuseModel> { | 274 | export class AbuseModel extends Model<AbuseModel> { |
253 | 275 | ||
254 | @AllowNull(false) | 276 | @AllowNull(false) |
255 | @Default(null) | 277 | @Default(null) |
256 | @Is('VideoAbuseReason', value => throwIfNotValid(value, isVideoAbuseReasonValid, 'reason')) | 278 | @Is('VideoAbuseReason', value => throwIfNotValid(value, isAbuseReasonValid, 'reason')) |
257 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.REASON.max)) | 279 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ABUSES.REASON.max)) |
258 | reason: string | 280 | reason: string |
259 | 281 | ||
260 | @AllowNull(false) | 282 | @AllowNull(false) |
261 | @Default(null) | 283 | @Default(null) |
262 | @Is('VideoAbuseState', value => throwIfNotValid(value, isVideoAbuseStateValid, 'state')) | 284 | @Is('VideoAbuseState', value => throwIfNotValid(value, isAbuseStateValid, 'state')) |
263 | @Column | 285 | @Column |
264 | state: VideoAbuseState | 286 | state: AbuseState |
265 | 287 | ||
266 | @AllowNull(true) | 288 | @AllowNull(true) |
267 | @Default(null) | 289 | @Default(null) |
268 | @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isVideoAbuseModerationCommentValid, 'moderationComment', true)) | 290 | @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isAbuseModerationCommentValid, 'moderationComment', true)) |
269 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) | 291 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ABUSES.MODERATION_COMMENT.max)) |
270 | moderationComment: string | 292 | moderationComment: string |
271 | 293 | ||
272 | @AllowNull(true) | 294 | @AllowNull(true) |
273 | @Default(null) | 295 | @Default(null) |
274 | @Column(DataType.JSONB) | ||
275 | deletedVideo: VideoDetails | ||
276 | |||
277 | @AllowNull(true) | ||
278 | @Default(null) | ||
279 | @Column(DataType.ARRAY(DataType.INTEGER)) | 296 | @Column(DataType.ARRAY(DataType.INTEGER)) |
280 | predefinedReasons: VideoAbusePredefinedReasons[] | 297 | predefinedReasons: AbusePredefinedReasons[] |
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 | 298 | ||
292 | @CreatedAt | 299 | @CreatedAt |
293 | createdAt: Date | 300 | createdAt: Date |
@@ -301,36 +308,65 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
301 | 308 | ||
302 | @BelongsTo(() => AccountModel, { | 309 | @BelongsTo(() => AccountModel, { |
303 | foreignKey: { | 310 | foreignKey: { |
311 | name: 'reporterAccountId', | ||
304 | allowNull: true | 312 | allowNull: true |
305 | }, | 313 | }, |
314 | as: 'ReporterAccount', | ||
306 | onDelete: 'set null' | 315 | onDelete: 'set null' |
307 | }) | 316 | }) |
308 | Account: AccountModel | 317 | ReporterAccount: AccountModel |
309 | 318 | ||
310 | @ForeignKey(() => VideoModel) | 319 | @ForeignKey(() => AccountModel) |
311 | @Column | 320 | @Column |
312 | videoId: number | 321 | flaggedAccountId: number |
313 | 322 | ||
314 | @BelongsTo(() => VideoModel, { | 323 | @BelongsTo(() => AccountModel, { |
315 | foreignKey: { | 324 | foreignKey: { |
325 | name: 'flaggedAccountId', | ||
316 | allowNull: true | 326 | allowNull: true |
317 | }, | 327 | }, |
328 | as: 'FlaggedAccount', | ||
318 | onDelete: 'set null' | 329 | onDelete: 'set null' |
319 | }) | 330 | }) |
320 | Video: VideoModel | 331 | FlaggedAccount: AccountModel |
332 | |||
333 | @HasOne(() => VideoCommentAbuseModel, { | ||
334 | foreignKey: { | ||
335 | name: 'abuseId', | ||
336 | allowNull: false | ||
337 | }, | ||
338 | onDelete: 'cascade' | ||
339 | }) | ||
340 | VideoCommentAbuse: VideoCommentAbuseModel | ||
321 | 341 | ||
322 | static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MVideoAbuse> { | 342 | @HasOne(() => VideoAbuseModel, { |
323 | const videoAttributes = {} | 343 | foreignKey: { |
324 | if (videoId) videoAttributes['videoId'] = videoId | 344 | name: 'abuseId', |
325 | if (uuid) videoAttributes['deletedVideo'] = { uuid } | 345 | allowNull: false |
346 | }, | ||
347 | onDelete: 'cascade' | ||
348 | }) | ||
349 | VideoAbuse: VideoAbuseModel | ||
350 | |||
351 | static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MAbuse> { | ||
352 | const videoWhere: WhereOptions = {} | ||
353 | |||
354 | if (videoId) videoWhere.videoId = videoId | ||
355 | if (uuid) videoWhere.deletedVideo = { uuid } | ||
326 | 356 | ||
327 | const query = { | 357 | const query = { |
358 | include: [ | ||
359 | { | ||
360 | model: VideoAbuseModel, | ||
361 | required: true, | ||
362 | where: videoWhere | ||
363 | } | ||
364 | ], | ||
328 | where: { | 365 | where: { |
329 | id, | 366 | id |
330 | ...videoAttributes | ||
331 | } | 367 | } |
332 | } | 368 | } |
333 | return VideoAbuseModel.findOne(query) | 369 | return AbuseModel.findOne(query) |
334 | } | 370 | } |
335 | 371 | ||
336 | static listForApi (parameters: { | 372 | static listForApi (parameters: { |
@@ -338,13 +374,15 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
338 | count: number | 374 | count: number |
339 | sort: string | 375 | sort: string |
340 | 376 | ||
377 | filter?: AbuseFilter | ||
378 | |||
341 | serverAccountId: number | 379 | serverAccountId: number |
342 | user?: MUserAccountId | 380 | user?: MUserAccountId |
343 | 381 | ||
344 | id?: number | 382 | id?: number |
345 | predefinedReason?: VideoAbusePredefinedReasonsString | 383 | predefinedReason?: AbusePredefinedReasonsString |
346 | state?: VideoAbuseState | 384 | state?: AbuseState |
347 | videoIs?: VideoAbuseVideoIs | 385 | videoIs?: AbuseVideoIs |
348 | 386 | ||
349 | search?: string | 387 | search?: string |
350 | searchReporter?: string | 388 | searchReporter?: string |
@@ -364,24 +402,26 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
364 | predefinedReason, | 402 | predefinedReason, |
365 | searchReportee, | 403 | searchReportee, |
366 | searchVideo, | 404 | searchVideo, |
405 | filter, | ||
367 | searchVideoChannel, | 406 | searchVideoChannel, |
368 | searchReporter, | 407 | searchReporter, |
369 | id | 408 | id |
370 | } = parameters | 409 | } = parameters |
371 | 410 | ||
372 | const userAccountId = user ? user.Account.id : undefined | 411 | const userAccountId = user ? user.Account.id : undefined |
373 | const predefinedReasonId = predefinedReason ? videoAbusePredefinedReasonsMap[predefinedReason] : undefined | 412 | const predefinedReasonId = predefinedReason ? abusePredefinedReasonsMap[predefinedReason] : undefined |
374 | 413 | ||
375 | const query = { | 414 | const query = { |
376 | offset: start, | 415 | offset: start, |
377 | limit: count, | 416 | limit: count, |
378 | order: getSort(sort), | 417 | order: getSort(sort), |
379 | col: 'VideoAbuseModel.id', | 418 | col: 'AbuseModel.id', |
380 | distinct: true | 419 | distinct: true |
381 | } | 420 | } |
382 | 421 | ||
383 | const filters = { | 422 | const filters = { |
384 | id, | 423 | id, |
424 | filter, | ||
385 | predefinedReasonId, | 425 | predefinedReasonId, |
386 | search, | 426 | search, |
387 | state, | 427 | state, |
@@ -394,7 +434,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
394 | userAccountId | 434 | userAccountId |
395 | } | 435 | } |
396 | 436 | ||
397 | return VideoAbuseModel | 437 | return AbuseModel |
398 | .scope([ | 438 | .scope([ |
399 | { method: [ ScopeNames.FOR_API, filters ] } | 439 | { method: [ ScopeNames.FOR_API, filters ] } |
400 | ]) | 440 | ]) |
@@ -404,8 +444,8 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
404 | }) | 444 | }) |
405 | } | 445 | } |
406 | 446 | ||
407 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { | 447 | toFormattedJSON (this: MAbuseFormattable): Abuse { |
408 | const predefinedReasons = VideoAbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) | 448 | const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) |
409 | const countReportsForVideo = this.get('countReportsForVideo') as number | 449 | const countReportsForVideo = this.get('countReportsForVideo') as number |
410 | const nthReportForVideo = this.get('nthReportForVideo') as number | 450 | const nthReportForVideo = this.get('nthReportForVideo') as number |
411 | const countReportsForReporterVideo = this.get('countReportsForReporter__video') as number | 451 | const countReportsForReporterVideo = this.get('countReportsForReporter__video') as number |
@@ -413,51 +453,70 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
413 | const countReportsForReporteeVideo = this.get('countReportsForReportee__video') as number | 453 | const countReportsForReporteeVideo = this.get('countReportsForReportee__video') as number |
414 | const countReportsForReporteeDeletedVideo = this.get('countReportsForReportee__deletedVideo') as number | 454 | const countReportsForReporteeDeletedVideo = this.get('countReportsForReportee__deletedVideo') as number |
415 | 455 | ||
416 | const video = this.Video | 456 | let video: VideoAbuse |
417 | ? this.Video | 457 | |
418 | : this.deletedVideo | 458 | if (this.VideoAbuse) { |
459 | const abuseModel = this.VideoAbuse | ||
460 | const entity = abuseModel.Video || abuseModel.deletedVideo | ||
461 | |||
462 | video = { | ||
463 | id: entity.id, | ||
464 | uuid: entity.uuid, | ||
465 | name: entity.name, | ||
466 | nsfw: entity.nsfw, | ||
467 | |||
468 | startAt: abuseModel.startAt, | ||
469 | endAt: abuseModel.endAt, | ||
470 | |||
471 | deleted: !abuseModel.Video, | ||
472 | blacklisted: abuseModel.Video?.isBlacklisted() || false, | ||
473 | thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(), | ||
474 | channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel | ||
475 | } | ||
476 | } | ||
419 | 477 | ||
420 | return { | 478 | return { |
421 | id: this.id, | 479 | id: this.id, |
422 | reason: this.reason, | 480 | reason: this.reason, |
423 | predefinedReasons, | 481 | predefinedReasons, |
424 | reporterAccount: this.Account.toFormattedJSON(), | 482 | |
483 | reporterAccount: this.ReporterAccount.toFormattedJSON(), | ||
484 | |||
425 | state: { | 485 | state: { |
426 | id: this.state, | 486 | id: this.state, |
427 | label: VideoAbuseModel.getStateLabel(this.state) | 487 | label: AbuseModel.getStateLabel(this.state) |
428 | }, | 488 | }, |
489 | |||
429 | moderationComment: this.moderationComment, | 490 | moderationComment: this.moderationComment, |
430 | video: { | 491 | |
431 | id: video.id, | 492 | video, |
432 | uuid: video.uuid, | 493 | comment: null, |
433 | name: video.name, | 494 | |
434 | nsfw: video.nsfw, | ||
435 | deleted: !this.Video, | ||
436 | blacklisted: this.Video?.isBlacklisted() || false, | ||
437 | thumbnailPath: this.Video?.getMiniatureStaticPath(), | ||
438 | channel: this.Video?.VideoChannel.toFormattedJSON() || this.deletedVideo?.channel | ||
439 | }, | ||
440 | createdAt: this.createdAt, | 495 | createdAt: this.createdAt, |
441 | updatedAt: this.updatedAt, | 496 | updatedAt: this.updatedAt, |
442 | startAt: this.startAt, | ||
443 | endAt: this.endAt, | ||
444 | count: countReportsForVideo || 0, | 497 | count: countReportsForVideo || 0, |
445 | nth: nthReportForVideo || 0, | 498 | nth: nthReportForVideo || 0, |
446 | countReportsForReporter: (countReportsForReporterVideo || 0) + (countReportsForReporterDeletedVideo || 0), | 499 | countReportsForReporter: (countReportsForReporterVideo || 0) + (countReportsForReporterDeletedVideo || 0), |
447 | countReportsForReportee: (countReportsForReporteeVideo || 0) + (countReportsForReporteeDeletedVideo || 0) | 500 | countReportsForReportee: (countReportsForReporteeVideo || 0) + (countReportsForReporteeDeletedVideo || 0), |
501 | |||
502 | // FIXME: deprecated in 2.3, remove this | ||
503 | startAt: null, | ||
504 | endAt: null | ||
448 | } | 505 | } |
449 | } | 506 | } |
450 | 507 | ||
451 | toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject { | 508 | toActivityPubObject (this: MAbuseAP): AbuseObject { |
452 | const predefinedReasons = VideoAbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) | 509 | const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) |
510 | |||
511 | const object = this.VideoAbuse?.Video?.url || this.VideoCommentAbuse?.VideoComment?.url || this.FlaggedAccount.Actor.url | ||
453 | 512 | ||
454 | const startAt = this.startAt | 513 | const startAt = this.VideoAbuse?.startAt |
455 | const endAt = this.endAt | 514 | const endAt = this.VideoAbuse?.endAt |
456 | 515 | ||
457 | return { | 516 | return { |
458 | type: 'Flag' as 'Flag', | 517 | type: 'Flag' as 'Flag', |
459 | content: this.reason, | 518 | content: this.reason, |
460 | object: this.Video.url, | 519 | object, |
461 | tag: predefinedReasons.map(r => ({ | 520 | tag: predefinedReasons.map(r => ({ |
462 | type: 'Hashtag' as 'Hashtag', | 521 | type: 'Hashtag' as 'Hashtag', |
463 | name: r | 522 | name: r |
@@ -468,12 +527,12 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
468 | } | 527 | } |
469 | 528 | ||
470 | private static getStateLabel (id: number) { | 529 | private static getStateLabel (id: number) { |
471 | return VIDEO_ABUSE_STATES[id] || 'Unknown' | 530 | return ABUSE_STATES[id] || 'Unknown' |
472 | } | 531 | } |
473 | 532 | ||
474 | private static getPredefinedReasonsStrings (predefinedReasons: VideoAbusePredefinedReasons[]): VideoAbusePredefinedReasonsString[] { | 533 | private static getPredefinedReasonsStrings (predefinedReasons: AbusePredefinedReasons[]): AbusePredefinedReasonsString[] { |
475 | return (predefinedReasons || []) | 534 | return (predefinedReasons || []) |
476 | .filter(r => r in VideoAbusePredefinedReasons) | 535 | .filter(r => r in AbusePredefinedReasons) |
477 | .map(r => invert(videoAbusePredefinedReasonsMap)[r] as VideoAbusePredefinedReasonsString) | 536 | .map(r => invert(abusePredefinedReasonsMap)[r] as AbusePredefinedReasonsString) |
478 | } | 537 | } |
479 | } | 538 | } |
diff --git a/server/models/abuse/video-abuse.ts b/server/models/abuse/video-abuse.ts new file mode 100644 index 000000000..d92bcf19f --- /dev/null +++ b/server/models/abuse/video-abuse.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { VideoDetails } from '@shared/models' | ||
3 | import { VideoModel } from '../video/video' | ||
4 | import { AbuseModel } from './abuse' | ||
5 | |||
6 | @Table({ | ||
7 | tableName: 'videoAbuse', | ||
8 | indexes: [ | ||
9 | { | ||
10 | fields: [ 'abuseId' ] | ||
11 | }, | ||
12 | { | ||
13 | fields: [ 'videoId' ] | ||
14 | } | ||
15 | ] | ||
16 | }) | ||
17 | export class VideoAbuseModel extends Model<VideoAbuseModel> { | ||
18 | |||
19 | @CreatedAt | ||
20 | createdAt: Date | ||
21 | |||
22 | @UpdatedAt | ||
23 | updatedAt: Date | ||
24 | |||
25 | @AllowNull(true) | ||
26 | @Default(null) | ||
27 | @Column | ||
28 | startAt: number | ||
29 | |||
30 | @AllowNull(true) | ||
31 | @Default(null) | ||
32 | @Column | ||
33 | endAt: number | ||
34 | |||
35 | @AllowNull(true) | ||
36 | @Default(null) | ||
37 | @Column(DataType.JSONB) | ||
38 | deletedVideo: VideoDetails | ||
39 | |||
40 | @ForeignKey(() => AbuseModel) | ||
41 | @Column | ||
42 | abuseId: number | ||
43 | |||
44 | @BelongsTo(() => AbuseModel, { | ||
45 | foreignKey: { | ||
46 | allowNull: false | ||
47 | }, | ||
48 | onDelete: 'cascade' | ||
49 | }) | ||
50 | Abuse: AbuseModel | ||
51 | |||
52 | @ForeignKey(() => VideoModel) | ||
53 | @Column | ||
54 | videoId: number | ||
55 | |||
56 | @BelongsTo(() => VideoModel, { | ||
57 | foreignKey: { | ||
58 | allowNull: true | ||
59 | }, | ||
60 | onDelete: 'set null' | ||
61 | }) | ||
62 | Video: VideoModel | ||
63 | } | ||
diff --git a/server/models/abuse/video-comment-abuse.ts b/server/models/abuse/video-comment-abuse.ts new file mode 100644 index 000000000..b4cc2762e --- /dev/null +++ b/server/models/abuse/video-comment-abuse.ts | |||
@@ -0,0 +1,53 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { VideoComment } from '@shared/models' | ||
3 | import { VideoCommentModel } from '../video/video-comment' | ||
4 | import { AbuseModel } from './abuse' | ||
5 | |||
6 | @Table({ | ||
7 | tableName: 'commentAbuse', | ||
8 | indexes: [ | ||
9 | { | ||
10 | fields: [ 'abuseId' ] | ||
11 | }, | ||
12 | { | ||
13 | fields: [ 'videoCommentId' ] | ||
14 | } | ||
15 | ] | ||
16 | }) | ||
17 | export class VideoCommentAbuseModel extends Model<VideoCommentAbuseModel> { | ||
18 | |||
19 | @CreatedAt | ||
20 | createdAt: Date | ||
21 | |||
22 | @UpdatedAt | ||
23 | updatedAt: Date | ||
24 | |||
25 | @AllowNull(true) | ||
26 | @Default(null) | ||
27 | @Column(DataType.JSONB) | ||
28 | deletedComment: VideoComment | ||
29 | |||
30 | @ForeignKey(() => AbuseModel) | ||
31 | @Column | ||
32 | abuseId: number | ||
33 | |||
34 | @BelongsTo(() => AbuseModel, { | ||
35 | foreignKey: { | ||
36 | allowNull: false | ||
37 | }, | ||
38 | onDelete: 'cascade' | ||
39 | }) | ||
40 | Abuse: AbuseModel | ||
41 | |||
42 | @ForeignKey(() => VideoCommentModel) | ||
43 | @Column | ||
44 | videoCommentId: number | ||
45 | |||
46 | @BelongsTo(() => VideoCommentModel, { | ||
47 | foreignKey: { | ||
48 | allowNull: true | ||
49 | }, | ||
50 | onDelete: 'set null' | ||
51 | }) | ||
52 | VideoComment: VideoCommentModel | ||
53 | } | ||
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index cf8872fd5..577b7dc19 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { AccountModel } from './account' | ||
3 | import { getSort, searchAttribute } from '../utils' | ||
4 | import { AccountBlock } from '../../../shared/models/blocklist' | ||
5 | import { Op } from 'sequelize' | ||
6 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { Op } from 'sequelize' | ||
3 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
7 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' | 4 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' |
5 | import { AccountBlock } from '../../../shared/models' | ||
8 | import { ActorModel } from '../activitypub/actor' | 6 | import { ActorModel } from '../activitypub/actor' |
9 | import { ServerModel } from '../server/server' | 7 | import { ServerModel } from '../server/server' |
8 | import { getSort, searchAttribute } from '../utils' | ||
9 | import { AccountModel } from './account' | ||
10 | 10 | ||
11 | enum ScopeNames { | 11 | enum ScopeNames { |
12 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' | 12 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 4395d179a..466d6258e 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -388,6 +388,10 @@ export class AccountModel extends Model<AccountModel> { | |||
388 | .findAll(query) | 388 | .findAll(query) |
389 | } | 389 | } |
390 | 390 | ||
391 | getClientUrl () { | ||
392 | return WEBSERVER.URL + '/accounts/' + this.Actor.getIdentifier() | ||
393 | } | ||
394 | |||
391 | toFormattedJSON (this: MAccountFormattable): Account { | 395 | toFormattedJSON (this: MAccountFormattable): Account { |
392 | const actor = this.Actor.toFormattedJSON() | 396 | const actor = this.Actor.toFormattedJSON() |
393 | const account = { | 397 | const account = { |
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index 30985bb0f..07db5a2db 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts | |||
@@ -1,22 +1,24 @@ | |||
1 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' | ||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' | ||
2 | import { UserNotification, UserNotificationType } from '../../../shared' | 4 | import { UserNotification, UserNotificationType } from '../../../shared' |
3 | import { getSort, throwIfNotValid } from '../utils' | ||
4 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 5 | import { isBooleanValid } from '../../helpers/custom-validators/misc' |
5 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' | 6 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' |
6 | import { UserModel } from './user' | 7 | import { AbuseModel } from '../abuse/abuse' |
7 | import { VideoModel } from '../video/video' | 8 | import { VideoAbuseModel } from '../abuse/video-abuse' |
8 | import { VideoCommentModel } from '../video/video-comment' | 9 | import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' |
9 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' | ||
10 | import { VideoChannelModel } from '../video/video-channel' | ||
11 | import { AccountModel } from './account' | ||
12 | import { VideoAbuseModel } from '../video/video-abuse' | ||
13 | import { VideoBlacklistModel } from '../video/video-blacklist' | ||
14 | import { VideoImportModel } from '../video/video-import' | ||
15 | import { ActorModel } from '../activitypub/actor' | 10 | import { ActorModel } from '../activitypub/actor' |
16 | import { ActorFollowModel } from '../activitypub/actor-follow' | 11 | import { ActorFollowModel } from '../activitypub/actor-follow' |
17 | import { AvatarModel } from '../avatar/avatar' | 12 | import { AvatarModel } from '../avatar/avatar' |
18 | import { ServerModel } from '../server/server' | 13 | import { ServerModel } from '../server/server' |
19 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' | 14 | import { getSort, throwIfNotValid } from '../utils' |
15 | import { VideoModel } from '../video/video' | ||
16 | import { VideoBlacklistModel } from '../video/video-blacklist' | ||
17 | import { VideoChannelModel } from '../video/video-channel' | ||
18 | import { VideoCommentModel } from '../video/video-comment' | ||
19 | import { VideoImportModel } from '../video/video-import' | ||
20 | import { AccountModel } from './account' | ||
21 | import { UserModel } from './user' | ||
20 | 22 | ||
21 | enum ScopeNames { | 23 | enum ScopeNames { |
22 | WITH_ALL = 'WITH_ALL' | 24 | WITH_ALL = 'WITH_ALL' |
@@ -87,9 +89,41 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
87 | 89 | ||
88 | { | 90 | { |
89 | attributes: [ 'id' ], | 91 | attributes: [ 'id' ], |
90 | model: VideoAbuseModel.unscoped(), | 92 | model: AbuseModel.unscoped(), |
91 | required: false, | 93 | required: false, |
92 | include: [ buildVideoInclude(true) ] | 94 | include: [ |
95 | { | ||
96 | attributes: [ 'id' ], | ||
97 | model: VideoAbuseModel.unscoped(), | ||
98 | required: false, | ||
99 | include: [ buildVideoInclude(true) ] | ||
100 | }, | ||
101 | { | ||
102 | attributes: [ 'id' ], | ||
103 | model: VideoCommentAbuseModel.unscoped(), | ||
104 | required: false, | ||
105 | include: [ | ||
106 | { | ||
107 | attributes: [ 'id', 'originCommentId' ], | ||
108 | model: VideoCommentModel, | ||
109 | required: true, | ||
110 | include: [ | ||
111 | { | ||
112 | attributes: [ 'uuid' ], | ||
113 | model: VideoModel.unscoped(), | ||
114 | required: true | ||
115 | } | ||
116 | ] | ||
117 | } | ||
118 | ] | ||
119 | }, | ||
120 | { | ||
121 | model: AccountModel, | ||
122 | as: 'FlaggedAccount', | ||
123 | required: true, | ||
124 | include: [ buildActorWithAvatarInclude() ] | ||
125 | } | ||
126 | ] | ||
93 | }, | 127 | }, |
94 | 128 | ||
95 | { | 129 | { |
@@ -179,9 +213,9 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
179 | } | 213 | } |
180 | }, | 214 | }, |
181 | { | 215 | { |
182 | fields: [ 'videoAbuseId' ], | 216 | fields: [ 'abuseId' ], |
183 | where: { | 217 | where: { |
184 | videoAbuseId: { | 218 | abuseId: { |
185 | [Op.ne]: null | 219 | [Op.ne]: null |
186 | } | 220 | } |
187 | } | 221 | } |
@@ -276,17 +310,17 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
276 | }) | 310 | }) |
277 | Comment: VideoCommentModel | 311 | Comment: VideoCommentModel |
278 | 312 | ||
279 | @ForeignKey(() => VideoAbuseModel) | 313 | @ForeignKey(() => AbuseModel) |
280 | @Column | 314 | @Column |
281 | videoAbuseId: number | 315 | abuseId: number |
282 | 316 | ||
283 | @BelongsTo(() => VideoAbuseModel, { | 317 | @BelongsTo(() => AbuseModel, { |
284 | foreignKey: { | 318 | foreignKey: { |
285 | allowNull: true | 319 | allowNull: true |
286 | }, | 320 | }, |
287 | onDelete: 'cascade' | 321 | onDelete: 'cascade' |
288 | }) | 322 | }) |
289 | VideoAbuse: VideoAbuseModel | 323 | Abuse: AbuseModel |
290 | 324 | ||
291 | @ForeignKey(() => VideoBlacklistModel) | 325 | @ForeignKey(() => VideoBlacklistModel) |
292 | @Column | 326 | @Column |
@@ -397,10 +431,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
397 | video: this.formatVideo(this.Comment.Video) | 431 | video: this.formatVideo(this.Comment.Video) |
398 | } : undefined | 432 | } : undefined |
399 | 433 | ||
400 | const videoAbuse = this.VideoAbuse ? { | 434 | const abuse = this.Abuse ? this.formatAbuse(this.Abuse) : undefined |
401 | id: this.VideoAbuse.id, | ||
402 | video: this.formatVideo(this.VideoAbuse.Video) | ||
403 | } : undefined | ||
404 | 435 | ||
405 | const videoBlacklist = this.VideoBlacklist ? { | 436 | const videoBlacklist = this.VideoBlacklist ? { |
406 | id: this.VideoBlacklist.id, | 437 | id: this.VideoBlacklist.id, |
@@ -439,7 +470,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
439 | video, | 470 | video, |
440 | videoImport, | 471 | videoImport, |
441 | comment, | 472 | comment, |
442 | videoAbuse, | 473 | abuse, |
443 | videoBlacklist, | 474 | videoBlacklist, |
444 | account, | 475 | account, |
445 | actorFollow, | 476 | actorFollow, |
@@ -456,6 +487,27 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
456 | } | 487 | } |
457 | } | 488 | } |
458 | 489 | ||
490 | formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) { | ||
491 | const commentAbuse = abuse.VideoCommentAbuse?.VideoComment ? { | ||
492 | threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), | ||
493 | |||
494 | video: { | ||
495 | uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid | ||
496 | } | ||
497 | } : undefined | ||
498 | |||
499 | const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined | ||
500 | |||
501 | const accountAbuse = (!commentAbuse && !videoAbuse) ? this.formatActor(abuse.FlaggedAccount) : undefined | ||
502 | |||
503 | return { | ||
504 | id: abuse.id, | ||
505 | video: videoAbuse, | ||
506 | comment: commentAbuse, | ||
507 | account: accountAbuse | ||
508 | } | ||
509 | } | ||
510 | |||
459 | formatActor ( | 511 | formatActor ( |
460 | this: UserNotificationModelForApi, | 512 | this: UserNotificationModelForApi, |
461 | accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor | 513 | accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index de193131a..f21eff04b 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -19,7 +19,7 @@ import { | |||
19 | Table, | 19 | Table, |
20 | UpdatedAt | 20 | UpdatedAt |
21 | } from 'sequelize-typescript' | 21 | } from 'sequelize-typescript' |
22 | import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoAbuseState, VideoPlaylistType, VideoPrivacy } from '../../../shared' | 22 | import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, AbuseState, VideoPlaylistType, VideoPrivacy } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
25 | isNoInstanceConfigWarningModal, | 25 | isNoInstanceConfigWarningModal, |
@@ -169,7 +169,7 @@ enum ScopeNames { | |||
169 | `SELECT concat_ws(':', "abuses", "acceptedAbuses") ` + | 169 | `SELECT concat_ws(':', "abuses", "acceptedAbuses") ` + |
170 | 'FROM (' + | 170 | 'FROM (' + |
171 | 'SELECT COUNT("videoAbuse"."id") AS "abuses", ' + | 171 | 'SELECT COUNT("videoAbuse"."id") AS "abuses", ' + |
172 | `COUNT("videoAbuse"."id") FILTER (WHERE "videoAbuse"."state" = ${VideoAbuseState.ACCEPTED}) AS "acceptedAbuses" ` + | 172 | `COUNT("videoAbuse"."id") FILTER (WHERE "videoAbuse"."state" = ${AbuseState.ACCEPTED}) AS "acceptedAbuses" ` + |
173 | 'FROM "videoAbuse" ' + | 173 | 'FROM "videoAbuse" ' + |
174 | 'INNER JOIN "video" ON "videoAbuse"."videoId" = "video"."id" ' + | 174 | 'INNER JOIN "video" ON "videoAbuse"."videoId" = "video"."id" ' + |
175 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | 175 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 30f0525e5..68cd72ee7 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { Op } from 'sequelize' | ||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' | ||
5 | import { ServerBlock } from '@shared/models' | ||
2 | import { AccountModel } from '../account/account' | 6 | import { AccountModel } from '../account/account' |
3 | import { ServerModel } from './server' | ||
4 | import { ServerBlock } from '../../../shared/models/blocklist' | ||
5 | import { getSort, searchAttribute } from '../utils' | 7 | import { getSort, searchAttribute } from '../utils' |
6 | import * as Bluebird from 'bluebird' | 8 | import { ServerModel } from './server' |
7 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' | ||
8 | import { Op } from 'sequelize' | ||
9 | 9 | ||
10 | enum ScopeNames { | 10 | enum ScopeNames { |
11 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 11 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index e2718300e..272bba0e1 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { remove } from 'fs-extra' | ||
2 | import { maxBy, minBy, pick } from 'lodash' | 3 | import { maxBy, minBy, pick } from 'lodash' |
3 | import { join } from 'path' | 4 | import { join } from 'path' |
4 | import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' | 5 | import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' |
@@ -23,10 +24,18 @@ import { | |||
23 | Table, | 24 | Table, |
24 | UpdatedAt | 25 | UpdatedAt |
25 | } from 'sequelize-typescript' | 26 | } from 'sequelize-typescript' |
26 | import { UserRight, VideoPrivacy, VideoState, ResultList } from '../../../shared' | 27 | import { buildNSFWFilter } from '@server/helpers/express-utils' |
28 | import { getPrivaciesForFederation, isPrivacyForFederation } from '@server/helpers/video' | ||
29 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | ||
30 | import { getServerActor } from '@server/models/application/application' | ||
31 | import { ModelCache } from '@server/models/model-cache' | ||
32 | import { VideoFile } from '@shared/models/videos/video-file.model' | ||
33 | import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared' | ||
27 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 34 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
28 | import { Video, VideoDetails } from '../../../shared/models/videos' | 35 | import { Video, VideoDetails } from '../../../shared/models/videos' |
36 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
29 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 37 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
38 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | ||
30 | import { peertubeTruncate } from '../../helpers/core-utils' | 39 | import { peertubeTruncate } from '../../helpers/core-utils' |
31 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 40 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
32 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 41 | import { isBooleanValid } from '../../helpers/custom-validators/misc' |
@@ -43,6 +52,7 @@ import { | |||
43 | } from '../../helpers/custom-validators/videos' | 52 | } from '../../helpers/custom-validators/videos' |
44 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' | 53 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' |
45 | import { logger } from '../../helpers/logger' | 54 | import { logger } from '../../helpers/logger' |
55 | import { CONFIG } from '../../initializers/config' | ||
46 | import { | 56 | import { |
47 | ACTIVITY_PUB, | 57 | ACTIVITY_PUB, |
48 | API_VERSION, | 58 | API_VERSION, |
@@ -59,40 +69,6 @@ import { | |||
59 | WEBSERVER | 69 | WEBSERVER |
60 | } from '../../initializers/constants' | 70 | } from '../../initializers/constants' |
61 | import { sendDeleteVideo } from '../../lib/activitypub/send' | 71 | import { sendDeleteVideo } from '../../lib/activitypub/send' |
62 | import { AccountModel } from '../account/account' | ||
63 | import { AccountVideoRateModel } from '../account/account-video-rate' | ||
64 | import { ActorModel } from '../activitypub/actor' | ||
65 | import { AvatarModel } from '../avatar/avatar' | ||
66 | import { ServerModel } from '../server/server' | ||
67 | import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' | ||
68 | import { TagModel } from './tag' | ||
69 | import { VideoAbuseModel } from './video-abuse' | ||
70 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | ||
71 | import { VideoCommentModel } from './video-comment' | ||
72 | import { VideoFileModel } from './video-file' | ||
73 | import { VideoShareModel } from './video-share' | ||
74 | import { VideoTagModel } from './video-tag' | ||
75 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | ||
76 | import { VideoCaptionModel } from './video-caption' | ||
77 | import { VideoBlacklistModel } from './video-blacklist' | ||
78 | import { remove } from 'fs-extra' | ||
79 | import { VideoViewModel } from './video-view' | ||
80 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
81 | import { | ||
82 | videoFilesModelToFormattedJSON, | ||
83 | VideoFormattingJSONOptions, | ||
84 | videoModelToActivityPubObject, | ||
85 | videoModelToFormattedDetailsJSON, | ||
86 | videoModelToFormattedJSON | ||
87 | } from './video-format-utils' | ||
88 | import { UserVideoHistoryModel } from '../account/user-video-history' | ||
89 | import { VideoImportModel } from './video-import' | ||
90 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | ||
91 | import { VideoPlaylistElementModel } from './video-playlist-element' | ||
92 | import { CONFIG } from '../../initializers/config' | ||
93 | import { ThumbnailModel } from './thumbnail' | ||
94 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
95 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | ||
96 | import { | 72 | import { |
97 | MChannel, | 73 | MChannel, |
98 | MChannelAccountDefault, | 74 | MChannelAccountDefault, |
@@ -118,15 +94,39 @@ import { | |||
118 | MVideoWithFile, | 94 | MVideoWithFile, |
119 | MVideoWithRights | 95 | MVideoWithRights |
120 | } from '../../types/models' | 96 | } from '../../types/models' |
121 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file' | ||
122 | import { MThumbnail } from '../../types/models/video/thumbnail' | 97 | import { MThumbnail } from '../../types/models/video/thumbnail' |
123 | import { VideoFile } from '@shared/models/videos/video-file.model' | 98 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file' |
124 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 99 | import { VideoAbuseModel } from '../abuse/video-abuse' |
125 | import { ModelCache } from '@server/models/model-cache' | 100 | import { AccountModel } from '../account/account' |
101 | import { AccountVideoRateModel } from '../account/account-video-rate' | ||
102 | import { UserVideoHistoryModel } from '../account/user-video-history' | ||
103 | import { ActorModel } from '../activitypub/actor' | ||
104 | import { AvatarModel } from '../avatar/avatar' | ||
105 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
106 | import { ServerModel } from '../server/server' | ||
107 | import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' | ||
108 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | ||
109 | import { TagModel } from './tag' | ||
110 | import { ThumbnailModel } from './thumbnail' | ||
111 | import { VideoBlacklistModel } from './video-blacklist' | ||
112 | import { VideoCaptionModel } from './video-caption' | ||
113 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | ||
114 | import { VideoCommentModel } from './video-comment' | ||
115 | import { VideoFileModel } from './video-file' | ||
116 | import { | ||
117 | videoFilesModelToFormattedJSON, | ||
118 | VideoFormattingJSONOptions, | ||
119 | videoModelToActivityPubObject, | ||
120 | videoModelToFormattedDetailsJSON, | ||
121 | videoModelToFormattedJSON | ||
122 | } from './video-format-utils' | ||
123 | import { VideoImportModel } from './video-import' | ||
124 | import { VideoPlaylistElementModel } from './video-playlist-element' | ||
126 | import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder' | 125 | import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder' |
127 | import { buildNSFWFilter } from '@server/helpers/express-utils' | 126 | import { VideoShareModel } from './video-share' |
128 | import { getServerActor } from '@server/models/application/application' | 127 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
129 | import { getPrivaciesForFederation, isPrivacyForFederation } from "@server/helpers/video" | 128 | import { VideoTagModel } from './video-tag' |
129 | import { VideoViewModel } from './video-view' | ||
130 | 130 | ||
131 | export enum ScopeNames { | 131 | export enum ScopeNames { |
132 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 132 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
diff --git a/server/tests/api/check-params/video-abuses.ts b/server/tests/api/check-params/video-abuses.ts index 557bf20eb..f122baef4 100644 --- a/server/tests/api/check-params/video-abuses.ts +++ b/server/tests/api/check-params/video-abuses.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | import { AbuseState, VideoAbuseCreate } from '@shared/models' | |
5 | import { | 5 | import { |
6 | cleanupTests, | 6 | cleanupTests, |
7 | createUser, | 7 | createUser, |
@@ -20,7 +20,8 @@ import { | |||
20 | checkBadSortPagination, | 20 | checkBadSortPagination, |
21 | checkBadStartPagination | 21 | checkBadStartPagination |
22 | } from '../../../../shared/extra-utils/requests/check-api-params' | 22 | } from '../../../../shared/extra-utils/requests/check-api-params' |
23 | import { VideoAbuseState, VideoAbuseCreate } from '../../../../shared/models/videos' | 23 | |
24 | // FIXME: deprecated in 2.3. Remove this controller | ||
24 | 25 | ||
25 | describe('Test video abuses API validators', function () { | 26 | describe('Test video abuses API validators', function () { |
26 | let server: ServerInfo | 27 | let server: ServerInfo |
@@ -136,7 +137,7 @@ describe('Test video abuses API validators', function () { | |||
136 | const fields = { reason: 'my super reason' } | 137 | const fields = { reason: 'my super reason' } |
137 | 138 | ||
138 | const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) | 139 | const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) |
139 | videoAbuseId = res.body.videoAbuse.id | 140 | videoAbuseId = res.body.abuse.id |
140 | }) | 141 | }) |
141 | 142 | ||
142 | it('Should fail with a wrong predefined reason', async function () { | 143 | it('Should fail with a wrong predefined reason', async function () { |
@@ -190,7 +191,7 @@ describe('Test video abuses API validators', function () { | |||
190 | }) | 191 | }) |
191 | 192 | ||
192 | it('Should succeed with the correct params', async function () { | 193 | it('Should succeed with the correct params', async function () { |
193 | const body = { state: VideoAbuseState.ACCEPTED } | 194 | const body = { state: AbuseState.ACCEPTED } |
194 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body) | 195 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body) |
195 | }) | 196 | }) |
196 | }) | 197 | }) |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 0a66bd1ce..88b68d977 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { MyUser, User, UserRole, Video, VideoAbuseState, VideoAbuseUpdate, VideoPlaylistType } from '../../../../shared/index' | 5 | import { MyUser, User, UserRole, Video, AbuseState, AbuseUpdate, VideoPlaylistType } from '@shared/models' |
6 | import { | 6 | import { |
7 | addVideoCommentThread, | 7 | addVideoCommentThread, |
8 | blockUser, | 8 | blockUser, |
@@ -937,7 +937,7 @@ describe('Test users', function () { | |||
937 | expect(user2.videoAbusesCount).to.equal(1) // number of incriminations | 937 | expect(user2.videoAbusesCount).to.equal(1) // number of incriminations |
938 | expect(user2.videoAbusesCreatedCount).to.equal(1) // number of reports created | 938 | expect(user2.videoAbusesCreatedCount).to.equal(1) // number of reports created |
939 | 939 | ||
940 | const body: VideoAbuseUpdate = { state: VideoAbuseState.ACCEPTED } | 940 | const body: AbuseUpdate = { state: AbuseState.ACCEPTED } |
941 | await updateVideoAbuse(server.url, server.accessToken, videoId, abuseId, body) | 941 | await updateVideoAbuse(server.url, server.accessToken, videoId, abuseId, body) |
942 | 942 | ||
943 | const res3 = await getUserInformation(server.url, server.accessToken, user17Id, true) | 943 | const res3 = await getUserInformation(server.url, server.accessToken, user17Id, true) |
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index 7383bd991..20975aa4a 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts | |||
@@ -1,21 +1,21 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { VideoAbuse, VideoAbuseState, VideoAbusePredefinedReasonsString } from '../../../../shared/models/videos' | 4 | import * as chai from 'chai' |
5 | import { Abuse, AbusePredefinedReasonsString, AbuseState } from '@shared/models' | ||
6 | import { | 6 | import { |
7 | cleanupTests, | 7 | cleanupTests, |
8 | createUser, | ||
8 | deleteVideoAbuse, | 9 | deleteVideoAbuse, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
10 | getVideoAbusesList, | 11 | getVideoAbusesList, |
11 | getVideosList, | 12 | getVideosList, |
13 | removeVideo, | ||
12 | reportVideoAbuse, | 14 | reportVideoAbuse, |
13 | ServerInfo, | 15 | ServerInfo, |
14 | setAccessTokensToServers, | 16 | setAccessTokensToServers, |
15 | updateVideoAbuse, | 17 | updateVideoAbuse, |
16 | uploadVideo, | 18 | uploadVideo, |
17 | removeVideo, | ||
18 | createUser, | ||
19 | userLogin | 19 | userLogin |
20 | } from '../../../../shared/extra-utils/index' | 20 | } from '../../../../shared/extra-utils/index' |
21 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | 21 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
@@ -29,9 +29,11 @@ import { | |||
29 | 29 | ||
30 | const expect = chai.expect | 30 | const expect = chai.expect |
31 | 31 | ||
32 | // FIXME: deprecated in 2.3. Remove this controller | ||
33 | |||
32 | describe('Test video abuses', function () { | 34 | describe('Test video abuses', function () { |
33 | let servers: ServerInfo[] = [] | 35 | let servers: ServerInfo[] = [] |
34 | let abuseServer2: VideoAbuse | 36 | let abuseServer2: Abuse |
35 | 37 | ||
36 | before(async function () { | 38 | before(async function () { |
37 | this.timeout(50000) | 39 | this.timeout(50000) |
@@ -95,7 +97,7 @@ describe('Test video abuses', function () { | |||
95 | expect(res1.body.data).to.be.an('array') | 97 | expect(res1.body.data).to.be.an('array') |
96 | expect(res1.body.data.length).to.equal(1) | 98 | expect(res1.body.data.length).to.equal(1) |
97 | 99 | ||
98 | const abuse: VideoAbuse = res1.body.data[0] | 100 | const abuse: Abuse = res1.body.data[0] |
99 | expect(abuse.reason).to.equal('my super bad reason') | 101 | expect(abuse.reason).to.equal('my super bad reason') |
100 | expect(abuse.reporterAccount.name).to.equal('root') | 102 | expect(abuse.reporterAccount.name).to.equal('root') |
101 | expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 103 | expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
@@ -128,23 +130,23 @@ describe('Test video abuses', function () { | |||
128 | expect(res1.body.data).to.be.an('array') | 130 | expect(res1.body.data).to.be.an('array') |
129 | expect(res1.body.data.length).to.equal(2) | 131 | expect(res1.body.data.length).to.equal(2) |
130 | 132 | ||
131 | const abuse1: VideoAbuse = res1.body.data[0] | 133 | const abuse1: Abuse = res1.body.data[0] |
132 | expect(abuse1.reason).to.equal('my super bad reason') | 134 | expect(abuse1.reason).to.equal('my super bad reason') |
133 | expect(abuse1.reporterAccount.name).to.equal('root') | 135 | expect(abuse1.reporterAccount.name).to.equal('root') |
134 | expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 136 | expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
135 | expect(abuse1.video.id).to.equal(servers[0].video.id) | 137 | expect(abuse1.video.id).to.equal(servers[0].video.id) |
136 | expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING) | 138 | expect(abuse1.state.id).to.equal(AbuseState.PENDING) |
137 | expect(abuse1.state.label).to.equal('Pending') | 139 | expect(abuse1.state.label).to.equal('Pending') |
138 | expect(abuse1.moderationComment).to.be.null | 140 | expect(abuse1.moderationComment).to.be.null |
139 | expect(abuse1.count).to.equal(1) | 141 | expect(abuse1.count).to.equal(1) |
140 | expect(abuse1.nth).to.equal(1) | 142 | expect(abuse1.nth).to.equal(1) |
141 | 143 | ||
142 | const abuse2: VideoAbuse = res1.body.data[1] | 144 | const abuse2: Abuse = res1.body.data[1] |
143 | expect(abuse2.reason).to.equal('my super bad reason 2') | 145 | expect(abuse2.reason).to.equal('my super bad reason 2') |
144 | expect(abuse2.reporterAccount.name).to.equal('root') | 146 | expect(abuse2.reporterAccount.name).to.equal('root') |
145 | expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 147 | expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
146 | expect(abuse2.video.id).to.equal(servers[1].video.id) | 148 | expect(abuse2.video.id).to.equal(servers[1].video.id) |
147 | expect(abuse2.state.id).to.equal(VideoAbuseState.PENDING) | 149 | expect(abuse2.state.id).to.equal(AbuseState.PENDING) |
148 | expect(abuse2.state.label).to.equal('Pending') | 150 | expect(abuse2.state.label).to.equal('Pending') |
149 | expect(abuse2.moderationComment).to.be.null | 151 | expect(abuse2.moderationComment).to.be.null |
150 | 152 | ||
@@ -157,25 +159,25 @@ describe('Test video abuses', function () { | |||
157 | expect(abuseServer2.reason).to.equal('my super bad reason 2') | 159 | expect(abuseServer2.reason).to.equal('my super bad reason 2') |
158 | expect(abuseServer2.reporterAccount.name).to.equal('root') | 160 | expect(abuseServer2.reporterAccount.name).to.equal('root') |
159 | expect(abuseServer2.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 161 | expect(abuseServer2.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
160 | expect(abuseServer2.state.id).to.equal(VideoAbuseState.PENDING) | 162 | expect(abuseServer2.state.id).to.equal(AbuseState.PENDING) |
161 | expect(abuseServer2.state.label).to.equal('Pending') | 163 | expect(abuseServer2.state.label).to.equal('Pending') |
162 | expect(abuseServer2.moderationComment).to.be.null | 164 | expect(abuseServer2.moderationComment).to.be.null |
163 | }) | 165 | }) |
164 | 166 | ||
165 | it('Should update the state of a video abuse', async function () { | 167 | it('Should update the state of a video abuse', async function () { |
166 | const body = { state: VideoAbuseState.REJECTED } | 168 | const body = { state: AbuseState.REJECTED } |
167 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | 169 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) |
168 | 170 | ||
169 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) | 171 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
170 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED) | 172 | expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED) |
171 | }) | 173 | }) |
172 | 174 | ||
173 | it('Should add a moderation comment', async function () { | 175 | it('Should add a moderation comment', async function () { |
174 | const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' } | 176 | const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' } |
175 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | 177 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) |
176 | 178 | ||
177 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) | 179 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
178 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED) | 180 | expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED) |
179 | expect(res.body.data[0].moderationComment).to.equal('It is valid') | 181 | expect(res.body.data[0].moderationComment).to.equal('It is valid') |
180 | }) | 182 | }) |
181 | 183 | ||
@@ -243,7 +245,7 @@ describe('Test video abuses', function () { | |||
243 | expect(res.body.data.length).to.equal(2, "wrong number of videos returned") | 245 | expect(res.body.data.length).to.equal(2, "wrong number of videos returned") |
244 | expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video") | 246 | expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video") |
245 | 247 | ||
246 | const abuse: VideoAbuse = res.body.data[0] | 248 | const abuse: Abuse = res.body.data[0] |
247 | expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") | 249 | expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") |
248 | expect(abuse.video.channel).to.exist | 250 | expect(abuse.video.channel).to.exist |
249 | expect(abuse.video.deleted).to.be.true | 251 | expect(abuse.video.deleted).to.be.true |
@@ -277,7 +279,7 @@ describe('Test video abuses', function () { | |||
277 | const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) | 279 | const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
278 | 280 | ||
279 | { | 281 | { |
280 | for (const abuse of res2.body.data as VideoAbuse[]) { | 282 | for (const abuse of res2.body.data as Abuse[]) { |
281 | if (abuse.video.id === video3.id) { | 283 | if (abuse.video.id === video3.id) { |
282 | expect(abuse.count).to.equal(1, "wrong reports count for video 3") | 284 | expect(abuse.count).to.equal(1, "wrong reports count for video 3") |
283 | expect(abuse.nth).to.equal(1, "wrong report position in report list for video 3") | 285 | expect(abuse.nth).to.equal(1, "wrong report position in report list for video 3") |
@@ -295,7 +297,7 @@ describe('Test video abuses', function () { | |||
295 | this.timeout(10000) | 297 | this.timeout(10000) |
296 | 298 | ||
297 | const reason5 = 'my super bad reason 5' | 299 | const reason5 = 'my super bad reason 5' |
298 | const predefinedReasons5: VideoAbusePredefinedReasonsString[] = [ 'violentOrRepulsive', 'captions' ] | 300 | const predefinedReasons5: AbusePredefinedReasonsString[] = [ 'violentOrRepulsive', 'captions' ] |
299 | const createdAbuse = (await reportVideoAbuse( | 301 | const createdAbuse = (await reportVideoAbuse( |
300 | servers[0].url, | 302 | servers[0].url, |
301 | servers[0].accessToken, | 303 | servers[0].accessToken, |
@@ -304,16 +306,16 @@ describe('Test video abuses', function () { | |||
304 | predefinedReasons5, | 306 | predefinedReasons5, |
305 | 1, | 307 | 1, |
306 | 5 | 308 | 5 |
307 | )).body.videoAbuse as VideoAbuse | 309 | )).body.abuse |
308 | 310 | ||
309 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) | 311 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
310 | 312 | ||
311 | { | 313 | { |
312 | const abuse = (res.body.data as VideoAbuse[]).find(a => a.id === createdAbuse.id) | 314 | const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id) |
313 | expect(abuse.reason).to.equals(reason5) | 315 | expect(abuse.reason).to.equals(reason5) |
314 | expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported") | 316 | expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported") |
315 | expect(abuse.startAt).to.equal(1, "starting timestamp doesn't match the one reported") | 317 | expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported") |
316 | expect(abuse.endAt).to.equal(5, "ending timestamp doesn't match the one reported") | 318 | expect(abuse.video.endAt).to.equal(5, "ending timestamp doesn't match the one reported") |
317 | } | 319 | } |
318 | }) | 320 | }) |
319 | 321 | ||
@@ -348,7 +350,7 @@ describe('Test video abuses', function () { | |||
348 | 350 | ||
349 | const res = await getVideoAbusesList(options) | 351 | const res = await getVideoAbusesList(options) |
350 | 352 | ||
351 | return res.body.data as VideoAbuse[] | 353 | return res.body.data as Abuse[] |
352 | } | 354 | } |
353 | 355 | ||
354 | expect(await list({ id: 56 })).to.have.lengthOf(0) | 356 | expect(await list({ id: 56 })).to.have.lengthOf(0) |
@@ -365,14 +367,14 @@ describe('Test video abuses', function () { | |||
365 | expect(await list({ searchReporter: 'user2' })).to.have.lengthOf(1) | 367 | expect(await list({ searchReporter: 'user2' })).to.have.lengthOf(1) |
366 | expect(await list({ searchReporter: 'root' })).to.have.lengthOf(5) | 368 | expect(await list({ searchReporter: 'root' })).to.have.lengthOf(5) |
367 | 369 | ||
368 | expect(await list({ searchReportee: 'root' })).to.have.lengthOf(4) | 370 | expect(await list({ searchReportee: 'root' })).to.have.lengthOf(5) |
369 | expect(await list({ searchReportee: 'aaaa' })).to.have.lengthOf(0) | 371 | expect(await list({ searchReportee: 'aaaa' })).to.have.lengthOf(0) |
370 | 372 | ||
371 | expect(await list({ videoIs: 'deleted' })).to.have.lengthOf(1) | 373 | expect(await list({ videoIs: 'deleted' })).to.have.lengthOf(1) |
372 | expect(await list({ videoIs: 'blacklisted' })).to.have.lengthOf(0) | 374 | expect(await list({ videoIs: 'blacklisted' })).to.have.lengthOf(0) |
373 | 375 | ||
374 | expect(await list({ state: VideoAbuseState.ACCEPTED })).to.have.lengthOf(0) | 376 | expect(await list({ state: AbuseState.ACCEPTED })).to.have.lengthOf(0) |
375 | expect(await list({ state: VideoAbuseState.PENDING })).to.have.lengthOf(6) | 377 | expect(await list({ state: AbuseState.PENDING })).to.have.lengthOf(6) |
376 | 378 | ||
377 | expect(await list({ predefinedReason: 'violentOrRepulsive' })).to.have.lengthOf(1) | 379 | expect(await list({ predefinedReason: 'violentOrRepulsive' })).to.have.lengthOf(1) |
378 | expect(await list({ predefinedReason: 'serverRules' })).to.have.lengthOf(0) | 380 | expect(await list({ predefinedReason: 'serverRules' })).to.have.lengthOf(0) |
diff --git a/server/types/models/index.ts b/server/types/models/index.ts index 78b4948ce..affa17425 100644 --- a/server/types/models/index.ts +++ b/server/types/models/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export * from './account' | 1 | export * from './account' |
2 | export * from './moderation' | ||
2 | export * from './oauth' | 3 | export * from './oauth' |
3 | export * from './server' | 4 | export * from './server' |
4 | export * from './user' | 5 | export * from './user' |
diff --git a/server/types/models/moderation/abuse.ts b/server/types/models/moderation/abuse.ts new file mode 100644 index 000000000..abbc93d6f --- /dev/null +++ b/server/types/models/moderation/abuse.ts | |||
@@ -0,0 +1,97 @@ | |||
1 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' | ||
2 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
3 | import { PickWith } from '@shared/core-utils' | ||
4 | import { AbuseModel } from '../../../models/abuse/abuse' | ||
5 | import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl } from '../account' | ||
6 | import { MCommentOwner, MCommentUrl, MVideoUrl, MCommentOwnerVideo } from '../video' | ||
7 | import { MVideo, MVideoAccountLightBlacklistAllFiles } from '../video/video' | ||
8 | |||
9 | type Use<K extends keyof AbuseModel, M> = PickWith<AbuseModel, K, M> | ||
10 | type UseVideoAbuse<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> | ||
11 | type UseCommentAbuse<K extends keyof VideoCommentAbuseModel, M> = PickWith<VideoCommentAbuseModel, K, M> | ||
12 | |||
13 | // ############################################################################ | ||
14 | |||
15 | export type MAbuse = Omit<AbuseModel, 'VideoCommentAbuse' | 'VideoAbuse' | 'ReporterAccount' | 'FlaggedAccount' | 'toActivityPubObject'> | ||
16 | |||
17 | export type MVideoAbuse = Omit<VideoAbuseModel, 'Abuse' | 'Video'> | ||
18 | |||
19 | export type MCommentAbuse = Omit<VideoCommentAbuseModel, 'Abuse' | 'VideoComment'> | ||
20 | |||
21 | // ############################################################################ | ||
22 | |||
23 | export type MVideoAbuseVideo = | ||
24 | MVideoAbuse & | ||
25 | UseVideoAbuse<'Video', MVideo> | ||
26 | |||
27 | export type MVideoAbuseVideoUrl = | ||
28 | MVideoAbuse & | ||
29 | UseVideoAbuse<'Video', MVideoUrl> | ||
30 | |||
31 | export type MVideoAbuseVideoFull = | ||
32 | MVideoAbuse & | ||
33 | UseVideoAbuse<'Video', MVideoAccountLightBlacklistAllFiles> | ||
34 | |||
35 | export type MVideoAbuseFormattable = | ||
36 | MVideoAbuse & | ||
37 | UseVideoAbuse<'Video', Pick<MVideoAccountLightBlacklistAllFiles, | ||
38 | 'id' | 'uuid' | 'name' | 'nsfw' | 'getMiniatureStaticPath' | 'isBlacklisted' | 'VideoChannel'>> | ||
39 | |||
40 | // ############################################################################ | ||
41 | |||
42 | export type MCommentAbuseAccount = | ||
43 | MCommentAbuse & | ||
44 | UseCommentAbuse<'VideoComment', MCommentOwner> | ||
45 | |||
46 | export type MCommentAbuseAccountVideo = | ||
47 | MCommentAbuse & | ||
48 | UseCommentAbuse<'VideoComment', MCommentOwnerVideo> | ||
49 | |||
50 | export type MCommentAbuseUrl = | ||
51 | MCommentAbuse & | ||
52 | UseCommentAbuse<'VideoComment', MCommentUrl> | ||
53 | |||
54 | // ############################################################################ | ||
55 | |||
56 | export type MAbuseId = Pick<AbuseModel, 'id'> | ||
57 | |||
58 | export type MAbuseVideo = | ||
59 | MAbuse & | ||
60 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
61 | Use<'VideoAbuse', MVideoAbuseVideo> | ||
62 | |||
63 | export type MAbuseUrl = | ||
64 | MAbuse & | ||
65 | Use<'VideoAbuse', MVideoAbuseVideoUrl> & | ||
66 | Use<'VideoCommentAbuse', MCommentAbuseUrl> | ||
67 | |||
68 | export type MAbuseAccountVideo = | ||
69 | MAbuse & | ||
70 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
71 | Use<'VideoAbuse', MVideoAbuseVideoFull> & | ||
72 | Use<'ReporterAccount', MAccountDefault> | ||
73 | |||
74 | export type MAbuseAP = | ||
75 | MAbuse & | ||
76 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
77 | Use<'ReporterAccount', MAccountUrl> & | ||
78 | Use<'FlaggedAccount', MAccountUrl> & | ||
79 | Use<'VideoAbuse', MVideoAbuseVideo> & | ||
80 | Use<'VideoCommentAbuse', MCommentAbuseAccount> | ||
81 | |||
82 | export type MAbuseFull = | ||
83 | MAbuse & | ||
84 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
85 | Use<'ReporterAccount', MAccountLight> & | ||
86 | Use<'FlaggedAccount', MAccountLight> & | ||
87 | Use<'VideoAbuse', MVideoAbuseVideoFull> & | ||
88 | Use<'VideoCommentAbuse', MCommentAbuseAccountVideo> | ||
89 | |||
90 | // ############################################################################ | ||
91 | |||
92 | // Format for API or AP object | ||
93 | |||
94 | export type MAbuseFormattable = | ||
95 | MAbuse & | ||
96 | Use<'ReporterAccount', MAccountFormattable> & | ||
97 | Use<'VideoAbuse', MVideoAbuseFormattable> | ||
diff --git a/server/types/models/moderation/index.ts b/server/types/models/moderation/index.ts new file mode 100644 index 000000000..8bea1708f --- /dev/null +++ b/server/types/models/moderation/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './abuse' | |||
diff --git a/server/types/models/user/user-notification.ts b/server/types/models/user/user-notification.ts index dd3de423b..92ea16768 100644 --- a/server/types/models/user/user-notification.ts +++ b/server/types/models/user/user-notification.ts | |||
@@ -1,16 +1,18 @@ | |||
1 | import { UserNotificationModel } from '../../../models/account/user-notification' | 1 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' |
2 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
2 | import { PickWith, PickWithOpt } from '@shared/core-utils' | 3 | import { PickWith, PickWithOpt } from '@shared/core-utils' |
3 | import { VideoModel } from '../../../models/video/video' | 4 | import { AbuseModel } from '../../../models/abuse/abuse' |
5 | import { AccountModel } from '../../../models/account/account' | ||
6 | import { UserNotificationModel } from '../../../models/account/user-notification' | ||
4 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { ServerModel } from '../../../models/server/server' | 8 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
6 | import { AvatarModel } from '../../../models/avatar/avatar' | 9 | import { AvatarModel } from '../../../models/avatar/avatar' |
10 | import { ServerModel } from '../../../models/server/server' | ||
11 | import { VideoModel } from '../../../models/video/video' | ||
12 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | 13 | import { VideoChannelModel } from '../../../models/video/video-channel' |
8 | import { AccountModel } from '../../../models/account/account' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | 14 | import { VideoCommentModel } from '../../../models/video/video-comment' |
10 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
11 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
12 | import { VideoImportModel } from '../../../models/video/video-import' | 15 | import { VideoImportModel } from '../../../models/video/video-import' |
13 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
14 | 16 | ||
15 | type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M> | 17 | type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M> |
16 | 18 | ||
@@ -47,6 +49,18 @@ export module UserNotificationIncludes { | |||
47 | Pick<VideoAbuseModel, 'id'> & | 49 | Pick<VideoAbuseModel, 'id'> & |
48 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | 50 | PickWith<VideoAbuseModel, 'Video', VideoInclude> |
49 | 51 | ||
52 | export type VideoCommentAbuseInclude = | ||
53 | Pick<VideoCommentAbuseModel, 'id'> & | ||
54 | PickWith<VideoCommentAbuseModel, 'VideoComment', | ||
55 | Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & | ||
56 | PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'uuid'>>> | ||
57 | |||
58 | export type AbuseInclude = | ||
59 | Pick<AbuseModel, 'id'> & | ||
60 | PickWith<AbuseModel, 'VideoAbuse', VideoAbuseInclude> & | ||
61 | PickWith<AbuseModel, 'VideoCommentAbuse', VideoCommentAbuseInclude> & | ||
62 | PickWith<AbuseModel, 'FlaggedAccount', AccountIncludeActor> | ||
63 | |||
50 | export type VideoBlacklistInclude = | 64 | export type VideoBlacklistInclude = |
51 | Pick<VideoBlacklistModel, 'id'> & | 65 | Pick<VideoBlacklistModel, 'id'> & |
52 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | 66 | PickWith<VideoAbuseModel, 'Video', VideoInclude> |
@@ -76,7 +90,7 @@ export module UserNotificationIncludes { | |||
76 | // ############################################################################ | 90 | // ############################################################################ |
77 | 91 | ||
78 | export type MUserNotification = | 92 | export type MUserNotification = |
79 | Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | | 93 | Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'Abuse' | 'VideoBlacklist' | |
80 | 'VideoImport' | 'Account' | 'ActorFollow'> | 94 | 'VideoImport' | 'Account' | 'ActorFollow'> |
81 | 95 | ||
82 | // ############################################################################ | 96 | // ############################################################################ |
@@ -85,7 +99,7 @@ export type UserNotificationModelForApi = | |||
85 | MUserNotification & | 99 | MUserNotification & |
86 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & | 100 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & |
87 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & | 101 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & |
88 | Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & | 102 | Use<'Abuse', UserNotificationIncludes.AbuseInclude> & |
89 | Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> & | 103 | Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> & |
90 | Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> & | 104 | Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> & |
91 | Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> & | 105 | Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> & |
diff --git a/server/types/models/video/index.ts b/server/types/models/video/index.ts index bd69c8a4b..25db23898 100644 --- a/server/types/models/video/index.ts +++ b/server/types/models/video/index.ts | |||
@@ -2,7 +2,6 @@ export * from './schedule-video-update' | |||
2 | export * from './tag' | 2 | export * from './tag' |
3 | export * from './thumbnail' | 3 | export * from './thumbnail' |
4 | export * from './video' | 4 | export * from './video' |
5 | export * from './video-abuse' | ||
6 | export * from './video-blacklist' | 5 | export * from './video-blacklist' |
7 | export * from './video-caption' | 6 | export * from './video-caption' |
8 | export * from './video-change-ownership' | 7 | export * from './video-change-ownership' |
diff --git a/server/types/models/video/video-abuse.ts b/server/types/models/video/video-abuse.ts deleted file mode 100644 index 279a87cf3..000000000 --- a/server/types/models/video/video-abuse.ts +++ /dev/null | |||
@@ -1,35 +0,0 @@ | |||
1 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
2 | import { PickWith } from '@shared/core-utils' | ||
3 | import { MVideoAccountLightBlacklistAllFiles, MVideo } from './video' | ||
4 | import { MAccountDefault, MAccountFormattable } from '../account' | ||
5 | |||
6 | type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivityPubObject'> | ||
11 | |||
12 | // ############################################################################ | ||
13 | |||
14 | export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> | ||
15 | |||
16 | export type MVideoAbuseVideo = | ||
17 | MVideoAbuse & | ||
18 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
19 | Use<'Video', MVideo> | ||
20 | |||
21 | export type MVideoAbuseAccountVideo = | ||
22 | MVideoAbuse & | ||
23 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
24 | Use<'Video', MVideoAccountLightBlacklistAllFiles> & | ||
25 | Use<'Account', MAccountDefault> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // Format for API or AP object | ||
30 | |||
31 | export type MVideoAbuseFormattable = | ||
32 | MVideoAbuse & | ||
33 | Use<'Account', MAccountFormattable> & | ||
34 | Use<'Video', Pick<MVideoAccountLightBlacklistAllFiles, | ||
35 | 'id' | 'uuid' | 'name' | 'nsfw' | 'getMiniatureStaticPath' | 'isBlacklisted' | 'VideoChannel'>> | ||
diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts index cac801e55..7595e6d86 100644 --- a/server/typings/express/index.d.ts +++ b/server/typings/express/index.d.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { RegisterServerAuthExternalOptions } from '@server/types' | 1 | import { RegisterServerAuthExternalOptions } from '@server/types' |
2 | import { | 2 | import { |
3 | MAbuse, | ||
3 | MAccountBlocklist, | 4 | MAccountBlocklist, |
4 | MActorUrl, | 5 | MActorUrl, |
5 | MStreamingPlaylist, | 6 | MStreamingPlaylist, |
@@ -26,7 +27,6 @@ import { | |||
26 | MComment, | 27 | MComment, |
27 | MCommentOwnerVideoReply, | 28 | MCommentOwnerVideoReply, |
28 | MUserDefault, | 29 | MUserDefault, |
29 | MVideoAbuse, | ||
30 | MVideoBlacklist, | 30 | MVideoBlacklist, |
31 | MVideoCaptionVideo, | 31 | MVideoCaptionVideo, |
32 | MVideoFullLight, | 32 | MVideoFullLight, |
@@ -77,7 +77,7 @@ declare module 'express' { | |||
77 | 77 | ||
78 | videoCaption?: MVideoCaptionVideo | 78 | videoCaption?: MVideoCaptionVideo |
79 | 79 | ||
80 | videoAbuse?: MVideoAbuse | 80 | abuse?: MAbuse |
81 | 81 | ||
82 | videoStreamingPlaylist?: MStreamingPlaylist | 82 | videoStreamingPlaylist?: MStreamingPlaylist |
83 | 83 | ||
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts index 2ac0c6338..af4d23856 100644 --- a/shared/extra-utils/index.ts +++ b/shared/extra-utils/index.ts | |||
@@ -17,6 +17,7 @@ export * from './videos/services' | |||
17 | export * from './videos/video-playlists' | 17 | export * from './videos/video-playlists' |
18 | export * from './users/users' | 18 | export * from './users/users' |
19 | export * from './users/accounts' | 19 | export * from './users/accounts' |
20 | export * from './moderation/abuses' | ||
20 | export * from './videos/video-abuses' | 21 | export * from './videos/video-abuses' |
21 | export * from './videos/video-blacklist' | 22 | export * from './videos/video-blacklist' |
22 | export * from './videos/video-captions' | 23 | export * from './videos/video-captions' |
diff --git a/shared/extra-utils/moderation/abuses.ts b/shared/extra-utils/moderation/abuses.ts new file mode 100644 index 000000000..48a51e2b8 --- /dev/null +++ b/shared/extra-utils/moderation/abuses.ts | |||
@@ -0,0 +1,112 @@ | |||
1 | import * as request from 'supertest' | ||
2 | import { AbusePredefinedReasonsString, AbuseState, AbuseUpdate, AbuseVideoIs } from '@shared/models' | ||
3 | import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' | ||
4 | |||
5 | function reportAbuse ( | ||
6 | url: string, | ||
7 | token: string, | ||
8 | videoId: number | string, | ||
9 | reason: string, | ||
10 | predefinedReasons?: AbusePredefinedReasonsString[], | ||
11 | startAt?: number, | ||
12 | endAt?: number, | ||
13 | specialStatus = 200 | ||
14 | ) { | ||
15 | const path = '/api/v1/videos/' + videoId + '/abuse' | ||
16 | |||
17 | return request(url) | ||
18 | .post(path) | ||
19 | .set('Accept', 'application/json') | ||
20 | .set('Authorization', 'Bearer ' + token) | ||
21 | .send({ reason, predefinedReasons, startAt, endAt }) | ||
22 | .expect(specialStatus) | ||
23 | } | ||
24 | |||
25 | function getAbusesList (options: { | ||
26 | url: string | ||
27 | token: string | ||
28 | id?: number | ||
29 | predefinedReason?: AbusePredefinedReasonsString | ||
30 | search?: string | ||
31 | state?: AbuseState | ||
32 | videoIs?: AbuseVideoIs | ||
33 | searchReporter?: string | ||
34 | searchReportee?: string | ||
35 | searchVideo?: string | ||
36 | searchVideoChannel?: string | ||
37 | }) { | ||
38 | const { | ||
39 | url, | ||
40 | token, | ||
41 | id, | ||
42 | predefinedReason, | ||
43 | search, | ||
44 | state, | ||
45 | videoIs, | ||
46 | searchReporter, | ||
47 | searchReportee, | ||
48 | searchVideo, | ||
49 | searchVideoChannel | ||
50 | } = options | ||
51 | const path = '/api/v1/videos/abuse' | ||
52 | |||
53 | const query = { | ||
54 | sort: 'createdAt', | ||
55 | id, | ||
56 | predefinedReason, | ||
57 | search, | ||
58 | state, | ||
59 | videoIs, | ||
60 | searchReporter, | ||
61 | searchReportee, | ||
62 | searchVideo, | ||
63 | searchVideoChannel | ||
64 | } | ||
65 | |||
66 | return makeGetRequest({ | ||
67 | url, | ||
68 | path, | ||
69 | token, | ||
70 | query, | ||
71 | statusCodeExpected: 200 | ||
72 | }) | ||
73 | } | ||
74 | |||
75 | function updateAbuse ( | ||
76 | url: string, | ||
77 | token: string, | ||
78 | videoId: string | number, | ||
79 | videoAbuseId: number, | ||
80 | body: AbuseUpdate, | ||
81 | statusCodeExpected = 204 | ||
82 | ) { | ||
83 | const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId | ||
84 | |||
85 | return makePutBodyRequest({ | ||
86 | url, | ||
87 | token, | ||
88 | path, | ||
89 | fields: body, | ||
90 | statusCodeExpected | ||
91 | }) | ||
92 | } | ||
93 | |||
94 | function deleteAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) { | ||
95 | const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId | ||
96 | |||
97 | return makeDeleteRequest({ | ||
98 | url, | ||
99 | token, | ||
100 | path, | ||
101 | statusCodeExpected | ||
102 | }) | ||
103 | } | ||
104 | |||
105 | // --------------------------------------------------------------------------- | ||
106 | |||
107 | export { | ||
108 | reportAbuse, | ||
109 | getAbusesList, | ||
110 | updateAbuse, | ||
111 | deleteAbuse | ||
112 | } | ||
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts index a17a39de9..62f3418c5 100644 --- a/shared/extra-utils/users/user-notifications.ts +++ b/shared/extra-utils/users/user-notifications.ts | |||
@@ -443,11 +443,11 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU | |||
443 | expect(notification).to.not.be.undefined | 443 | expect(notification).to.not.be.undefined |
444 | expect(notification.type).to.equal(notificationType) | 444 | expect(notification.type).to.equal(notificationType) |
445 | 445 | ||
446 | expect(notification.videoAbuse.id).to.be.a('number') | 446 | expect(notification.abuse.id).to.be.a('number') |
447 | checkVideo(notification.videoAbuse.video, videoName, videoUUID) | 447 | checkVideo(notification.abuse.video, videoName, videoUUID) |
448 | } else { | 448 | } else { |
449 | expect(notification).to.satisfy((n: UserNotification) => { | 449 | expect(notification).to.satisfy((n: UserNotification) => { |
450 | return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID | 450 | return n === undefined || n.abuse === undefined || n.abuse.video.uuid !== videoUUID |
451 | }) | 451 | }) |
452 | } | 452 | } |
453 | } | 453 | } |
diff --git a/shared/extra-utils/videos/video-abuses.ts b/shared/extra-utils/videos/video-abuses.ts index ff006672a..8827b8196 100644 --- a/shared/extra-utils/videos/video-abuses.ts +++ b/shared/extra-utils/videos/video-abuses.ts | |||
@@ -1,15 +1,15 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model' | 2 | import { AbusePredefinedReasonsString, AbuseState, AbuseUpdate, AbuseVideoIs } from '@shared/models' |
3 | import { makeDeleteRequest, makePutBodyRequest, makeGetRequest } from '../requests/requests' | 3 | import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' |
4 | import { VideoAbuseState, VideoAbusePredefinedReasonsString } from '@shared/models' | 4 | |
5 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' | 5 | // FIXME: deprecated in 2.3. Remove this file |
6 | 6 | ||
7 | function reportVideoAbuse ( | 7 | function reportVideoAbuse ( |
8 | url: string, | 8 | url: string, |
9 | token: string, | 9 | token: string, |
10 | videoId: number | string, | 10 | videoId: number | string, |
11 | reason: string, | 11 | reason: string, |
12 | predefinedReasons?: VideoAbusePredefinedReasonsString[], | 12 | predefinedReasons?: AbusePredefinedReasonsString[], |
13 | startAt?: number, | 13 | startAt?: number, |
14 | endAt?: number, | 14 | endAt?: number, |
15 | specialStatus = 200 | 15 | specialStatus = 200 |
@@ -28,10 +28,10 @@ function getVideoAbusesList (options: { | |||
28 | url: string | 28 | url: string |
29 | token: string | 29 | token: string |
30 | id?: number | 30 | id?: number |
31 | predefinedReason?: VideoAbusePredefinedReasonsString | 31 | predefinedReason?: AbusePredefinedReasonsString |
32 | search?: string | 32 | search?: string |
33 | state?: VideoAbuseState | 33 | state?: AbuseState |
34 | videoIs?: VideoAbuseVideoIs | 34 | videoIs?: AbuseVideoIs |
35 | searchReporter?: string | 35 | searchReporter?: string |
36 | searchReportee?: string | 36 | searchReportee?: string |
37 | searchVideo?: string | 37 | searchVideo?: string |
@@ -79,7 +79,7 @@ function updateVideoAbuse ( | |||
79 | token: string, | 79 | token: string, |
80 | videoId: string | number, | 80 | videoId: string | number, |
81 | videoAbuseId: number, | 81 | videoAbuseId: number, |
82 | body: VideoAbuseUpdate, | 82 | body: AbuseUpdate, |
83 | statusCodeExpected = 204 | 83 | statusCodeExpected = 204 |
84 | ) { | 84 | ) { |
85 | const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId | 85 | const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId |
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 31b9e4673..5b4ce214a 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { ActivityPubActor } from './activitypub-actor' | 1 | import { ActivityPubActor } from './activitypub-actor' |
2 | import { ActivityPubSignature } from './activitypub-signature' | 2 | import { ActivityPubSignature } from './activitypub-signature' |
3 | import { CacheFileObject, VideoTorrentObject, ActivityFlagReasonObject } from './objects' | 3 | import { ActivityFlagReasonObject, CacheFileObject, VideoTorrentObject } from './objects' |
4 | import { AbuseObject } from './objects/abuse-object' | ||
4 | import { DislikeObject } from './objects/dislike-object' | 5 | import { DislikeObject } from './objects/dislike-object' |
5 | import { VideoAbuseObject } from './objects/video-abuse-object' | ||
6 | import { VideoCommentObject } from './objects/video-comment-object' | ||
7 | import { ViewObject } from './objects/view-object' | ||
8 | import { APObject } from './objects/object.model' | 6 | import { APObject } from './objects/object.model' |
9 | import { PlaylistObject } from './objects/playlist-object' | 7 | import { PlaylistObject } from './objects/playlist-object' |
8 | import { VideoCommentObject } from './objects/video-comment-object' | ||
9 | import { ViewObject } from './objects/view-object' | ||
10 | 10 | ||
11 | export type Activity = | 11 | export type Activity = |
12 | ActivityCreate | | 12 | ActivityCreate | |
@@ -53,7 +53,7 @@ export interface BaseActivity { | |||
53 | 53 | ||
54 | export interface ActivityCreate extends BaseActivity { | 54 | export interface ActivityCreate extends BaseActivity { |
55 | type: 'Create' | 55 | type: 'Create' |
56 | object: VideoTorrentObject | VideoAbuseObject | ViewObject | DislikeObject | VideoCommentObject | CacheFileObject | PlaylistObject | 56 | object: VideoTorrentObject | AbuseObject | ViewObject | DislikeObject | VideoCommentObject | CacheFileObject | PlaylistObject |
57 | } | 57 | } |
58 | 58 | ||
59 | export interface ActivityUpdate extends BaseActivity { | 59 | export interface ActivityUpdate extends BaseActivity { |
diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/abuse-object.ts index 73add8ef4..ad45cc064 100644 --- a/shared/models/activitypub/objects/video-abuse-object.ts +++ b/shared/models/activitypub/objects/abuse-object.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import { ActivityFlagReasonObject } from './common-objects' | 1 | import { ActivityFlagReasonObject } from './common-objects' |
2 | 2 | ||
3 | export interface VideoAbuseObject { | 3 | export interface AbuseObject { |
4 | type: 'Flag' | 4 | type: 'Flag' |
5 | content: string | 5 | content: string |
6 | object: string | string[] | 6 | object: string | string[] |
7 | |||
7 | tag?: ActivityFlagReasonObject[] | 8 | tag?: ActivityFlagReasonObject[] |
9 | |||
8 | startAt?: number | 10 | startAt?: number |
9 | endAt?: number | 11 | endAt?: number |
10 | } | 12 | } |
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts index 096d422ea..711ce45f4 100644 --- a/shared/models/activitypub/objects/common-objects.ts +++ b/shared/models/activitypub/objects/common-objects.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { VideoAbusePredefinedReasonsString } from '@shared/models/videos' | 1 | import { AbusePredefinedReasonsString } from '@shared/models' |
2 | 2 | ||
3 | export interface ActivityIdentifierObject { | 3 | export interface ActivityIdentifierObject { |
4 | identifier: string | 4 | identifier: string |
@@ -85,7 +85,7 @@ export interface ActivityMentionObject { | |||
85 | 85 | ||
86 | export interface ActivityFlagReasonObject { | 86 | export interface ActivityFlagReasonObject { |
87 | type: 'Hashtag' | 87 | type: 'Hashtag' |
88 | name: VideoAbusePredefinedReasonsString | 88 | name: AbusePredefinedReasonsString |
89 | } | 89 | } |
90 | 90 | ||
91 | export type ActivityTagObject = | 91 | export type ActivityTagObject = |
diff --git a/shared/models/activitypub/objects/index.ts b/shared/models/activitypub/objects/index.ts index fba61e12f..a6a20e87a 100644 --- a/shared/models/activitypub/objects/index.ts +++ b/shared/models/activitypub/objects/index.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | export * from './abuse-object' | ||
1 | export * from './cache-file-object' | 2 | export * from './cache-file-object' |
2 | export * from './common-objects' | 3 | export * from './common-objects' |
3 | export * from './video-abuse-object' | 4 | export * from './dislike-object' |
4 | export * from './video-torrent-object' | 5 | export * from './video-torrent-object' |
5 | export * from './view-object' | 6 | export * from './view-object' |
6 | export * from './dislike-object' | ||
diff --git a/shared/models/index.ts b/shared/models/index.ts index 3d4bdedde..a68f57148 100644 --- a/shared/models/index.ts +++ b/shared/models/index.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | export * from './activitypub' | 1 | export * from './activitypub' |
2 | export * from './actors' | 2 | export * from './actors' |
3 | export * from './avatars' | 3 | export * from './avatars' |
4 | export * from './blocklist' | 4 | export * from './moderation' |
5 | export * from './bulk' | 5 | export * from './bulk' |
6 | export * from './redundancy' | 6 | export * from './redundancy' |
7 | export * from './users' | 7 | export * from './users' |
@@ -14,4 +14,3 @@ export * from './search' | |||
14 | export * from './server' | 14 | export * from './server' |
15 | export * from './oauth-client-local.model' | 15 | export * from './oauth-client-local.model' |
16 | export * from './result-list.model' | 16 | export * from './result-list.model' |
17 | export * from './server/server-config.model' | ||
diff --git a/shared/models/moderation/abuse/abuse-create.model.ts b/shared/models/moderation/abuse/abuse-create.model.ts new file mode 100644 index 000000000..c0d04e46d --- /dev/null +++ b/shared/models/moderation/abuse/abuse-create.model.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import { AbusePredefinedReasonsString } from './abuse-reason.model' | ||
2 | |||
3 | export interface AbuseCreate { | ||
4 | accountId: number | ||
5 | |||
6 | reason: string | ||
7 | predefinedReasons?: AbusePredefinedReasonsString[] | ||
8 | |||
9 | video?: { | ||
10 | id: number | ||
11 | startAt?: number | ||
12 | endAt?: number | ||
13 | } | ||
14 | |||
15 | comment?: { | ||
16 | id: number | ||
17 | } | ||
18 | } | ||
19 | |||
20 | // FIXME: deprecated in 2.3. Remove it | ||
21 | export interface VideoAbuseCreate { | ||
22 | reason: string | ||
23 | predefinedReasons?: AbusePredefinedReasonsString[] | ||
24 | startAt?: number | ||
25 | endAt?: number | ||
26 | } | ||
diff --git a/shared/models/moderation/abuse/abuse-filter.ts b/shared/models/moderation/abuse/abuse-filter.ts new file mode 100644 index 000000000..03303bbab --- /dev/null +++ b/shared/models/moderation/abuse/abuse-filter.ts | |||
@@ -0,0 +1 @@ | |||
export type AbuseFilter = 'video' | 'comment' | |||
diff --git a/shared/models/moderation/abuse/abuse-reason.model.ts b/shared/models/moderation/abuse/abuse-reason.model.ts new file mode 100644 index 000000000..36875969d --- /dev/null +++ b/shared/models/moderation/abuse/abuse-reason.model.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | export enum AbusePredefinedReasons { | ||
2 | VIOLENT_OR_REPULSIVE = 1, | ||
3 | HATEFUL_OR_ABUSIVE, | ||
4 | SPAM_OR_MISLEADING, | ||
5 | PRIVACY, | ||
6 | RIGHTS, | ||
7 | SERVER_RULES, | ||
8 | THUMBNAILS, | ||
9 | CAPTIONS | ||
10 | } | ||
11 | |||
12 | export type AbusePredefinedReasonsString = | ||
13 | 'violentOrRepulsive' | | ||
14 | 'hatefulOrAbusive' | | ||
15 | 'spamOrMisleading' | | ||
16 | 'privacy' | | ||
17 | 'rights' | | ||
18 | 'serverRules' | | ||
19 | 'thumbnails' | | ||
20 | 'captions' | ||
21 | |||
22 | export const abusePredefinedReasonsMap: { | ||
23 | [key in AbusePredefinedReasonsString]: AbusePredefinedReasons | ||
24 | } = { | ||
25 | violentOrRepulsive: AbusePredefinedReasons.VIOLENT_OR_REPULSIVE, | ||
26 | hatefulOrAbusive: AbusePredefinedReasons.HATEFUL_OR_ABUSIVE, | ||
27 | spamOrMisleading: AbusePredefinedReasons.SPAM_OR_MISLEADING, | ||
28 | privacy: AbusePredefinedReasons.PRIVACY, | ||
29 | rights: AbusePredefinedReasons.RIGHTS, | ||
30 | serverRules: AbusePredefinedReasons.SERVER_RULES, | ||
31 | thumbnails: AbusePredefinedReasons.THUMBNAILS, | ||
32 | captions: AbusePredefinedReasons.CAPTIONS | ||
33 | } | ||
diff --git a/shared/models/videos/abuse/video-abuse-state.model.ts b/shared/models/moderation/abuse/abuse-state.model.ts index 529f034bd..b00cccad8 100644 --- a/shared/models/videos/abuse/video-abuse-state.model.ts +++ b/shared/models/moderation/abuse/abuse-state.model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export enum VideoAbuseState { | 1 | export enum AbuseState { |
2 | PENDING = 1, | 2 | PENDING = 1, |
3 | REJECTED = 2, | 3 | REJECTED = 2, |
4 | ACCEPTED = 3 | 4 | ACCEPTED = 3 |
diff --git a/shared/models/moderation/abuse/abuse-update.model.ts b/shared/models/moderation/abuse/abuse-update.model.ts new file mode 100644 index 000000000..4360fe7ac --- /dev/null +++ b/shared/models/moderation/abuse/abuse-update.model.ts | |||
@@ -0,0 +1,7 @@ | |||
1 | import { AbuseState } from './abuse-state.model' | ||
2 | |||
3 | export interface AbuseUpdate { | ||
4 | moderationComment?: string | ||
5 | |||
6 | state?: AbuseState | ||
7 | } | ||
diff --git a/shared/models/moderation/abuse/abuse-video-is.type.ts b/shared/models/moderation/abuse/abuse-video-is.type.ts new file mode 100644 index 000000000..74937f3b9 --- /dev/null +++ b/shared/models/moderation/abuse/abuse-video-is.type.ts | |||
@@ -0,0 +1 @@ | |||
export type AbuseVideoIs = 'deleted' | 'blacklisted' | |||
diff --git a/shared/models/moderation/abuse/abuse.model.ts b/shared/models/moderation/abuse/abuse.model.ts new file mode 100644 index 000000000..9ff150c4a --- /dev/null +++ b/shared/models/moderation/abuse/abuse.model.ts | |||
@@ -0,0 +1,53 @@ | |||
1 | import { Account } from '../../actors/account.model' | ||
2 | import { AbuseState } from './abuse-state.model' | ||
3 | import { AbusePredefinedReasonsString } from './abuse-reason.model' | ||
4 | import { VideoConstant } from '../../videos/video-constant.model' | ||
5 | import { VideoChannel } from '../../videos/channel/video-channel.model' | ||
6 | |||
7 | export interface VideoAbuse { | ||
8 | id: number | ||
9 | name: string | ||
10 | uuid: string | ||
11 | nsfw: boolean | ||
12 | deleted: boolean | ||
13 | blacklisted: boolean | ||
14 | |||
15 | startAt: number | null | ||
16 | endAt: number | null | ||
17 | |||
18 | thumbnailPath?: string | ||
19 | channel?: VideoChannel | ||
20 | } | ||
21 | |||
22 | export interface VideoCommentAbuse { | ||
23 | id: number | ||
24 | account?: Account | ||
25 | text: string | ||
26 | deleted: boolean | ||
27 | } | ||
28 | |||
29 | export interface Abuse { | ||
30 | id: number | ||
31 | reason: string | ||
32 | predefinedReasons?: AbusePredefinedReasonsString[] | ||
33 | reporterAccount: Account | ||
34 | |||
35 | state: VideoConstant<AbuseState> | ||
36 | moderationComment?: string | ||
37 | |||
38 | video?: VideoAbuse | ||
39 | comment?: VideoCommentAbuse | ||
40 | |||
41 | createdAt: Date | ||
42 | updatedAt: Date | ||
43 | |||
44 | // FIXME: deprecated in 2.3, remove this | ||
45 | startAt: null | ||
46 | endAt: null | ||
47 | |||
48 | count?: number | ||
49 | nth?: number | ||
50 | |||
51 | countReportsForReporter?: number | ||
52 | countReportsForReportee?: number | ||
53 | } | ||
diff --git a/shared/models/moderation/abuse/index.ts b/shared/models/moderation/abuse/index.ts new file mode 100644 index 000000000..32a6b4e6c --- /dev/null +++ b/shared/models/moderation/abuse/index.ts | |||
@@ -0,0 +1,6 @@ | |||
1 | export * from './abuse-create.model' | ||
2 | export * from './abuse-reason.model' | ||
3 | export * from './abuse-state.model' | ||
4 | export * from './abuse-update.model' | ||
5 | export * from './abuse-video-is.type' | ||
6 | export * from './abuse.model' | ||
diff --git a/shared/models/blocklist/account-block.model.ts b/shared/models/moderation/account-block.model.ts index a942ed614..a942ed614 100644 --- a/shared/models/blocklist/account-block.model.ts +++ b/shared/models/moderation/account-block.model.ts | |||
diff --git a/shared/models/blocklist/index.ts b/shared/models/moderation/index.ts index fc7873270..8b6042e97 100644 --- a/shared/models/blocklist/index.ts +++ b/shared/models/moderation/index.ts | |||
@@ -1,2 +1,3 @@ | |||
1 | export * from './abuse' | ||
1 | export * from './account-block.model' | 2 | export * from './account-block.model' |
2 | export * from './server-block.model' | 3 | export * from './server-block.model' |
diff --git a/shared/models/blocklist/server-block.model.ts b/shared/models/moderation/server-block.model.ts index a8b8af0b7..a8b8af0b7 100644 --- a/shared/models/blocklist/server-block.model.ts +++ b/shared/models/moderation/server-block.model.ts | |||
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts index e9be1ca7f..39090f5a1 100644 --- a/shared/models/users/user-notification.model.ts +++ b/shared/models/users/user-notification.model.ts | |||
@@ -64,9 +64,20 @@ export interface UserNotification { | |||
64 | video: VideoInfo | 64 | video: VideoInfo |
65 | } | 65 | } |
66 | 66 | ||
67 | videoAbuse?: { | 67 | abuse?: { |
68 | id: number | 68 | id: number |
69 | video: VideoInfo | 69 | |
70 | video?: VideoInfo | ||
71 | |||
72 | comment?: { | ||
73 | threadId: number | ||
74 | |||
75 | video: { | ||
76 | uuid: string | ||
77 | } | ||
78 | } | ||
79 | |||
80 | account?: ActorInfo | ||
70 | } | 81 | } |
71 | 82 | ||
72 | videoBlacklist?: { | 83 | videoBlacklist?: { |
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts index 2f88a65de..4a7ae4373 100644 --- a/shared/models/users/user-right.enum.ts +++ b/shared/models/users/user-right.enum.ts | |||
@@ -11,7 +11,7 @@ export enum UserRight { | |||
11 | 11 | ||
12 | MANAGE_SERVER_REDUNDANCY, | 12 | MANAGE_SERVER_REDUNDANCY, |
13 | 13 | ||
14 | MANAGE_VIDEO_ABUSES, | 14 | MANAGE_ABUSES, |
15 | 15 | ||
16 | MANAGE_JOBS, | 16 | MANAGE_JOBS, |
17 | 17 | ||
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts index 2b08b5850..772988c0c 100644 --- a/shared/models/users/user-role.ts +++ b/shared/models/users/user-role.ts | |||
@@ -20,7 +20,7 @@ const userRoleRights: { [ id in UserRole ]: UserRight[] } = { | |||
20 | 20 | ||
21 | [UserRole.MODERATOR]: [ | 21 | [UserRole.MODERATOR]: [ |
22 | UserRight.MANAGE_VIDEO_BLACKLIST, | 22 | UserRight.MANAGE_VIDEO_BLACKLIST, |
23 | UserRight.MANAGE_VIDEO_ABUSES, | 23 | UserRight.MANAGE_ABUSES, |
24 | UserRight.REMOVE_ANY_VIDEO, | 24 | UserRight.REMOVE_ANY_VIDEO, |
25 | UserRight.REMOVE_ANY_VIDEO_CHANNEL, | 25 | UserRight.REMOVE_ANY_VIDEO_CHANNEL, |
26 | UserRight.REMOVE_ANY_VIDEO_PLAYLIST, | 26 | UserRight.REMOVE_ANY_VIDEO_PLAYLIST, |
diff --git a/shared/models/videos/abuse/index.ts b/shared/models/videos/abuse/index.ts deleted file mode 100644 index f70bc736f..000000000 --- a/shared/models/videos/abuse/index.ts +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | export * from './video-abuse-create.model' | ||
2 | export * from './video-abuse-reason.model' | ||
3 | export * from './video-abuse-state.model' | ||
4 | export * from './video-abuse-update.model' | ||
5 | export * from './video-abuse-video-is.type' | ||
6 | export * from './video-abuse.model' | ||
diff --git a/shared/models/videos/abuse/video-abuse-create.model.ts b/shared/models/videos/abuse/video-abuse-create.model.ts deleted file mode 100644 index c93cb8b2c..000000000 --- a/shared/models/videos/abuse/video-abuse-create.model.ts +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | import { VideoAbusePredefinedReasonsString } from './video-abuse-reason.model' | ||
2 | |||
3 | export interface VideoAbuseCreate { | ||
4 | reason: string | ||
5 | predefinedReasons?: VideoAbusePredefinedReasonsString[] | ||
6 | startAt?: number | ||
7 | endAt?: number | ||
8 | } | ||
diff --git a/shared/models/videos/abuse/video-abuse-reason.model.ts b/shared/models/videos/abuse/video-abuse-reason.model.ts deleted file mode 100644 index 9064f0c1a..000000000 --- a/shared/models/videos/abuse/video-abuse-reason.model.ts +++ /dev/null | |||
@@ -1,33 +0,0 @@ | |||
1 | export enum VideoAbusePredefinedReasons { | ||
2 | VIOLENT_OR_REPULSIVE = 1, | ||
3 | HATEFUL_OR_ABUSIVE, | ||
4 | SPAM_OR_MISLEADING, | ||
5 | PRIVACY, | ||
6 | RIGHTS, | ||
7 | SERVER_RULES, | ||
8 | THUMBNAILS, | ||
9 | CAPTIONS | ||
10 | } | ||
11 | |||
12 | export type VideoAbusePredefinedReasonsString = | ||
13 | 'violentOrRepulsive' | | ||
14 | 'hatefulOrAbusive' | | ||
15 | 'spamOrMisleading' | | ||
16 | 'privacy' | | ||
17 | 'rights' | | ||
18 | 'serverRules' | | ||
19 | 'thumbnails' | | ||
20 | 'captions' | ||
21 | |||
22 | export const videoAbusePredefinedReasonsMap: { | ||
23 | [key in VideoAbusePredefinedReasonsString]: VideoAbusePredefinedReasons | ||
24 | } = { | ||
25 | violentOrRepulsive: VideoAbusePredefinedReasons.VIOLENT_OR_REPULSIVE, | ||
26 | hatefulOrAbusive: VideoAbusePredefinedReasons.HATEFUL_OR_ABUSIVE, | ||
27 | spamOrMisleading: VideoAbusePredefinedReasons.SPAM_OR_MISLEADING, | ||
28 | privacy: VideoAbusePredefinedReasons.PRIVACY, | ||
29 | rights: VideoAbusePredefinedReasons.RIGHTS, | ||
30 | serverRules: VideoAbusePredefinedReasons.SERVER_RULES, | ||
31 | thumbnails: VideoAbusePredefinedReasons.THUMBNAILS, | ||
32 | captions: VideoAbusePredefinedReasons.CAPTIONS | ||
33 | } | ||
diff --git a/shared/models/videos/abuse/video-abuse-update.model.ts b/shared/models/videos/abuse/video-abuse-update.model.ts deleted file mode 100644 index 9b32aae48..000000000 --- a/shared/models/videos/abuse/video-abuse-update.model.ts +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | import { VideoAbuseState } from './video-abuse-state.model' | ||
2 | |||
3 | export interface VideoAbuseUpdate { | ||
4 | moderationComment?: string | ||
5 | state?: VideoAbuseState | ||
6 | } | ||
diff --git a/shared/models/videos/abuse/video-abuse-video-is.type.ts b/shared/models/videos/abuse/video-abuse-video-is.type.ts deleted file mode 100644 index e86018993..000000000 --- a/shared/models/videos/abuse/video-abuse-video-is.type.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export type VideoAbuseVideoIs = 'deleted' | 'blacklisted' | ||
diff --git a/shared/models/videos/abuse/video-abuse.model.ts b/shared/models/videos/abuse/video-abuse.model.ts deleted file mode 100644 index 38605dcac..000000000 --- a/shared/models/videos/abuse/video-abuse.model.ts +++ /dev/null | |||
@@ -1,38 +0,0 @@ | |||
1 | import { Account } from '../../actors/index' | ||
2 | import { VideoConstant } from '../video-constant.model' | ||
3 | import { VideoAbuseState } from './video-abuse-state.model' | ||
4 | import { VideoChannel } from '../channel/video-channel.model' | ||
5 | import { VideoAbusePredefinedReasonsString } from './video-abuse-reason.model' | ||
6 | |||
7 | export interface VideoAbuse { | ||
8 | id: number | ||
9 | reason: string | ||
10 | predefinedReasons?: VideoAbusePredefinedReasonsString[] | ||
11 | reporterAccount: Account | ||
12 | |||
13 | state: VideoConstant<VideoAbuseState> | ||
14 | moderationComment?: string | ||
15 | |||
16 | video: { | ||
17 | id: number | ||
18 | name: string | ||
19 | uuid: string | ||
20 | nsfw: boolean | ||
21 | deleted: boolean | ||
22 | blacklisted: boolean | ||
23 | thumbnailPath?: string | ||
24 | channel?: VideoChannel | ||
25 | } | ||
26 | |||
27 | createdAt: Date | ||
28 | updatedAt: Date | ||
29 | |||
30 | startAt: number | ||
31 | endAt: number | ||
32 | |||
33 | count?: number | ||
34 | nth?: number | ||
35 | |||
36 | countReportsForReporter?: number | ||
37 | countReportsForReportee?: number | ||
38 | } | ||
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts index e1d96b40a..20b9638ab 100644 --- a/shared/models/videos/index.ts +++ b/shared/models/videos/index.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | export * from './abuse' | ||
2 | export * from './blacklist' | 1 | export * from './blacklist' |
3 | export * from './caption' | 2 | export * from './caption' |
4 | export * from './channel' | 3 | export * from './channel' |