diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html index 2204bb371..c1ce093d7 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html @@ -4,6 +4,17 @@ [showCurrentPageReport]="true" i18n-currentPageReportTemplate currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" > + +
+
+ +
+
+
+ @@ -40,18 +51,14 @@ - +
-
- - Deleted -
+
{{ videoAbuse.video.name }} - - +
by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }}
@@ -60,7 +67,20 @@
- {{ videoAbuse.createdAt }} + +
+
Deleted
+
+
+ {{ videoAbuse.video.name }} + +
+
by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }}
+
+
+ + + {{ videoAbuse.createdAt }} diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss index 09402fda7..9b60c39dc 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss @@ -1,6 +1,14 @@ @import 'mixins'; @import 'miniature'; +.caption { + justify-content: flex-end; + + input { + @include peertube-input-text(250px); + } +} + .video-abuse-video-link { @include disable-outline; position: relative; diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index cc5014ae8..6dcf96ccf 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts @@ -16,6 +16,8 @@ import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' import { DomSanitizer } from '@angular/platform-browser' import { BlocklistService } from '@app/shared/blocklist' import { VideoService } from '@app/shared/video/video.service' +import { ActivatedRoute } from '@angular/router' +import { first } from 'rxjs/operators' @Component({ selector: 'my-video-abuse-list', @@ -43,7 +45,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { private confirmService: ConfirmService, private i18n: I18n, private markdownRenderer: MarkdownService, - private sanitizer: DomSanitizer + private sanitizer: DomSanitizer, + private route: ActivatedRoute, ) { super() @@ -185,6 +188,10 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { ngOnInit () { this.initialize() + + this.route.queryParams + .pipe(first(params => params.search !== undefined && params.search !== null)) + .subscribe(params => this.search = params.search) } getIdentifier () { @@ -253,26 +260,29 @@ export class VideoAbuseListComponent extends RestTable implements OnInit { } protected loadData () { - return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) - .subscribe( - async resultList => { - this.totalRecords = resultList.total + return this.videoAbuseService.getVideoAbuses({ + pagination: this.pagination, + sort: this.sort, + search: this.search + }).subscribe( + async resultList => { + this.totalRecords = resultList.total - this.videoAbuses = resultList.data + this.videoAbuses = resultList.data - for (const abuse of this.videoAbuses) { - Object.assign(abuse, { - reasonHtml: await this.toHtml(abuse.reason), - moderationCommentHtml: await this.toHtml(abuse.moderationComment), - embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), - reporterAccount: new Account(abuse.reporterAccount) - }) - } + for (const abuse of this.videoAbuses) { + Object.assign(abuse, { + reasonHtml: await this.toHtml(abuse.reason), + moderationCommentHtml: await this.toHtml(abuse.moderationComment), + embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), + reporterAccount: new Account(abuse.reporterAccount) + }) + } - }, + }, - err => this.notifier.error(err.message) - ) + err => this.notifier.error(err.message) + ) } private toHtml (text: string) { diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts index 61a328575..a39ad31d4 100644 --- a/client/src/app/shared/video-abuse/video-abuse.service.ts +++ b/client/src/app/shared/video-abuse/video-abuse.service.ts @@ -17,12 +17,19 @@ export class VideoAbuseService { private restExtractor: RestExtractor ) {} - getVideoAbuses (pagination: RestPagination, sort: SortMeta): Observable> { + getVideoAbuses (options: { + pagination: RestPagination, + sort: SortMeta, + search?: string + }): Observable> { + const { pagination, sort, search } = options const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse' let params = new HttpParams() params = this.restService.addRestGetParams(params, pagination, sort) + if (search) params = params.append('search', search) + return this.authHttp.get>(url, { params }) .pipe( map(res => this.restExtractor.convertResultListDateToHuman(res)), diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 4ae899b7e..f37d90896 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts @@ -69,6 +69,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { start: req.query.start, count: req.query.count, sort: req.query.sort, + search: req.query.search, serverAccountId: serverActor.Account.id, user }) diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index ea943ffdf..5ead02eca 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -1,5 +1,5 @@ import { - AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt, DefaultScope + AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt, Scopes } from 'sequelize-typescript' import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' import { VideoAbuse } from '../../../shared/models/videos' @@ -21,34 +21,99 @@ import { VideoChannelModel } from './video-channel' import { ActorModel } from '../activitypub/actor' import { VideoBlacklistModel } from './video-blacklist' -@DefaultScope(() => ({ - include: [ - { - model: AccountModel, - required: true - }, - { - model: VideoModel, - required: false, +export enum ScopeNames { + FOR_API = 'FOR_API' +} + +@Scopes(() => ({ + [ScopeNames.FOR_API]: (options: { + search?: string + searchReporter?: string + searchVideo?: string + searchVideoChannel?: string + serverAccountId: number + userAccountId: any + }) => { + const search = (sourceField, targetField) => sourceField ? ({ + [targetField]: { + [Op.iLike]: `%${sourceField}%` + } + }) : {} + + let where = { + reporterAccountId: { + [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')') + } + } + + if (options.search) { + where = Object.assign(where, { + [Op.or]: [ + { + [Op.and]: [ + { videoId: { [Op.not]: null } }, + { '$Video.name$': { [Op.iLike]: `%${options.search}%` } } + ] + }, + { + [Op.and]: [ + { videoId: { [Op.not]: null } }, + { '$Video.VideoChannel.name$': { [Op.iLike]: `%${options.search}%` } } + ] + }, + { + [Op.and]: [ + { deletedVideo: { [Op.not]: null } }, + { deletedVideo: { name: { [Op.iLike]: `%${options.search}%` } } } + ] + }, + { + [Op.and]: [ + { deletedVideo: { [Op.not]: null } }, + { deletedVideo: { channel: { displayName: { [Op.iLike]: `%${options.search}%` } } } } + ] + }, + { '$Account.name$': { [Op.iLike]: `%${options.search}%` } } + ] + }) + } + + console.log(where) + + return { include: [ { - model: ThumbnailModel + model: AccountModel, + required: true, + where: { ...search(options.searchReporter, 'name') } }, { - model: VideoChannelModel.unscoped(), + model: VideoModel, + required: false, + where: { ...search(options.searchVideo, 'name') }, include: [ { - model: ActorModel + model: ThumbnailModel + }, + { + model: VideoChannelModel.unscoped(), + where: { ...search(options.searchVideoChannel, 'name') }, + include: [ + { + model: ActorModel + } + ] + }, + { + attributes: [ 'id', 'reason', 'unfederated' ], + model: VideoBlacklistModel } ] - }, - { - attributes: [ 'id', 'reason', 'unfederated' ], - model: VideoBlacklistModel } - ] + ], + where } - ] + } })) @Table({ tableName: 'videoAbuse', @@ -134,26 +199,30 @@ export class VideoAbuseModel extends Model { start: number count: number sort: string + search?: string serverAccountId: number user?: MUserAccountId }) { - const { start, count, sort, user, serverAccountId } = parameters + const { start, count, sort, search, user, serverAccountId } = parameters const userAccountId = user ? user.Account.id : undefined const query = { offset: start, limit: count, order: getSort(sort), - where: { - reporterAccountId: { - [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')') - } - }, col: 'VideoAbuseModel.id', distinct: true } - return VideoAbuseModel.findAndCountAll(query) + const filters = { + search, + serverAccountId, + userAccountId + } + + return VideoAbuseModel + .scope({ method: [ ScopeNames.FOR_API, filters ] }) + .findAndCountAll(query) .then(({ rows, count }) => { return { total: count, data: rows } })