aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/admin.module.ts3
-rw-r--r--client/src/app/+admin/users/index.ts2
-rw-r--r--client/src/app/+admin/users/shared/user.service.ts14
-rw-r--r--client/src/app/+admin/users/user-add/index.ts1
-rw-r--r--client/src/app/+admin/users/user-edit/index.ts2
-rw-r--r--client/src/app/+admin/users/user-edit/user-add.component.ts (renamed from client/src/app/+admin/users/user-add/user-add.component.ts)20
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.component.html (renamed from client/src/app/+admin/users/user-add/user-add.component.html)21
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.ts16
-rw-r--r--client/src/app/+admin/users/user-edit/user-update.component.ts106
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html2
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts17
-rw-r--r--client/src/app/+admin/users/users.routes.ts11
-rw-r--r--client/src/app/account/account-change-password/account-change-password.component.ts1
-rw-r--r--client/src/app/account/account-details/account-details.component.ts9
-rw-r--r--client/src/app/shared/users/user.service.ts12
-rw-r--r--client/src/app/shared/utils.ts4
-rw-r--r--server/controllers/api/users.ts46
-rw-r--r--server/middlewares/validators/users.ts41
-rw-r--r--shared/models/users/index.ts1
-rw-r--r--shared/models/users/user-update-me.model.ts5
-rw-r--r--shared/models/users/user-update.model.ts3
21 files changed, 288 insertions, 49 deletions
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index e4ba8e5b7..853085a83 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -4,7 +4,7 @@ import { AdminComponent } from './admin.component'
4import { AdminRoutingModule } from './admin-routing.module' 4import { AdminRoutingModule } from './admin-routing.module'
5import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends' 5import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends'
6import { RequestSchedulersComponent, RequestSchedulersStatsComponent, RequestSchedulersService } from './request-schedulers' 6import { RequestSchedulersComponent, RequestSchedulersStatsComponent, RequestSchedulersService } from './request-schedulers'
7import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users' 7import { UsersComponent, UserAddComponent, UserUpdateComponent, UserListComponent, UserService } from './users'
8import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses' 8import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses'
9import { SharedModule } from '../shared' 9import { SharedModule } from '../shared'
10 10
@@ -26,6 +26,7 @@ import { SharedModule } from '../shared'
26 26
27 UsersComponent, 27 UsersComponent,
28 UserAddComponent, 28 UserAddComponent,
29 UserUpdateComponent,
29 UserListComponent, 30 UserListComponent,
30 31
31 VideoAbusesComponent, 32 VideoAbusesComponent,
diff --git a/client/src/app/+admin/users/index.ts b/client/src/app/+admin/users/index.ts
index cef2c282c..efcd0d9cb 100644
--- a/client/src/app/+admin/users/index.ts
+++ b/client/src/app/+admin/users/index.ts
@@ -1,5 +1,5 @@
1export * from './shared' 1export * from './shared'
2export * from './user-add' 2export * from './user-edit'
3export * from './user-list' 3export * from './user-list'
4export * from './users.component' 4export * from './users.component'
5export * from './users.routes' 5export * from './users.routes'
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts
index ffd7ba7da..999013bcc 100644
--- a/client/src/app/+admin/users/shared/user.service.ts
+++ b/client/src/app/+admin/users/shared/user.service.ts
@@ -5,7 +5,7 @@ import 'rxjs/add/operator/map'
5import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' 5import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
6 6
7import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' 7import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared'
8import { UserCreate } from '../../../../../../shared' 8import { UserCreate, UserUpdate } from '../../../../../../shared'
9 9
10@Injectable() 10@Injectable()
11export class UserService { 11export class UserService {
@@ -23,6 +23,18 @@ export class UserService {
23 .catch(this.restExtractor.handleError) 23 .catch(this.restExtractor.handleError)
24 } 24 }
25 25
26 updateUser (userId: number, userUpdate: UserUpdate) {
27 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
28 .map(this.restExtractor.extractDataBool)
29 .catch(this.restExtractor.handleError)
30 }
31
32 getUser (userId: number) {
33 return this.authHttp.get(UserService.BASE_USERS_URL + userId)
34 .map(this.restExtractor.extractDataGet)
35 .catch(this.restExtractor.handleError)
36 }
37
26 getDataSource () { 38 getDataSource () {
27 return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this)) 39 return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this))
28 } 40 }
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
index 3a4654101..000000000
--- a/client/src/app/+admin/users/user-add/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './user-add.component'
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
index 000000000..edec02fbb
--- /dev/null
+++ b/client/src/app/+admin/users/user-edit/index.ts
@@ -0,0 +1,2 @@
1export * from './user-add.component'
2export * from './user-update.component'
diff --git a/client/src/app/+admin/users/user-add/user-add.component.ts b/client/src/app/+admin/users/user-edit/user-add.component.ts
index 91377a933..40f649cff 100644
--- a/client/src/app/+admin/users/user-add/user-add.component.ts
+++ b/client/src/app/+admin/users/user-edit/user-add.component.ts
@@ -6,20 +6,20 @@ import { NotificationsService } from 'angular2-notifications'
6 6
7import { UserService } from '../shared' 7import { UserService } from '../shared'
8import { 8import {
9 FormReactive,
10 USER_USERNAME, 9 USER_USERNAME,
11 USER_EMAIL, 10 USER_EMAIL,
12 USER_PASSWORD, 11 USER_PASSWORD,
13 USER_VIDEO_QUOTA 12 USER_VIDEO_QUOTA
14} from '../../../shared' 13} from '../../../shared'
15import { UserCreate } from '../../../../../../shared' 14import { UserCreate } from '../../../../../../shared'
15import { UserEdit } from './user-edit'
16 16
17@Component({ 17@Component({
18 selector: 'my-user-add', 18 selector: 'my-user-add',
19 templateUrl: './user-add.component.html' 19 templateUrl: './user-edit.component.html'
20}) 20})
21export class UserAddComponent extends FormReactive implements OnInit { 21export class UserAddComponent extends UserEdit implements OnInit {
22 error: string = null 22 error: string
23 23
24 form: FormGroup 24 form: FormGroup
25 formErrors = { 25 formErrors = {
@@ -59,8 +59,8 @@ export class UserAddComponent extends FormReactive implements OnInit {
59 this.buildForm() 59 this.buildForm()
60 } 60 }
61 61
62 addUser () { 62 formValidated () {
63 this.error = null 63 this.error = undefined
64 64
65 const userCreate: UserCreate = this.form.value 65 const userCreate: UserCreate = this.form.value
66 66
@@ -76,4 +76,12 @@ export class UserAddComponent extends FormReactive implements OnInit {
76 err => this.error = err.text 76 err => this.error = err.text
77 ) 77 )
78 } 78 }
79
80 isCreation () {
81 return true
82 }
83
84 getFormButtonTitle () {
85 return 'Add user'
86 }
79} 87}
diff --git a/client/src/app/+admin/users/user-add/user-add.component.html b/client/src/app/+admin/users/user-edit/user-edit.component.html
index f84d72c7c..0e23cb731 100644
--- a/client/src/app/+admin/users/user-add/user-add.component.html
+++ b/client/src/app/+admin/users/user-edit/user-edit.component.html
@@ -1,12 +1,13 @@
1<div class="row"> 1<div class="row">
2 <div class="content-padding"> 2 <div class="content-padding">
3 3
4 <h3>Add user</h3> 4 <h3 *ngIf="isCreation() === true">Add user</h3>
5 <h3 *ngIf="isCreation() === false">Edit user {{ username }}</h3>
5 6
6 <div *ngIf="error" class="alert alert-danger">{{ error }}</div> 7 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
7 8
8 <form role="form" (ngSubmit)="addUser()" [formGroup]="form"> 9 <form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
9 <div class="form-group"> 10 <div class="form-group" *ngIf="isCreation()">
10 <label for="username">Username</label> 11 <label for="username">Username</label>
11 <input 12 <input
12 type="text" class="form-control" id="username" placeholder="john" 13 type="text" class="form-control" id="username" placeholder="john"
@@ -28,7 +29,7 @@
28 </div> 29 </div>
29 </div> 30 </div>
30 31
31 <div class="form-group"> 32 <div class="form-group" *ngIf="isCreation()">
32 <label for="password">Password</label> 33 <label for="password">Password</label>
33 <input 34 <input
34 type="password" class="form-control" id="password" 35 type="password" class="form-control" id="password"
@@ -42,17 +43,13 @@
42 <div class="form-group"> 43 <div class="form-group">
43 <label for="videoQuota">Video quota</label> 44 <label for="videoQuota">Video quota</label>
44 <select class="form-control" id="videoQuota" formControlName="videoQuota"> 45 <select class="form-control" id="videoQuota" formControlName="videoQuota">
45 <option value="-1">Unlimited</option> 46 <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
46 <option value="100000000">100MB</option> 47 {{ videoQuotaOption.label }}
47 <option value="500000000">500MB</option> 48 </option>
48 <option value="1000000000">1GB</option>
49 <option value="5000000000">5GB</option>
50 <option value="20000000000">20GB</option>
51 <option value="50000000000">50GB</option>
52 </select> 49 </select>
53 </div> 50 </div>
54 51
55 <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid"> 52 <input type="submit" value="{{ getFormButtonTitle() }}" class="btn btn-default" [disabled]="!form.valid">
56 </form> 53 </form>
57 </div> 54 </div>
58</div> 55</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
index 000000000..61db8a906
--- /dev/null
+++ b/client/src/app/+admin/users/user-edit/user-edit.ts
@@ -0,0 +1,16 @@
1import { FormReactive } from '../../../shared'
2
3export abstract class UserEdit extends FormReactive {
4 videoQuotaOptions = [
5 { value: -1, label: 'Unlimited' },
6 { value: 100 * 1024 * 1024, label: '100MB' },
7 { value: 5 * 1024 * 1024, label: '500MB' },
8 { value: 1024 * 1024 * 1024, label: '1GB' },
9 { value: 5 * 1024 * 1024 * 1024, label: '5GB' },
10 { value: 20 * 1024 * 1024 * 1024, label: '20GB' },
11 { value: 50 * 1024 * 1024 * 1024, label: '50GB' }
12 ]
13
14 abstract isCreation (): boolean
15 abstract getFormButtonTitle (): string
16}
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
index 000000000..dbac5f974
--- /dev/null
+++ b/client/src/app/+admin/users/user-edit/user-update.component.ts
@@ -0,0 +1,106 @@
1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'
3import { ActivatedRoute, Router } from '@angular/router'
4import { Subscription } from 'rxjs/Subscription'
5
6import { NotificationsService } from 'angular2-notifications'
7
8import { UserService } from '../shared'
9import { USER_EMAIL, USER_VIDEO_QUOTA } from '../../../shared'
10import { UserUpdate } from '../../../../../../shared/models/users/user-update.model'
11import { User } from '../../../shared/users/user.model'
12import { UserEdit } from './user-edit'
13
14@Component({
15 selector: 'my-user-update',
16 templateUrl: './user-edit.component.html'
17})
18export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
19 error: string
20 userId: number
21 username: string
22
23 form: FormGroup
24 formErrors = {
25 'email': '',
26 'videoQuota': ''
27 }
28 validationMessages = {
29 'email': USER_EMAIL.MESSAGES,
30 'videoQuota': USER_VIDEO_QUOTA.MESSAGES
31 }
32
33 private paramsSub: Subscription
34
35 constructor (
36 private formBuilder: FormBuilder,
37 private route: ActivatedRoute,
38 private router: Router,
39 private notificationsService: NotificationsService,
40 private userService: UserService
41 ) {
42 super()
43 }
44
45 buildForm () {
46 this.form = this.formBuilder.group({
47 email: [ '', USER_EMAIL.VALIDATORS ],
48 videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ]
49 })
50
51 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
52 }
53
54 ngOnInit () {
55 this.buildForm()
56
57 this.paramsSub = this.route.params.subscribe(routeParams => {
58 const userId = routeParams['id']
59 this.userService.getUser(userId).subscribe(
60 user => this.onUserFetched(user),
61
62 err => this.error = err.text
63 )
64 })
65 }
66
67 ngOnDestroy () {
68 this.paramsSub.unsubscribe()
69 }
70
71 formValidated () {
72 this.error = undefined
73
74 const userUpdate: UserUpdate = this.form.value
75
76 // A select in HTML is always mapped as a string, we convert it to number
77 userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
78
79 this.userService.updateUser(this.userId, userUpdate).subscribe(
80 () => {
81 this.notificationsService.success('Success', `User ${this.username} updated.`)
82 this.router.navigate([ '/admin/users/list' ])
83 },
84
85 err => this.error = err.text
86 )
87 }
88
89 isCreation () {
90 return false
91 }
92
93 getFormButtonTitle () {
94 return 'Update user'
95 }
96
97 private onUserFetched (userJson: User) {
98 this.userId = userJson.id
99 this.username = userJson.username
100
101 this.form.patchValue({
102 email: userJson.email,
103 videoQuota: userJson.videoQuota
104 })
105 }
106}
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 bb4c99a3f..eb5bc9d4a 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
@@ -5,7 +5,7 @@
5 5
6 <ng2-smart-table 6 <ng2-smart-table
7 [settings]="tableSettings" [source]="usersSource" 7 [settings]="tableSettings" [source]="usersSource"
8 (delete)="removeUser($event)" 8 (delete)="removeUser($event)" (edit)="editUser($event)"
9 ></ng2-smart-table> 9 ></ng2-smart-table>
10 10
11 <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']"> 11 <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index dbb85cedd..7187a2008 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -5,6 +5,7 @@ import { NotificationsService } from 'angular2-notifications'
5import { ConfirmService } from '../../../core' 5import { ConfirmService } from '../../../core'
6import { RestDataSource, User, Utils } from '../../../shared' 6import { RestDataSource, User, Utils } from '../../../shared'
7import { UserService } from '../shared' 7import { UserService } from '../shared'
8import { Router } from '@angular/router'
8 9
9@Component({ 10@Component({
10 selector: 'my-user-list', 11 selector: 'my-user-list',
@@ -22,15 +23,18 @@ export class UserListComponent {
22 actions: { 23 actions: {
23 position: 'right', 24 position: 'right',
24 add: false, 25 add: false,
25 edit: false, 26 edit: true,
26 delete: true 27 delete: true
27 }, 28 },
28 delete: { 29 delete: {
29 deleteButtonContent: Utils.getRowDeleteButton() 30 deleteButtonContent: Utils.getRowDeleteButton()
30 }, 31 },
32 edit: {
33 editButtonContent: Utils.getRowEditButton()
34 },
31 pager: { 35 pager: {
32 display: true, 36 display: true,
33 perPage: 1 37 perPage: 10
34 }, 38 },
35 columns: { 39 columns: {
36 id: { 40 id: {
@@ -58,6 +62,7 @@ export class UserListComponent {
58 } 62 }
59 63
60 constructor ( 64 constructor (
65 private router: Router,
61 private notificationsService: NotificationsService, 66 private notificationsService: NotificationsService,
62 private confirmService: ConfirmService, 67 private confirmService: ConfirmService,
63 private userService: UserService 68 private userService: UserService
@@ -65,8 +70,12 @@ export class UserListComponent {
65 this.usersSource = this.userService.getDataSource() 70 this.usersSource = this.userService.getDataSource()
66 } 71 }
67 72
68 removeUser ({ data }) { 73 editUser ({ data }: { data: User }) {
69 const user: User = data 74 this.router.navigate([ '/admin', 'users', data.id, 'update' ])
75 }
76
77 removeUser ({ data }: { data: User }) {
78 const user = data
70 79
71 if (user.username === 'root') { 80 if (user.username === 'root') {
72 this.notificationsService.error('Error', 'You cannot delete root.') 81 this.notificationsService.error('Error', 'You cannot delete root.')
diff --git a/client/src/app/+admin/users/users.routes.ts b/client/src/app/+admin/users/users.routes.ts
index 723c5715d..a6a9c4c19 100644
--- a/client/src/app/+admin/users/users.routes.ts
+++ b/client/src/app/+admin/users/users.routes.ts
@@ -1,7 +1,7 @@
1import { Routes } from '@angular/router' 1import { Routes } from '@angular/router'
2 2
3import { UsersComponent } from './users.component' 3import { UsersComponent } from './users.component'
4import { UserAddComponent } from './user-add' 4import { UserAddComponent, UserUpdateComponent } from './user-edit'
5import { UserListComponent } from './user-list' 5import { UserListComponent } from './user-list'
6 6
7export const UsersRoutes: Routes = [ 7export const UsersRoutes: Routes = [
@@ -31,6 +31,15 @@ export const UsersRoutes: Routes = [
31 title: 'Add a user' 31 title: 'Add a user'
32 } 32 }
33 } 33 }
34 },
35 {
36 path: ':id/update',
37 component: UserUpdateComponent,
38 data: {
39 meta: {
40 title: 'Update a user'
41 }
42 }
34 } 43 }
35 ] 44 ]
36 } 45 }
diff --git a/client/src/app/account/account-change-password/account-change-password.component.ts b/client/src/app/account/account-change-password/account-change-password.component.ts
index ce786cfa3..dba141296 100644
--- a/client/src/app/account/account-change-password/account-change-password.component.ts
+++ b/client/src/app/account/account-change-password/account-change-password.component.ts
@@ -26,7 +26,6 @@ export class AccountChangePasswordComponent extends FormReactive implements OnIn
26 26
27 constructor ( 27 constructor (
28 private formBuilder: FormBuilder, 28 private formBuilder: FormBuilder,
29 private router: Router,
30 private notificationsService: NotificationsService, 29 private notificationsService: NotificationsService,
31 private userService: UserService 30 private userService: UserService
32 ) { 31 ) {
diff --git a/client/src/app/account/account-details/account-details.component.ts b/client/src/app/account/account-details/account-details.component.ts
index 547f045c4..8cbed5009 100644
--- a/client/src/app/account/account-details/account-details.component.ts
+++ b/client/src/app/account/account-details/account-details.component.ts
@@ -11,7 +11,7 @@ import {
11 UserService, 11 UserService,
12 USER_PASSWORD 12 USER_PASSWORD
13} from '../../shared' 13} from '../../shared'
14import { UserUpdate } from '../../../../../shared' 14import { UserUpdateMe } from '../../../../../shared'
15 15
16@Component({ 16@Component({
17 selector: 'my-account-details', 17 selector: 'my-account-details',
@@ -30,7 +30,6 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
30 constructor ( 30 constructor (
31 private authService: AuthService, 31 private authService: AuthService,
32 private formBuilder: FormBuilder, 32 private formBuilder: FormBuilder,
33 private router: Router,
34 private notificationsService: NotificationsService, 33 private notificationsService: NotificationsService,
35 private userService: UserService 34 private userService: UserService
36 ) { 35 ) {
@@ -51,14 +50,14 @@ export class AccountDetailsComponent extends FormReactive implements OnInit {
51 50
52 updateDetails () { 51 updateDetails () {
53 const displayNSFW = this.form.value['displayNSFW'] 52 const displayNSFW = this.form.value['displayNSFW']
54 const details: UserUpdate = { 53 const details: UserUpdateMe = {
55 displayNSFW 54 displayNSFW
56 } 55 }
57 56
58 this.error = null 57 this.error = null
59 this.userService.updateDetails(details).subscribe( 58 this.userService.updateMyDetails(details).subscribe(
60 () => { 59 () => {
61 this.notificationsService.success('Success', 'Informations updated.') 60 this.notificationsService.success('Success', 'Information updated.')
62 61
63 this.authService.refreshUserInformations() 62 this.authService.refreshUserInformations()
64 }, 63 },
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index b479ac034..35180be4d 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -6,7 +6,7 @@ import 'rxjs/add/operator/map'
6import { AuthService } from '../../core' 6import { AuthService } from '../../core'
7import { AuthHttp } from '../auth' 7import { AuthHttp } from '../auth'
8import { RestExtractor } from '../rest' 8import { RestExtractor } from '../rest'
9import { UserCreate, UserUpdate } from '../../../../../shared' 9import { UserCreate, UserUpdateMe } from '../../../../../shared'
10 10
11@Injectable() 11@Injectable()
12export class UserService { 12export class UserService {
@@ -22,13 +22,13 @@ export class UserService {
22 checkTokenValidity () { 22 checkTokenValidity () {
23 const url = UserService.BASE_USERS_URL + 'me' 23 const url = UserService.BASE_USERS_URL + 'me'
24 24
25 // AuthHttp will redirect us to the login page if the oken is not valid anymore 25 // AuthHttp will redirect us to the login page if the token is not valid anymore
26 this.authHttp.get(url).subscribe() 26 this.authHttp.get(url).subscribe()
27 } 27 }
28 28
29 changePassword (newPassword: string) { 29 changePassword (newPassword: string) {
30 const url = UserService.BASE_USERS_URL + this.authService.getUser().id 30 const url = UserService.BASE_USERS_URL + 'me'
31 const body: UserUpdate = { 31 const body: UserUpdateMe = {
32 password: newPassword 32 password: newPassword
33 } 33 }
34 34
@@ -37,8 +37,8 @@ export class UserService {
37 .catch((res) => this.restExtractor.handleError(res)) 37 .catch((res) => this.restExtractor.handleError(res))
38 } 38 }
39 39
40 updateDetails (details: UserUpdate) { 40 updateMyDetails (details: UserUpdateMe) {
41 const url = UserService.BASE_USERS_URL + this.authService.getUser().id 41 const url = UserService.BASE_USERS_URL + 'me'
42 42
43 return this.authHttp.put(url, details) 43 return this.authHttp.put(url, details)
44 .map(this.restExtractor.extractDataBool) 44 .map(this.restExtractor.extractDataBool)
diff --git a/client/src/app/shared/utils.ts b/client/src/app/shared/utils.ts
index 832311f89..c3189a570 100644
--- a/client/src/app/shared/utils.ts
+++ b/client/src/app/shared/utils.ts
@@ -9,4 +9,8 @@ export class Utils {
9 static getRowDeleteButton () { 9 static getRowDeleteButton () {
10 return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>' 10 return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>'
11 } 11 }
12
13 static getRowEditButton () {
14 return '<span class="glyphicon glyphicon-pencil glyphicon-black"></span>'
15 }
12} 16}
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts
index 1b5b7f903..6922661ae 100644
--- a/server/controllers/api/users.ts
+++ b/server/controllers/api/users.ts
@@ -9,15 +9,22 @@ import {
9 ensureUserRegistrationAllowed, 9 ensureUserRegistrationAllowed,
10 usersAddValidator, 10 usersAddValidator,
11 usersUpdateValidator, 11 usersUpdateValidator,
12 usersUpdateMeValidator,
12 usersRemoveValidator, 13 usersRemoveValidator,
13 usersVideoRatingValidator, 14 usersVideoRatingValidator,
15 usersGetValidator,
14 paginationValidator, 16 paginationValidator,
15 setPagination, 17 setPagination,
16 usersSortValidator, 18 usersSortValidator,
17 setUsersSort, 19 setUsersSort,
18 token 20 token
19} from '../../middlewares' 21} from '../../middlewares'
20import { UserVideoRate as FormattedUserVideoRate, UserCreate, UserUpdate } from '../../../shared' 22import {
23 UserVideoRate as FormattedUserVideoRate,
24 UserCreate,
25 UserUpdate,
26 UserUpdateMe
27} from '../../../shared'
21 28
22const usersRouter = express.Router() 29const usersRouter = express.Router()
23 30
@@ -40,6 +47,11 @@ usersRouter.get('/',
40 listUsers 47 listUsers
41) 48)
42 49
50usersRouter.get('/:id',
51 usersGetValidator,
52 getUser
53)
54
43usersRouter.post('/', 55usersRouter.post('/',
44 authenticate, 56 authenticate,
45 ensureIsAdmin, 57 ensureIsAdmin,
@@ -53,8 +65,15 @@ usersRouter.post('/register',
53 createUser 65 createUser
54) 66)
55 67
68usersRouter.put('/me',
69 authenticate,
70 usersUpdateMeValidator,
71 updateMe
72)
73
56usersRouter.put('/:id', 74usersRouter.put('/:id',
57 authenticate, 75 authenticate,
76 ensureIsAdmin,
58 usersUpdateValidator, 77 usersUpdateValidator,
59 updateUser 78 updateUser
60) 79)
@@ -105,6 +124,10 @@ function getUserInformation (req: express.Request, res: express.Response, next:
105 .catch(err => next(err)) 124 .catch(err => next(err))
106} 125}
107 126
127function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
128 return res.json(res.locals.user.toFormattedJSON())
129}
130
108function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { 131function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) {
109 const videoId = +req.params.videoId 132 const videoId = +req.params.videoId
110 const userId = +res.locals.oauth.token.User.id 133 const userId = +res.locals.oauth.token.User.id
@@ -139,14 +162,15 @@ function removeUser (req: express.Request, res: express.Response, next: express.
139 }) 162 })
140} 163}
141 164
142function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 165function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) {
143 const body: UserUpdate = req.body 166 const body: UserUpdateMe = req.body
144 167
168 // FIXME: user is not already a Sequelize instance?
145 db.User.loadByUsername(res.locals.oauth.token.user.username) 169 db.User.loadByUsername(res.locals.oauth.token.user.username)
146 .then(user => { 170 .then(user => {
147 if (body.password) user.password = body.password 171 if (body.password !== undefined) user.password = body.password
172 if (body.email !== undefined) user.email = body.email
148 if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW 173 if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW
149 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
150 174
151 return user.save() 175 return user.save()
152 }) 176 })
@@ -154,6 +178,18 @@ function updateUser (req: express.Request, res: express.Response, next: express.
154 .catch(err => next(err)) 178 .catch(err => next(err))
155} 179}
156 180
181function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) {
182 const body: UserUpdate = req.body
183 const user = res.locals.user
184
185 if (body.email !== undefined) user.email = body.email
186 if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota
187
188 return user.save()
189 .then(() => res.sendStatus(204))
190 .catch(err => next(err))
191}
192
157function success (req: express.Request, res: express.Response, next: express.NextFunction) { 193function success (req: express.Request, res: express.Response, next: express.NextFunction) {
158 res.end() 194 res.end()
159} 195}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index eeb0e3557..ebb343535 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -53,16 +53,35 @@ function usersRemoveValidator (req: express.Request, res: express.Response, next
53 53
54function usersUpdateValidator (req: express.Request, res: express.Response, next: express.NextFunction) { 54function usersUpdateValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
55 req.checkParams('id', 'Should have a valid id').notEmpty().isInt() 55 req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
56 req.checkBody('email', 'Should have a valid email attribute').optional().isEmail()
57 req.checkBody('videoQuota', 'Should have a valid user quota').optional().isUserVideoQuotaValid()
58
59 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
60
61 checkErrors(req, res, () => {
62 checkUserExists(req.params.id, res, next)
63 })
64}
65
66function usersUpdateMeValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
56 // Add old password verification 67 // Add old password verification
57 req.checkBody('password', 'Should have a valid password').optional().isUserPasswordValid() 68 req.checkBody('password', 'Should have a valid password').optional().isUserPasswordValid()
69 req.checkBody('email', 'Should have a valid email attribute').optional().isEmail()
58 req.checkBody('displayNSFW', 'Should have a valid display Not Safe For Work attribute').optional().isUserDisplayNSFWValid() 70 req.checkBody('displayNSFW', 'Should have a valid display Not Safe For Work attribute').optional().isUserDisplayNSFWValid()
59 req.checkBody('videoQuota', 'Should have a valid user quota').optional().isUserVideoQuotaValid()
60 71
61 logger.debug('Checking usersUpdate parameters', { parameters: req.body }) 72 logger.debug('Checking usersUpdate parameters', { parameters: req.body })
62 73
63 checkErrors(req, res, next) 74 checkErrors(req, res, next)
64} 75}
65 76
77function usersGetValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
78 req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
79
80 checkErrors(req, res, () => {
81 checkUserExists(req.params.id, res, next)
82 })
83}
84
66function usersVideoRatingValidator (req: express.Request, res: express.Response, next: express.NextFunction) { 85function usersVideoRatingValidator (req: express.Request, res: express.Response, next: express.NextFunction) {
67 req.checkParams('videoId', 'Should have a valid video id').notEmpty().isVideoIdOrUUIDValid() 86 req.checkParams('videoId', 'Should have a valid video id').notEmpty().isVideoIdOrUUIDValid()
68 87
@@ -106,6 +125,24 @@ export {
106 usersAddValidator, 125 usersAddValidator,
107 usersRemoveValidator, 126 usersRemoveValidator,
108 usersUpdateValidator, 127 usersUpdateValidator,
128 usersUpdateMeValidator,
109 usersVideoRatingValidator, 129 usersVideoRatingValidator,
110 ensureUserRegistrationAllowed 130 ensureUserRegistrationAllowed,
131 usersGetValidator
132}
133
134// ---------------------------------------------------------------------------
135
136function checkUserExists (id: number, res: express.Response, callback: () => void) {
137 db.User.loadById(id)
138 .then(user => {
139 if (!user) return res.status(404).send('User not found')
140
141 res.locals.user = user
142 callback()
143 })
144 .catch(err => {
145 logger.error('Error in user request validator.', err)
146 return res.sendStatus(500)
147 })
111} 148}
diff --git a/shared/models/users/index.ts b/shared/models/users/index.ts
index 414aaab08..45dbc7b8f 100644
--- a/shared/models/users/index.ts
+++ b/shared/models/users/index.ts
@@ -1,4 +1,5 @@
1export * from './user.model' 1export * from './user.model'
2export * from './user-create.model' 2export * from './user-create.model'
3export * from './user-update.model' 3export * from './user-update.model'
4export * from './user-update-me.model'
4export * from './user-role.type' 5export * 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
index 000000000..0ee41a79b
--- /dev/null
+++ b/shared/models/users/user-update-me.model.ts
@@ -0,0 +1,5 @@
1export interface UserUpdateMe {
2 displayNSFW?: boolean
3 email?: string
4 password?: string
5}
diff --git a/shared/models/users/user-update.model.ts b/shared/models/users/user-update.model.ts
index 895ec0681..e22166fdc 100644
--- a/shared/models/users/user-update.model.ts
+++ b/shared/models/users/user-update.model.ts
@@ -1,5 +1,4 @@
1export interface UserUpdate { 1export interface UserUpdate {
2 displayNSFW?: boolean 2 email?: string
3 password?: string
4 videoQuota?: number 3 videoQuota?: number
5} 4}