Add user update for admins
authorChocobozzz <florian.bigard@gmail.com>
Tue, 5 Sep 2017 19:29:39 +0000 (21:29 +0200)
committerChocobozzz <florian.bigard@gmail.com>
Tue, 5 Sep 2017 19:29:39 +0000 (21:29 +0200)
23 files changed:
client/src/app/+admin/admin.module.ts
client/src/app/+admin/users/index.ts
client/src/app/+admin/users/shared/user.service.ts
client/src/app/+admin/users/user-add/index.ts [deleted file]
client/src/app/+admin/users/user-add/user-add.component.html [deleted file]
client/src/app/+admin/users/user-add/user-add.component.ts [deleted file]
client/src/app/+admin/users/user-edit/index.ts [new file with mode: 0644]
client/src/app/+admin/users/user-edit/user-add.component.ts [new file with mode: 0644]
client/src/app/+admin/users/user-edit/user-edit.component.html [new file with mode: 0644]
client/src/app/+admin/users/user-edit/user-edit.ts [new file with mode: 0644]
client/src/app/+admin/users/user-edit/user-update.component.ts [new file with mode: 0644]
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/+admin/users/users.routes.ts
client/src/app/account/account-change-password/account-change-password.component.ts
client/src/app/account/account-details/account-details.component.ts
client/src/app/shared/users/user.service.ts
client/src/app/shared/utils.ts
server/controllers/api/users.ts
server/middlewares/validators/users.ts
shared/models/users/index.ts
shared/models/users/user-update-me.model.ts [new file with mode: 0644]
shared/models/users/user-update.model.ts

index e4ba8e5b74c5ec006c07ba38fc9ff20736cc253d..853085a8309f447635935e8d73beef8b5211d4cd 100644 (file)
@@ -4,7 +4,7 @@ import { AdminComponent } from './admin.component'
 import { AdminRoutingModule } from './admin-routing.module'
 import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends'
 import { RequestSchedulersComponent, RequestSchedulersStatsComponent, RequestSchedulersService } from './request-schedulers'
-import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users'
+import { UsersComponent, UserAddComponent, UserUpdateComponent, UserListComponent, UserService } from './users'
 import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
 import { SharedModule } from '../shared'
 
@@ -26,6 +26,7 @@ import { SharedModule } from '../shared'
 
     UsersComponent,
     UserAddComponent,
+    UserUpdateComponent,
     UserListComponent,
 
     VideoAbusesComponent,
index cef2c282cb59e0589896a66accd19e85561aaae1..efcd0d9cb3b000f7a6340f3d5ba5c20af86e09ac 100644 (file)
@@ -1,5 +1,5 @@
 export * from './shared'
-export * from './user-add'
+export * from './user-edit'
 export * from './user-list'
 export * from './users.component'
 export * from './users.routes'
index ffd7ba7da299e2f30b6673cd965582d187c798ac..999013bccdbf83aed68f8da12666abac996fb90b 100644 (file)
@@ -5,7 +5,7 @@ import 'rxjs/add/operator/map'
 import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
 
 import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'
-import { UserCreate } from '../../../../../../shared'
+import { UserCreate, UserUpdate } from '../../../../../../shared'
 
 @Injectable()
 export class UserService {
@@ -23,6 +23,18 @@ export class UserService {
                         .catch(this.restExtractor.handleError)
   }
 
+  updateUser (userId: number, userUpdate: UserUpdate) {
+    return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
+               .map(this.restExtractor.extractDataBool)
+               .catch(this.restExtractor.handleError)
+  }
+
+  getUser (userId: number) {
+    return this.authHttp.get(UserService.BASE_USERS_URL + userId)
+                        .map(this.restExtractor.extractDataGet)
+                        .catch(this.restExtractor.handleError)
+  }
+
   getDataSource () {
     return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this))
   }
