Add search for video, reporter and channel name fields

This commit is contained in:
Rigel Kent 2020-04-18 10:00:19 +02:00 committed by Rigel Kent
parent 36d0677ec9
commit 844db39ee5
6 changed files with 167 additions and 52 deletions

View File

@ -4,6 +4,17 @@
[showCurrentPageReport]="true" i18n-currentPageReportTemplate [showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports"
> >
<ng-template pTemplate="caption">
<div class="caption">
<div class="ml-auto">
<input
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
(keyup)="onSearch($event)"
>
</div>
</div>
</ng-template>
<ng-template pTemplate="header"> <ng-template pTemplate="header">
<tr> <!-- header --> <tr> <!-- header -->
<th style="width: 40px;"></th> <th style="width: 40px;"></th>
@ -40,18 +51,14 @@
</a> </a>
</td> </td>
<td> <td *ngIf="!videoAbuse.video.deleted">
<a [href]="getVideoUrl(videoAbuse)" class="video-abuse-video-link" i18n-title title="Open video in a new tab" target="_blank" rel="noopener noreferrer"> <a [href]="getVideoUrl(videoAbuse)" class="video-abuse-video-link" i18n-title title="Open video in a new tab" target="_blank" rel="noopener noreferrer">
<div class="video-abuse-video"> <div class="video-abuse-video">
<div class="video-abuse-video-image"> <div class="video-abuse-video-image"><img [src]="videoAbuse.video.thumbnailPath"></div>
<img *ngIf="!videoAbuse.video.deleted" [src]="videoAbuse.video.thumbnailPath">
<span *ngIf="videoAbuse.video.deleted" i18n>Deleted</span>
</div>
<div class="video-abuse-video-text"> <div class="video-abuse-video-text">
<div> <div>
{{ videoAbuse.video.name }} {{ videoAbuse.video.name }}
<span *ngIf="!videoAbuse.video.deleted && !videoAbuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span> <span *ngIf="!videoAbuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span>
<span *ngIf="videoAbuse.video.deleted" i18n-title title="Video was deleted" class="glyphicon glyphicon-trash"></span>
<span *ngIf="videoAbuse.video.blacklisted" i18n-title title="Video was blacklisted" class="glyphicon glyphicon-ban-circle"></span> <span *ngIf="videoAbuse.video.blacklisted" i18n-title title="Video was blacklisted" class="glyphicon glyphicon-ban-circle"></span>
</div> </div>
<div class="text-muted">by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }} </div> <div class="text-muted">by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }} </div>
@ -60,7 +67,20 @@
</a> </a>
</td> </td>
<td>{{ videoAbuse.createdAt }}</td> <td *ngIf="videoAbuse.video.deleted" class="c-hand" [pRowToggler]="videoAbuse">
<div class="video-abuse-video" i18n-title title="Video was deleted">
<div class="video-abuse-video-image"><span i18n>Deleted</span></div>
<div class="video-abuse-video-text">
<div>
{{ videoAbuse.video.name }}
<span class="glyphicon glyphicon-trash"></span>
</div>
<div class="text-muted">by {{ videoAbuse.video.channel?.displayName }} on {{ videoAbuse.video.channel?.host }} </div>
</div>
</div>
</td>
<td class="c-hand" [pRowToggler]="videoAbuse">{{ videoAbuse.createdAt }}</td>
<td class="c-hand video-abuse-states" [pRowToggler]="videoAbuse"> <td class="c-hand video-abuse-states" [pRowToggler]="videoAbuse">
<span *ngIf="isVideoAbuseAccepted(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-ok"></span> <span *ngIf="isVideoAbuseAccepted(videoAbuse)" [title]="videoAbuse.state.label" class="glyphicon glyphicon-ok"></span>

View File

