Move user moderation tool in a separate component
authorChocobozzz <me@florianbigard.com>
Fri, 5 Oct 2018 13:24:29 +0000 (15:24 +0200)
committerChocobozzz <me@florianbigard.com>
Fri, 5 Oct 2018 15:02:09 +0000 (17:02 +0200)
20 files changed:
client/src/app/+admin/admin.module.ts
client/src/app/+admin/users/index.ts
client/src/app/+admin/users/shared/index.ts [deleted file]
client/src/app/+admin/users/shared/user.service.ts [deleted file]
client/src/app/+admin/users/user-edit/user-create.component.ts
client/src/app/+admin/users/user-edit/user-update.component.ts
client/src/app/+admin/users/user-list/user-ban-modal.component.html [deleted file]
client/src/app/+admin/users/user-list/user-ban-modal.component.scss [deleted file]
client/src/app/+admin/users/user-list/user-ban-modal.component.ts [deleted file]
client/src/app/+admin/users/user-list/user-list.component.html
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/shared/moderation/index.ts [new file with mode: 0644]
client/src/app/shared/moderation/user-ban-modal.component.html [new file with mode: 0644]
client/src/app/shared/moderation/user-ban-modal.component.scss [new file with mode: 0644]
client/src/app/shared/moderation/user-ban-modal.component.ts [new file with mode: 0644]
client/src/app/shared/moderation/user-moderation-dropdown.component.html [new file with mode: 0644]
client/src/app/shared/moderation/user-moderation-dropdown.component.scss [new file with mode: 0644]
client/src/app/shared/moderation/user-moderation-dropdown.component.ts [new file with mode: 0644]
client/src/app/shared/shared.module.ts
client/src/app/shared/users/user.service.ts

index 5784609efd25df332ee60f3bbaa46d7191f73031..8c6db98d9f4a1d222b2e56e3b76753004bd671c9 100644 (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
   ]
index efcd0d9cb3b000f7a6340f3d5ba5c20af86e09ac..156e54d8999198062668ae53f1e40b64c4b9d568 100644 (file)
@@ -1,4 +1,3 @@
-export * from './shared'
 export * from './user-edit'
 export * from './user-list'
 export * from './users.component'