diff --git a/client/src/app/+admin/users/user-add/index.ts b/client/src/app/+admin/users/user-add/index.ts
deleted file mode 100644 (file)
index 3a46541..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './user-add.component'
diff --git a/client/src/app/+admin/users/user-add/user-add.component.html b/client/src/app/+admin/users/user-add/user-add.component.html
deleted file mode 100644 (file)
index f84d72c..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<div class="row">
-  <div class="content-padding">
-
-    <h3>Add user</h3>
-
-    <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-
-    <form role="form" (ngSubmit)="addUser()" [formGroup]="form">
-      <div class="form-group">
-        <label for="username">Username</label>
-        <input
-          type="text" class="form-control" id="username" placeholder="john"
-          formControlName="username"
-        >
-        <div *ngIf="formErrors.username" class="alert alert-danger">
-          {{ formErrors.username }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label for="email">Email</label>
-        <input
-          type="text" class="form-control" id="email" placeholder="mail@example.com"
-          formControlName="email"
-        >
-        <div *ngIf="formErrors.email" class="alert alert-danger">
-          {{ formErrors.email }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label for="password">Password</label>
-        <input
-          type="password" class="form-control" id="password"
-          formControlName="password"
-        >
-        <div *ngIf="formErrors.password" class="alert alert-danger">
-          {{ formErrors.password }}
-        </div>
-      </div>
-
-      <div class="form-group">
-        <label for="videoQuota">Video quota</label>
-        <select class="form-control" id="videoQuota" formControlName="videoQuota">
-          <option value="-1">Unlimited</option>
-          <option value="100000000">100MB</option>
-          <option value="500000000">500MB</option>
-          <option value="1000000000">1GB</option>
-          <option value="5000000000">5GB</option>
-          <option value="20000000000">20GB</option>
-          <option value="50000000000">50GB</option>
-        </select>
-      </div>
-
-      <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid">
-    </form>
-  </div>
-</div>
diff --git a/client/src/app/+admin/users/user-add/user-add.component.ts b/client/src/app/+admin/users/user-add/user-add.component.ts
deleted file mode 100644 (file)
index 91377a9..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-import { Component, OnInit } from '@angular/core'
-import { FormBuilder, FormGroup } from '@angular/forms'
-import { Router } from '@angular/router'
-
-import { NotificationsService } from 'angular2-notifications'
-
-import { UserService } from '../shared'
-import {
-  FormReactive,
-  USER_USERNAME,
-  USER_EMAIL,
-  USER_PASSWORD,
-  USER_VIDEO_QUOTA
-} from '../../../shared'
-import { UserCreate } from '../../../../../../shared'
-
-@Component({
-  selector: 'my-user-add',
-  templateUrl: './user-add.component.html'
-})
-export class UserAddComponent extends FormReactive implements OnInit {
-  error: string = null
-
-  form: FormGroup
-  formErrors = {
-    'username': '',
-    'email': '',
-    'password': '',
-    'videoQuota': ''
-  }
-  validationMessages = {
-    'username': USER_USERNAME.MESSAGES,
-    'email': USER_EMAIL.MESSAGES,
-    'password': USER_PASSWORD.MESSAGES,
-    'videoQuota': USER_VIDEO_QUOTA.MESSAGES
-  }
-
-  constructor (
-    private formBuilder: FormBuilder,
-    private router: Router,
-    private notificationsService: NotificationsService,
-    private userService: UserService
-  ) {
-    super()
-  }
-
-  buildForm () {
-    this.form = this.formBuilder.group({
-      username: [ '', USER_USERNAME.VALIDATORS ],
-      email:    [ '', USER_EMAIL.VALIDATORS ],
-      password: [ '', USER_PASSWORD.VALIDATORS ],
-      videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
-    })
-
-    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
-  }
-
-  ngOnInit () {
-    this.buildForm()
-  }
-
-  addUser () {
-    this.error = null
-
-    const userCreate: UserCreate = this.form.value
-
-    // A select in HTML is always mapped as a string, we convert it to number
-    userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
-
-    this.userService.addUser(userCreate).subscribe(
-      () => {
-        this.notificationsService.success('Success', `User ${userCreate.username} created.`)
-        this.router.navigate([ '/admin/users/list' ])
-      },
-
-      err => this.error = err.text
-    )
-  }
-}
diff --git a/client/src/app/+admin/users/user-edit/index.ts b/client/src/app/+admin/users/user-edit/index.ts
new file mode 100644 (file)
index 0000000..edec02f
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './user-add.component'
+export * from './user-update.component'
diff --git a/client/src/app/+admin/users/user-edit/user-add.component.ts b/client/src/app/+admin/users/user-edit/user-add.component.ts
new file mode 100644 (file)
index 0000000..40f649c
--- /dev/null
@@ -0,0 +1,87 @@
+import { Component, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { Router } from '@angular/router'
+
+import { NotificationsService } from 'angular2-notifications'
+
+import { UserService } from '../shared'
+import {
+  USER_USERNAME,
+  USER_EMAIL,
+  USER_PASSWORD,
+  USER_VIDEO_QUOTA
+} from '../../../shared'
+import { UserCreate } from '../../../../../../shared'
+import { UserEdit } from './user-edit'
+
+@Component({
+  selector: 'my-user-add',
+  templateUrl: './user-edit.component.html'
+})
+export class UserAddComponent extends UserEdit implements OnInit {
+  error: string
+
+  form: FormGroup
+  formErrors = {
+    'username': '',
+    'email': '',
+    'password': '',
+    'videoQuota': ''
+  }
+  validationMessages = {
+    'username': USER_USERNAME.MESSAGES,
+    'email': USER_EMAIL.MESSAGES,
+    'password': USER_PASSWORD.MESSAGES,
+    'videoQuota': USER_VIDEO_QUOTA.MESSAGES
+  }
+
+  constructor (
+    private formBuilder: FormBuilder,
+    private router: Router,
+    private notificationsService: NotificationsService,
+    private userService: UserService
+  ) {
+    super()
+  }
+
+  buildForm () {
+    this.form = this.formBuilder.group({
+      username: [ '', USER_USERNAME.VALIDATORS ],
+      email:    [ '', USER_EMAIL.VALIDATORS ],
+      password: [ '', USER_PASSWORD.VALIDATORS ],
+      videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
+    })
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+  }
+
+  ngOnInit () {
+    this.buildForm()
+  }
+
+  formValidated () {
+    this.error = undefined
+
+    const userCreate: UserCreate = this.form.value
+
+    // A select in HTML is always mapped as a string, we convert it to number
+    userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
+
+    this.userService.addUser(userCreate).subscribe(
+      () => {
+        this.notificationsService.success('Success', `User ${userCreate.username} created.`)
+        this.router.navigate([ '/admin/users/list' ])
+      },
+
+      err => this.error = err.text
+    )
+  }
+
+  isCreation () {
+    return true
+  }
+
+  getFormButtonTitle () {
+    return 'Add user'
+  }
+}
diff --git a/client/src/app/+admin/users/user-edit/user-edit.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html
new file mode 100644 (file)
index 0000000..0e23cb7
--- /dev/null
@@ -0,0 +1,55 @@
+<div class="row">
+  <div class="content-padding">
+
+    <h3 *ngIf="isCreation() === true">Add user</h3>
+    <h3 *ngIf="isCreation() === false">Edit user {{ username }}</h3>
+
+    <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+    <form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
+      <div class="form-group" *ngIf="isCreation()">
+        <label for="username">Username</label>
+        <input
+          type="text" class="form-control" id="username" placeholder="john"
+          formControlName="username"
+        >
+        <div *ngIf="formErrors.username" class="alert alert-danger">
+          {{ formErrors.username }}
+        </div>
+      </div>
+
+      <div class="form-group">
+        <label for="email">Email</label>
+        <input
+          type="text" class="form-control" id="email" placeholder="mail@example.com"
+          formControlName="email"
+        >
+        <div *ngIf="formErrors.email" class="alert alert-danger">
+          {{ formErrors.email }}
+        </div>
+      </div>
+
+      <div class="form-group" *ngIf="isCreation()">
+        <label for="password">Password</label>
+        <input
+          type="password" class="form-control" id="password"
+          formControlName="password"
+        >
+        <div *ngIf="formErrors.password" class="alert alert-danger">
+          {{ formErrors.password }}
+        </div>
+      </div>
+
+      <div class="form-group">
+        <label for="videoQuota">Video quota</label>
+        <select class="form-control" id="videoQuota" formControlName="videoQuota">
+          <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
+            {{ videoQuotaOption.label }}
+          </option>
+        </select>
+      </div>
+
+      <input type="submit" value="{{ getFormButtonTitle() }}" class="btn btn-default" [disabled]="!form.valid">
+    </form>
+  </div>
+</div>
diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts
new file mode 100644 (file)
index 0000000..61db8a9
--- /dev/null
@@ -0,0 +1,16 @@
+import { FormReactive } from '../../../shared'
+
+export abstract class UserEdit extends FormReactive {
+  videoQuotaOptions = [
+    { value: -1, label: 'Unlimited' },
+    { value: 100 * 1024 * 1024, label: '100MB' },
+    { value: 5 * 1024 * 1024, label: '500MB' },
+    { value: 1024 * 1024 * 1024, label: '1GB' },
+    { value: 5 * 1024 * 1024 * 1024, label: '5GB' },
+    { value: 20 * 1024 * 1024 * 1024, label: '20GB' },
+    { value: 50 * 1024 * 1024 * 1024, label: '50GB' }
+  ]
+
+  abstract isCreation (): boolean
+  abstract getFormButtonTitle (): string
+}
diff --git a/client/src/app/+admin/users/user-edit/user-update.component.ts b/client/src/app/+admin/users/user-edit/user-update.component.ts
new file mode 100644 (file)
index 0000000..dbac5f9
--- /dev/null
@@ -0,0 +1,106 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { FormBuilder, FormGroup } from '@angular/forms'
+import { ActivatedRoute, Router } from '@angular/router'
+import { Subscription } from 'rxjs/Subscription'
+
+import { NotificationsService } from 'angular2-notifications'
+
+import { UserService } from '../shared'
+import { USER_EMAIL, USER_VIDEO_QUOTA } from '../../../shared'
+import { UserUpdate } from '../../../../../../shared/models/users/user-update.model'
+import { User } from '../../../shared/users/user.model'
+import { UserEdit } from './user-edit'
+
+@Component({
+  selector: 'my-user-update',
+  templateUrl: './user-edit.component.html'
+})
+export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
+  error: string
+  userId: number
+  username: string
+
+  form: FormGroup
+  formErrors = {
+    'email': '',
+    'videoQuota': ''
+  }
+  validationMessages = {
+    'email': USER_EMAIL.MESSAGES,
+    'videoQuota': USER_VIDEO_QUOTA.MESSAGES
+  }
+
+  private paramsSub: Subscription
+
+  constructor (
+    private formBuilder: FormBuilder,
+    private route: ActivatedRoute,
+    private router: Router,
+    private notificationsService: NotificationsService,
+    private userService: UserService
+  ) {
+    super()
+  }
+
+  buildForm () {
+    this.form = this.formBuilder.group({
+      email:    [ '', USER_EMAIL.VALIDATORS ],
+      videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
+    })
+
+    this.form.valueChanges.subscribe(data => this.onValueChanged(data))
+  }
+
+  ngOnInit () {
+    this.buildForm()
+
+    this.paramsSub = this.route.params.subscribe(routeParams => {
+      const userId = routeParams['id']
+      this.userService.getUser(userId).subscribe(
+        user => this.onUserFetched(user),
+
+        err => this.error = err.text
+      )
+    })
+  }
+
+  ngOnDestroy () {
+    this.paramsSub.unsubscribe()
+  }
+
+  formValidated () {
+    this.error = undefined
+
+    const userUpdate: UserUpdate = this.form.value
+
+    // A select in HTML is always mapped as a string, we convert it to number
+    userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
+
+    this.userService.updateUser(this.userId, userUpdate).subscribe(
+      () => {
+        this.notificationsService.success('Success', `User ${this.username} updated.`)
+        this.router.navigate([ '/admin/users/list' ])
+      },
+
+      err => this.error = err.text
+    )
+  }
+
+  isCreation () {
+    return false
+  }
+
+  getFormButtonTitle () {
+    return 'Update user'
+  }
+
+  private onUserFetched (userJson: User) {
+    this.userId = userJson.id
+    this.username = userJson.username
+
+    this.form.patchValue({
+      email: userJson.email,
+      videoQuota: userJson.videoQuota
+    })
+  }
+}
index bb4c99a3f980444e040b7c81273a4510925dfa0c..eb5bc9d4a891ac32f21c600c7b37a5d0e52549c9 100644 (file)
@@ -5,7 +5,7 @@
 
     <ng2-smart-table
       [settings]="tableSettings" [source]="usersSource"
-      (delete)="removeUser($event)"
+      (delete)="removeUser($event)" (edit)="editUser($event)"
     ></ng2-smart-table>
 
     <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
index dbb85cedd7ac28bb1e652c5ecda4870ae36da307..7187a2008a3f97a3d0d99e47ae71281df11f1257 100644 (file)
@@ -5,6 +5,7 @@ import { NotificationsService } from 'angular2-notifications'
 import { ConfirmService } from '../../../core'
 import { RestDataSource, User, Utils } from '../../../shared'
 import { UserService } from '../shared'
+import { Router } from '@angular/router'
 
 @Component({
   selector: 'my-user-list',
@@ -22,15 +23,18 @@ export class UserListComponent {
     actions: {
       position: 'right',
       add: false,
-      edit: false,
+      edit: true,
       delete: true
     },
     delete: {
       deleteButtonContent: Utils.getRowDeleteButton()
     },
+    edit: {
+      editButtonContent: Utils.getRowEditButton()
+    },
     pager: {
       display: true,
-      perPage: 1
+      perPage: 10
     },
     columns: {
       id: {
@@ -58,6 +62,7 @@ export class UserListComponent {
   }
 
   constructor (
+    private router: Router,
     private notificationsService: NotificationsService,
     private confirmService: ConfirmService,
     private userService: UserService
@@ -65,8 +70,12 @@ export class UserListComponent {
     this.usersSource = this.userService.getDataSource()
   }
 
-  removeUser ({ data }) {
-    const user: User = data
+  editUser ({ data }: { data: User }) {
+    this.router.navigate([ '/admin', 'users', data.id, 'update' ])
+  }
+
+  removeUser ({ data }: { data: User }) {
+    const user = data
 
     if (user.username === 'root') {
       this.notificationsService.error('Error', 'You cannot delete root.')
index 723c5715d9baff8d855beb678bfcb1c2306c4d61..a6a9c4c193ede6fd7579da49a8c6d3b1dd8af3cd 100644 (file)
@@ -1,7 +1,7 @@
 import { Routes } from '@angular/router'
 
 import { UsersComponent } from './users.component'
-import { UserAddComponent } from './user-add'
+import { UserAddComponent, UserUpdateComponent } from './user-edit'
 import { UserListComponent } from './user-list'
 
 export const UsersRoutes: Routes = [
@@ -31,6 +31,15 @@ export const UsersRoutes: Routes = [
             title: 'Add a user'
           }
         }
+      },
+      {
+        path: ':id/update',
+        component: UserUpdateComponent,
+        data: {
+          meta: {
+            title: 'Update a user'
+          }
+        }
       }
     ]
   }
index ce786cfa3da78c53eddecd9ff0bc9a1200ab50e7..dba141296f2cca599a8b9a0edd93d920ad7e52fb 100644 (file)
@@ -26,7 +26,6 @@ export class AccountChangePasswordComponent extends FormReactive implements OnIn
 
   constructor (
     private formBuilder: FormBuilder,
-    private router: Router,
     private notificationsService: NotificationsService,
     private userService: UserService
   ) {
index 547f045c4c6fce9e59a92691cf0501da7e03acaf..8cbed50095279d7dee59d2d5afb987257e5a9b3a 100644 (file)
@@ -11,7 +11,7 @@ import {
   UserService,
   USER_PASSWORD
 } from '../../shared'
-import { UserUpdate } from '../../../../../shared'
+import { UserUpdateMe } from '../../../../../shared'
 
 @Component({
   selector: 'my-account-details',
@@ -30,7 +30,6 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
   constructor (
     private authService: AuthService,
     private formBuilder: FormBuilder,
-    private router: Router,
     private notificationsService: NotificationsService,
     private userService: UserService
   ) {
@@ -51,14 +50,14 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
 
   updateDetails () {
     const displayNSFW = this.form.value['displayNSFW']
-    const details: UserUpdate = {
+    const details: UserUpdateMe = {
       displayNSFW
     }
 
     this.error = null
-    this.userService.updateDetails(details).subscribe(
+    this.userService.updateMyDetails(details).subscribe(
       () => {
-        this.notificationsService.success('Success', 'Informations updated.')
+        this.notificationsService.success('Success', 'Information updated.')
 
         this.authService.refreshUserInformations()
       },
index b479ac0348d14155f90e74d7ae2566c0ef5f4fa0..35180be4d5a9359be1044591c6df77b5c9c05e14 100644 (file)
@@ -6,7 +6,7 @@ import 'rxjs/add/operator/map'
 import { AuthService } from '../../core'
 import { AuthHttp } from '../auth'
 import { RestExtractor } from '../rest'
-import { UserCreate, UserUpdate } from '../../../../../shared'
+import { UserCreate, UserUpdateMe } from '../../../../../shared'
 
 @Injectable()
 export class UserService {
@@ -22,13 +22,13 @@ export class UserService {
   checkTokenValidity () {
     const url = UserService.BASE_USERS_URL + 'me'
 
-    // AuthHttp will redirect us to the login page if the oken is not valid anymore
+    // AuthHttp will redirect us to the login page if the token is not valid anymore
     this.authHttp.get(url).subscribe()
   }
 
   changePassword (newPassword: string) {
-    const url = UserService.BASE_USERS_URL + this.authService.getUser().id
-    const body: UserUpdate = {
+    const url = UserService.BASE_USERS_URL + 'me'
+    const body: UserUpdateMe = {
       password: newPassword
     }
 
@@ -37,8 +37,8 @@ export class UserService {
                         .catch((res) => this.restExtractor.handleError(res))
   }
 
-  updateDetails (details: UserUpdate) {
-    const url = UserService.BASE_USERS_URL + this.authService.getUser().id
+  updateMyDetails (details: UserUpdateMe) {
+    const url = UserService.BASE_USERS_URL + 'me'
 
     return this.authHttp.put(url, details)
                         .map(this.restExtractor.extractDataBool)
index 832311f892c900059aa9c29cfbdee8c04f558af6..c3189a570500680ed132d83593adee7ae8c8a8a0 100644 (file)
@@ -9,4 +9,8 @@ export class Utils {
   static getRowDeleteButton () {
     return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'
   }
+
+  static getRowEditButton () {
+    return '<span class="glyphicon glyphicon-pencil glyphicon-black"></span>'
+  }
 }
index 1b5b7f9031db14cf9bdb98f783f6ffba7e652aea..6922661ae5ff578d5bf1dff686c996e50149e75c 100644 (file)
@@ -9,15 +9,22 @@ import {
   ensureUserRegistrationAllowed,
   usersAddValidator,
   usersUpdateValidator,
+  usersUpdateMeValidator,
   usersRemoveValidator,
   usersVideoRatingValidator,
+  usersGetValidator,
   paginationValidator,
   setPagination,
   usersSortValidator,
   setUsersSort,
   token
 } from '../../middlewares'
-import { UserVideoRate as FormattedUserVideoRate, UserCreate, UserUpdate } from '../../../shared'
+import {
+  UserVideoRate as FormattedUserVideoRate,
+  UserCreate,
+  UserUpdate,
+  UserUpdateMe
+} from '../../../shared'
 
 const usersRouter = express.Router()
 
@@ -40,6 +47,11 @@ usersRouter.get('/',
   listUsers
 )
 
+usersRouter.get('/:id',
+  usersGetValidator,
+  getUser
+)
+
 usersRouter.post('/',
   authenticate,
   ensureIsAdmin,
@@ -53,8 +65,15 @@ usersRouter.post('/register',
   createUser
 )
 
+usersRouter.put('/me',
+  authenticate,
+  usersUpdateMeValidator,
+  updateMe
+)
+
 usersRouter.put('/:id',
   authenticate,
+  ensureIsAdmin,
   usersUpdateValidator,
   updateUser
 )
@@ -105,6 +124,10 @@ function getUserInformation (req: express.Request, res: express.Response, next:
     .catch(err => next(err))
 }
 
+function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+  return res.json(res.locals.user.toFormattedJSON())
+}
+
 function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
   const videoId = +req.params.videoId
   const userId = +res.locals.oauth.token.User.id
@@ -139,14 +162,15 @@ function removeUser (req: express.Request, res: express.Response, next: express.
     })
 }
 
-function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const body: UserUpdate = req.body
+function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const body: UserUpdateMe = req.body
 
+  // FIXME: user is not already a Sequelize instance?
   db.User.loadByUsername(res.locals.oauth.token.user.username)
     .then(user => {
-      if (body.password) user.password = body.password
+      if (body.password !== undefined) user.password = body.password
+      if (body.email !== undefined) user.email = body.email
       if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
-      if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
 
       return user.save()
     })
@@ -154,6 +178,18 @@ function updateUser (req: express.Request, res: express.Response, next: express.
     .catch(err => next(err))
 }
 
+function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const body: UserUpdate = req.body
+  const user = res.locals.user
+
+  if (body.email !== undefined) user.email = body.email
+  if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
+
+  return user.save()
+    .then(() => res.sendStatus(204))
+    .catch(err => next(err))
+}
+
 function success (req: express.Request, res: express.Response, next: express.NextFunction) {
   res.end()
 }
index eeb0e3557571744acae76b70dd489ef91c8b809e..ebb3435355a50ef8632ca45d42c86112b7413abd 100644 (file)
@@ -53,16 +53,35 @@ function usersRemoveValidator (req: express.Request, res: express.Response, next
 
 function usersUpdateValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
   req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
+  req.checkBody('email', 'Should have a valid email attribute').optional().isEmail()
+  req.checkBody('videoQuota', 'Should have a valid user quota').optional().isUserVideoQuotaValid()
+
+  logger.debug('Checking usersUpdate parameters', { parameters: req.body })
+
+  checkErrors(req, res, () => {
+    checkUserExists(req.params.id, res, next)
+  })
+}
+
+function usersUpdateMeValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
   // Add old password verification
   req.checkBody('password', 'Should have a valid password').optional().isUserPasswordValid()
+  req.checkBody('email', 'Should have a valid email attribute').optional().isEmail()
   req.checkBody('displayNSFW', 'Should have a valid display Not Safe For Work attribute').optional().isUserDisplayNSFWValid()
-  req.checkBody('videoQuota', 'Should have a valid user quota').optional().isUserVideoQuotaValid()
 
   logger.debug('Checking usersUpdate parameters', { parameters: req.body })
 
   checkErrors(req, res, next)
 }
 
+function usersGetValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
+  req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
+
+  checkErrors(req, res, () => {
+    checkUserExists(req.params.id, res, next)
+  })
+}
+
 function usersVideoRatingValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
   req.checkParams('videoId', 'Should have a valid video id').notEmpty().isVideoIdOrUUIDValid()
 
@@ -106,6 +125,24 @@ export {
   usersAddValidator,
   usersRemoveValidator,
   usersUpdateValidator,
+  usersUpdateMeValidator,
   usersVideoRatingValidator,
-  ensureUserRegistrationAllowed
+  ensureUserRegistrationAllowed,
+  usersGetValidator
+}
+
+// ---------------------------------------------------------------------------
+
+function checkUserExists (id: number, res: express.Response, callback: () => void) {
+  db.User.loadById(id)
+    .then(user => {
+      if (!user) return res.status(404).send('User not found')
+
+      res.locals.user = user
+      callback()
+    })
+    .catch(err => {
+      logger.error('Error in user request validator.', err)
+      return res.sendStatus(500)
+    })
 }
index 414aaab08503fbd2ef59404301a9808eda1243d1..45dbc7b8fb3aae9332dc0f9ee4e809c460a7f947 100644 (file)
@@ -1,4 +1,5 @@
 export * from './user.model'
 export * from './user-create.model'
 export * from './user-update.model'
+export * from './user-update-me.model'
 export * from './user-role.type'
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts
new file mode 100644 (file)
index 0000000..0ee41a7
--- /dev/null
@@ -0,0 +1,5 @@
+export interface UserUpdateMe {
+  displayNSFW?: boolean
+  email?: string
+  password?: string
+}
index 895ec0681a66a106c19ea3d13fdca8ac2177a06a..e22166fdc9ab15004fa376d8bc67157d897cd497 100644 (file)
@@ -1,5 +1,4 @@
 export interface UserUpdate {
-  displayNSFW?: boolean
-  password?: string
+  email?: string
   videoQuota?: number
 }