@ -1,6 +1,14 @@
@import 'mixins'; @import 'mixins';
@import 'miniature'; @import 'miniature';
.caption {
justify-content: flex-end;
input {
@include peertube-input-text(250px);
}
}
.video-abuse-video-link { .video-abuse-video-link {
@include disable-outline; @include disable-outline;
position: relative; position: relative;

View File

@ -16,6 +16,8 @@ import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
import { DomSanitizer } from '@angular/platform-browser' import { DomSanitizer } from '@angular/platform-browser'
import { BlocklistService } from '@app/shared/blocklist' import { BlocklistService } from '@app/shared/blocklist'
import { VideoService } from '@app/shared/video/video.service' import { VideoService } from '@app/shared/video/video.service'
import { ActivatedRoute } from '@angular/router'
import { first } from 'rxjs/operators'
@Component({ @Component({
selector: 'my-video-abuse-list', selector: 'my-video-abuse-list',
@ -43,7 +45,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
private confirmService: ConfirmService, private confirmService: ConfirmService,
private i18n: I18n, private i18n: I18n,
private markdownRenderer: MarkdownService, private markdownRenderer: MarkdownService,
private sanitizer: DomSanitizer private sanitizer: DomSanitizer,
private route: ActivatedRoute,
) { ) {
super() super()
@ -185,6 +188,10 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
ngOnInit () { ngOnInit () {
this.initialize() this.initialize()
this.route.queryParams
.pipe(first(params => params.search !== undefined && params.search !== null))
.subscribe(params => this.search = params.search)
} }
getIdentifier () { getIdentifier () {
@ -253,26 +260,29 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
} }
protected loadData () { protected loadData () {
return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) return this.videoAbuseService.getVideoAbuses({
.subscribe( pagination: this.pagination,
async resultList => { sort: this.sort,
this.totalRecords = resultList.total search: this.search
}).subscribe(
async resultList => {
this.totalRecords = resultList.total
this.videoAbuses = resultList.data this.videoAbuses = resultList.data
for (const abuse of this.videoAbuses) { for (const abuse of this.videoAbuses) {
Object.assign(abuse, { Object.assign(abuse, {
reasonHtml: await this.toHtml(abuse.reason), reasonHtml: await this.toHtml(abuse.reason),
moderationCommentHtml: await this.toHtml(abuse.moderationComment), moderationCommentHtml: await this.toHtml(abuse.moderationComment),
embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)),
reporterAccount: new Account(abuse.reporterAccount) reporterAccount: new Account(abuse.reporterAccount)
}) })
} }
}, },
err => this.notifier.error(err.message) err => this.notifier.error(err.message)
) )
} }
private toHtml (text: string) { private toHtml (text: string) {

View File

@ -17,12 +17,19 @@ export class VideoAbuseService {
private restExtractor: RestExtractor private restExtractor: RestExtractor
) {} ) {}
getVideoAbuses (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoAbuse>> { getVideoAbuses (options: {
pagination: RestPagination,
sort: SortMeta,
search?: string
}): Observable<ResultList<VideoAbuse>> {
const { pagination, sort, search } = options
const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse' const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse'
let params = new HttpParams() let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort) params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search)
return this.authHttp.get<ResultList<VideoAbuse>>(url, { params }) return this.authHttp.get<ResultList<VideoAbuse>>(url, { params })
.pipe( .pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)), map(res => this.restExtractor.convertResultListDateToHuman(res)),

View File

@ -69,6 +69,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
start: req.query.start, start: req.query.start,
count: req.query.count, count: req.query.count,
sort: req.query.sort, sort: req.query.sort,
search: req.query.search,
serverAccountId: serverActor.Account.id, serverAccountId: serverActor.Account.id,
user user
}) })

View File

@ -1,5 +1,5 @@
import { 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' } from 'sequelize-typescript'
import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
import { VideoAbuse } from '../../../shared/models/videos' import { VideoAbuse } from '../../../shared/models/videos'
@ -21,34 +21,99 @@ import { VideoChannelModel } from './video-channel'
import { ActorModel } from '../activitypub/actor' import { ActorModel } from '../activitypub/actor'
import { VideoBlacklistModel } from './video-blacklist' import { VideoBlacklistModel } from './video-blacklist'
@DefaultScope(() => ({ export enum ScopeNames {
include: [ FOR_API = 'FOR_API'
{ }
model: AccountModel,
required: true @Scopes(() => ({
}, [ScopeNames.FOR_API]: (options: {
{ search?: string
model: VideoModel, searchReporter?: string
required: false, 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: [ 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: [ 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({ @Table({
tableName: 'videoAbuse', tableName: 'videoAbuse',
@ -134,26 +199,30 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
start: number start: number
count: number count: number
sort: string sort: string
search?: string
serverAccountId: number serverAccountId: number
user?: MUserAccountId 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 userAccountId = user ? user.Account.id : undefined
const query = { const query = {
offset: start, offset: start,
limit: count, limit: count,
order: getSort(sort), order: getSort(sort),
where: {
reporterAccountId: {
[Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')')
}
},
col: 'VideoAbuseModel.id', col: 'VideoAbuseModel.id',
distinct: true distinct: true
} }
return VideoAbuseModel.findAndCountAll(query) const filters = {
search,
serverAccountId,
userAccountId
}
return VideoAbuseModel
.scope({ method: [ ScopeNames.FOR_API, filters ] })
.findAndCountAll(query)
.then(({ rows, count }) => { .then(({ rows, count }) => {
return { total: count, data: rows } return { total: count, data: rows }
}) })