-
+
{{ formErrors.message }}
@@ -31,7 +41,7 @@
-
+
diff --git a/client/src/app/shared/shared-moderation/abuse-message-modal.component.scss b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss
similarity index 56%
rename from client/src/app/shared/shared-moderation/abuse-message-modal.component.scss
rename to client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss
index 89d6b88c1..4dd025fc4 100644
--- a/client/src/app/shared/shared-moderation/abuse-message-modal.component.scss
+++ b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.scss
@@ -3,6 +3,11 @@
form {
margin: 20px 20px 0 0;
+
+ .form-group:first-child {
+ // Keep place to display error message without modifying the height
+ min-height: 125px;
+ }
}
textarea {
@@ -15,35 +20,29 @@ textarea {
display: flex;
flex-direction: column;
overflow-y: scroll;
- margin-right: 5px;
+}
+
+.no-messages {
+ display: flex;
+ font-size: 15px;
+ justify-content: center;
}
.message-block {
- margin-bottom: 10px;
+ margin: 0 5px 10px 0;
max-width: 60%;
.author {
color: var(--greyForegroundColor);
font-size: 14px;
+ padding: 0 0 3px 10px;
}
.bubble {
- color: var(--mainForegroundColor);
- background-color: var(--greyBackgroundColor);
border-radius: 10px;
padding: 5px 10px;
-
- &.by-me {
- color: var(--mainForegroundColor);
- background-color: var(--secondaryColor);
- }
-
- &.by-moderator {
- color: #fff;
- background-color: var(--mainColor);
-
- align-self: flex-end;
- }
+ color: var(--mainForegroundColor);
+ background-color: var(--greyBackgroundColor);
.content {
font-size: 15px;
@@ -54,4 +53,20 @@ textarea {
color: var(--greyForegroundColor);
}
}
+
+ &.by-me {
+
+ .bubble {
+ color: var(--mainBackgroundColor);
+ background-color: var(--mainColorLighter);
+
+ .date {
+ color: var(--mainBackgroundColor);
+ }
+ }
+ }
+
+ &.by-moderator {
+ align-self: flex-end;
+ }
}
diff --git a/client/src/app/shared/shared-moderation/abuse-message-modal.component.ts b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
similarity index 84%
rename from client/src/app/shared/shared-moderation/abuse-message-modal.component.ts
rename to client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
index 5822dfe1d..03f5ad735 100644
--- a/client/src/app/shared/shared-moderation/abuse-message-modal.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-message-modal.component.ts
@@ -1,11 +1,11 @@
-import { Component, ElementRef, EventEmitter, Output, ViewChild, OnInit } from '@angular/core'
-import { Notifier, AuthService } from '@app/core'
-import { FormReactive, FormValidatorService, AbuseValidatorsService } from '@app/shared/shared-forms'
+import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
+import { AuthService, Notifier } from '@app/core'
+import { AbuseValidatorsService, FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { AbuseMessage, UserAbuse } from '@shared/models'
-import { AbuseService } from './abuse.service'
+import { AbuseService } from '../shared-moderation'
@Component({
selector: 'my-abuse-message-modal',
@@ -16,11 +16,14 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
@ViewChild('modal', { static: true }) modal: NgbModal
@ViewChild('messagesBlock', { static: false }) messagesBlock: ElementRef
+ @Input() isAdminView: boolean
+
@Output() countMessagesUpdated = new EventEmitter<{ abuseId: number, countMessages: number }>()
abuseMessages: AbuseMessage[] = []
textareaMessage: string
sendingMessage = false
+ noResults = false
private openedModal: NgbModalRef
private abuse: UserAbuse
@@ -29,9 +32,9 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
protected formValidatorService: FormValidatorService,
private abuseValidatorsService: AbuseValidatorsService,
private modalService: NgbModal,
+ private i18n: I18n,
private auth: AuthService,
private notifier: Notifier,
- private i18n: I18n,
private abuseService: AbuseService
) {
super()
@@ -94,11 +97,20 @@ export class AbuseMessageModalComponent extends FormReactive implements OnInit {
return this.auth.getUser().account.id === abuseMessage.account.id
}
+ getPlaceholderMessage () {
+ if (this.isAdminView) {
+ return this.i18n('Add a message to communicate with the reporter')
+ }
+
+ return this.i18n('Add a message to communicate with the moderation team')
+ }
+
private loadMessages () {
this.abuseService.listAbuseMessages(this.abuse)
.subscribe(
res => {
this.abuseMessages = res.data
+ this.noResults = this.abuseMessages.length === 0
setTimeout(() => {
if (!this.messagesBlock) return
diff --git a/client/src/app/shared/shared-abuse-list/index.ts b/client/src/app/shared/shared-abuse-list/index.ts
new file mode 100644
index 000000000..3bdd18201
--- /dev/null
+++ b/client/src/app/shared/shared-abuse-list/index.ts
@@ -0,0 +1,7 @@
+export * from './abuse-message-modal.component'
+export * from './abuse-list-table.component'
+export * from './abuse-details.component'
+export * from './moderation-comment-modal.component'
+export * from './processed-abuse.model'
+
+export * from './shared-abuse-list.module'
diff --git a/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.html b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.html
similarity index 100%
rename from client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.html
rename to client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.html
diff --git a/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.scss b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.scss
similarity index 100%
rename from client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.scss
rename to client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.scss
diff --git a/client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.ts b/client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
similarity index 100%
rename from client/src/app/+admin/moderation/abuse-list/moderation-comment-modal.component.ts
rename to client/src/app/shared/shared-abuse-list/moderation-comment-modal.component.ts
diff --git a/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
new file mode 100644
index 000000000..fce1a8db3
--- /dev/null
+++ b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
@@ -0,0 +1,25 @@
+import { SafeHtml } from '@angular/platform-browser'
+import { AdminAbuse } from '@shared/models'
+import { Account } from '@app/shared/shared-main'
+
+// Don't use an abuse model because we need external services to compute some properties
+// And this model is only used in this component
+export type ProcessedAbuse = AdminAbuse & {
+ moderationCommentHtml?: string,
+ reasonHtml?: string
+ embedHtml?: SafeHtml
+ updatedAt?: Date
+
+ // override bare server-side definitions with rich client-side definitions
+ reporterAccount?: Account
+ flaggedAccount?: Account
+
+ truncatedCommentHtml?: string
+ commentHtml?: string
+
+ video: AdminAbuse['video'] & {
+ channel: AdminAbuse['video']['channel'] & {
+ ownerAccount: Account
+ }
+ }
+}
diff --git a/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
new file mode 100644
index 000000000..663cd902b
--- /dev/null
+++ b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
@@ -0,0 +1,42 @@
+
+import { TableModule } from 'primeng/table'
+import { NgModule } from '@angular/core'
+import { SharedFormModule } from '../shared-forms/shared-form.module'
+import { SharedGlobalIconModule } from '../shared-icons'
+import { SharedMainModule } from '../shared-main/shared-main.module'
+import { SharedModerationModule } from '../shared-moderation'
+import { SharedVideoCommentModule } from '../shared-video-comment'
+import { AbuseDetailsComponent } from './abuse-details.component'
+import { AbuseListTableComponent } from './abuse-list-table.component'
+import { AbuseMessageModalComponent } from './abuse-message-modal.component'
+import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
+
+@NgModule({
+ imports: [
+ TableModule,
+
+ SharedMainModule,
+ SharedFormModule,
+ SharedModerationModule,
+ SharedGlobalIconModule,
+ SharedVideoCommentModule
+ ],
+
+ declarations: [
+ AbuseDetailsComponent,
+ AbuseListTableComponent,
+ ModerationCommentModalComponent,
+ AbuseMessageModalComponent
+ ],
+
+ exports: [
+ AbuseDetailsComponent,
+ AbuseListTableComponent,
+ ModerationCommentModalComponent,
+ AbuseMessageModalComponent
+ ],
+
+ providers: [
+ ]
+})
+export class SharedAbuseListModule { }
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 bda88bdee..950e256ff 100644
--- a/client/src/app/shared/shared-main/account/actor.model.ts
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -41,6 +41,13 @@ export abstract class Actor implements ActorServer {
return accountName + '@' + host
}
+ static IS_LOCAL (host: string) {
+ const absoluteAPIUrl = getAbsoluteAPIUrl()
+ const thisHost = new URL(absoluteAPIUrl).host
+
+ return host.trim() === thisHost
+ }
+
protected constructor (hash: ActorServer) {
this.id = hash.id
this.url = hash.url
@@ -53,10 +60,7 @@ export abstract class Actor implements ActorServer {
if (hash.updatedAt) this.updatedAt = new Date(hash.updatedAt.toString())
this.avatar = hash.avatar
-
- const absoluteAPIUrl = getAbsoluteAPIUrl()
- const thisHost = new URL(absoluteAPIUrl).host
- this.isLocal = this.host.trim() === thisHost
+ this.isLocal = Actor.IS_LOCAL(this.host)
this.updateComputedAttributes()
}
diff --git a/client/src/app/shared/shared-moderation/abuse.service.ts b/client/src/app/shared/shared-moderation/abuse.service.ts
index 652d8370f..c1aa62023 100644
--- a/client/src/app/shared/shared-moderation/abuse.service.ts
+++ b/client/src/app/shared/shared-moderation/abuse.service.ts
@@ -5,13 +5,24 @@ import { catchError, map } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core'
-import { AdminAbuse, AbuseCreate, AbuseFilter, AbusePredefinedReasonsString, AbuseState, AbuseUpdate, ResultList, UserAbuse, AbuseMessage } from '@shared/models'
-import { environment } from '../../../environments/environment'
import { I18n } from '@ngx-translate/i18n-polyfill'
+import {
+ AbuseCreate,
+ AbuseFilter,
+ AbuseMessage,
+ AbusePredefinedReasonsString,
+ AbuseState,
+ AbuseUpdate,
+ AdminAbuse,
+ ResultList,
+ UserAbuse
+} from '@shared/models'
+import { environment } from '../../../environments/environment'
@Injectable()
export class AbuseService {
private static BASE_ABUSE_URL = environment.apiUrl + '/api/v1/abuses'
+ private static BASE_MY_ABUSE_URL = environment.apiUrl + '/api/v1/users/me/abuses'
constructor (
private i18n: I18n,
@@ -32,33 +43,7 @@ export class AbuseService {
params = this.restService.addRestGetParams(params, pagination, sort)
if (search) {
- const filters = this.restService.parseQueryStringFilter(search, {
- id: { prefix: '#' },
- state: {
- prefix: 'state:',
- handler: v => {
- if (v === 'accepted') return AbuseState.ACCEPTED
- if (v === 'pending') return AbuseState.PENDING
- if (v === 'rejected') return AbuseState.REJECTED
-
- return undefined
- }
- },
- videoIs: {
- prefix: 'videoIs:',
- handler: v => {
- if (v === 'deleted') return v
- if (v === 'blacklisted') return v
-
- return undefined
- }
- },
- searchReporter: { prefix: 'reporter:' },
- searchReportee: { prefix: 'reportee:' },
- predefinedReason: { prefix: 'tag:' }
- })
-
- params = this.restService.addObjectParams(params, filters)
+ params = this.buildParamsFromSearch(search, params)
}
return this.authHttp.get
>(url, { params })
@@ -67,6 +52,27 @@ export class AbuseService {
)
}
+ getUserAbuses (options: {
+ pagination: RestPagination,
+ sort: SortMeta,
+ search?: string
+ }): Observable> {
+ const { pagination, sort, search } = options
+ const url = AbuseService.BASE_MY_ABUSE_URL
+
+ let params = new HttpParams()
+ params = this.restService.addRestGetParams(params, pagination, sort)
+
+ if (search) {
+ params = this.buildParamsFromSearch(search, params)
+ }
+
+ return this.authHttp.get>(url, { params })
+ .pipe(
+ catchError(res => this.restExtractor.handleError(res))
+ )
+ }
+
reportVideo (parameters: AbuseCreate) {
const url = AbuseService.BASE_ABUSE_URL
@@ -180,4 +186,33 @@ export class AbuseService {
return reasons
}
+ private buildParamsFromSearch (search: string, params: HttpParams) {
+ const filters = this.restService.parseQueryStringFilter(search, {
+ id: { prefix: '#' },
+ state: {
+ prefix: 'state:',
+ handler: v => {
+ if (v === 'accepted') return AbuseState.ACCEPTED
+ if (v === 'pending') return AbuseState.PENDING
+ if (v === 'rejected') return AbuseState.REJECTED
+
+ return undefined
+ }
+ },
+ videoIs: {
+ prefix: 'videoIs:',
+ handler: v => {
+ if (v === 'deleted') return v
+ if (v === 'blacklisted') return v
+
+ return undefined
+ }
+ },
+ searchReporter: { prefix: 'reporter:' },
+ searchReportee: { prefix: 'reportee:' },
+ predefinedReason: { prefix: 'tag:' }
+ })
+
+ return this.restService.addObjectParams(params, filters)
+ }
}
diff --git a/client/src/app/shared/shared-moderation/index.ts b/client/src/app/shared/shared-moderation/index.ts
index c8082d4b3..41c910ffe 100644
--- a/client/src/app/shared/shared-moderation/index.ts
+++ b/client/src/app/shared/shared-moderation/index.ts
@@ -1,6 +1,5 @@
export * from './report-modals'
-export * from './abuse-message-modal.component'
export * from './abuse.service'
export * from './account-block.model'
export * from './account-blocklist.component'
diff --git a/client/src/app/shared/shared-moderation/moderation.scss b/client/src/app/shared/shared-moderation/moderation.scss
new file mode 100644
index 000000000..260346dc5
--- /dev/null
+++ b/client/src/app/shared/shared-moderation/moderation.scss
@@ -0,0 +1,50 @@
+@import 'variables';
+@import 'mixins';
+@import 'miniature';
+
+.moderation-expanded {
+ font-size: 90%;
+
+ .moderation-expanded-label {
+ font-weight: $font-semibold;
+ display: inline-block;
+ vertical-align: top;
+ text-align: right;
+ }
+
+ .moderation-expanded-text {
+ display: inline-flex;
+ word-wrap: break-word;
+
+ ::ng-deep p:last-child {
+ margin-bottom: 0px !important;
+ }
+ }
+}
+
+.input-group {
+ @include peertube-input-group(300px);
+
+ .dropdown-toggle::after {
+ margin-left: 0;
+ }
+}
+
+.chip {
+ @include chip;
+}
+
+.caption {
+ justify-content: flex-end;
+
+ input {
+ @include peertube-input-text(250px);
+ flex-grow: 1;
+ }
+}
+
+my-action-dropdown.show {
+ ::ng-deep .dropdown-root {
+ display: block !important;
+ }
+}
diff --git a/client/src/app/shared/shared-moderation/server-blocklist.component.scss b/client/src/app/shared/shared-moderation/server-blocklist.component.scss
index 9ddb76850..31db4d92b 100644
--- a/client/src/app/shared/shared-moderation/server-blocklist.component.scss
+++ b/client/src/app/shared/shared-moderation/server-blocklist.component.scss
@@ -32,3 +32,16 @@ a {
.block-button {
@include create-button;
}
+
+.caption {
+ justify-content: flex-end;
+
+ input {
+ @include peertube-input-text(250px);
+ flex-grow: 1;
+ }
+}
+
+.chip {
+ @include chip;
+}
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 b5b6daf27..b1b98f8d0 100644
--- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts
+++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
@@ -4,7 +4,6 @@ import { SharedFormModule } from '../shared-forms/shared-form.module'
import { SharedGlobalIconModule } from '../shared-icons'
import { SharedMainModule } from '../shared-main/shared-main.module'
import { SharedVideoCommentModule } from '../shared-video-comment'
-import { AbuseMessageModalComponent } from './abuse-message-modal.component'
import { AbuseService } from './abuse.service'
import { BatchDomainsModalComponent } from './batch-domains-modal.component'
import { BlocklistService } from './blocklist.service'
@@ -30,8 +29,7 @@ import { VideoBlockService } from './video-block.service'
VideoReportComponent,
BatchDomainsModalComponent,
CommentReportComponent,
- AccountReportComponent,
- AbuseMessageModalComponent
+ AccountReportComponent
],
exports: [
@@ -41,8 +39,7 @@ import { VideoBlockService } from './video-block.service'
VideoReportComponent,
BatchDomainsModalComponent,
CommentReportComponent,
- AccountReportComponent,
- AbuseMessageModalComponent
+ AccountReportComponent
],
providers: [
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts
index 50d068157..72e62fc0b 100644
--- a/server/controllers/api/abuse.ts
+++ b/server/controllers/api/abuse.ts
@@ -16,6 +16,7 @@ import {
asyncMiddleware,
asyncRetryTransactionMiddleware,
authenticate,
+ checkAbuseValidForMessagesValidator,
deleteAbuseMessageValidator,
ensureUserHasRight,
getAbuseValidator,
@@ -58,12 +59,14 @@ abuseRouter.delete('/:id',
abuseRouter.get('/:id/messages',
authenticate,
asyncMiddleware(getAbuseValidator),
+ checkAbuseValidForMessagesValidator,
asyncRetryTransactionMiddleware(listAbuseMessages)
)
abuseRouter.post('/:id/messages',
authenticate,
asyncMiddleware(getAbuseValidator),
+ checkAbuseValidForMessagesValidator,
addAbuseMessageValidator,
asyncRetryTransactionMiddleware(addAbuseMessage)
)
@@ -71,6 +74,7 @@ abuseRouter.post('/:id/messages',
abuseRouter.delete('/:id/messages/:messageId',
authenticate,
asyncMiddleware(getAbuseValidator),
+ checkAbuseValidForMessagesValidator,
asyncMiddleware(deleteAbuseMessageValidator),
asyncRetryTransactionMiddleware(deleteAbuseMessage)
)
diff --git a/server/controllers/api/users/my-abuses.ts b/server/controllers/api/users/my-abuses.ts
index e43fc483e..fcd0ce3fc 100644
--- a/server/controllers/api/users/my-abuses.ts
+++ b/server/controllers/api/users/my-abuses.ts
@@ -43,6 +43,6 @@ async function listMyAbuses (req: express.Request, res: express.Response) {
return res.json({
total: resultList.total,
- data: resultList.data.map(d => d.toFormattedAdminJSON())
+ data: resultList.data.map(d => d.toFormattedUserJSON())
})
}
diff --git a/server/helpers/middlewares/abuses.ts b/server/helpers/middlewares/abuses.ts
index be8c8b449..659ad8939 100644
--- a/server/helpers/middlewares/abuses.ts
+++ b/server/helpers/middlewares/abuses.ts
@@ -26,7 +26,7 @@ async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: stri
}
async function doesAbuseExist (abuseId: number | string, res: Response) {
- const abuse = await AbuseModel.loadById(parseInt(abuseId + '', 10))
+ const abuse = await AbuseModel.loadByIdWithReporter(parseInt(abuseId + '', 10))
if (!abuse) {
res.status(404)
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts
index cb0bc658a..2a096e0af 100644
--- a/server/middlewares/validators/abuse.ts
+++ b/server/middlewares/validators/abuse.ts
@@ -201,6 +201,21 @@ const getAbuseValidator = [
}
]
+const checkAbuseValidForMessagesValidator = [
+ (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking checkAbuseValidForMessagesValidator parameters', { parameters: req.body })
+
+ const abuse = res.locals.abuse
+ if (abuse.ReporterAccount.isOwned() === false) {
+ return res.status(400).json({
+ error: 'This abuse was created by a user of your instance.',
+ })
+ }
+
+ return next()
+ }
+]
+
const addAbuseMessageValidator = [
body('message').custom(isAbuseMessageValid).not().isEmpty().withMessage('Should have a valid abuse message'),
@@ -357,6 +372,7 @@ export {
abuseReportValidator,
abuseGetValidator,
addAbuseMessageValidator,
+ checkAbuseValidForMessagesValidator,
abuseUpdateValidator,
deleteAbuseMessageValidator,
abuseListForUserValidator,
diff --git a/server/models/abuse/abuse.ts b/server/models/abuse/abuse.ts
index 7002502d5..3353e9e41 100644
--- a/server/models/abuse/abuse.ts
+++ b/server/models/abuse/abuse.ts
@@ -25,14 +25,14 @@ import {
AbusePredefinedReasonsString,
AbuseState,
AbuseVideoIs,
- AdminVideoAbuse,
AdminAbuse,
+ AdminVideoAbuse,
AdminVideoCommentAbuse,
UserAbuse,
UserVideoAbuse
} from '@shared/models'
import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
-import { MAbuse, MAbuseAdminFormattable, MAbuseAP, MUserAccountId, MAbuseUserFormattable } from '../../types/models'
+import { MAbuse, MAbuseAdminFormattable, MAbuseAP, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models'
import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
import { getSort, throwIfNotValid } from '../utils'
import { ThumbnailModel } from '../video/thumbnail'
@@ -266,7 +266,7 @@ export class AbuseModel extends Model {
VideoAbuse: VideoAbuseModel
// FIXME: deprecated in 2.3. Remove these validators
- static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird {
+ static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird {
const videoWhere: WhereOptions = {}
if (videoId) videoWhere.videoId = videoId
@@ -278,6 +278,10 @@ export class AbuseModel extends Model {
model: VideoAbuseModel,
required: true,
where: videoWhere
+ },
+ {
+ model: AccountModel,
+ as: 'ReporterAccount'
}
],
where: {
@@ -287,11 +291,17 @@ export class AbuseModel extends Model {
return AbuseModel.findOne(query)
}
- static loadById (id: number): Bluebird {
+ static loadByIdWithReporter (id: number): Bluebird {
const query = {
where: {
id
- }
+ },
+ include: [
+ {
+ model: AccountModel,
+ as: 'ReporterAccount'
+ }
+ ]
}
return AbuseModel.findOne(query)
@@ -466,8 +476,6 @@ export class AbuseModel extends Model {
label: AbuseModel.getStateLabel(this.state)
},
- moderationComment: this.moderationComment,
-
countMessages,
createdAt: this.createdAt,
@@ -500,6 +508,8 @@ export class AbuseModel extends Model {
video,
comment,
+ moderationComment: this.moderationComment,
+
reporterAccount: this.ReporterAccount
? this.ReporterAccount.toFormattedJSON()
: null,
@@ -519,7 +529,7 @@ export class AbuseModel extends Model {
const countMessages = this.get('countMessages') as number
const video = this.buildBaseVideoAbuse()
- const comment: AdminVideoCommentAbuse = this.buildBaseVideoCommentAbuse()
+ const comment = this.buildBaseVideoCommentAbuse()
const abuse = this.buildBaseAbuse(countMessages || 0)
return Object.assign(abuse, {
diff --git a/server/tests/api/check-params/abuses.ts b/server/tests/api/check-params/abuses.ts
index 5e1d66c25..0ef8f6cac 100644
--- a/server/tests/api/check-params/abuses.ts
+++ b/server/tests/api/check-params/abuses.ts
@@ -3,21 +3,26 @@
import 'mocha'
import { AbuseCreate, AbuseState } from '@shared/models'
import {
+ addAbuseMessage,
cleanupTests,
createUser,
deleteAbuse,
+ deleteAbuseMessage,
+ doubleFollow,
flushAndRunServer,
+ generateUserAccessToken,
+ getAdminAbusesList,
+ getVideoIdFromUUID,
+ listAbuseMessages,
makeGetRequest,
makePostBodyRequest,
+ reportAbuse,
ServerInfo,
setAccessTokensToServers,
updateAbuse,
uploadVideo,
userLogin,
- generateUserAccessToken,
- addAbuseMessage,
- listAbuseMessages,
- deleteAbuseMessage
+ waitJobs
} from '../../../../shared/extra-utils'
import {
checkBadCountPagination,
@@ -29,6 +34,7 @@ describe('Test abuses API validators', function () {
const basePath = '/api/v1/abuses/'
let server: ServerInfo
+
let userAccessToken = ''
let userAccessToken2 = ''
let abuseId: number
@@ -321,7 +327,7 @@ describe('Test abuses API validators', function () {
})
})
- describe('When listing abuse message', function () {
+ describe('When listing abuse messages', function () {
it('Should fail with an invalid abuse id', async function () {
await listAbuseMessages(server.url, userAccessToken, 888, 404)
@@ -382,7 +388,43 @@ describe('Test abuses API validators', function () {
})
})
+ describe('When trying to manage messages of a remote abuse', function () {
+ let remoteAbuseId: number
+ let anotherServer: ServerInfo
+
+ before(async function () {
+ this.timeout(20000)
+
+ anotherServer = await flushAndRunServer(2)
+ await setAccessTokensToServers([ anotherServer ])
+
+ await doubleFollow(anotherServer, server)
+
+ const server2VideoId = await getVideoIdFromUUID(anotherServer.url, server.video.uuid)
+ await reportAbuse({
+ url: anotherServer.url,
+ token: anotherServer.accessToken,
+ reason: 'remote server',
+ videoId: server2VideoId
+ })
+
+ await waitJobs([ server, anotherServer ])
+
+ const res = await getAdminAbusesList({ url: server.url, token: server.accessToken, sort: '-createdAt' })
+ remoteAbuseId = res.body.data[0].id
+ })
+
+ it('Should fail when listing abuse messages of a remote abuse', async function () {
+ await listAbuseMessages(server.url, server.accessToken, remoteAbuseId, 400)
+ })
+
+ it('Should fail when creating abuse message of a remote abuse', async function () {
+ await addAbuseMessage(server.url, server.accessToken, remoteAbuseId, 'message', 400)
+ })
+ })
+
after(async function () {
await cleanupTests([ server ])
})
})
+
diff --git a/server/tests/api/moderation/abuses.ts b/server/tests/api/moderation/abuses.ts
index 601125fdf..fb765e7e3 100644
--- a/server/tests/api/moderation/abuses.ts
+++ b/server/tests/api/moderation/abuses.ts
@@ -2,12 +2,23 @@
import 'mocha'
import * as chai from 'chai'
-import { AbuseFilter, AbusePredefinedReasonsString, AbuseState, Account, AdminAbuse, UserAbuse, VideoComment, AbuseMessage } from '@shared/models'
import {
+ AbuseFilter,
+ AbuseMessage,
+ AbusePredefinedReasonsString,
+ AbuseState,
+ Account,
+ AdminAbuse,
+ UserAbuse,
+ VideoComment
+} from '@shared/models'
+import {
+ addAbuseMessage,
addVideoCommentThread,
cleanupTests,
createUser,
deleteAbuse,
+ deleteAbuseMessage,
deleteVideoComment,
flushAndRunMultipleServers,
generateUserAccessToken,
@@ -18,6 +29,7 @@ import {
getVideoIdFromUUID,
getVideosList,
immutableAssign,
+ listAbuseMessages,
removeUser,
removeVideo,
reportAbuse,
@@ -26,10 +38,7 @@ import {
updateAbuse,
uploadVideo,
uploadVideoAndGetId,
- userLogin,
- addAbuseMessage,
- listAbuseMessages,
- deleteAbuseMessage
+ userLogin
} from '../../../../shared/extra-utils/index'
import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
diff --git a/server/types/models/moderation/abuse.ts b/server/types/models/moderation/abuse.ts
index 39ef50771..d793a720f 100644
--- a/server/types/models/moderation/abuse.ts
+++ b/server/types/models/moderation/abuse.ts
@@ -2,7 +2,7 @@ import { VideoAbuseModel } from '@server/models/abuse/video-abuse'
import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse'
import { PickWith } from '@shared/core-utils'
import { AbuseModel } from '../../../models/abuse/abuse'
-import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl } from '../account'
+import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl, MAccount } from '../account'
import { MCommentOwner, MCommentUrl, MVideoUrl, MCommentOwnerVideo, MComment, MCommentVideo } from '../video'
import { MVideo, MVideoAccountLightBlacklistAllFiles } from '../video/video'
@@ -18,6 +18,10 @@ export type MVideoAbuse = Omit
export type MCommentAbuse = Omit
+export type MAbuseReporter =
+ MAbuse &
+ Use<'ReporterAccount', MAccountDefault>
+
// ############################################################################
export type MVideoAbuseVideo =
diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts
index 452c6e1a0..d95b8925d 100644
--- a/server/typings/express/index.d.ts
+++ b/server/typings/express/index.d.ts
@@ -9,7 +9,8 @@ import {
MVideoFile,
MVideoImmutable,
MVideoPlaylistFull,
- MVideoPlaylistFullSummary
+ MVideoPlaylistFullSummary,
+ MAbuseReporter
} from '@server/types/models'
import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token'
import { MPlugin, MServer, MServerBlocklist } from '@server/types/models/server'
@@ -78,7 +79,7 @@ declare module 'express' {
videoCaption?: MVideoCaptionVideo
- abuse?: MAbuse
+ abuse?: MAbuseReporter
abuseMessage?: MAbuseMessage
videoStreamingPlaylist?: MStreamingPlaylist
diff --git a/shared/models/moderation/abuse/abuse.model.ts b/shared/models/moderation/abuse/abuse.model.ts
index 7f126ba4a..781870b1a 100644
--- a/shared/models/moderation/abuse/abuse.model.ts
+++ b/shared/models/moderation/abuse/abuse.model.ts
@@ -79,4 +79,4 @@ export type UserVideoAbuse = Omit
export type UserVideoCommentAbuse = AdminVideoCommentAbuse
export type UserAbuse = Omit
+| 'count' | 'nth' | 'moderationComment'>