diff --git a/client/src/app/+admin/users/shared/index.ts b/client/src/app/+admin/users/shared/index.ts
deleted file mode 100644 (file)
index 1f1302d..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './user.service'
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts
deleted file mode 100644 (file)
index 470beef..0000000
+++ /dev/null
@@ -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
-    })
-  }
-}
index 132e280b9c60105c1892e523c5fabed7180bdc84..dd8e4efd53e21920a75402f6922346190e0a78e3 100644 (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',
index 9eb91ac95f19f3e3f4402ae8c0517f597831bcde..cd3885a9922824c077495cd8a09cec210fd71964 100644 (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',
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.html b/client/src/app/+admin/users/user-list/user-ban-modal.component.html
deleted file mode 100644 (file)
index b2958ca..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-<ng-template #modal>
-  <div class="modal-header">
-    <h4 i18n class="modal-title">Ban {{ userToBan.username }}</h4>
-    <span class="close" aria-hidden="true" (click)="hideBanUserModal()"></span>
-  </div>
-
-  <div class="modal-body">
-    <form novalidate [formGroup]="form" (ngSubmit)="banUser()">
-      <div class="form-group">
-        <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
-        </textarea>
-        <div *ngIf="formErrors.reason" class="form-error">
-          {{ formErrors.reason }}
-        </div>
-      </div>
-
-      <div i18n>
-        A banned user will no longer be able to login.
-      </div>
-
-      <div class="form-group inputs">
-        <span i18n class="action-button action-button-cancel" (click)="hideBanUserModal()">Cancel</span>
-
-        <input
-          type="submit" i18n-value value="Ban this user" class="action-button-submit"
-          [disabled]="!form.valid"
-        >
-      </div>
-    </form>
-  </div>
-
-</ng-template>
\ No newline at end of file
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.scss b/client/src/app/+admin/users/user-list/user-ban-modal.component.scss
deleted file mode 100644 (file)
index 84562f1..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-@import 'variables';
-@import 'mixins';
-
-textarea {
-  @include peertube-textarea(100%, 60px);
-}
diff --git a/client/src/app/+admin/users/user-list/user-ban-modal.component.ts b/client/src/app/+admin/users/user-list/user-ban-modal.component.ts
deleted file mode 100644 (file)
index 4fd4d56..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-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'
-
-@Component({
-  selector: 'my-user-ban-modal',
-  templateUrl: './user-ban-modal.component.html',
-  styleUrls: [ './user-ban-modal.component.scss' ]
-})
-export class UserBanModalComponent extends FormReactive implements OnInit {
-  @ViewChild('modal') modal: NgbModal
-  @Output() userBanned = new EventEmitter<User>()
-
-  private userToBan: User
-  private openedModal: NgbModalRef
-
-  constructor (
-    protected formValidatorService: FormValidatorService,
-    private modalService: NgbModal,
-    private notificationsService: NotificationsService,
-    private userService: UserService,
-    private userValidatorsService: UserValidatorsService,
-    private i18n: I18n
-  ) {
-    super()
-  }
-
-  ngOnInit () {
-    this.buildForm({
-      reason: this.userValidatorsService.USER_BAN_REASON
-    })
-  }
-
-  openModal (user: User) {
-    this.userToBan = user
-    this.openedModal = this.modalService.open(this.modal)
-  }
-
-  hideBanUserModal () {
-    this.userToBan = undefined
-    this.openedModal.close()
-  }
-
-  async banUser () {
-    const reason = this.form.value['reason'] || undefined
-
-    this.userService.banUser(this.userToBan, reason)
-      .subscribe(
-        () => {
-          this.notificationsService.success(
-            this.i18n('Success'),
-            this.i18n('User {{username}} banned.', { username: this.userToBan.username })
-          )
-
-          this.userBanned.emit(this.userToBan)
-          this.hideBanUserModal()
-        },
-
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
-      )
-  }
-
-}
index bb1b264428747e91850e5d8ce6a8879302219866..2479ce9e47013adbcfef3b8955ac3afedfc304c1 100644 (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>
\ No newline at end of file
index 100ffc00e6eee3deb8993e472a057add73996537..dee3ed6431fd5b82a747f1aa7682778d1fea1e82 100644 (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(
diff --git a/client/src/app/shared/moderation/index.ts b/client/src/app/shared/moderation/index.ts
new file mode 100644 (file)
index 0000000..2245294
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './user-ban-modal.component'
+export * from './user-moderation-dropdown.component'
\ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.html b/client/src/app/shared/moderation/user-ban-modal.component.html
new file mode 100644 (file)
index 0000000..b2958ca
--- /dev/null
@@ -0,0 +1,32 @@
+<ng-template #modal>
+  <div class="modal-header">
+    <h4 i18n class="modal-title">Ban {{ userToBan.username }}</h4>
+    <span class="close" aria-hidden="true" (click)="hideBanUserModal()"></span>
+  </div>
+
+  <div class="modal-body">
+    <form novalidate [formGroup]="form" (ngSubmit)="banUser()">
+      <div class="form-group">
+        <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
+        </textarea>
+        <div *ngIf="formErrors.reason" class="form-error">
+          {{ formErrors.reason }}
+        </div>
+      </div>
+
+      <div i18n>
+        A banned user will no longer be able to login.
+      </div>
+
+      <div class="form-group inputs">
+        <span i18n class="action-button action-button-cancel" (click)="hideBanUserModal()">Cancel</span>
+
+        <input
+          type="submit" i18n-value value="Ban this user" class="action-button-submit"
+          [disabled]="!form.valid"
+        >
+      </div>
+    </form>
+  </div>
+
+</ng-template>
\ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.scss b/client/src/app/shared/moderation/user-ban-modal.component.scss
new file mode 100644 (file)
index 0000000..84562f1
--- /dev/null
@@ -0,0 +1,6 @@
+@import 'variables';
+@import 'mixins';
+
+textarea {
+  @include peertube-textarea(100%, 60px);
+}
diff --git a/client/src/app/shared/moderation/user-ban-modal.component.ts b/client/src/app/shared/moderation/user-ban-modal.component.ts
new file mode 100644 (file)
index 0000000..d49783c
--- /dev/null
@@ -0,0 +1,68 @@
+import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
+import { NotificationsService } from 'angular2-notifications'
+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 { FormReactive, UserValidatorsService } from '@app/shared/forms'
+import { User, UserService } from '@app/shared/users'
+
+@Component({
+  selector: 'my-user-ban-modal',
+  templateUrl: './user-ban-modal.component.html',
+  styleUrls: [ './user-ban-modal.component.scss' ]
+})
+export class UserBanModalComponent extends FormReactive implements OnInit {
+  @ViewChild('modal') modal: NgbModal
+  @Output() userBanned = new EventEmitter<User>()
+
+  private userToBan: User
+  private openedModal: NgbModalRef
+
+  constructor (
+    protected formValidatorService: FormValidatorService,
+    private modalService: NgbModal,
+    private notificationsService: NotificationsService,
+    private userService: UserService,
+    private userValidatorsService: UserValidatorsService,
+    private i18n: I18n
+  ) {
+    super()
+  }
+
+  ngOnInit () {
+    this.buildForm({
+      reason: this.userValidatorsService.USER_BAN_REASON
+    })
+  }
+
+  openModal (user: User) {
+    this.userToBan = user
+    this.openedModal = this.modalService.open(this.modal)
+  }
+
+  hideBanUserModal () {
+    this.userToBan = undefined
+    this.openedModal.close()
+  }
+
+  async banUser () {
+    const reason = this.form.value['reason'] || undefined
+
+    this.userService.banUser(this.userToBan, reason)
+      .subscribe(
+        () => {
+          this.notificationsService.success(
+            this.i18n('Success'),
+            this.i18n('User {{username}} banned.', { username: this.userToBan.username })
+          )
+
+          this.userBanned.emit(this.userToBan)
+          this.hideBanUserModal()
+        },
+
+          err => this.notificationsService.error(this.i18n('Error'), err.message)
+      )
+  }
+
+}
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.html b/client/src/app/shared/moderation/user-moderation-dropdown.component.html
new file mode 100644 (file)
index 0000000..ed8a4dc
--- /dev/null
@@ -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>
\ No newline at end of file
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.scss b/client/src/app/shared/moderation/user-moderation-dropdown.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/src/app/shared/moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/moderation/user-moderation-dropdown.component.ts
new file mode 100644 (file)
index 0000000..d924234
--- /dev/null
@@ -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 ]
+  }
+}
index 076f1d2750a594cc3832b9bceb2d7b5ca3633bb3..9647a7966288f86d6f4147217055e45d2bf2b1d1 100644 (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,
index bd5cd45d4730a5a744a4c5085d85f0d6134a31b9..5ab290a59dea9f3dcf4083a25b59a45c2a614264 100644 (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
+    })
+  }
 }