Move user moderation tool in a separate component

This commit is contained in:
Chocobozzz 2018-10-05 15:24:29 +02:00
parent 21c54ac5f6
commit e724fa93c7
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
17 changed files with 235 additions and 207 deletions

View File

@ -10,9 +10,8 @@ import { FollowingListComponent } from './follows/following-list/following-list.
import { JobsComponent } from './jobs/job.component'
import { JobsListComponent } from './jobs/jobs-list/jobs-list.component'
import { JobService } from './jobs/shared/job.service'
import { UserCreateComponent, UserListComponent, UsersComponent, UserService, UserUpdateComponent } from './users'
import { UserCreateComponent, UserListComponent, UsersComponent, UserUpdateComponent } from './users'
import { ModerationCommentModalComponent, VideoAbuseListComponent, VideoBlacklistListComponent } from './moderation'
import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component'
import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service'
@ -37,7 +36,6 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service
UserCreateComponent,
UserUpdateComponent,
UserListComponent,
UserBanModalComponent,
ModerationComponent,
VideoBlacklistListComponent,
@ -58,7 +56,6 @@ import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service
providers: [
FollowService,
RedundancyService,
UserService,
JobService,
ConfigService
]

View File

@ -1,4 +1,3 @@
export * from './shared'
export * from './user-edit'
export * from './user-list'
export * from './users.component'

View File

@ -1 +0,0 @@
export * from './user.service'

View File

@ -1,96 +0,0 @@
import { catchError, map } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { BytesPipe } from 'ngx-pipes'
import { SortMeta } from 'primeng/components/common/sortmeta'
import { Observable } from 'rxjs'
import { ResultList, UserCreate, UserUpdate, User, UserRole } from '../../../../../../shared'
import { environment } from '../../../../environments/environment'
import { RestExtractor, RestPagination, RestService } from '../../../shared'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Injectable()
export class UserService {
private static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
private bytesPipe = new BytesPipe()
constructor (
private authHttp: HttpClient,
private restService: RestService,
private restExtractor: RestExtractor,
private i18n: I18n
) { }
addUser (userCreate: UserCreate) {
return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
.pipe(
map(this.restExtractor.extractDataBool),
catchError(err => this.restExtractor.handleError(err))
)
}
updateUser (userId: number, userUpdate: UserUpdate) {
return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
.pipe(
map(this.restExtractor.extractDataBool),
catchError(err => this.restExtractor.handleError(err))
)
}
getUser (userId: number) {
return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> {
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params })
.pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)),
map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
catchError(err => this.restExtractor.handleError(err))
)
}
removeUser (user: User) {
return this.authHttp.delete(UserService.BASE_USERS_URL + user.id)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
banUser (user: User, reason?: string) {
const body = reason ? { reason } : {}
return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
unbanUser (user: User) {
return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {})
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
private formatUser (user: User) {
let videoQuota
if (user.videoQuota === -1) {
videoQuota = this.i18n('Unlimited')
} else {
videoQuota = this.bytesPipe.transform(user.videoQuota, 0)
}
const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0)
const roleLabels: { [ id in UserRole ]: string } = {
[UserRole.USER]: this.i18n('User'),
[UserRole.ADMINISTRATOR]: this.i18n('Administrator'),
[UserRole.MODERATOR]: this.i18n('Moderator')
}
return Object.assign(user, {
roleLabel: roleLabels[user.role],
videoQuota,
videoQuotaUsed
})
}
}

View File

