Add filter inputs for blacklisted videos and muted accounts/servers

This commit is contained in:
Rigel Kent 2020-04-19 14:11:40 +02:00 committed by Rigel Kent
parent aeb1bed983
commit e0a929179a
19 changed files with 202 additions and 61 deletions

View File

@ -1,9 +1,19 @@
<p-table
[value]="blockedAccounts" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[value]="blockedAccounts" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted accounts"
>
<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">
<tr>

View File

@ -7,13 +7,14 @@ import { AccountBlock, BlocklistService } from '@app/shared/blocklist'
@Component({
selector: 'my-instance-account-blocklist',
styleUrls: [ './instance-account-blocklist.component.scss' ],
styleUrls: [ '../moderation.component.scss', './instance-account-blocklist.component.scss' ],
templateUrl: './instance-account-blocklist.component.html'
})
export class InstanceAccountBlocklistComponent extends RestTable implements OnInit {
blockedAccounts: AccountBlock[] = []
totalRecords = 0
rowsPerPage = 10
rowsPerPageOptions = [ 20, 50, 100 ]
rowsPerPage = this.rowsPerPageOptions[0]
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
@ -49,7 +50,11 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn
}
protected loadData () {
return this.blocklistService.getInstanceAccountBlocklist(this.pagination, this.sort)
return this.blocklistService.getInstanceAccountBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
.subscribe(
resultList => {
this.blockedAccounts = resultList.data

View File

@ -1,12 +1,18 @@
<p-table
[value]="blockedServers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[value]="blockedServers" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} muted instances"
>
<ng-template pTemplate="caption">
<div class="caption">
<a class="ml-auto block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()">
<div class="ml-auto">
<input
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
(keyup)="onSearch($event)"
>
</div>
<a class="ml-2 block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()">
<my-global-icon iconName="add"></my-global-icon>
<ng-container i18n>Mute domain</ng-container>
</a>

View File

@ -9,7 +9,7 @@ import { BatchDomainsModalComponent } from '@app/+admin/config/shared/batch-doma
@Component({
selector: 'my-instance-server-blocklist',
styleUrls: [ './instance-server-blocklist.component.scss' ],
styleUrls: [ '../moderation.component.scss', './instance-server-blocklist.component.scss' ],
templateUrl: './instance-server-blocklist.component.html'
})
export class InstanceServerBlocklistComponent extends RestTable implements OnInit {
@ -17,7 +17,8 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni
blockedServers: ServerBlock[] = []
totalRecords = 0
rowsPerPage = 10
rowsPerPageOptions = [ 20, 50, 100 ]
rowsPerPage = this.rowsPerPageOptions[0]
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
@ -72,7 +73,11 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni
}
protected loadData () {
return this.blocklistService.getInstanceServerBlocklist(this.pagination, this.sort)
return this.blocklistService.getInstanceServerBlocklist({
pagination: this.pagination,
sort: this.sort,
search: this.search
})
.subscribe(
resultList => {
this.blockedServers = resultList.data

View File

@ -7,6 +7,14 @@
margin-right: 30px;
}
.caption {
justify-content: flex-end;
input {
@include peertube-input-text(250px);
}
}
.moderation-expanded {
font-size: 90%;

View File

@ -1,14 +1,6 @@
@import 'mixins';
@import 'miniature';
.caption {
justify-content: flex-end;
input {
@include peertube-input-text(250px);
}
}
.video-details-date-updated {
font-size: 90%;
margin-top: .1rem;

View File

@ -1,9 +1,20 @@
<p-table
[value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[value]="blacklist" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blacklisted videos"
>
<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">
<tr>
<th style="width: 40px"></th>
@ -33,7 +44,7 @@
<div class="video-table-video-text">
<div>
{{ videoBlacklist.video.name }}
<span class="glyphicon glyphicon-new-window"></span>
<span i18n-title title="Video was blacklisted" class="glyphicon glyphicon-ban-circle"></span>
</div>
<div class="text-muted">by {{ videoBlacklist.video.channel?.displayName }} on {{ videoBlacklist.video.channel?.host }} </div>
</div>
@ -53,7 +64,10 @@
</ng-container>
<td class="action-cell">
<my-action-dropdown i18n-label placement="bottom-right" label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown>
<my-action-dropdown
[ngClass]="{ 'show': expanded }" placement="bottom-right" container="body"
i18n-label label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"
></my-action-dropdown>
</td>
</tr>
</ng-template>
@ -61,8 +75,10 @@
<ng-template pTemplate="rowexpansion" let-videoBlacklist>
<tr>
<td class="expand-cell" colspan="6">
<span class="col-2 moderation-expanded-label" i18n>Blacklist reason:</span>
<span class="col-9 moderation-expanded-text" [innerHTML]="videoBlacklist.reasonHtml"></span>
<div class="d-flex moderation-expanded">
<span class="col-2 moderation-expanded-label" i18n>Blacklist reason:</span>
<span class="col-9 moderation-expanded-text" [innerHTML]="videoBlacklist.reasonHtml"></span>
</div>
</td>
</tr>
</ng-template>

View File

@ -17,7 +17,8 @@ import { MarkdownService } from '@app/shared/renderer'
export class VideoBlacklistListComponent extends RestTable implements OnInit {
blacklist: (VideoBlacklist & { reasonHtml?: string })[] = []
totalRecords = 0
rowsPerPage = 10
rowsPerPageOptions = [ 20, 50, 100 ]
rowsPerPage = this.rowsPerPageOptions[0]
sort: SortMeta = { field: 'createdAt', order: -1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
listBlacklistTypeFilter: VideoBlacklistType = undefined
@ -38,7 +39,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
ngOnInit () {
this.serverService.getConfig()
.subscribe(config => {
// don't filter if auto-blacklist not enabled as this will be the only list
// don't filter if auto-blacklist is not enabled as this will be the only list
if (config.autoBlacklist.videos.ofUsers.enabled) {
this.listBlacklistTypeFilter = VideoBlacklistType.MANUAL
}
@ -91,7 +92,12 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
}
protected loadData () {
this.videoBlacklistService.listBlacklist(this.pagination, this.sort, this.listBlacklistTypeFilter)
this.videoBlacklistService.listBlacklist({
pagination: this.pagination,
sort: this.sort,
search: this.search,
type: this.listBlacklistTypeFilter
})
.subscribe(
async resultList => {
this.totalRecords = resultList.total

View File

@ -76,10 +76,14 @@ export class BlocklistService {
/*********************** Instance -> Account blocklist ***********************/
getInstanceAccountBlocklist (pagination: RestPagination, sort: SortMeta) {
getInstanceAccountBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) {
const { pagination, sort, search } = options
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search)
return this.authHttp.get<ResultList<AccountBlock>>(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/accounts', { params })
.pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)),
@ -104,10 +108,14 @@ export class BlocklistService {
/*********************** Instance -> Server blocklist ***********************/
getInstanceServerBlocklist (pagination: RestPagination, sort: SortMeta) {
getInstanceServerBlocklist (options: { pagination: RestPagination, sort: SortMeta, search: string }) {
const { pagination, sort, search } = options
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
if (search) params = params.append('search', search)
return this.authHttp.get<ResultList<ServerBlock>>(BlocklistService.BASE_SERVER_BLOCKLIST_URL + '/servers', { params })
.pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)),

View File

@ -19,13 +19,19 @@ export class VideoBlacklistService {
private restExtractor: RestExtractor
) {}
listBlacklist (pagination: RestPagination, sort: SortMeta, type?: VideoBlacklistType): Observable<ResultList<VideoBlacklist>> {
listBlacklist (options: {
pagination: RestPagination,
sort: SortMeta,
search?: string
type?: VideoBlacklistType
}): Observable<ResultList<VideoBlacklist>> {
const { pagination, sort, search, type } = options
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
if (type) {
params = params.set('type', type.toString())
}
if (search) params = params.append('search', search)
if (type) params = params.append('type', type.toString())
return this.authHttp.get<ResultList<VideoBlacklist>>(VideoBlacklistService.BASE_VIDEOS_URL + 'blacklist', { params })
.pipe(

View File

@ -82,7 +82,13 @@ export {
async function listBlockedAccounts (req: express.Request, res: express.Response) {
const serverActor = await getServerActor()
const resultList = await AccountBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort)
const resultList = await AccountBlocklistModel.listForApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
search: req.query.search,
accountId: serverActor.Account.id
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
@ -107,7 +113,13 @@ async function unblockAccount (req: express.Request, res: express.Response) {
async function listBlockedServers (req: express.Request, res: express.Response) {
const serverActor = await getServerActor()
const resultList = await ServerBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort)
const resultList = await ServerBlocklistModel.listForApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
search: req.query.search,
accountId: serverActor.Account.id
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
}

View File

@ -74,7 +74,13 @@ export {
async function listBlockedAccounts (req: express.Request, res: express.Response) {
const user = res.locals.oauth.token.User
const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
const resultList = await AccountBlocklistModel.listForApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
search: req.query.search,
accountId: user.Account.id
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
@ -99,7 +105,13 @@ async function unblockAccount (req: express.Request, res: express.Response) {
async function listBlockedServers (req: express.Request, res: express.Response) {
const user = res.locals.oauth.token.User
const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
const resultList = await ServerBlocklistModel.listForApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
search: req.query.search,
accountId: user.Account.id
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
}

View File

@ -102,7 +102,13 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
}
async function listBlacklist (req: express.Request, res: express.Response) {
const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type)
const resultList = await VideoBlacklistModel.listForApi({
start: req.query.start,
count: req.query.count,
sort: req.query.sort,
search: req.query.search,
type: req.query.type
})
return res.json(getFormattedObjects(resultList.data, resultList.total))
}

View File

@ -69,6 +69,10 @@ const videosBlacklistFiltersValidator = [
query('type')
.optional()
.custom(isVideoBlacklistTypeValid).withMessage('Should have a valid video blacklist type attribute'),
query('search')
.optional()
.not()
.isEmpty().withMessage('Should have a valid search'),
(req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking videos blacklist filters query', { parameters: req.query })

View File

@ -1,6 +1,6 @@
import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
import { AccountModel } from './account'
import { getSort } from '../utils'
import { getSort, searchAttribute } from '../utils'
import { AccountBlock } from '../../../shared/models/blocklist'
import { Op } from 'sequelize'
import * as Bluebird from 'bluebird'
@ -111,16 +111,36 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
return AccountBlocklistModel.findOne(query)
}
static listForApi (accountId: number, start: number, count: number, sort: string) {
static listForApi (parameters: {
start: number
count: number
sort: string
search?: string
accountId: number
}) {
const { start, count, sort, search, accountId } = parameters
const query = {
offset: start,
limit: count,
order: getSort(sort),
where: {
accountId
}
order: getSort(sort)
}
const where = {
accountId
}
if (search) {
Object.assign(where, {
[Op.or]: [
{ ...searchAttribute(search, '$BlockedAccount.name$') },
{ ...searchAttribute(search, '$BlockedAccount.Actor.url$') }
]
})
}
Object.assign(query, { where })
return AccountBlocklistModel
.scope([ ScopeNames.WITH_ACCOUNTS ])
.findAndCountAll<MAccountBlocklistAccounts>(query)

View File

@ -2,7 +2,7 @@ import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, Updated
import { AccountModel } from '../account/account'
import { ServerModel } from './server'
import { ServerBlock } from '../../../shared/models/blocklist'
import { getSort } from '../utils'
import { getSort, searchAttribute } from '../utils'
import * as Bluebird from 'bluebird'
import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models'
import { Op } from 'sequelize'
@ -120,16 +120,27 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
return ServerBlocklistModel.findOne(query)
}
static listForApi (accountId: number, start: number, count: number, sort: string) {
static listForApi (parameters: {
start: number
count: number
sort: string
search?: string
accountId: number
}) {
const { start, count, sort, search, accountId } = parameters
const query = {
offset: start,
limit: count,
order: getSort(sort),
where: {
accountId
accountId,
...searchAttribute(search, '$BlockedServer.host$')
}
}
console.log(search)
return ServerBlocklistModel
.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ])
.findAndCountAll<MServerBlocklistAccountServer>(query)

View File

@ -1,7 +1,7 @@
import { Model, Sequelize } from 'sequelize-typescript'
import validator from 'validator'
import { Col } from 'sequelize/types/lib/utils'
import { literal, OrderItem } from 'sequelize'
import { literal, OrderItem, Op } from 'sequelize'
type Primitive = string | Function | number | boolean | Symbol | undefined | null
type DeepOmitHelper<T, K extends keyof T> = {
@ -207,6 +207,16 @@ function buildDirectionAndField (value: string) {
return { direction, field }
}
function searchAttribute (sourceField, targetField) {
return sourceField
? {
[targetField]: {
[Op.iLike]: `%${sourceField}%`
}
}
: {}
}
// ---------------------------------------------------------------------------
export {
@ -228,7 +238,8 @@ export {
parseAggregateResult,
getFollowsSort,
buildDirectionAndField,
createSafeIn
createSafeIn,
searchAttribute
}
// ---------------------------------------------------------------------------

View File

@ -9,7 +9,7 @@ import {
isVideoAbuseStateValid
} from '../../helpers/custom-validators/video-abuses'
import { AccountModel } from '../account/account'
import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils'
import { buildBlockedAccountSQL, getSort, throwIfNotValid, searchAttribute } from '../utils'
import { VideoModel } from './video'
import { VideoAbuseState, VideoDetails } from '../../../shared'
import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
@ -17,8 +17,8 @@ import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo }
import * as Bluebird from 'bluebird'
import { literal, Op } from 'sequelize'
import { ThumbnailModel } from './thumbnail'
import { VideoChannelModel } from './video-channel'
import { VideoBlacklistModel } from './video-blacklist'
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
export enum ScopeNames {
FOR_API = 'FOR_API'
@ -33,12 +33,6 @@ export enum ScopeNames {
serverAccountId: number
userAccountId: any
}) => {
const search = (sourceField, targetField) => sourceField ? ({
[targetField]: {
[Op.iLike]: `%${sourceField}%`
}
}) : {}
let where = {
reporterAccountId: {
[Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')')
@ -148,19 +142,19 @@ export enum ScopeNames {
{
model: AccountModel,
required: true,
where: { ...search(options.searchReporter, 'name') }
where: { ...searchAttribute(options.searchReporter, 'name') }
},
{
model: VideoModel,
required: false,
where: { ...search(options.searchVideo, 'name') },
where: { ...searchAttribute(options.searchVideo, 'name') },
include: [
{
model: ThumbnailModel
},
{
model: VideoChannelModel.scope([ 'WITH_ACTOR', 'WITH_ACCOUNT' ]),
where: { ...search(options.searchVideoChannel, 'name') }
model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),
where: { ...searchAttribute(options.searchVideoChannel, 'name') }
},
{
attributes: [ 'id', 'reason', 'unfederated' ],

View File

@ -1,5 +1,5 @@
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
import { getBlacklistSort, SortType, throwIfNotValid } from '../utils'
import { getBlacklistSort, SortType, throwIfNotValid, searchAttribute } from '../utils'
import { VideoModel } from './video'
import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
@ -54,7 +54,15 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
})
Video: VideoModel
static listForApi (start: number, count: number, sort: SortType, type?: VideoBlacklistType) {
static listForApi (parameters: {
start: number
count: number
sort: SortType
search?: string
type?: VideoBlacklistType
}) {
const { start, count, sort, search, type } = parameters
function buildBaseQuery (): FindOptions {
return {
offset: start,
@ -70,6 +78,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
{
model: VideoModel,
required: true,
where: { ...searchAttribute(search, 'name') },
include: [
{
model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),