Add search for video, reporter and channel name fields
This commit is contained in:
parent
36d0677ec9
commit
844db39ee5
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)),
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 }
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue