diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html
index 036e794d2..60dbcdf1d 100644
--- a/client/src/app/+accounts/accounts.component.html
+++ b/client/src/app/+accounts/accounts.component.html
@@ -10,8 +10,13 @@
+
{{ account.followersCount }} subscribers
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 287ab3e46..0374b70ef 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
@@ -9,7 +9,7 @@
Created |
Video |
State |
- |
+ |
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
index 0585e0490..ff4543b97 100644
--- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
+++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
@@ -8,7 +8,7 @@
Video name |
Sensitive |
Date |
- |
+ |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index afa9ccfe4..eb8d30e17 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -60,8 +60,10 @@
- {{ user.username }}
- (banned)
+
+ {{ user.username }}
+ (banned)
+
|
{{ user.email }} |
{{ user.videoQuotaUsed }} / {{ user.videoQuota }} |
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.html b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.html
new file mode 100644
index 000000000..a96a11f5e
--- /dev/null
+++ b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Account |
+ Muted at |
+
+
+
+
+
+ {{ accountBlock.blockedAccount.nameWithHost }} |
+ {{ accountBlock.createdAt }} |
+
+
+ |
+
+
+
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.scss b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.scss
new file mode 100644
index 000000000..6028b75ea
--- /dev/null
+++ b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.scss
@@ -0,0 +1,7 @@
+@import '_variables';
+@import '_mixins';
+
+.unblock-button {
+ @include peertube-button;
+ @include grey-button;
+}
\ No newline at end of file
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts
new file mode 100644
index 000000000..fbad28410
--- /dev/null
+++ b/client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts
@@ -0,0 +1,59 @@
+import { Component, OnInit } from '@angular/core'
+import { NotificationsService } from 'angular2-notifications'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { RestPagination, RestTable } from '@app/shared'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+import { BlocklistService, AccountBlock } from '@app/shared/blocklist'
+
+@Component({
+ selector: 'my-account-blocklist',
+ styleUrls: [ './my-account-blocklist.component.scss' ],
+ templateUrl: './my-account-blocklist.component.html'
+})
+export class MyAccountBlocklistComponent extends RestTable implements OnInit {
+ blockedAccounts: AccountBlock[] = []
+ totalRecords = 0
+ rowsPerPage = 10
+ sort: SortMeta = { field: 'createdAt', order: -1 }
+ pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
+
+ constructor (
+ private notificationsService: NotificationsService,
+ private blocklistService: BlocklistService,
+ private i18n: I18n
+ ) {
+ super()
+ }
+
+ ngOnInit () {
+ this.initialize()
+ }
+
+ unblockAccount (accountBlock: AccountBlock) {
+ const blockedAccount = accountBlock.blockedAccount
+
+ this.blocklistService.unblockAccountByUser(blockedAccount)
+ .subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost })
+ )
+
+ this.loadData()
+ }
+ )
+ }
+
+ protected loadData () {
+ return this.blocklistService.getUserAccountBlocklist(this.pagination, this.sort)
+ .subscribe(
+ resultList => {
+ this.blockedAccounts = resultList.data
+ this.totalRecords = resultList.total
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+}
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.html b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.html
new file mode 100644
index 000000000..680334740
--- /dev/null
+++ b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Instance |
+ Muted at |
+ |
+
+
+
+
+
+ {{ serverBlock.blockedServer.host }} |
+ {{ serverBlock.createdAt }} |
+
+
+ |
+
+
+
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.scss b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.scss
new file mode 100644
index 000000000..6028b75ea
--- /dev/null
+++ b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.scss
@@ -0,0 +1,7 @@
+@import '_variables';
+@import '_mixins';
+
+.unblock-button {
+ @include peertube-button;
+ @include grey-button;
+}
\ No newline at end of file
diff --git a/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts
new file mode 100644
index 000000000..b994c2c99
--- /dev/null
+++ b/client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts
@@ -0,0 +1,60 @@
+import { Component, OnInit } from '@angular/core'
+import { NotificationsService } from 'angular2-notifications'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { RestPagination, RestTable } from '@app/shared'
+import { SortMeta } from 'primeng/components/common/sortmeta'
+import { ServerBlock } from '../../../../../shared'
+import { BlocklistService } from '@app/shared/blocklist'
+
+@Component({
+ selector: 'my-account-server-blocklist',
+ styleUrls: [ './my-account-server-blocklist.component.scss' ],
+ templateUrl: './my-account-server-blocklist.component.html'
+})
+export class MyAccountServerBlocklistComponent extends RestTable implements OnInit {
+ blockedAccounts: ServerBlock[] = []
+ totalRecords = 0
+ rowsPerPage = 10
+ sort: SortMeta = { field: 'createdAt', order: -1 }
+ pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
+
+ constructor (
+ private notificationsService: NotificationsService,
+ private blocklistService: BlocklistService,
+ private i18n: I18n
+ ) {
+ super()
+ }
+
+ ngOnInit () {
+ this.initialize()
+ }
+
+ unblockServer (serverBlock: ServerBlock) {
+ const host = serverBlock.blockedServer.host
+
+ this.blocklistService.unblockServerByUser(host)
+ .subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Instance {{host}} unmuted.', { host })
+ )
+
+ this.loadData()
+ }
+ )
+ }
+
+ protected loadData () {
+ return this.blocklistService.getUserServerBlocklist(this.pagination, this.sort)
+ .subscribe(
+ resultList => {
+ this.blockedAccounts = resultList.data
+ this.totalRecords = resultList.total
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+}
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts
index 4b2168e35..49f9c94a7 100644
--- a/client/src/app/+my-account/my-account-routing.module.ts
+++ b/client/src/app/+my-account/my-account-routing.module.ts
@@ -11,6 +11,8 @@ import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-accoun
import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
import { MyAccountOwnershipComponent } from '@app/+my-account/my-account-ownership/my-account-ownership.component'
+import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component'
+import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component'
const myAccountRoutes: Routes = [
{
@@ -94,6 +96,24 @@ const myAccountRoutes: Routes = [
title: 'Ownership changes'
}
}
+ },
+ {
+ path: 'blocklist/accounts',
+ component: MyAccountBlocklistComponent,
+ data: {
+ meta: {
+ title: 'Accounts blocklist'
+ }
+ }
+ },
+ {
+ path: 'blocklist/servers',
+ component: MyAccountServerBlocklistComponent,
+ data: {
+ meta: {
+ title: 'Instances blocklist'
+ }
+ }
}
]
}
diff --git a/client/src/app/+my-account/my-account.component.html b/client/src/app/+my-account/my-account.component.html
index b602fd69f..41333c25a 100644
--- a/client/src/app/+my-account/my-account.component.html
+++ b/client/src/app/+my-account/my-account.component.html
@@ -19,7 +19,21 @@
- Ownership changes
+
+
+ Misc
+ - {{ miscLabel }}
+
+
+
+
+
diff --git a/client/src/app/+my-account/my-account.component.scss b/client/src/app/+my-account/my-account.component.scss
index 20b2639b5..6243c6dcf 100644
--- a/client/src/app/+my-account/my-account.component.scss
+++ b/client/src/app/+my-account/my-account.component.scss
@@ -1,4 +1,4 @@
-.my-library {
+.my-library, .misc {
span[role=button] {
cursor: pointer;
}
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts
index bad60a8fb..d728caf07 100644
--- a/client/src/app/+my-account/my-account.component.ts
+++ b/client/src/app/+my-account/my-account.component.ts
@@ -13,6 +13,7 @@ import { Subscription } from 'rxjs'
export class MyAccountComponent implements OnInit, OnDestroy {
libraryLabel = ''
+ miscLabel = ''
private routeSub: Subscription
@@ -23,11 +24,11 @@ export class MyAccountComponent implements OnInit, OnDestroy {
) {}
ngOnInit () {
- this.updateLibraryLabel(this.router.url)
+ this.updateLabels(this.router.url)
this.routeSub = this.router.events
.pipe(filter(event => event instanceof NavigationStart))
- .subscribe((event: NavigationStart) => this.updateLibraryLabel(event.url))
+ .subscribe((event: NavigationStart) => this.updateLabels(event.url))
}
ngOnDestroy () {
@@ -40,7 +41,7 @@ export class MyAccountComponent implements OnInit, OnDestroy {
return importConfig.http.enabled || importConfig.torrent.enabled
}
- private updateLibraryLabel (url: string) {
+ private updateLabels (url: string) {
const [ path ] = url.split('?')
if (path.startsWith('/my-account/video-channels')) {
@@ -54,5 +55,13 @@ export class MyAccountComponent implements OnInit, OnDestroy {
} else {
this.libraryLabel = ''
}
+
+ if (path.startsWith('/my-account/blocklist/accounts')) {
+ this.miscLabel = this.i18n('Muted accounts')
+ } else if (path.startsWith('/my-account/blocklist/servers')) {
+ this.miscLabel = this.i18n('Muted instances')
+ } else {
+ this.miscLabel = ''
+ }
}
}
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index ad21162a8..017ebd57d 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -19,6 +19,8 @@ import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-i
import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone'
import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
+import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component'
+import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component'
@NgModule({
imports: [
@@ -45,7 +47,9 @@ import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-sub
ActorAvatarInfoComponent,
MyAccountVideoImportsComponent,
MyAccountDangerZoneComponent,
- MyAccountSubscriptionsComponent
+ MyAccountSubscriptionsComponent,
+ MyAccountBlocklistComponent,
+ MyAccountServerBlocklistComponent
],
exports: [
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts
index 42f2cfeaf..0aba9428a 100644
--- a/client/src/app/shared/account/account.model.ts
+++ b/client/src/app/shared/account/account.model.ts
@@ -5,6 +5,8 @@ export class Account extends Actor implements ServerAccount {
displayName: string
description: string
nameWithHost: string
+ muted: boolean
+ mutedServer: boolean
userId?: number
@@ -15,5 +17,8 @@ export class Account extends Actor implements ServerAccount {
this.description = hash.description
this.userId = hash.userId
this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host)
+
+ this.muted = false
+ this.mutedServer = false
}
}
diff --git a/client/src/app/shared/blocklist/account-block.model.ts b/client/src/app/shared/blocklist/account-block.model.ts
new file mode 100644
index 000000000..336680f65
--- /dev/null
+++ b/client/src/app/shared/blocklist/account-block.model.ts
@@ -0,0 +1,14 @@
+import { AccountBlock as AccountBlockServer } from '../../../../../shared'
+import { Account } from '../account/account.model'
+
+export class AccountBlock implements AccountBlockServer {
+ byAccount: Account
+ blockedAccount: Account
+ createdAt: Date | string
+
+ constructor (block: AccountBlockServer) {
+ this.byAccount = new Account(block.byAccount)
+ this.blockedAccount = new Account(block.blockedAccount)
+ this.createdAt = block.createdAt
+ }
+}
\ No newline at end of file
diff --git a/client/src/app/shared/blocklist/blocklist.service.ts b/client/src/app/shared/blocklist/blocklist.service.ts
new file mode 100644
index 000000000..d9c318258
--- /dev/null
+++ b/client/src/app/shared/blocklist/blocklist.service.ts
@@ -0,0 +1,79 @@
+import { Injectable } from '@angular/core'
+import { environment } from '../../../environments/environment'
+import { HttpClient, HttpParams } from '@angular/common/http'
+import { RestExtractor, RestPagination, RestService } from '../rest'
+import { SortMeta } from 'primeng/api'
+import { catchError, map } from 'rxjs/operators'
+import { AccountBlock as AccountBlockServer, ResultList, ServerBlock } from '../../../../../shared'
+import { Account } from '@app/shared/account/account.model'
+import { AccountBlock } from '@app/shared/blocklist/account-block.model'
+
+@Injectable()
+export class BlocklistService {
+ static BASE_USER_BLOCKLIST_URL = environment.apiUrl + '/api/v1/users/me/blocklist'
+
+ constructor (
+ private authHttp: HttpClient,
+ private restExtractor: RestExtractor,
+ private restService: RestService
+ ) { }
+
+ /*********************** User -> Account blocklist ***********************/
+
+ getUserAccountBlocklist (pagination: RestPagination, sort: SortMeta) {
+ let params = new HttpParams()
+ params = this.restService.addRestGetParams(params, pagination, sort)
+
+ return this.authHttp.get>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', { params })
+ .pipe(
+ map(res => this.restExtractor.convertResultListDateToHuman(res)),
+ map(res => this.restExtractor.applyToResultListData(res, this.formatAccountBlock.bind(this))),
+ catchError(err => this.restExtractor.handleError(err))
+ )
+ }
+
+ blockAccountByUser (account: Account) {
+ const body = { accountName: account.nameWithHost }
+
+ return this.authHttp.post(BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts', body)
+ .pipe(catchError(err => this.restExtractor.handleError(err)))
+ }
+
+ unblockAccountByUser (account: Account) {
+ const path = BlocklistService.BASE_USER_BLOCKLIST_URL + '/accounts/' + account.nameWithHost
+
+ return this.authHttp.delete(path)
+ .pipe(catchError(err => this.restExtractor.handleError(err)))
+ }
+
+ /*********************** User -> Server blocklist ***********************/
+
+ getUserServerBlocklist (pagination: RestPagination, sort: SortMeta) {
+ let params = new HttpParams()
+ params = this.restService.addRestGetParams(params, pagination, sort)
+
+ return this.authHttp.get>(BlocklistService.BASE_USER_BLOCKLIST_URL + '/servers', { params })
+ .pipe(
+ map(res => this.restExtractor.convertResultListDateToHuman(res)),
+ catchError(err => this.restExtractor.handleError(err))
+ )
+ }
+
+ blockServerByUser (host: string) {
+ const body = { host }
+
+ return this.authHttp.post(BlocklistService.BASE_USER_BLOCKLIST_URL + '/servers', body)
+ .pipe(catchError(err => this.restExtractor.handleError(err)))
+ }
+
+ unblockServerByUser (host: string) {
+ const path = BlocklistService.BASE_USER_BLOCKLIST_URL + '/servers/' + host
+
+ return this.authHttp.delete(path)
+ .pipe(catchError(err => this.restExtractor.handleError(err)))
+ }
+
+ private formatAccountBlock (accountBlock: AccountBlockServer) {
+ return new AccountBlock(accountBlock)
+ }
+}
diff --git a/client/src/app/shared/blocklist/index.ts b/client/src/app/shared/blocklist/index.ts
new file mode 100644
index 000000000..8cf6a55f7
--- /dev/null
+++ b/client/src/app/shared/blocklist/index.ts
@@ -0,0 +1,2 @@
+export * from './blocklist.service'
+export * from './account-block.model'
\ No newline at end of file
diff --git a/client/src/app/shared/buttons/action-dropdown.component.html b/client/src/app/shared/buttons/action-dropdown.component.html
index 111627424..48230d6d8 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.html
+++ b/client/src/app/shared/buttons/action-dropdown.component.html
@@ -9,13 +9,13 @@
\ No newline at end of file
diff --git a/client/src/app/shared/buttons/action-dropdown.component.scss b/client/src/app/shared/buttons/action-dropdown.component.scss
index 0a9aa7b04..92c4d1d2c 100644
--- a/client/src/app/shared/buttons/action-dropdown.component.scss
+++ b/client/src/app/shared/buttons/action-dropdown.component.scss
@@ -46,5 +46,10 @@
.dropdown-item {
cursor: pointer;
color: #000 !important;
+
+ a, span {
+ display: block;
+ width: 100%;
+ }
}
}
\ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.html b/client/src/app/shared/moderation/user-moderation-dropdown.component.html
index 01db7cd4a..7367a7e59 100644
--- a/client/src/app/shared/moderation/user-moderation-dropdown.component.html
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.html
@@ -1,5 +1,8 @@
-
+
-
+
\ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
index 105c99d8b..2f4a55f37 100644
--- a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
+++ b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
@@ -1,4 +1,4 @@
-import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
+import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
@@ -6,16 +6,20 @@ import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.com
import { UserService } from '@app/shared/users'
import { AuthService, ConfirmService } from '@app/core'
import { User, UserRight } from '../../../../../shared/models/users'
+import { Account } from '@app/shared/account/account.model'
+import { BlocklistService } from '@app/shared/blocklist'
@Component({
selector: 'my-user-moderation-dropdown',
templateUrl: './user-moderation-dropdown.component.html',
styleUrls: [ './user-moderation-dropdown.component.scss' ]
})
-export class UserModerationDropdownComponent implements OnInit {
+export class UserModerationDropdownComponent implements OnChanges {
@ViewChild('userBanModal') userBanModal: UserBanModalComponent
@Input() user: User
+ @Input() account: Account
+
@Input() buttonSize: 'normal' | 'small' = 'normal'
@Input() placement = 'left'
@@ -29,10 +33,11 @@ export class UserModerationDropdownComponent implements OnInit {
private notificationsService: NotificationsService,
private confirmService: ConfirmService,
private userService: UserService,
+ private blocklistService: BlocklistService,
private i18n: I18n
) { }
- ngOnInit () {
+ ngOnChanges () {
this.buildActions()
}
@@ -92,6 +97,74 @@ export class UserModerationDropdownComponent implements OnInit {
)
}
+ blockAccountByUser (account: Account) {
+ this.blocklistService.blockAccountByUser(account)
+ .subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Account {{nameWithHost}} muted.', { nameWithHost: account.nameWithHost })
+ )
+
+ this.account.muted = true
+ this.userChanged.emit()
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+
+ unblockAccountByUser (account: Account) {
+ this.blocklistService.unblockAccountByUser(account)
+ .subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: account.nameWithHost })
+ )
+
+ this.account.muted = false
+ this.userChanged.emit()
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+
+ blockServerByUser (host: string) {
+ this.blocklistService.blockServerByUser(host)
+ .subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Instance {{host}} muted.', { host })
+ )
+
+ this.account.mutedServer = true
+ this.userChanged.emit()
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+
+ unblockServerByUser (host: string) {
+ this.blocklistService.unblockServerByUser(host)
+ .subscribe(
+ () => {
+ this.notificationsService.success(
+ this.i18n('Success'),
+ this.i18n('Instance {{host}} unmuted.', { host })
+ )
+
+ this.account.mutedServer = false
+ this.userChanged.emit()
+ },
+
+ err => this.notificationsService.error(this.i18n('Error'), err.message)
+ )
+ }
+
getRouterUserEditLink (user: User) {
return [ '/admin', 'users', 'update', user.id ]
}
@@ -102,25 +175,53 @@ export class UserModerationDropdownComponent implements OnInit {
if (this.authService.isLoggedIn()) {
const authUser = this.authService.getUser()
- if (authUser.hasRight(UserRight.MANAGE_USERS)) {
+ if (this.user && authUser.id === this.user.id) return
+
+ if (this.user && authUser.hasRight(UserRight.MANAGE_USERS)) {
this.userActions = this.userActions.concat([
{
label: this.i18n('Edit'),
- linkBuilder: this.getRouterUserEditLink
+ linkBuilder: ({ user }) => this.getRouterUserEditLink(user)
},
{
label: this.i18n('Delete'),
- handler: user => this.removeUser(user)
+ handler: ({ user }) => this.removeUser(user)
},
{
label: this.i18n('Ban'),
- handler: user => this.openBanUserModal(user),
- isDisplayed: user => !user.blocked
+ handler: ({ user }) => this.openBanUserModal(user),
+ isDisplayed: ({ user }) => !user.muted
},
{
label: this.i18n('Unban'),
- handler: user => this.unbanUser(user),
- isDisplayed: user => user.blocked
+ handler: ({ user }) => this.unbanUser(user),
+ isDisplayed: ({ user }) => user.muted
+ }
+ ])
+ }
+
+ // User actions on accounts/servers
+ if (this.account) {
+ this.userActions = this.userActions.concat([
+ {
+ label: this.i18n('Mute this account'),
+ isDisplayed: ({ account }) => account.muted === false,
+ handler: ({ account }) => this.blockAccountByUser(account)
+ },
+ {
+ label: this.i18n('Unmute this account'),
+ isDisplayed: ({ account }) => account.muted === true,
+ handler: ({ account }) => this.unblockAccountByUser(account)
+ },
+ {
+ label: this.i18n('Mute the instance'),
+ isDisplayed: ({ account }) => !account.userId && account.mutedServer === false,
+ handler: ({ account }) => this.blockServerByUser(account.host)
+ },
+ {
+ label: this.i18n('Unmute the instance'),
+ isDisplayed: ({ account }) => !account.userId && account.mutedServer === true,
+ handler: ({ account }) => this.unblockServerByUser(account.host)
}
])
}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 9647a7966..40e05fcc7 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -58,6 +58,7 @@ import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-fe
import { OverviewService } from '@app/shared/overview'
import { UserBanModalComponent } from '@app/shared/moderation'
import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
+import { BlocklistService } from '@app/shared/blocklist'
@NgModule({
imports: [
@@ -172,6 +173,7 @@ import { UserModerationDropdownComponent } from '@app/shared/moderation/user-mod
OverviewService,
VideoChangeOwnershipValidatorsService,
VideoAcceptOwnershipValidatorsService,
+ BlocklistService,
I18nPrimengCalendarService,
ScreenService,
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts
index e955ffde9..95a4105ec 100644
--- a/server/controllers/api/users/my-blocklist.ts
+++ b/server/controllers/api/users/my-blocklist.ts
@@ -6,7 +6,6 @@ import {
asyncRetryTransactionMiddleware,
authenticate,
paginationValidator,
- serverGetValidator,
setDefaultPagination,
setDefaultSort,
unblockAccountByAccountValidator
@@ -14,6 +13,7 @@ import {
import {
accountsBlocklistSortValidator,
blockAccountByAccountValidator,
+ blockServerByAccountValidator,
serversBlocklistSortValidator,
unblockServerByAccountValidator
} from '../../../middlewares/validators'
@@ -58,7 +58,7 @@ myBlocklistRouter.get('/me/blocklist/servers',
myBlocklistRouter.post('/me/blocklist/servers',
authenticate,
- asyncMiddleware(serverGetValidator),
+ asyncMiddleware(blockServerByAccountValidator),
asyncRetryTransactionMiddleware(blockServer)
)
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts
index 394c24537..1633e500c 100644
--- a/server/lib/blocklist.ts
+++ b/server/lib/blocklist.ts
@@ -4,7 +4,7 @@ import { ServerBlocklistModel } from '../models/server/server-blocklist'
function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
return sequelizeTypescript.transaction(async t => {
- return AccountBlocklistModel.create({
+ return AccountBlocklistModel.upsert({
accountId: byAccountId,
targetAccountId: targetAccountId
}, { transaction: t })
@@ -13,7 +13,7 @@ function addAccountInBlocklist (byAccountId: number, targetAccountId: number) {
function addServerInBlocklist (byAccountId: number, targetServerId: number) {
return sequelizeTypescript.transaction(async t => {
- return ServerBlocklistModel.create({
+ return ServerBlocklistModel.upsert({
accountId: byAccountId,
targetServerId
}, { transaction: t })
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts
index 9dbd5e512..25c054d6b 100644
--- a/server/middlewares/validators/blocklist.ts
+++ b/server/middlewares/validators/blocklist.ts
@@ -1,4 +1,4 @@
-import { param, body } from 'express-validator/check'
+import { body, param } from 'express-validator/check'
import * as express from 'express'
import { logger } from '../../helpers/logger'
import { areValidationErrors } from './utils'
@@ -7,6 +7,8 @@ import { UserModel } from '../../models/account/user'
import { AccountBlocklistModel } from '../../models/account/account-blocklist'
import { isHostValid } from '../../helpers/custom-validators/servers'
import { ServerBlocklistModel } from '../../models/server/server-blocklist'
+import { ServerModel } from '../../models/server/server'
+import { CONFIG } from '../../initializers'
const blockAccountByAccountValidator = [
body('accountName').exists().withMessage('Should have an account name with host'),
@@ -17,6 +19,17 @@ const blockAccountByAccountValidator = [
if (areValidationErrors(req, res)) return
if (!await isAccountNameWithHostExist(req.body.accountName, res)) return
+ const user = res.locals.oauth.token.User as UserModel
+ const accountToBlock = res.locals.account
+
+ if (user.Account.id === accountToBlock.id) {
+ res.status(409)
+ .send({ error: 'You cannot block yourself.' })
+ .end()
+
+ return
+ }
+
return next()
}
]
@@ -38,6 +51,35 @@ const unblockAccountByAccountValidator = [
}
]
+const blockServerByAccountValidator = [
+ body('host').custom(isHostValid).withMessage('Should have a valid host'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking serverGetValidator parameters', { parameters: req.body })
+
+ if (areValidationErrors(req, res)) return
+
+ const host: string = req.body.host
+
+ if (host === CONFIG.WEBSERVER.HOST) {
+ return res.status(409)
+ .send({ error: 'You cannot block your own server.' })
+ .end()
+ }
+
+ const server = await ServerModel.loadByHost(host)
+ if (!server) {
+ return res.status(404)
+ .send({ error: 'Server host not found.' })
+ .end()
+ }
+
+ res.locals.server = server
+
+ return next()
+ }
+]
+
const unblockServerByAccountValidator = [
param('host').custom(isHostValid).withMessage('Should have an account name with host'),
@@ -56,6 +98,7 @@ const unblockServerByAccountValidator = [
// ---------------------------------------------------------------------------
export {
+ blockServerByAccountValidator,
blockAccountByAccountValidator,
unblockAccountByAccountValidator,
unblockServerByAccountValidator
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index bacd122e8..fa2819235 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -18,7 +18,7 @@ enum ScopeNames {
{
model: () => AccountModel,
required: true,
- as: 'AccountBlocked'
+ as: 'BlockedAccount'
}
]
}
@@ -67,10 +67,10 @@ export class AccountBlocklistModel extends Model {
name: 'targetAccountId',
allowNull: false
},
- as: 'AccountBlocked',
+ as: 'BlockedAccount',
onDelete: 'CASCADE'
})
- AccountBlocked: AccountModel
+ BlockedAccount: AccountModel
static loadByAccountAndTarget (accountId: number, targetAccountId: number) {
const query = {
@@ -104,7 +104,7 @@ export class AccountBlocklistModel extends Model {
toFormattedJSON (): AccountBlock {
return {
byAccount: this.ByAccount.toFormattedJSON(),
- accountBlocked: this.AccountBlocked.toFormattedJSON(),
+ blockedAccount: this.BlockedAccount.toFormattedJSON(),
createdAt: this.createdAt
}
}
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index 705ed2c6b..450f27152 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -72,7 +72,7 @@ export class ServerBlocklistModel extends Model {
},
onDelete: 'CASCADE'
})
- ServerBlocked: ServerModel
+ BlockedServer: ServerModel
static loadByAccountAndHost (accountId: number, host: string) {
const query = {
@@ -114,7 +114,7 @@ export class ServerBlocklistModel extends Model {
toFormattedJSON (): ServerBlock {
return {
byAccount: this.ByAccount.toFormattedJSON(),
- serverBlocked: this.ServerBlocked.toFormattedJSON(),
+ blockedServer: this.BlockedServer.toFormattedJSON(),
createdAt: this.createdAt
}
}
diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts
index 8117c46a6..d24d9323f 100644
--- a/server/tests/api/check-params/blocklist.ts
+++ b/server/tests/api/check-params/blocklist.ts
@@ -85,6 +85,16 @@ describe('Test blocklist API validators', function () {
})
})
+ it('Should fail to block ourselves', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ token: server.accessToken,
+ path,
+ fields: { accountName: 'root' },
+ statusCodeExpected: 409
+ })
+ })
+
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
@@ -170,6 +180,16 @@ describe('Test blocklist API validators', function () {
})
})
+ it('Should fail with our own server', async function () {
+ await makePostBodyRequest({
+ url: server.url,
+ token: server.accessToken,
+ path,
+ fields: { host: 'localhost:9001' },
+ statusCodeExpected: 409
+ })
+ })
+
it('Should succeed with the correct params', async function () {
await makePostBodyRequest({
url: server.url,
diff --git a/server/tests/api/users/account-blocklist.ts b/server/tests/api/users/account-blocklist.ts
index 00ad51461..026971331 100644
--- a/server/tests/api/users/account-blocklist.ts
+++ b/server/tests/api/users/account-blocklist.ts
@@ -183,9 +183,9 @@ describe('Test accounts blocklist', function () {
const block = blocks[0]
expect(block.byAccount.displayName).to.equal('root')
expect(block.byAccount.name).to.equal('root')
- expect(block.accountBlocked.displayName).to.equal('user2')
- expect(block.accountBlocked.name).to.equal('user2')
- expect(block.accountBlocked.host).to.equal('localhost:9002')
+ expect(block.blockedAccount.displayName).to.equal('user2')
+ expect(block.blockedAccount.name).to.equal('user2')
+ expect(block.blockedAccount.host).to.equal('localhost:9002')
}
{
@@ -197,9 +197,9 @@ describe('Test accounts blocklist', function () {
const block = blocks[0]
expect(block.byAccount.displayName).to.equal('root')
expect(block.byAccount.name).to.equal('root')
- expect(block.accountBlocked.displayName).to.equal('user1')
- expect(block.accountBlocked.name).to.equal('user1')
- expect(block.accountBlocked.host).to.equal('localhost:9001')
+ expect(block.blockedAccount.displayName).to.equal('user1')
+ expect(block.blockedAccount.name).to.equal('user1')
+ expect(block.blockedAccount.host).to.equal('localhost:9001')
}
})
@@ -267,7 +267,7 @@ describe('Test accounts blocklist', function () {
const block = blocks[0]
expect(block.byAccount.displayName).to.equal('root')
expect(block.byAccount.name).to.equal('root')
- expect(block.serverBlocked.host).to.equal('localhost:9002')
+ expect(block.blockedServer.host).to.equal('localhost:9002')
})
it('Should unblock the remote server', async function () {
diff --git a/shared/models/blocklist/account-block.model.ts b/shared/models/blocklist/account-block.model.ts
index d6f8840c5..a942ed614 100644
--- a/shared/models/blocklist/account-block.model.ts
+++ b/shared/models/blocklist/account-block.model.ts
@@ -2,6 +2,6 @@ import { Account } from '../actors'
export interface AccountBlock {
byAccount: Account
- accountBlocked: Account
+ blockedAccount: Account
createdAt: Date | string
}
diff --git a/shared/models/blocklist/server-block.model.ts b/shared/models/blocklist/server-block.model.ts
index efba672bd..a8b8af0b7 100644
--- a/shared/models/blocklist/server-block.model.ts
+++ b/shared/models/blocklist/server-block.model.ts
@@ -2,7 +2,7 @@ import { Account } from '../actors'
export interface ServerBlock {
byAccount: Account
- serverBlocked: {
+ blockedServer: {
host: string
}
createdAt: Date | string