@ -1,7 +1,6 @@
import { Component, OnInit } from '@angular/core'
import { Router } from '@angular/router'
import { NotificationsService } from 'angular2-notifications'
import { UserService } from '../shared'
import { ServerService } from '../../../core'
import { UserCreate, UserRole } from '../../../../../../shared'
import { UserEdit } from './user-edit'
@ -9,6 +8,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
import { ConfigService } from '@app/+admin/config/shared/config.service'
import { UserService } from '@app/shared'
@Component({
selector: 'my-user-create',

View File

@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Subscription } from 'rxjs'
import { NotificationsService } from 'angular2-notifications'
import { UserService } from '../shared'
import { ServerService } from '../../../core'
import { UserEdit } from './user-edit'
import { User, UserUpdate } from '../../../../../../shared'
@ -10,6 +9,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
import { ConfigService } from '@app/+admin/config/shared/config.service'
import { UserService } from '@app/shared'
@Component({
selector: 'my-user-update',

View File

@ -40,7 +40,7 @@
<td>{{ user.roleLabel }}</td>
<td>{{ user.createdAt }}</td>
<td class="action-cell">
<my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user"></my-action-dropdown>
<my-user-moderation-dropdown [user]="user" (userChanged)="onUserChanged()"></my-user-moderation-dropdown>
</td>
</tr>
</ng-template>
@ -55,4 +55,3 @@
</ng-template>
</p-table>
<my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal>

View File

@ -1,13 +1,9 @@
import { Component, OnInit, ViewChild } from '@angular/core'
import { Component, OnInit } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'
import { SortMeta } from 'primeng/components/common/sortmeta'
import { ConfirmService } from '../../../core'
import { RestPagination, RestTable } from '../../../shared'
import { UserService } from '../shared'
import { RestPagination, RestTable, UserService } from '../../../shared'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { UserBanModalComponent } from '@app/+admin/users/user-list/user-ban-modal.component'
import { User } from '../../../../../../shared'
@Component({
@ -16,16 +12,11 @@ import { User } from '../../../../../../shared'
styleUrls: [ './user-list.component.scss' ]
})
export class UserListComponent extends RestTable implements OnInit {
@ViewChild('userBanModal') userBanModal: UserBanModalComponent
users: User[] = []
totalRecords = 0
rowsPerPage = 10
sort: SortMeta = { field: 'createdAt', order: 1 }
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
userActions: DropdownAction<User>[] = []
private openedModal: NgbModalRef
constructor (
private notificationsService: NotificationsService,
@ -34,96 +25,16 @@ export class UserListComponent extends RestTable implements OnInit {
private i18n: I18n
) {
super()
this.userActions = [
{
label: this.i18n('Edit'),
linkBuilder: this.getRouterUserEditLink
},
{
label: this.i18n('Delete'),
handler: user => this.removeUser(user)
},
{
label: this.i18n('Ban'),
handler: user => this.openBanUserModal(user),
isDisplayed: user => !user.blocked
},
{
label: this.i18n('Unban'),
handler: user => this.unbanUser(user),
isDisplayed: user => user.blocked
}
]
}
ngOnInit () {
this.loadSort()
}
hideBanUserModal () {
this.openedModal.close()
}
openBanUserModal (user: User) {
if (user.username === 'root') {
this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.'))
return
}
this.userBanModal.openModal(user)
}
onUserBanned () {
onUserChanged () {
this.loadData()
}
async unbanUser (user: User) {
const message = this.i18n('Do you really want to unban {{username}}?', { username: user.username })
const res = await this.confirmService.confirm(message, this.i18n('Unban'))
if (res === false) return
this.userService.unbanUser(user)
.subscribe(
() => {
this.notificationsService.success(
this.i18n('Success'),
this.i18n('User {{username}} unbanned.', { username: user.username })
)
this.loadData()
},
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
async removeUser (user: User) {
if (user.username === 'root') {
this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.'))
return
}
const message = this.i18n('If you remove this user, you will not be able to create another with the same username!')
const res = await this.confirmService.confirm(message, this.i18n('Delete'))
if (res === false) return
this.userService.removeUser(user).subscribe(
() => {
this.notificationsService.success(
this.i18n('Success'),
this.i18n('User {{username}} deleted.', { username: user.username })
)
this.loadData()
},
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
getRouterUserEditLink (user: User) {
return [ '/admin', 'users', 'update', user.id ]
}
protected loadData () {
this.userService.getUsers(this.pagination, this.sort)
.subscribe(

View File

@ -0,0 +1,2 @@
export * from './user-ban-modal.component'
export * from './user-moderation-dropdown.component'

View File

@ -1,12 +1,11 @@
import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
import { NotificationsService } from 'angular2-notifications'
import { FormReactive, UserValidatorsService } from '../../../shared'
import { UserService } from '../shared'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
import { User } from '../../../../../../shared'
import { FormReactive, UserValidatorsService } from '@app/shared/forms'
import { User, UserService } from '@app/shared/users'
@Component({
selector: 'my-user-ban-modal',

View File

@ -0,0 +1,3 @@
<my-user-ban-modal #userBanModal (userBanned)="onUserBanned()"></my-user-ban-modal>
<my-action-dropdown i18n-label label="Actions" [actions]="userActions" [entry]="user"></my-action-dropdown>

View File

@ -0,0 +1,128 @@
import { Component, EventEmitter, Input, OnInit, 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'
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component'
import { User, UserService } from '@app/shared/users'
import { AuthService, ConfirmService } from '@app/core'
import { UserRight } from '../../../../../shared/models/users'
@Component({
selector: 'my-user-moderation-dropdown',
templateUrl: './user-moderation-dropdown.component.html',
styleUrls: [ './user-moderation-dropdown.component.scss' ]
})
export class UserModerationDropdownComponent implements OnInit {
@ViewChild('userBanModal') userBanModal: UserBanModalComponent
@Input() user: User
@Output() userChanged = new EventEmitter()
userActions: DropdownAction<User>[] = []
private openedModal: NgbModalRef
constructor (
private authService: AuthService,
private notificationsService: NotificationsService,
private confirmService: ConfirmService,
private userService: UserService,
private i18n: I18n
) { }
ngOnInit () {
this.userActions = []
if (this.authService.isLoggedIn()) {
const authUser = this.authService.getUser()
if (authUser.hasRight(UserRight.MANAGE_USERS)) {
this.userActions = this.userActions.concat([
{
label: this.i18n('Edit'),
linkBuilder: this.getRouterUserEditLink
},
{
label: this.i18n('Delete'),
handler: user => this.removeUser(user)
},
{
label: this.i18n('Ban'),
handler: user => this.openBanUserModal(user),
isDisplayed: user => !user.blocked
},
{
label: this.i18n('Unban'),
handler: user => this.unbanUser(user),
isDisplayed: user => user.blocked
}
])
}
}
}
hideBanUserModal () {
this.openedModal.close()
}
openBanUserModal (user: User) {
if (user.username === 'root') {
this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.'))
return
}
this.userBanModal.openModal(user)
}
onUserBanned () {
this.userChanged.emit()
}
async unbanUser (user: User) {
const message = this.i18n('Do you really want to unban {{username}}?', { username: user.username })
const res = await this.confirmService.confirm(message, this.i18n('Unban'))
if (res === false) return
this.userService.unbanUser(user)
.subscribe(
() => {
this.notificationsService.success(
this.i18n('Success'),
this.i18n('User {{username}} unbanned.', { username: user.username })
)
this.userChanged.emit()
},
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
async removeUser (user: User) {
if (user.username === 'root') {
this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.'))
return
}
const message = this.i18n('If you remove this user, you will not be able to create another with the same username!')
const res = await this.confirmService.confirm(message, this.i18n('Delete'))
if (res === false) return
this.userService.removeUser(user).subscribe(
() => {
this.notificationsService.success(
this.i18n('Success'),
this.i18n('User {{username}} deleted.', { username: user.username })
)
this.userChanged.emit()
},
err => this.notificationsService.error(this.i18n('Error'), err.message)
)
}
getRouterUserEditLink (user: User) {
return [ '/admin', 'users', 'update', user.id ]
}
}

View File

@ -56,6 +56,8 @@ import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, N
import { SubscribeButtonComponent, RemoteSubscribeComponent, UserSubscriptionService } from '@app/shared/user-subscription'
import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
import { OverviewService } from '@app/shared/overview'
import { UserBanModalComponent } from '@app/shared/moderation'
import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
@NgModule({
imports: [
@ -94,7 +96,9 @@ import { OverviewService } from '@app/shared/overview'
PeertubeCheckboxComponent,
SubscribeButtonComponent,
RemoteSubscribeComponent,
InstanceFeaturesTableComponent
InstanceFeaturesTableComponent,
UserBanModalComponent,
UserModerationDropdownComponent
],
exports: [
@ -130,6 +134,8 @@ import { OverviewService } from '@app/shared/overview'
SubscribeButtonComponent,
RemoteSubscribeComponent,
InstanceFeaturesTableComponent,
UserBanModalComponent,
UserModerationDropdownComponent,
NumberFormatterPipe,
ObjectLengthPipe,

View File

@ -2,20 +2,26 @@ import { Observable } from 'rxjs'
import { catchError, map } from 'rxjs/operators'
import { HttpClient, HttpParams } from '@angular/common/http'
import { Injectable } from '@angular/core'
import { UserCreate, UserUpdateMe, UserVideoQuota } from '../../../../../shared'
import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared'
import { environment } from '../../../environments/environment'
import { RestExtractor } from '../rest'
import { RestExtractor, RestPagination, RestService } from '../rest'
import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
import { SortMeta } from 'primeng/api'
import { BytesPipe } from 'ngx-pipes'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Injectable()
export class UserService {
static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
private bytesPipe = new BytesPipe()
constructor (
private authHttp: HttpClient,
private restExtractor: RestExtractor
) {
}
private restExtractor: RestExtractor,
private restService: RestService,
private i18n: I18n
) { }
changePassword (currentPassword: string, newPassword: string) {
const url = UserService.BASE_USERS_URL + 'me'
@ -128,4 +134,79 @@ export class UserService {
.get<string[]>(url, { params })
.pipe(catchError(res => this.restExtractor.handleError(res)))
}
/* ###### Admin methods ###### */
addUser (userCreate: UserCreate) {
return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
.pipe(
map(this.restExtractor.extractDataBool),
catchError(err => this.restExtractor.handleError(err))
)
}
updateUser (userId: number, userUpdate: UserUpdate) {
return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
.pipe(
map(this.restExtractor.extractDataBool),
catchError(err => this.restExtractor.handleError(err))
)
}
getUser (userId: number) {
return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> {
let params = new HttpParams()
params = this.restService.addRestGetParams(params, pagination, sort)
return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params })
.pipe(
map(res => this.restExtractor.convertResultListDateToHuman(res)),
map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
catchError(err => this.restExtractor.handleError(err))
)
}
removeUser (user: User) {
return this.authHttp.delete(UserService.BASE_USERS_URL + user.id)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
banUser (user: User, reason?: string) {
const body = reason ? { reason } : {}
return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body)
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
unbanUser (user: User) {
return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {})
.pipe(catchError(err => this.restExtractor.handleError(err)))
}
private formatUser (user: User) {
let videoQuota
if (user.videoQuota === -1) {
videoQuota = this.i18n('Unlimited')
} else {
videoQuota = this.bytesPipe.transform(user.videoQuota, 0)
}
const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0)
const roleLabels: { [ id in UserRole ]: string } = {
[UserRole.USER]: this.i18n('User'),
[UserRole.ADMINISTRATOR]: this.i18n('Administrator'),
[UserRole.MODERATOR]: this.i18n('Moderator')
}
return Object.assign(user, {
roleLabel: roleLabels[user.role],
videoQuota,
videoQuotaUsed
})
}
}