diff options
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' | |||
4 | import { AdminRoutingModule } from './admin-routing.module' | 4 | import { AdminRoutingModule } from './admin-routing.module' |
5 | import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends' | 5 | import { FriendsComponent, FriendAddComponent, FriendListComponent, FriendService } from './friends' |
6 | import { RequestSchedulersComponent, RequestSchedulersStatsComponent, RequestSchedulersService } from './request-schedulers' | 6 | import { RequestSchedulersComponent, RequestSchedulersStatsComponent, RequestSchedulersService } from './request-schedulers' |
7 | import { UsersComponent, UserAddComponent, UserListComponent, UserService } from './users' | 7 | import { UsersComponent, UserAddComponent, UserUpdateComponent, UserListComponent, UserService } from './users' |
8 | import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses' | 8 | import { VideoAbusesComponent, VideoAbuseListComponent } from './video-abuses' |
9 | import { SharedModule } from '../shared' | 9 | import { 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 @@ | |||
1 | export * from './shared' | 1 | export * from './shared' |
2 | export * from './user-add' | 2 | export * from './user-edit' |
3 | export * from './user-list' | 3 | export * from './user-list' |
4 | export * from './users.component' | 4 | export * from './users.component' |
5 | export * from './users.routes' | 5 | export * 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' | |||
5 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' | 5 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' |
6 | 6 | ||
7 | import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' | 7 | import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' |
8 | import { UserCreate } from '../../../../../../shared' | 8 | import { UserCreate, UserUpdate } from '../../../../../../shared' |
9 | 9 | ||
10 | @Injectable() | 10 | @Injectable() |
11 | export class UserService { | 11 | export 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 @@ | |||
1 | export * 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 @@ | |||
1 | export * from './user-add.component' | ||
2 | export * 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 | ||
7 | import { UserService } from '../shared' | 7 | import { UserService } from '../shared' |
8 | import { | 8 | import { |
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' |
15 | import { UserCreate } from '../../../../../../shared' | 14 | import { UserCreate } from '../../../../../../shared' |
15 | import { 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 | }) |
21 | export class UserAddComponent extends FormReactive implements OnInit { | 21 | export 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 @@ | |||
1 | import { FormReactive } from '../../../shared' | ||
2 | |||
3 | export 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 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { FormBuilder, FormGroup } from '@angular/forms' | ||
3 | import { ActivatedRoute, Router } from '@angular/router' | ||
4 | import { Subscription } from 'rxjs/Subscription' | ||
5 | |||
6 | import { NotificationsService } from 'angular2-notifications' | ||
7 | |||
8 | import { UserService } from '../shared' | ||
9 | import { USER_EMAIL, USER_VIDEO_QUOTA } from '../../../shared' | ||
10 | import { UserUpdate } from '../../../../../../shared/models/users/user-update.model' | ||
11 | import { User } from '../../../shared/users/user.model' | ||
12 | import { UserEdit } from './user-edit' | ||
13 | |||
14 | @Component({ | ||
15 | selector: 'my-user-update', | ||
16 | templateUrl: './user-edit.component.html' | ||
17 | }) | ||
18 | export 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' | |||
5 | import { ConfirmService } from '../../../core' | 5 | import { ConfirmService } from '../../../core' |
6 | import { RestDataSource, User, Utils } from '../../../shared' | 6 | import { RestDataSource, User, Utils } from '../../../shared' |
7 | import { UserService } from '../shared' | 7 | import { UserService } from '../shared' |
8 | import { 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 @@ | |||
1 | import { Routes } from '@angular/router' | 1 | import { Routes } from '@angular/router' |
2 | 2 | ||
3 | import { UsersComponent } from './users.component' | 3 | import { UsersComponent } from './users.component' |
4 | import { UserAddComponent } from './user-add' | 4 | import { UserAddComponent, UserUpdateComponent } from './user-edit' |
5 | import { UserListComponent } from './user-list' | 5 | import { UserListComponent } from './user-list' |
6 | 6 | ||
7 | export const UsersRoutes: Routes = [ | 7 | export 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' |
14 | import { UserUpdate } from '../../../../../shared' | 14 | import { 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' | |||
6 | import { AuthService } from '../../core' | 6 | import { AuthService } from '../../core' |
7 | import { AuthHttp } from '../auth' | 7 | import { AuthHttp } from '../auth' |
8 | import { RestExtractor } from '../rest' | 8 | import { RestExtractor } from '../rest' |
9 | import { UserCreate, UserUpdate } from '../../../../../shared' | 9 | import { UserCreate, UserUpdateMe } from '../../../../../shared' |
10 | 10 | ||
11 | @Injectable() | 11 | @Injectable() |
12 | export class UserService { | 12 | export 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' |
20 | import { UserVideoRate as FormattedUserVideoRate, UserCreate, UserUpdate } from '../../../shared' | 22 | import { |
23 | UserVideoRate as FormattedUserVideoRate, | ||
24 | UserCreate, | ||
25 | UserUpdate, | ||
26 | UserUpdateMe | ||
27 | } from '../../../shared' | ||
21 | 28 | ||
22 | const usersRouter = express.Router() | 29 | const usersRouter = express.Router() |
23 | 30 | ||
@@ -40,6 +47,11 @@ usersRouter.get('/', | |||
40 | listUsers | 47 | listUsers |
41 | ) | 48 | ) |
42 | 49 | ||
50 | usersRouter.get('/:id', | ||
51 | usersGetValidator, | ||
52 | getUser | ||
53 | ) | ||
54 | |||
43 | usersRouter.post('/', | 55 | usersRouter.post('/', |
44 | authenticate, | 56 | authenticate, |
45 | ensureIsAdmin, | 57 | ensureIsAdmin, |
@@ -53,8 +65,15 @@ usersRouter.post('/register', | |||
53 | createUser | 65 | createUser |
54 | ) | 66 | ) |
55 | 67 | ||
68 | usersRouter.put('/me', | ||
69 | authenticate, | ||
70 | usersUpdateMeValidator, | ||
71 | updateMe | ||
72 | ) | ||
73 | |||
56 | usersRouter.put('/:id', | 74 | usersRouter.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 | ||
127 | function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
128 | return res.json(res.locals.user.toFormattedJSON()) | ||
129 | } | ||
130 | |||
108 | function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { | 131 | function 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 | ||
142 | function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 165 | function 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 | ||
181 | function 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 | |||
157 | function success (req: express.Request, res: express.Response, next: express.NextFunction) { | 193 | function 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 | ||
54 | function usersUpdateValidator (req: express.Request, res: express.Response, next: express.NextFunction) { | 54 | function 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 | |||
66 | function 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 | ||
77 | function 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 | |||
66 | function usersVideoRatingValidator (req: express.Request, res: express.Response, next: express.NextFunction) { | 85 | function 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 | |||
136 | function 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 @@ | |||
1 | export * from './user.model' | 1 | export * from './user.model' |
2 | export * from './user-create.model' | 2 | export * from './user-create.model' |
3 | export * from './user-update.model' | 3 | export * from './user-update.model' |
4 | export * from './user-update-me.model' | ||
4 | export * from './user-role.type' | 5 | 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 index 000000000..0ee41a79b --- /dev/null +++ b/shared/models/users/user-update-me.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export 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 @@ | |||
1 | export interface UserUpdate { | 1 | export interface UserUpdate { |
2 | displayNSFW?: boolean | 2 | email?: string |
3 | password?: string | ||
4 | videoQuota?: number | 3 | videoQuota?: number |
5 | } | 4 | } |