diff options
41 files changed, 620 insertions, 696 deletions
diff --git a/client/package.json b/client/package.json index d69a7b10b..caec34e44 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -62,7 +62,6 @@ | |||
62 | "json-loader": "^0.5.4", | 62 | "json-loader": "^0.5.4", |
63 | "ng-router-loader": "^2.0.0", | 63 | "ng-router-loader": "^2.0.0", |
64 | "ng2-file-upload": "^1.1.4-2", | 64 | "ng2-file-upload": "^1.1.4-2", |
65 | "ng2-smart-table": "1.2.1", | ||
66 | "ngc-webpack": "3.2.2", | 65 | "ngc-webpack": "3.2.2", |
67 | "ngx-bootstrap": "1.9.1", | 66 | "ngx-bootstrap": "1.9.1", |
68 | "ngx-chips": "1.5.3", | 67 | "ngx-chips": "1.5.3", |
@@ -97,6 +96,7 @@ | |||
97 | "add-asset-html-webpack-plugin": "^2.0.1", | 96 | "add-asset-html-webpack-plugin": "^2.0.1", |
98 | "codelyzer": "^3.0.0-beta.4", | 97 | "codelyzer": "^3.0.0-beta.4", |
99 | "extract-text-webpack-plugin": "^3.0.0", | 98 | "extract-text-webpack-plugin": "^3.0.0", |
99 | "primeng": "^4.2.0", | ||
100 | "purify-css": "^1.2.5", | 100 | "purify-css": "^1.2.5", |
101 | "purifycss-webpack": "^0.7.0", | 101 | "purifycss-webpack": "^0.7.0", |
102 | "standard": "^10.0.0", | 102 | "standard": "^10.0.0", |
diff --git a/client/src/app/+admin/friends/friend-add/friend-add.component.ts b/client/src/app/+admin/friends/friend-add/friend-add.component.ts index 0449d26a9..02543d393 100644 --- a/client/src/app/+admin/friends/friend-add/friend-add.component.ts +++ b/client/src/app/+admin/friends/friend-add/friend-add.component.ts | |||
@@ -93,8 +93,9 @@ export class FriendAddComponent implements OnInit { | |||
93 | 93 | ||
94 | this.friendService.makeFriends(notEmptyHosts).subscribe( | 94 | this.friendService.makeFriends(notEmptyHosts).subscribe( |
95 | status => { | 95 | status => { |
96 | this.notificationsService.success('Sucess', 'Make friends request sent!') | 96 | this.notificationsService.success('Success', 'Make friends request sent!') |
97 | this.router.navigate([ '/admin/friends/list' ]) | 97 | // Wait requests between pods |
98 | setTimeout(() => this.router.navigate([ '/admin/friends/list' ]), 1000) | ||
98 | }, | 99 | }, |
99 | 100 | ||
100 | err => this.notificationsService.error('Error', err.text) | 101 | err => this.notificationsService.error('Error', err.text) |
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.html b/client/src/app/+admin/friends/friend-list/friend-list.component.html index 7b9fff304..7887bc5e3 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.html +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.html | |||
@@ -2,13 +2,24 @@ | |||
2 | <div class="content-padding"> | 2 | <div class="content-padding"> |
3 | <h3>Friends list</h3> | 3 | <h3>Friends list</h3> |
4 | 4 | ||
5 | <ng2-smart-table [settings]="tableSettings" [source]="friendsSource" (delete)="removeFriend($event)"></ng2-smart-table> | 5 | <p-dataTable [value]="friends"> |
6 | <p-column field="id" header="ID"></p-column> | ||
7 | <p-column field="host" header="Host"></p-column> | ||
8 | <p-column field="email" header="Email"></p-column> | ||
9 | <p-column field="score" header="Score"></p-column> | ||
10 | <p-column field="createdAt" header="Created date"></p-column> | ||
11 | <p-column header="Delete" styleClass="action-cell"> | ||
12 | <ng-template pTemplate="body" let-pod="rowData"> | ||
13 | <span (click)="removeFriend(pod)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this pod"></span> | ||
14 | </ng-template> | ||
15 | </p-column> | ||
16 | </p-dataTable> | ||
6 | 17 | ||
7 | <a *ngIf="hasFriends()" class="btn btn-danger pull-left" (click)="quitFriends()"> | 18 | <a *ngIf="hasFriends()" class="btn btn-danger pull-left" (click)="quitFriends()"> |
8 | Quit friends | 19 | Quit friends |
9 | </a> | 20 | </a> |
10 | 21 | ||
11 | <a *ngIf="!hasFriends()" class="btn btn-success pull-right" [routerLink]="['/admin/friends/add']"> | 22 | <a *ngIf="!hasFriends()" class="btn btn-success pull-right" [routerLink]="[ '/admin/friends/add' ]"> |
12 | Make friends | 23 | Make friends |
13 | </a> | 24 | </a> |
14 | </div> | 25 | </div> |
diff --git a/client/src/app/+admin/friends/friend-list/friend-list.component.ts b/client/src/app/+admin/friends/friend-list/friend-list.component.ts index 822a112cc..6a8bd492c 100644 --- a/client/src/app/+admin/friends/friend-list/friend-list.component.ts +++ b/client/src/app/+admin/friends/friend-list/friend-list.component.ts | |||
@@ -1,71 +1,31 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | 2 | ||
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { NotificationsService } from 'angular2-notifications' |
4 | import { ServerDataSource } from 'ng2-smart-table' | ||
5 | 4 | ||
6 | import { ConfirmService } from '../../../core' | 5 | import { ConfirmService } from '../../../core' |
7 | import { Utils } from '../../../shared' | ||
8 | import { FriendService } from '../shared' | 6 | import { FriendService } from '../shared' |
9 | import { Pod } from '../../../../../../shared' | 7 | import { Pod } from '../../../../../../shared' |
10 | 8 | ||
11 | @Component({ | 9 | @Component({ |
12 | selector: 'my-friend-list', | 10 | selector: 'my-friend-list', |
13 | templateUrl: './friend-list.component.html', | 11 | templateUrl: './friend-list.component.html', |
14 | styleUrls: [ './friend-list.component.scss' ] | 12 | styleUrls: ['./friend-list.component.scss'] |
15 | }) | 13 | }) |
16 | export class FriendListComponent { | 14 | export class FriendListComponent implements OnInit { |
17 | friendsSource = null | 15 | friends: Pod[] = [] |
18 | tableSettings = { | ||
19 | mode: 'external', | ||
20 | attr: { | ||
21 | class: 'table-hover' | ||
22 | }, | ||
23 | hideSubHeader: true, | ||
24 | actions: { | ||
25 | position: 'right', | ||
26 | add: false, | ||
27 | edit: false, | ||
28 | delete: true | ||
29 | }, | ||
30 | delete: { | ||
31 | deleteButtonContent: Utils.getRowDeleteButton() | ||
32 | }, | ||
33 | columns: { | ||
34 | id: { | ||
35 | title: 'ID', | ||
36 | sort: false, | ||
37 | sortDirection: 'asc' | ||
38 | }, | ||
39 | host: { | ||
40 | title: 'Host', | ||
41 | sort: false | ||
42 | }, | ||
43 | email: { | ||
44 | title: 'Email', | ||
45 | sort: false | ||
46 | }, | ||
47 | score: { | ||
48 | title: 'Score', | ||
49 | sort: false | ||
50 | }, | ||
51 | createdAt: { | ||
52 | title: 'Created Date', | ||
53 | sort: false, | ||
54 | valuePrepareFunction: Utils.dateToHuman | ||
55 | } | ||
56 | } | ||
57 | } | ||
58 | 16 | ||
59 | constructor ( | 17 | constructor ( |
60 | private notificationsService: NotificationsService, | 18 | private notificationsService: NotificationsService, |
61 | private confirmService: ConfirmService, | 19 | private confirmService: ConfirmService, |
62 | private friendService: FriendService | 20 | private friendService: FriendService |
63 | ) { | 21 | ) {} |
64 | this.friendsSource = this.friendService.getDataSource() | 22 | |
23 | ngOnInit () { | ||
24 | this.loadData() | ||
65 | } | 25 | } |
66 | 26 | ||
67 | hasFriends () { | 27 | hasFriends () { |
68 | return this.friendsSource.count() !== 0 | 28 | return this.friends.length !== 0 |
69 | } | 29 | } |
70 | 30 | ||
71 | quitFriends () { | 31 | quitFriends () { |
@@ -77,32 +37,42 @@ export class FriendListComponent { | |||
77 | this.friendService.quitFriends().subscribe( | 37 | this.friendService.quitFriends().subscribe( |
78 | status => { | 38 | status => { |
79 | this.notificationsService.success('Success', 'Friends left!') | 39 | this.notificationsService.success('Success', 'Friends left!') |
80 | this.friendsSource.refresh() | 40 | this.loadData() |
81 | }, | 41 | }, |
82 | 42 | ||
83 | err => this.notificationsService.error('Error', err.text) | 43 | err => this.notificationsService.error('Error', err) |
84 | ) | 44 | ) |
85 | } | 45 | } |
86 | ) | 46 | ) |
87 | } | 47 | } |
88 | 48 | ||
89 | removeFriend ({ data }) { | 49 | removeFriend (friend: Pod) { |
90 | const confirmMessage = 'Do you really want to remove this friend ? All its videos will be deleted.' | 50 | const confirmMessage = 'Do you really want to remove this friend ? All its videos will be deleted.' |
91 | const friend: Pod = data | ||
92 | 51 | ||
93 | this.confirmService.confirm(confirmMessage, 'Remove').subscribe( | 52 | this.confirmService.confirm(confirmMessage, 'Remove').subscribe( |
94 | res => { | 53 | res => { |
95 | if (res === false) return | 54 | if (res === false) return |
96 | 55 | ||
97 | this.friendService.removeFriend(friend).subscribe( | 56 | this.friendService.removeFriend(friend).subscribe( |
98 | status => { | 57 | status => { |
99 | this.notificationsService.success('Success', 'Friend removed') | 58 | this.notificationsService.success('Success', 'Friend removed') |
100 | this.friendsSource.refresh() | 59 | this.loadData() |
101 | }, | 60 | }, |
102 | 61 | ||
103 | err => this.notificationsService.error('Error', err.text) | 62 | err => this.notificationsService.error('Error', err) |
104 | ) | 63 | ) |
105 | } | 64 | } |
106 | ) | 65 | ) |
107 | } | 66 | } |
67 | |||
68 | private loadData () { | ||
69 | this.friendService.getFriends() | ||
70 | .subscribe( | ||
71 | resultList => { | ||
72 | this.friends = resultList.data | ||
73 | }, | ||
74 | |||
75 | err => this.notificationsService.error('Error', err) | ||
76 | ) | ||
77 | } | ||
108 | } | 78 | } |
diff --git a/client/src/app/+admin/friends/shared/friend.service.ts b/client/src/app/+admin/friends/shared/friend.service.ts index 9b3ff04b1..45607e28d 100644 --- a/client/src/app/+admin/friends/shared/friend.service.ts +++ b/client/src/app/+admin/friends/shared/friend.service.ts | |||
@@ -1,24 +1,24 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Observable } from 'rxjs/Observable' | 2 | import { HttpClient } from '@angular/common/http' |
3 | import 'rxjs/add/operator/catch' | 3 | import 'rxjs/add/operator/catch' |
4 | import 'rxjs/add/operator/map' | 4 | import 'rxjs/add/operator/map' |
5 | 5 | ||
6 | import { ServerDataSource } from 'ng2-smart-table' | 6 | import { RestExtractor, } from '../../../shared' |
7 | 7 | import { Pod, ResultList } from '../../../../../../shared' | |
8 | import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared' | ||
9 | import { Pod } from '../../../../../../shared' | ||
10 | 8 | ||
11 | @Injectable() | 9 | @Injectable() |
12 | export class FriendService { | 10 | export class FriendService { |
13 | private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/' | 11 | private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/' |
14 | 12 | ||
15 | constructor ( | 13 | constructor ( |
16 | private authHttp: AuthHttp, | 14 | private authHttp: HttpClient, |
17 | private restExtractor: RestExtractor | 15 | private restExtractor: RestExtractor |
18 | ) {} | 16 | ) {} |
19 | 17 | ||
20 | getDataSource () { | 18 | getFriends () { |
21 | return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL) | 19 | return this.authHttp.get<ResultList<Pod>>(FriendService.BASE_FRIEND_URL) |
20 | .map(res => this.restExtractor.convertResultListDateToHuman(res)) | ||
21 | .catch(res => this.restExtractor.handleError(res)) | ||
22 | } | 22 | } |
23 | 23 | ||
24 | makeFriends (notEmptyHosts: String[]) { | 24 | makeFriends (notEmptyHosts: String[]) { |
@@ -28,18 +28,18 @@ export class FriendService { | |||
28 | 28 | ||
29 | return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'make-friends', body) | 29 | return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'make-friends', body) |
30 | .map(this.restExtractor.extractDataBool) | 30 | .map(this.restExtractor.extractDataBool) |
31 | .catch((res) => this.restExtractor.handleError(res)) | 31 | .catch(res => this.restExtractor.handleError(res)) |
32 | } | 32 | } |
33 | 33 | ||
34 | quitFriends () { | 34 | quitFriends () { |
35 | return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quit-friends') | 35 | return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quit-friends') |
36 | .map(res => res.status) | 36 | .map(this.restExtractor.extractDataBool) |
37 | .catch((res) => this.restExtractor.handleError(res)) | 37 | .catch(res => this.restExtractor.handleError(res)) |
38 | } | 38 | } |
39 | 39 | ||
40 | removeFriend (friend: Pod) { | 40 | removeFriend (friend: Pod) { |
41 | return this.authHttp.delete(FriendService.BASE_FRIEND_URL + friend.id) | 41 | return this.authHttp.delete(FriendService.BASE_FRIEND_URL + friend.id) |
42 | .map(this.restExtractor.extractDataBool) | 42 | .map(this.restExtractor.extractDataBool) |
43 | .catch((res) => this.restExtractor.handleError(res)) | 43 | .catch(res => this.restExtractor.handleError(res)) |
44 | } | 44 | } |
45 | } | 45 | } |
diff --git a/client/src/app/+admin/request-schedulers/shared/request-schedulers.service.ts b/client/src/app/+admin/request-schedulers/shared/request-schedulers.service.ts index e9b166f78..44d9cbc3e 100644 --- a/client/src/app/+admin/request-schedulers/shared/request-schedulers.service.ts +++ b/client/src/app/+admin/request-schedulers/shared/request-schedulers.service.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { HttpClient } from '@angular/common/http' | ||
2 | import { Observable } from 'rxjs/Observable' | 3 | import { Observable } from 'rxjs/Observable' |
3 | import 'rxjs/add/operator/catch' | 4 | import 'rxjs/add/operator/catch' |
4 | import 'rxjs/add/operator/map' | 5 | import 'rxjs/add/operator/map' |
5 | 6 | ||
6 | import { RequestSchedulerStats } from '../../../../../../shared' | 7 | import { RequestSchedulerStats } from '../../../../../../shared' |
7 | import { AuthHttp, RestExtractor } from '../../../shared' | 8 | import { RestExtractor } from '../../../shared' |
8 | import { RequestSchedulerStatsAttributes } from './request-schedulers-stats-attributes.model' | 9 | import { RequestSchedulerStatsAttributes } from './request-schedulers-stats-attributes.model' |
9 | 10 | ||
10 | @Injectable() | 11 | @Injectable() |
@@ -12,19 +13,18 @@ export class RequestSchedulersService { | |||
12 | private static BASE_REQUEST_URL = API_URL + '/api/v1/request-schedulers/' | 13 | private static BASE_REQUEST_URL = API_URL + '/api/v1/request-schedulers/' |
13 | 14 | ||
14 | constructor ( | 15 | constructor ( |
15 | private authHttp: AuthHttp, | 16 | private authHttp: HttpClient, |
16 | private restExtractor: RestExtractor | 17 | private restExtractor: RestExtractor |
17 | ) {} | 18 | ) {} |
18 | 19 | ||
19 | getStats (): Observable<RequestSchedulerStats> { | 20 | getStats () { |
20 | return this.authHttp.get(RequestSchedulersService.BASE_REQUEST_URL + 'stats') | 21 | return this.authHttp.get<RequestSchedulerStats>(RequestSchedulersService.BASE_REQUEST_URL + 'stats') |
21 | .map(this.restExtractor.extractDataGet) | 22 | .map(res => this.buildRequestObjects(res)) |
22 | .map(this.buildRequestObjects) | 23 | .catch(res => this.restExtractor.handleError(res)) |
23 | .catch((res) => this.restExtractor.handleError(res)) | ||
24 | } | 24 | } |
25 | 25 | ||
26 | private buildRequestObjects (data: RequestSchedulerStats) { | 26 | private buildRequestObjects (data: RequestSchedulerStats) { |
27 | const requestSchedulers = {} | 27 | const requestSchedulers: { [ id: string ]: RequestSchedulerStatsAttributes } = {} |
28 | 28 | ||
29 | Object.keys(data).forEach(requestSchedulerName => { | 29 | Object.keys(data).forEach(requestSchedulerName => { |
30 | requestSchedulers[requestSchedulerName] = new RequestSchedulerStatsAttributes(data[requestSchedulerName]) | 30 | requestSchedulers[requestSchedulerName] = new RequestSchedulerStatsAttributes(data[requestSchedulerName]) |
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts index 999013bcc..a4b89bf1d 100644 --- a/client/src/app/+admin/users/shared/user.service.ts +++ b/client/src/app/+admin/users/shared/user.service.ts | |||
@@ -1,11 +1,14 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
3 | import { Observable } from 'rxjs/Observable' | ||
2 | import 'rxjs/add/operator/catch' | 4 | import 'rxjs/add/operator/catch' |
3 | import 'rxjs/add/operator/map' | 5 | import 'rxjs/add/operator/map' |
4 | 6 | ||
7 | import { SortMeta } from 'primeng/primeng' | ||
5 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' | 8 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' |
6 | 9 | ||
7 | import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' | 10 | import { RestExtractor, User, RestPagination, RestService } from '../../../shared' |
8 | import { UserCreate, UserUpdate } from '../../../../../../shared' | 11 | import { UserCreate, UserUpdate, ResultList } from '../../../../../../shared' |
9 | 12 | ||
10 | @Injectable() | 13 | @Injectable() |
11 | export class UserService { | 14 | export class UserService { |
@@ -13,53 +16,52 @@ export class UserService { | |||
13 | private bytesPipe = new BytesPipe() | 16 | private bytesPipe = new BytesPipe() |
14 | 17 | ||
15 | constructor ( | 18 | constructor ( |
16 | private authHttp: AuthHttp, | 19 | private authHttp: HttpClient, |
20 | private restService: RestService, | ||
17 | private restExtractor: RestExtractor | 21 | private restExtractor: RestExtractor |
18 | ) {} | 22 | ) {} |
19 | 23 | ||
20 | addUser (userCreate: UserCreate) { | 24 | addUser (userCreate: UserCreate) { |
21 | return this.authHttp.post(UserService.BASE_USERS_URL, userCreate) | 25 | return this.authHttp.post(UserService.BASE_USERS_URL, userCreate) |
22 | .map(this.restExtractor.extractDataBool) | 26 | .map(this.restExtractor.extractDataBool) |
23 | .catch(this.restExtractor.handleError) | 27 | .catch(err => this.restExtractor.handleError(err)) |
24 | } | 28 | } |
25 | 29 | ||
26 | updateUser (userId: number, userUpdate: UserUpdate) { | 30 | updateUser (userId: number, userUpdate: UserUpdate) { |
27 | return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate) | 31 | return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate) |
28 | .map(this.restExtractor.extractDataBool) | 32 | .map(this.restExtractor.extractDataBool) |
29 | .catch(this.restExtractor.handleError) | 33 | .catch(err => this.restExtractor.handleError(err)) |
30 | } | 34 | } |
31 | 35 | ||
32 | getUser (userId: number) { | 36 | getUser (userId: number) { |
33 | return this.authHttp.get(UserService.BASE_USERS_URL + userId) | 37 | return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId) |
34 | .map(this.restExtractor.extractDataGet) | 38 | .catch(err => this.restExtractor.handleError(err)) |
35 | .catch(this.restExtractor.handleError) | ||
36 | } | 39 | } |
37 | 40 | ||
38 | getDataSource () { | 41 | getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> { |
39 | return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this)) | 42 | let params = new HttpParams() |
43 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
44 | |||
45 | return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params }) | ||
46 | .map(res => this.restExtractor.convertResultListDateToHuman(res)) | ||
47 | .map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))) | ||
48 | .catch(err => this.restExtractor.handleError(err)) | ||
40 | } | 49 | } |
41 | 50 | ||
42 | removeUser (user: User) { | 51 | removeUser (user: User) { |
43 | return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) | 52 | return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) |
44 | } | 53 | } |
45 | 54 | ||
46 | private formatDataSource (users: User[]) { | 55 | private formatUser (user: User) { |
47 | const newUsers = [] | 56 | let videoQuota |
57 | if (user.videoQuota === -1) { | ||
58 | videoQuota = 'Unlimited' | ||
59 | } else { | ||
60 | videoQuota = this.bytesPipe.transform(user.videoQuota) | ||
61 | } | ||
48 | 62 | ||
49 | users.forEach(user => { | 63 | return Object.assign(user, { |
50 | let videoQuota | 64 | videoQuota |
51 | if (user.videoQuota === -1) { | ||
52 | videoQuota = 'Unlimited' | ||
53 | } else { | ||
54 | videoQuota = this.bytesPipe.transform(user.videoQuota) | ||
55 | } | ||
56 | |||
57 | const newUser = Object.assign(user, { | ||
58 | videoQuota | ||
59 | }) | ||
60 | newUsers.push(newUser) | ||
61 | }) | 65 | }) |
62 | |||
63 | return newUsers | ||
64 | } | 66 | } |
65 | } | 67 | } |
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 eb5bc9d4a..2944e3cbf 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 | |||
@@ -3,10 +3,29 @@ | |||
3 | 3 | ||
4 | <h3>Users list</h3> | 4 | <h3>Users list</h3> |
5 | 5 | ||
6 | <ng2-smart-table | 6 | <p-dataTable |
7 | [settings]="tableSettings" [source]="usersSource" | 7 | [value]="users" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
8 | (delete)="removeUser($event)" (edit)="editUser($event)" | 8 | sortField="id" (onLazyLoad)="loadLazy($event)" |
9 | ></ng2-smart-table> | 9 | > |
10 | <p-column field="id" header="ID" [sortable]="true"></p-column> | ||
11 | <p-column field="username" header="Username" [sortable]="true"></p-column> | ||
12 | <p-column field="email" header="Email"></p-column> | ||
13 | <p-column field="videoQuota" header="Video quota"></p-column> | ||
14 | <p-column field="role" header="Role"></p-column> | ||
15 | <p-column field="createdAt" header="Created date" [sortable]="true"></p-column> | ||
16 | <p-column header="Edit" styleClass="action-cell"> | ||
17 | <ng-template pTemplate="body" let-user="rowData"> | ||
18 | <a [routerLink]="getRouterUserEditLink(user)" title="Edit this user"> | ||
19 | <span class="glyphicon glyphicon-pencil glyphicon-black"></span> | ||
20 | </a> | ||
21 | </ng-template> | ||
22 | </p-column> | ||
23 | <p-column header="Delete" styleClass="action-cell"> | ||
24 | <ng-template pTemplate="body" let-user="rowData"> | ||
25 | <span (click)="removeUser(user)" class="glyphicon glyphicon-remove glyphicon-black" title="Remove this user"></span> | ||
26 | </ng-template> | ||
27 | </p-column> | ||
28 | </p-dataTable> | ||
10 | 29 | ||
11 | <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']"> | 30 | <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']"> |
12 | <span class="glyphicon glyphicon-plus"></span> | 31 | <span class="glyphicon glyphicon-plus"></span> |
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 7187a2008..c3fa55825 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 | |||
@@ -1,82 +1,37 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { SortMeta } from 'primeng/primeng' | ||
2 | 3 | ||
3 | import { NotificationsService } from 'angular2-notifications' | 4 | import { NotificationsService } from 'angular2-notifications' |
4 | 5 | ||
5 | import { ConfirmService } from '../../../core' | 6 | import { ConfirmService } from '../../../core' |
6 | import { RestDataSource, User, Utils } from '../../../shared' | 7 | import { RestTable, RestPagination, User } from '../../../shared' |
7 | import { UserService } from '../shared' | 8 | import { UserService } from '../shared' |
8 | import { Router } from '@angular/router' | ||
9 | 9 | ||
10 | @Component({ | 10 | @Component({ |
11 | selector: 'my-user-list', | 11 | selector: 'my-user-list', |
12 | templateUrl: './user-list.component.html', | 12 | templateUrl: './user-list.component.html', |
13 | styleUrls: [ './user-list.component.scss' ] | 13 | styleUrls: [ './user-list.component.scss' ] |
14 | }) | 14 | }) |
15 | export class UserListComponent { | 15 | export class UserListComponent extends RestTable implements OnInit { |
16 | usersSource: RestDataSource = null | 16 | users: User[] = [] |
17 | tableSettings = { | 17 | totalRecords = 0 |
18 | mode: 'external', | 18 | rowsPerPage = 10 |
19 | attr: { | 19 | sort: SortMeta = { field: 'id', order: 1 } |
20 | class: 'table-hover' | 20 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
21 | }, | ||
22 | hideSubHeader: true, | ||
23 | actions: { | ||
24 | position: 'right', | ||
25 | add: false, | ||
26 | edit: true, | ||
27 | delete: true | ||
28 | }, | ||
29 | delete: { | ||
30 | deleteButtonContent: Utils.getRowDeleteButton() | ||
31 | }, | ||
32 | edit: { | ||
33 | editButtonContent: Utils.getRowEditButton() | ||
34 | }, | ||
35 | pager: { | ||
36 | display: true, | ||
37 | perPage: 10 | ||
38 | }, | ||
39 | columns: { | ||
40 | id: { | ||
41 | title: 'ID', | ||
42 | sortDirection: 'asc' | ||
43 | }, | ||
44 | username: { | ||
45 | title: 'Username' | ||
46 | }, | ||
47 | email: { | ||
48 | title: 'Email' | ||
49 | }, | ||
50 | videoQuota: { | ||
51 | title: 'Video quota' | ||
52 | }, | ||
53 | role: { | ||
54 | title: 'Role', | ||
55 | sort: false | ||
56 | }, | ||
57 | createdAt: { | ||
58 | title: 'Created Date', | ||
59 | valuePrepareFunction: Utils.dateToHuman | ||
60 | } | ||
61 | } | ||
62 | } | ||
63 | 21 | ||
64 | constructor ( | 22 | constructor ( |
65 | private router: Router, | ||
66 | private notificationsService: NotificationsService, | 23 | private notificationsService: NotificationsService, |
67 | private confirmService: ConfirmService, | 24 | private confirmService: ConfirmService, |
68 | private userService: UserService | 25 | private userService: UserService |
69 | ) { | 26 | ) { |
70 | this.usersSource = this.userService.getDataSource() | 27 | super() |
71 | } | 28 | } |
72 | 29 | ||
73 | editUser ({ data }: { data: User }) { | 30 | ngOnInit () { |
74 | this.router.navigate([ '/admin', 'users', data.id, 'update' ]) | 31 | this.loadData() |
75 | } | 32 | } |
76 | 33 | ||
77 | removeUser ({ data }: { data: User }) { | 34 | removeUser (user: User) { |
78 | const user = data | ||
79 | |||
80 | if (user.username === 'root') { | 35 | if (user.username === 'root') { |
81 | this.notificationsService.error('Error', 'You cannot delete root.') | 36 | this.notificationsService.error('Error', 'You cannot delete root.') |
82 | return | 37 | return |
@@ -89,12 +44,28 @@ export class UserListComponent { | |||
89 | this.userService.removeUser(user).subscribe( | 44 | this.userService.removeUser(user).subscribe( |
90 | () => { | 45 | () => { |
91 | this.notificationsService.success('Success', `User ${user.username} deleted.`) | 46 | this.notificationsService.success('Success', `User ${user.username} deleted.`) |
92 | this.usersSource.refresh() | 47 | this.loadData() |
93 | }, | 48 | }, |
94 | 49 | ||
95 | err => this.notificationsService.error('Error', err.text) | 50 | err => this.notificationsService.error('Error', err) |
96 | ) | 51 | ) |
97 | } | 52 | } |
98 | ) | 53 | ) |
99 | } | 54 | } |
55 | |||
56 | getRouterUserEditLink (user: User) { | ||
57 | return [ '/admin', 'users', user.id, 'update' ] | ||
58 | } | ||
59 | |||
60 | protected loadData () { | ||
61 | this.userService.getUsers(this.pagination, this.sort) | ||
62 | .subscribe( | ||
63 | resultList => { | ||
64 | this.users = resultList.data | ||
65 | this.totalRecords = resultList.total | ||
66 | }, | ||
67 | |||
68 | err => this.notificationsService.error('Error', err) | ||
69 | ) | ||
70 | } | ||
100 | } | 71 | } |
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html index c6723a734..e73f38112 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html | |||
@@ -3,9 +3,21 @@ | |||
3 | 3 | ||
4 | <h3>Video abuses list</h3> | 4 | <h3>Video abuses list</h3> |
5 | 5 | ||
6 | <ng2-smart-table | 6 | <p-dataTable |
7 | [settings]="tableSettings" [source]="videoAbusesSource" | 7 | [value]="videoAbuses" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
8 | ></ng2-smart-table> | 8 | sortField="id" (onLazyLoad)="loadLazy($event)" |
9 | > | ||
10 | <p-column field="id" header="ID" [sortable]="true"></p-column> | ||
11 | <p-column field="reason" header="Reason"></p-column> | ||
12 | <p-column field="reporterPodHost" header="Reporter pod host"></p-column> | ||
13 | <p-column field="reporterUsername" header="Reporter username"></p-column> | ||
14 | <p-column header="Video" styleClass="action-cell"> | ||
15 | <ng-template pTemplate="body" let-videoAbuse="rowData"> | ||
16 | <a [routerLink]="getRouterVideoLink(videoAbuse.videoId)" title="Go to the video">{{ videoAbuse.videoId }}</a> | ||
17 | </ng-template> | ||
18 | </p-column> | ||
19 | <p-column field="createdAt" header="Created date" [sortable]="true"></p-column> | ||
20 | </p-dataTable> | ||
9 | 21 | ||
10 | </div> | 22 | </div> |
11 | </div> | 23 | </div> |
diff --git a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts index 7c838fbf0..cc9c1bdf4 100644 --- a/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts | |||
@@ -1,72 +1,46 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | 2 | ||
3 | import { NotificationsService } from 'angular2-notifications' | 3 | import { NotificationsService } from 'angular2-notifications' |
4 | import { SortMeta } from 'primeng/primeng' | ||
4 | 5 | ||
5 | import { Utils, VideoAbuseService } from '../../../shared' | 6 | import { RestTable, RestPagination, VideoAbuseService } from '../../../shared' |
6 | import { VideoAbuse } from '../../../../../shared' | 7 | import { VideoAbuse } from '../../../../../../shared' |
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-video-abuse-list', | 10 | selector: 'my-video-abuse-list', |
10 | templateUrl: './video-abuse-list.component.html' | 11 | templateUrl: './video-abuse-list.component.html' |
11 | }) | 12 | }) |
12 | export class VideoAbuseListComponent { | 13 | export class VideoAbuseListComponent extends RestTable implements OnInit { |
13 | videoAbusesSource = null | 14 | videoAbuses: VideoAbuse[] = [] |
14 | tableSettings = { | 15 | totalRecords = 0 |
15 | mode: 'external', | 16 | rowsPerPage = 1 |
16 | attr: { | 17 | sort: SortMeta = { field: 'id', order: 1 } |
17 | class: 'table-hover' | 18 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
18 | }, | ||
19 | hideSubHeader: true, | ||
20 | actions: { | ||
21 | position: 'right', | ||
22 | add: false, | ||
23 | edit: false, | ||
24 | delete: false | ||
25 | }, | ||
26 | pager: { | ||
27 | display: true, | ||
28 | perPage: 10 | ||
29 | }, | ||
30 | columns: { | ||
31 | id: { | ||
32 | title: 'ID', | ||
33 | sortDirection: 'asc' | ||
34 | }, | ||
35 | reason: { | ||
36 | title: 'Reason', | ||
37 | sort: false | ||
38 | }, | ||
39 | reporterPodHost: { | ||
40 | title: 'Reporter pod host', | ||
41 | sort: false | ||
42 | }, | ||
43 | reporterUsername: { | ||
44 | title: 'Reporter username', | ||
45 | sort: false | ||
46 | }, | ||
47 | videoId: { | ||
48 | title: 'Video', | ||
49 | type: 'html', | ||
50 | sort: false, | ||
51 | valuePrepareFunction: this.buildVideoLink | ||
52 | }, | ||
53 | createdAt: { | ||
54 | title: 'Created Date', | ||
55 | valuePrepareFunction: Utils.dateToHuman | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | 19 | ||
60 | constructor ( | 20 | constructor ( |
61 | private notificationsService: NotificationsService, | 21 | private notificationsService: NotificationsService, |
62 | private videoAbuseService: VideoAbuseService | 22 | private videoAbuseService: VideoAbuseService |
63 | ) { | 23 | ) { |
64 | this.videoAbusesSource = this.videoAbuseService.getDataSource() | 24 | super() |
25 | } | ||
26 | |||
27 | ngOnInit () { | ||
28 | this.loadData() | ||
65 | } | 29 | } |
66 | 30 | ||
67 | buildVideoLink (videoId: string) { | 31 | getRouterVideoLink (videoId: number) { |
68 | // TODO: transform to routerLink | 32 | return [ '/videos', videoId ] |
69 | // https://github.com/akveo/ng2-smart-table/issues/57 | 33 | } |
70 | return `<a href="/videos/${videoId}" title="Go to the video">${videoId}</a>` | 34 | |
35 | protected loadData () { | ||
36 | return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) | ||
37 | .subscribe( | ||
38 | resultList => { | ||
39 | this.videoAbuses = resultList.data | ||
40 | this.totalRecords = resultList.total | ||
41 | }, | ||
42 | |||
43 | err => this.notificationsService.error('Error', err) | ||
44 | ) | ||
71 | } | 45 | } |
72 | } | 46 | } |
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 8cbed5009..78e365a62 100644 --- a/client/src/app/account/account-details/account-details.component.ts +++ b/client/src/app/account/account-details/account-details.component.ts | |||
@@ -59,7 +59,7 @@ export class AccountDetailsComponent extends FormReactive implements OnInit { | |||
59 | () => { | 59 | () => { |
60 | this.notificationsService.success('Success', 'Information updated.') | 60 | this.notificationsService.success('Success', 'Information updated.') |
61 | 61 | ||
62 | this.authService.refreshUserInformations() | 62 | this.authService.refreshUserInformation() |
63 | }, | 63 | }, |
64 | 64 | ||
65 | err => this.error = err | 65 | err => this.error = err |
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index a90654e26..57bf64f69 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -36,6 +36,8 @@ export class AppComponent implements OnInit { | |||
36 | ) {} | 36 | ) {} |
37 | 37 | ||
38 | ngOnInit () { | 38 | ngOnInit () { |
39 | this.authService.loadClientCredentials() | ||
40 | |||
39 | if (this.authService.isLoggedIn()) { | 41 | if (this.authService.isLoggedIn()) { |
40 | // The service will automatically redirect to the login page if the token is not valid anymore | 42 | // The service will automatically redirect to the login page if the token is not valid anymore |
41 | this.userService.checkTokenValidity() | 43 | this.userService.checkTokenValidity() |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 804a7a71e..6aa56b8a7 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -7,8 +7,6 @@ import { | |||
7 | } from '@angularclass/hmr' | 7 | } from '@angularclass/hmr' |
8 | 8 | ||
9 | import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' | 9 | import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' |
10 | // TODO: remove, we need this to avoid error in ng2-smart-table | ||
11 | import 'rxjs/add/operator/toPromise' | ||
12 | import 'bootstrap-loader' | 10 | import 'bootstrap-loader' |
13 | 11 | ||
14 | import { ENV_PROVIDERS } from './environment' | 12 | import { ENV_PROVIDERS } from './environment' |
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index de9e14b2d..522efb23c 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Headers, Http, Response, URLSearchParams } from '@angular/http' | ||
3 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
4 | import { Observable } from 'rxjs/Observable' | 3 | import { Observable } from 'rxjs/Observable' |
5 | import { Subject } from 'rxjs/Subject' | 4 | import { Subject } from 'rxjs/Subject' |
5 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' | ||
6 | import 'rxjs/add/operator/map' | 6 | import 'rxjs/add/operator/map' |
7 | import 'rxjs/add/operator/mergeMap' | 7 | import 'rxjs/add/operator/mergeMap' |
8 | import 'rxjs/add/observable/throw' | 8 | import 'rxjs/add/observable/throw' |
@@ -11,15 +11,35 @@ import { NotificationsService } from 'angular2-notifications' | |||
11 | 11 | ||
12 | import { AuthStatus } from './auth-status.model' | 12 | import { AuthStatus } from './auth-status.model' |
13 | import { AuthUser } from './auth-user.model' | 13 | import { AuthUser } from './auth-user.model' |
14 | import { OAuthClientLocal, UserRole } from '../../../../../shared' | 14 | import { OAuthClientLocal, UserRole, UserRefreshToken } from '../../../../../shared' |
15 | // Do not use the barrel (dependency loop) | 15 | // Do not use the barrel (dependency loop) |
16 | import { RestExtractor } from '../../shared/rest' | 16 | import { RestExtractor } from '../../shared/rest' |
17 | import { UserLogin } from '../../../../../shared/models/users/user-login.model' | ||
18 | import { User } from '../../shared/users/user.model' | ||
19 | |||
20 | interface UserLoginWithUsername extends UserLogin { | ||
21 | access_token: string | ||
22 | refresh_token: string | ||
23 | token_type: string | ||
24 | username: string | ||
25 | } | ||
26 | |||
27 | interface UserLoginWithUserInformation extends UserLogin { | ||
28 | access_token: string | ||
29 | refresh_token: string | ||
30 | token_type: string | ||
31 | username: string | ||
32 | id: number | ||
33 | role: UserRole | ||
34 | displayNSFW: boolean | ||
35 | email: string | ||
36 | } | ||
17 | 37 | ||
18 | @Injectable() | 38 | @Injectable() |
19 | export class AuthService { | 39 | export class AuthService { |
20 | private static BASE_CLIENT_URL = API_URL + '/api/v1/oauth-clients/local' | 40 | private static BASE_CLIENT_URL = API_URL + '/api/v1/oauth-clients/local' |
21 | private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token' | 41 | private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token' |
22 | private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me' | 42 | private static BASE_USER_INFORMATION_URL = API_URL + '/api/v1/users/me' |
23 | 43 | ||
24 | loginChangedSource: Observable<AuthStatus> | 44 | loginChangedSource: Observable<AuthStatus> |
25 | 45 | ||
@@ -29,7 +49,7 @@ export class AuthService { | |||
29 | private user: AuthUser = null | 49 | private user: AuthUser = null |
30 | 50 | ||
31 | constructor ( | 51 | constructor ( |
32 | private http: Http, | 52 | private http: HttpClient, |
33 | private notificationsService: NotificationsService, | 53 | private notificationsService: NotificationsService, |
34 | private restExtractor: RestExtractor, | 54 | private restExtractor: RestExtractor, |
35 | private router: Router | 55 | private router: Router |
@@ -37,32 +57,33 @@ export class AuthService { | |||
37 | this.loginChanged = new Subject<AuthStatus>() | 57 | this.loginChanged = new Subject<AuthStatus>() |
38 | this.loginChangedSource = this.loginChanged.asObservable() | 58 | this.loginChangedSource = this.loginChanged.asObservable() |
39 | 59 | ||
40 | // Fetch the client_id/client_secret | ||
41 | // FIXME: save in local storage? | ||
42 | this.http.get(AuthService.BASE_CLIENT_URL) | ||
43 | .map(this.restExtractor.extractDataGet) | ||
44 | .catch(res => this.restExtractor.handleError(res)) | ||
45 | .subscribe( | ||
46 | (result: OAuthClientLocal) => { | ||
47 | this.clientId = result.client_id | ||
48 | this.clientSecret = result.client_secret | ||
49 | console.log('Client credentials loaded.') | ||
50 | }, | ||
51 | |||
52 | error => { | ||
53 | let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n` | ||
54 | errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' | ||
55 | |||
56 | // We put a bigger timeout | ||
57 | // This is an important message | ||
58 | this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }) | ||
59 | } | ||
60 | ) | ||
61 | |||
62 | // Return null if there is nothing to load | 60 | // Return null if there is nothing to load |
63 | this.user = AuthUser.load() | 61 | this.user = AuthUser.load() |
64 | } | 62 | } |
65 | 63 | ||
64 | loadClientCredentials () { | ||
65 | // Fetch the client_id/client_secret | ||
66 | // FIXME: save in local storage? | ||
67 | this.http.get<OAuthClientLocal>(AuthService.BASE_CLIENT_URL) | ||
68 | .catch(res => this.restExtractor.handleError(res)) | ||
69 | .subscribe( | ||
70 | res => { | ||
71 | this.clientId = res.client_id | ||
72 | this.clientSecret = res.client_secret | ||
73 | console.log('Client credentials loaded.') | ||
74 | }, | ||
75 | |||
76 | error => { | ||
77 | let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n` | ||
78 | errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' | ||
79 | |||
80 | // We put a bigger timeout | ||
81 | // This is an important message | ||
82 | this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }) | ||
83 | } | ||
84 | ) | ||
85 | } | ||
86 | |||
66 | getRefreshToken () { | 87 | getRefreshToken () { |
67 | if (this.user === null) return null | 88 | if (this.user === null) return null |
68 | 89 | ||
@@ -70,7 +91,11 @@ export class AuthService { | |||
70 | } | 91 | } |
71 | 92 | ||
72 | getRequestHeaderValue () { | 93 | getRequestHeaderValue () { |
73 | return `${this.getTokenType()} ${this.getAccessToken()}` | 94 | const accessToken = this.getAccessToken() |
95 | |||
96 | if (accessToken === null) return null | ||
97 | |||
98 | return `${this.getTokenType()} ${accessToken}` | ||
74 | } | 99 | } |
75 | 100 | ||
76 | getAccessToken () { | 101 | getAccessToken () { |
@@ -96,39 +121,26 @@ export class AuthService { | |||
96 | } | 121 | } |
97 | 122 | ||
98 | isLoggedIn () { | 123 | isLoggedIn () { |
99 | if (this.getAccessToken()) { | 124 | return !!this.getAccessToken() |
100 | return true | ||
101 | } else { | ||
102 | return false | ||
103 | } | ||
104 | } | 125 | } |
105 | 126 | ||
106 | login (username: string, password: string) { | 127 | login (username: string, password: string) { |
107 | let body = new URLSearchParams() | 128 | // Form url encoded |
108 | body.set('client_id', this.clientId) | 129 | const body = new HttpParams().set('client_id', this.clientId) |
109 | body.set('client_secret', this.clientSecret) | 130 | .set('client_secret', this.clientSecret) |
110 | body.set('response_type', 'code') | 131 | .set('response_type', 'code') |
111 | body.set('grant_type', 'password') | 132 | .set('grant_type', 'password') |
112 | body.set('scope', 'upload') | 133 | .set('scope', 'upload') |
113 | body.set('username', username) | 134 | .set('username', username) |
114 | body.set('password', password) | 135 | .set('password', password) |
115 | 136 | ||
116 | let headers = new Headers() | 137 | const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') |
117 | headers.append('Content-Type', 'application/x-www-form-urlencoded') | 138 | |
118 | 139 | return this.http.post<UserLogin>(AuthService.BASE_TOKEN_URL, body, { headers }) | |
119 | let options = { | 140 | .map(res => Object.assign(res, { username })) |
120 | headers: headers | 141 | .flatMap(res => this.mergeUserInformation(res)) |
121 | } | ||
122 | |||
123 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | ||
124 | .map(this.restExtractor.extractDataGet) | ||
125 | .map(res => { | ||
126 | res.username = username | ||
127 | return res | ||
128 | }) | ||
129 | .flatMap(res => this.mergeUserInformations(res)) | ||
130 | .map(res => this.handleLogin(res)) | 142 | .map(res => this.handleLogin(res)) |
131 | .catch((res) => this.restExtractor.handleError(res)) | 143 | .catch(res => this.restExtractor.handleError(res)) |
132 | } | 144 | } |
133 | 145 | ||
134 | logout () { | 146 | logout () { |
@@ -145,33 +157,26 @@ export class AuthService { | |||
145 | 157 | ||
146 | const refreshToken = this.getRefreshToken() | 158 | const refreshToken = this.getRefreshToken() |
147 | 159 | ||
148 | let body = new URLSearchParams() | 160 | // Form url encoded |
149 | body.set('refresh_token', refreshToken) | 161 | const body = new HttpParams().set('refresh_token', refreshToken) |
150 | body.set('client_id', this.clientId) | 162 | .set('client_id', this.clientId) |
151 | body.set('client_secret', this.clientSecret) | 163 | .set('client_secret', this.clientSecret) |
152 | body.set('response_type', 'code') | 164 | .set('response_type', 'code') |
153 | body.set('grant_type', 'refresh_token') | 165 | .set('grant_type', 'refresh_token') |
154 | |||
155 | let headers = new Headers() | ||
156 | headers.append('Content-Type', 'application/x-www-form-urlencoded') | ||
157 | 166 | ||
158 | let options = { | 167 | const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') |
159 | headers: headers | ||
160 | } | ||
161 | 168 | ||
162 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | 169 | return this.http.post<UserRefreshToken>(AuthService.BASE_TOKEN_URL, body, { headers }) |
163 | .map(this.restExtractor.extractDataGet) | ||
164 | .map(res => this.handleRefreshToken(res)) | 170 | .map(res => this.handleRefreshToken(res)) |
165 | .catch((res: Response) => { | 171 | .catch(res => { |
166 | // The refresh token is invalid? | 172 | // The refresh token is invalid? |
167 | if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { | 173 | if (res.status === 400 && res.error === 'invalid_grant') { |
168 | console.error('Cannot refresh token -> logout...') | 174 | console.error('Cannot refresh token -> logout...') |
169 | this.logout() | 175 | this.logout() |
170 | this.router.navigate(['/login']) | 176 | this.router.navigate(['/login']) |
171 | 177 | ||
172 | return Observable.throw({ | 178 | return Observable.throw({ |
173 | json: () => '', | 179 | error: 'You need to reconnect.' |
174 | text: () => 'You need to reconnect.' | ||
175 | }) | 180 | }) |
176 | } | 181 | } |
177 | 182 | ||
@@ -179,7 +184,7 @@ export class AuthService { | |||
179 | }) | 184 | }) |
180 | } | 185 | } |
181 | 186 | ||
182 | refreshUserInformations () { | 187 | refreshUserInformation () { |
183 | const obj = { | 188 | const obj = { |
184 | access_token: this.user.getAccessToken(), | 189 | access_token: this.user.getAccessToken(), |
185 | refresh_token: null, | 190 | refresh_token: null, |
@@ -187,7 +192,7 @@ export class AuthService { | |||
187 | username: this.user.username | 192 | username: this.user.username |
188 | } | 193 | } |
189 | 194 | ||
190 | this.mergeUserInformations (obj) | 195 | this.mergeUserInformation(obj) |
191 | .subscribe( | 196 | .subscribe( |
192 | res => { | 197 | res => { |
193 | this.user.displayNSFW = res.displayNSFW | 198 | this.user.displayNSFW = res.displayNSFW |
@@ -198,42 +203,25 @@ export class AuthService { | |||
198 | ) | 203 | ) |
199 | } | 204 | } |
200 | 205 | ||
201 | private mergeUserInformations (obj: { | 206 | private mergeUserInformation (obj: UserLoginWithUsername): Observable<UserLoginWithUserInformation> { |
202 | access_token: string, | 207 | // User is not loaded yet, set manually auth header |
203 | refresh_token: string, | 208 | const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) |
204 | token_type: string, | 209 | |
205 | username: string | 210 | return this.http.get<User>(AuthService.BASE_USER_INFORMATION_URL, { headers }) |
206 | }) { | 211 | .map(res => { |
207 | // Do not call authHttp here to avoid circular dependencies headaches | 212 | const newProperties = { |
208 | 213 | id: res.id as number, | |
209 | const headers = new Headers() | 214 | role: res.role as UserRole, |
210 | headers.set('Authorization', `Bearer ${obj.access_token}`) | 215 | displayNSFW: res.displayNSFW as boolean, |
211 | 216 | email: res.email as string | |
212 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) | 217 | } |
213 | .map(res => res.json()) | ||
214 | .map(res => { | ||
215 | const newProperties = { | ||
216 | id: res.id as number, | ||
217 | role: res.role as UserRole, | ||
218 | displayNSFW: res.displayNSFW as boolean, | ||
219 | email: res.email as string | ||
220 | } | ||
221 | 218 | ||
222 | return Object.assign(obj, newProperties) | 219 | return Object.assign(obj, newProperties) |
223 | } | 220 | } |
224 | ) | 221 | ) |
225 | } | 222 | } |
226 | 223 | ||
227 | private handleLogin (obj: { | 224 | private handleLogin (obj: UserLoginWithUserInformation) { |
228 | access_token: string, | ||
229 | refresh_token: string, | ||
230 | token_type: string, | ||
231 | id: number, | ||
232 | username: string, | ||
233 | email: string, | ||
234 | role: UserRole, | ||
235 | displayNSFW: boolean | ||
236 | }) { | ||
237 | const id = obj.id | 225 | const id = obj.id |
238 | const username = obj.username | 226 | const username = obj.username |
239 | const role = obj.role | 227 | const role = obj.role |
@@ -251,7 +239,7 @@ export class AuthService { | |||
251 | this.setStatus(AuthStatus.LoggedIn) | 239 | this.setStatus(AuthStatus.LoggedIn) |
252 | } | 240 | } |
253 | 241 | ||
254 | private handleRefreshToken (obj: { access_token: string, refresh_token: string }) { | 242 | private handleRefreshToken (obj: UserRefreshToken) { |
255 | this.user.refreshTokens(obj.access_token, obj.refresh_token) | 243 | this.user.refreshTokens(obj.access_token, obj.refresh_token) |
256 | this.user.save() | 244 | this.user.save() |
257 | } | 245 | } |
@@ -259,5 +247,4 @@ export class AuthService { | |||
259 | private setStatus (status: AuthStatus) { | 247 | private setStatus (status: AuthStatus) { |
260 | this.loginChanged.next(status) | 248 | this.loginChanged.next(status) |
261 | } | 249 | } |
262 | |||
263 | } | 250 | } |
diff --git a/client/src/app/core/config/config.service.ts b/client/src/app/core/config/config.service.ts index acdc12cc6..3c479bcb8 100644 --- a/client/src/app/core/config/config.service.ts +++ b/client/src/app/core/config/config.service.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Http } from '@angular/http' | 2 | import { HttpClient } from '@angular/common/http' |
3 | 3 | ||
4 | import { RestExtractor } from '../../shared/rest' | ||
5 | import { ServerConfig } from '../../../../../shared' | 4 | import { ServerConfig } from '../../../../../shared' |
6 | 5 | ||
7 | @Injectable() | 6 | @Injectable() |
@@ -14,17 +13,11 @@ export class ConfigService { | |||
14 | } | 13 | } |
15 | } | 14 | } |
16 | 15 | ||
17 | constructor ( | 16 | constructor (private http: HttpClient) {} |
18 | private http: Http, | ||
19 | private restExtractor: RestExtractor | ||
20 | ) {} | ||
21 | 17 | ||
22 | loadConfig () { | 18 | loadConfig () { |
23 | this.http.get(ConfigService.BASE_CONFIG_URL) | 19 | this.http.get<ServerConfig>(ConfigService.BASE_CONFIG_URL) |
24 | .map(this.restExtractor.extractDataGet) | 20 | .subscribe(data => this.config = data) |
25 | .subscribe(data => { | ||
26 | this.config = data | ||
27 | }) | ||
28 | } | 21 | } |
29 | 22 | ||
30 | getConfig () { | 23 | getConfig () { |
diff --git a/client/src/app/shared/auth/auth-http.service.ts b/client/src/app/shared/auth/auth-http.service.ts deleted file mode 100644 index 0fbaab0a8..000000000 --- a/client/src/app/shared/auth/auth-http.service.ts +++ /dev/null | |||
@@ -1,93 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core' | ||
2 | import { | ||
3 | ConnectionBackend, | ||
4 | Headers, | ||
5 | Http, | ||
6 | Request, | ||
7 | RequestMethod, | ||
8 | RequestOptions, | ||
9 | RequestOptionsArgs, | ||
10 | Response, | ||
11 | XHRBackend | ||
12 | } from '@angular/http' | ||
13 | import { Observable } from 'rxjs/Observable' | ||
14 | |||
15 | import { AuthService } from '../../core' | ||
16 | |||
17 | @Injectable() | ||
18 | export class AuthHttp extends Http { | ||
19 | constructor (backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) { | ||
20 | super(backend, defaultOptions) | ||
21 | } | ||
22 | |||
23 | request (url: string | Request, options?: RequestOptionsArgs): Observable<Response> { | ||
24 | if (!options) options = {} | ||
25 | |||
26 | options.headers = new Headers() | ||
27 | this.setAuthorizationHeader(options.headers) | ||
28 | |||
29 | return super.request(url, options) | ||
30 | .catch((err) => { | ||
31 | if (err.status === 401) { | ||
32 | return this.handleTokenExpired(url, options) | ||
33 | } | ||
34 | |||
35 | return Observable.throw(err) | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | delete (url: string, options?: RequestOptionsArgs): Observable<Response> { | ||
40 | if (!options) options = {} | ||
41 | options.method = RequestMethod.Delete | ||
42 | |||
43 | return this.request(url, options) | ||
44 | } | ||
45 | |||
46 | get (url: string, options?: RequestOptionsArgs): Observable<Response> { | ||
47 | if (!options) options = {} | ||
48 | options.method = RequestMethod.Get | ||
49 | |||
50 | return this.request(url, options) | ||
51 | } | ||
52 | |||
53 | post (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { | ||
54 | if (!options) options = {} | ||
55 | options.method = RequestMethod.Post | ||
56 | options.body = body | ||
57 | |||
58 | return this.request(url, options) | ||
59 | } | ||
60 | |||
61 | put (url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { | ||
62 | if (!options) options = {} | ||
63 | options.method = RequestMethod.Put | ||
64 | options.body = body | ||
65 | |||
66 | return this.request(url, options) | ||
67 | } | ||
68 | |||
69 | private handleTokenExpired (url: string | Request, options: RequestOptionsArgs) { | ||
70 | return this.authService.refreshAccessToken() | ||
71 | .flatMap(() => { | ||
72 | this.setAuthorizationHeader(options.headers) | ||
73 | |||
74 | return super.request(url, options) | ||
75 | }) | ||
76 | } | ||
77 | |||
78 | private setAuthorizationHeader (headers: Headers) { | ||
79 | headers.set('Authorization', this.authService.getRequestHeaderValue()) | ||
80 | } | ||
81 | } | ||
82 | |||
83 | export function useFactory (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) { | ||
84 | return new AuthHttp(backend, defaultOptions, authService) | ||
85 | } | ||
86 | |||
87 | export const AUTH_HTTP_PROVIDERS = [ | ||
88 | { | ||
89 | provide: AuthHttp, | ||
90 | useFactory, | ||
91 | deps: [ XHRBackend, RequestOptions, AuthService ] | ||
92 | } | ||
93 | ] | ||
diff --git a/client/src/app/shared/auth/auth-interceptor.service.ts b/client/src/app/shared/auth/auth-interceptor.service.ts new file mode 100644 index 000000000..1e890d8f3 --- /dev/null +++ b/client/src/app/shared/auth/auth-interceptor.service.ts | |||
@@ -0,0 +1,62 @@ | |||
1 | import { Injectable, Injector } from '@angular/core' | ||
2 | import { | ||
3 | HttpInterceptor, | ||
4 | HttpRequest, | ||
5 | HttpEvent, | ||
6 | HttpHandler, HTTP_INTERCEPTORS | ||
7 | } from '@angular/common/http' | ||
8 | import { Observable } from 'rxjs/Observable' | ||
9 | |||
10 | import { AuthService } from '../../core' | ||
11 | import 'rxjs/add/operator/switchMap' | ||
12 | |||
13 | @Injectable() | ||
14 | export class AuthInterceptor implements HttpInterceptor { | ||
15 | private authService: AuthService | ||
16 | |||
17 | // https://github.com/angular/angular/issues/18224#issuecomment-316957213 | ||
18 | constructor (private injector: Injector) {} | ||
19 | |||
20 | intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | ||
21 | if (this.authService === undefined) { | ||
22 | this.authService = this.injector.get(AuthService) | ||
23 | } | ||
24 | |||
25 | const authReq = this.cloneRequestWithAuth(req) | ||
26 | |||
27 | // Pass on the cloned request instead of the original request | ||
28 | // Catch 401 errors (refresh token expired) | ||
29 | return next.handle(authReq) | ||
30 | .catch(err => { | ||
31 | if (err.status === 401) { | ||
32 | return this.handleTokenExpired(req, next) | ||
33 | } | ||
34 | |||
35 | return Observable.throw(err) | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | private handleTokenExpired (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { | ||
40 | return this.authService.refreshAccessToken() | ||
41 | .switchMap(() => { | ||
42 | const authReq = this.cloneRequestWithAuth(req) | ||
43 | |||
44 | return next.handle(authReq) | ||
45 | }) | ||
46 | } | ||
47 | |||
48 | private cloneRequestWithAuth (req: HttpRequest<any>) { | ||
49 | const authHeaderValue = this.authService.getRequestHeaderValue() | ||
50 | |||
51 | if (authHeaderValue === null) return req | ||
52 | |||
53 | // Clone the request to add the new header | ||
54 | return req.clone({ headers: req.headers.set('Authorization', authHeaderValue) }) | ||
55 | } | ||
56 | } | ||
57 | |||
58 | export const AUTH_INTERCEPTOR_PROVIDER = { | ||
59 | provide: HTTP_INTERCEPTORS, | ||
60 | useClass: AuthInterceptor, | ||
61 | multi: true | ||
62 | } | ||
diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/auth/index.ts index 0f2bfb0d6..84a07196f 100644 --- a/client/src/app/shared/auth/index.ts +++ b/client/src/app/shared/auth/index.ts | |||
@@ -1 +1 @@ | |||
export * from './auth-http.service' | export * from './auth-interceptor.service' | ||
diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts index e0be155cf..3f1996130 100644 --- a/client/src/app/shared/rest/index.ts +++ b/client/src/app/shared/rest/index.ts | |||
@@ -2,3 +2,4 @@ export * from './rest-data-source' | |||
2 | export * from './rest-extractor.service' | 2 | export * from './rest-extractor.service' |
3 | export * from './rest-pagination' | 3 | export * from './rest-pagination' |
4 | export * from './rest.service' | 4 | export * from './rest.service' |
5 | export * from './rest-table' | ||
diff --git a/client/src/app/shared/rest/rest-data-source.ts b/client/src/app/shared/rest/rest-data-source.ts index 5c205d280..57a2efb57 100644 --- a/client/src/app/shared/rest/rest-data-source.ts +++ b/client/src/app/shared/rest/rest-data-source.ts | |||
@@ -1,68 +1,32 @@ | |||
1 | import { Http, RequestOptionsArgs, URLSearchParams, Response } from '@angular/http' | 1 | export class RestDataSource { |
2 | 2 | // protected addSortRequestOptions (requestOptions: RequestOptionsArgs) { | |
3 | import { ServerDataSource } from 'ng2-smart-table' | 3 | // const searchParams = requestOptions.params as URLSearchParams |
4 | 4 | // | |
5 | export class RestDataSource extends ServerDataSource { | 5 | // if (this.sortConf) { |
6 | private updateResponse: (input: any[]) => any[] | 6 | // this.sortConf.forEach((fieldConf) => { |
7 | 7 | // const sortPrefix = fieldConf.direction === 'desc' ? '-' : '' | |
8 | constructor (http: Http, endpoint: string, updateResponse?: (input: any[]) => any[]) { | 8 | // |
9 | const options = { | 9 | // searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field) |
10 | endPoint: endpoint, | 10 | // }) |
11 | sortFieldKey: 'sort', | 11 | // } |
12 | dataKey: 'data' | 12 | // |
13 | } | 13 | // return requestOptions |
14 | super(http, options) | 14 | // } |
15 | 15 | // | |
16 | if (updateResponse) { | 16 | // protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) { |
17 | this.updateResponse = updateResponse | 17 | // const searchParams = requestOptions.params as URLSearchParams |
18 | } | 18 | // |
19 | } | 19 | // if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { |
20 | 20 | // const perPage = this.pagingConf['perPage'] | |
21 | protected extractDataFromResponse (res: Response) { | 21 | // const page = this.pagingConf['page'] |
22 | const json = res.json() | 22 | // |
23 | if (!json) return [] | 23 | // const start = (page - 1) * perPage |
24 | let data = json.data | 24 | // const count = perPage |
25 | 25 | // | |
26 | if (this.updateResponse !== undefined) { | 26 | // searchParams.set('start', start.toString()) |
27 | data = this.updateResponse(data) | 27 | // searchParams.set('count', count.toString()) |
28 | } | 28 | // } |
29 | 29 | // | |
30 | return data | 30 | // return requestOptions |
31 | } | 31 | // } |
32 | |||
33 | protected extractTotalFromResponse (res: Response) { | ||
34 | const rawData = res.json() | ||
35 | return rawData ? parseInt(rawData.total, 10) : 0 | ||
36 | } | ||
37 | |||
38 | protected addSortRequestOptions (requestOptions: RequestOptionsArgs) { | ||
39 | const searchParams = requestOptions.params as URLSearchParams | ||
40 | |||
41 | if (this.sortConf) { | ||
42 | this.sortConf.forEach((fieldConf) => { | ||
43 | const sortPrefix = fieldConf.direction === 'desc' ? '-' : '' | ||
44 | |||
45 | searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field) | ||
46 | }) | ||
47 | } | ||
48 | |||
49 | return requestOptions | ||
50 | } | ||
51 | |||
52 | protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) { | ||
53 | const searchParams = requestOptions.params as URLSearchParams | ||
54 | |||
55 | if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { | ||
56 | const perPage = this.pagingConf['perPage'] | ||
57 | const page = this.pagingConf['page'] | ||
58 | |||
59 | const start = (page - 1) * perPage | ||
60 | const count = perPage | ||
61 | |||
62 | searchParams.set('start', start.toString()) | ||
63 | searchParams.set('count', count.toString()) | ||
64 | } | ||
65 | |||
66 | return requestOptions | ||
67 | } | ||
68 | } | 32 | } |
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts index f6a818ec8..32dad5c73 100644 --- a/client/src/app/shared/rest/rest-extractor.service.ts +++ b/client/src/app/shared/rest/rest-extractor.service.ts | |||
@@ -1,52 +1,58 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Response } from '@angular/http' | ||
3 | import { Observable } from 'rxjs/Observable' | 2 | import { Observable } from 'rxjs/Observable' |
3 | import { HttpErrorResponse } from '@angular/common/http' | ||
4 | 4 | ||
5 | export interface ResultList { | 5 | import { Utils } from '../utils' |
6 | data: any[] | 6 | import { ResultList } from '../../../../../shared' |
7 | total: number | ||
8 | } | ||
9 | 7 | ||
10 | @Injectable() | 8 | @Injectable() |
11 | export class RestExtractor { | 9 | export class RestExtractor { |
12 | 10 | ||
13 | extractDataBool (res: Response) { | 11 | extractDataBool () { |
14 | return true | 12 | return true |
15 | } | 13 | } |
16 | 14 | ||
17 | extractDataList (res: Response) { | 15 | applyToResultListData <T> (result: ResultList<T>, fun: Function, additionalArgs?: any[]): ResultList<T> { |
18 | const body = res.json() | 16 | const data: T[] = result.data |
17 | const newData: T[] = [] | ||
19 | 18 | ||
20 | const ret: ResultList = { | 19 | data.forEach(d => newData.push(fun.call(this, d, additionalArgs))) |
21 | data: body.data, | ||
22 | total: body.total | ||
23 | } | ||
24 | 20 | ||
25 | return ret | 21 | return { |
22 | total: result.total, | ||
23 | data: newData | ||
24 | } | ||
26 | } | 25 | } |
27 | 26 | ||
28 | extractDataGet (res: Response) { | 27 | convertResultListDateToHuman <T> (result: ResultList<T>, fieldsToConvert: string[] = [ 'createdAt' ]): ResultList<T> { |
29 | return res.json() | 28 | return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ]) |
30 | } | 29 | } |
31 | 30 | ||
32 | handleError (res: Response) { | 31 | convertDateToHuman (target: object, fieldsToConvert: string[]) { |
33 | let text = 'Server error: ' | 32 | const source = {} |
34 | text += res.text() | 33 | fieldsToConvert.forEach(field => { |
35 | let json = '' | 34 | source[field] = Utils.dateToHuman(target[field]) |
35 | }) | ||
36 | 36 | ||
37 | try { | 37 | return Object.assign(target, source) |
38 | json = res.json() | 38 | } |
39 | } catch (err) { | ||
40 | console.error('Cannot get JSON from response.') | ||
41 | } | ||
42 | 39 | ||
43 | const error = { | 40 | handleError (err: HttpErrorResponse) { |
44 | json, | 41 | let errorMessage |
45 | text | 42 | |
43 | if (err.error instanceof Error) { | ||
44 | // A client-side or network error occurred. Handle it accordingly. | ||
45 | errorMessage = err.error.message | ||
46 | console.error('An error occurred:', errorMessage) | ||
47 | } else if (err.status !== undefined) { | ||
48 | // The backend returned an unsuccessful response code. | ||
49 | // The response body may contain clues as to what went wrong, | ||
50 | errorMessage = err.error | ||
51 | console.error(`Backend returned code ${err.status}, body was: ${errorMessage}`) | ||
52 | } else { | ||
53 | errorMessage = err | ||
46 | } | 54 | } |
47 | 55 | ||
48 | console.error(error) | 56 | return Observable.throw(errorMessage) |
49 | |||
50 | return Observable.throw(error) | ||
51 | } | 57 | } |
52 | } | 58 | } |
diff --git a/client/src/app/shared/rest/rest-pagination.ts b/client/src/app/shared/rest/rest-pagination.ts index 766e7a9e5..0faa59303 100644 --- a/client/src/app/shared/rest/rest-pagination.ts +++ b/client/src/app/shared/rest/rest-pagination.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | export interface RestPagination { | 1 | export interface RestPagination { |
2 | currentPage: number | 2 | start: number |
3 | itemsPerPage: number | 3 | count: number |
4 | totalItems: number | ||
5 | } | 4 | } |
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts new file mode 100644 index 000000000..db2cb5e14 --- /dev/null +++ b/client/src/app/shared/rest/rest-table.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { LazyLoadEvent, SortMeta } from 'primeng/primeng' | ||
2 | |||
3 | import { RestPagination } from './rest-pagination' | ||
4 | |||
5 | export abstract class RestTable { | ||
6 | abstract totalRecords: number | ||
7 | abstract rowsPerPage: number | ||
8 | abstract sort: SortMeta | ||
9 | abstract pagination: RestPagination | ||
10 | |||
11 | protected abstract loadData (): void | ||
12 | |||
13 | loadLazy (event: LazyLoadEvent) { | ||
14 | this.sort = { | ||
15 | order: event.sortOrder, | ||
16 | field: event.sortField | ||
17 | } | ||
18 | |||
19 | this.pagination = { | ||
20 | start: event.first, | ||
21 | count: this.rowsPerPage | ||
22 | } | ||
23 | |||
24 | this.loadData() | ||
25 | } | ||
26 | |||
27 | } | ||
diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts index 43dc20b34..f7838ba06 100644 --- a/client/src/app/shared/rest/rest.service.ts +++ b/client/src/app/shared/rest/rest.service.ts | |||
@@ -1,27 +1,34 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { URLSearchParams } from '@angular/http' | 2 | import { HttpParams } from '@angular/common/http' |
3 | import { SortMeta } from 'primeng/primeng' | ||
3 | 4 | ||
4 | import { RestPagination } from './rest-pagination' | 5 | import { RestPagination } from './rest-pagination' |
5 | 6 | ||
6 | @Injectable() | 7 | @Injectable() |
7 | export class RestService { | 8 | export class RestService { |
8 | 9 | ||
9 | buildRestGetParams (pagination?: RestPagination, sort?: string) { | 10 | addRestGetParams (params: HttpParams, pagination?: RestPagination, sort?: SortMeta | string) { |
10 | const params = new URLSearchParams() | 11 | let newParams = params |
11 | 12 | ||
12 | if (pagination) { | 13 | if (pagination !== undefined) { |
13 | const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage | 14 | newParams = newParams.set('start', pagination.start.toString()) |
14 | const count: number = pagination.itemsPerPage | 15 | .set('count', pagination.count.toString()) |
15 | |||
16 | params.set('start', start.toString()) | ||
17 | params.set('count', count.toString()) | ||
18 | } | 16 | } |
19 | 17 | ||
20 | if (sort) { | 18 | if (sort !== undefined) { |
21 | params.set('sort', sort) | 19 | let sortString = '' |
20 | |||
21 | if (typeof sort === 'string') { | ||
22 | sortString = sort | ||
23 | } else { | ||
24 | const sortPrefix = sort.order === 1 ? '' : '-' | ||
25 | sortString = sortPrefix + sort.field | ||
26 | } | ||
27 | |||
28 | newParams = newParams.set('sort', sortString) | ||
22 | } | 29 | } |
23 | 30 | ||
24 | return params | 31 | return newParams |
25 | } | 32 | } |
26 | 33 | ||
27 | } | 34 | } |
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 99b51aa4e..118ce822d 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { HttpClientModule } from '@angular/common/http' | ||
2 | import { CommonModule } from '@angular/common' | 3 | import { CommonModule } from '@angular/common' |
3 | import { HttpModule } from '@angular/http' | ||
4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' | 4 | import { FormsModule, ReactiveFormsModule } from '@angular/forms' |
5 | import { RouterModule } from '@angular/router' | 5 | import { RouterModule } from '@angular/router' |
6 | 6 | ||
@@ -11,9 +11,9 @@ import { ProgressbarModule } from 'ngx-bootstrap/progressbar' | |||
11 | import { PaginationModule } from 'ngx-bootstrap/pagination' | 11 | import { PaginationModule } from 'ngx-bootstrap/pagination' |
12 | import { ModalModule } from 'ngx-bootstrap/modal' | 12 | import { ModalModule } from 'ngx-bootstrap/modal' |
13 | import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload' | 13 | import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload' |
14 | import { Ng2SmartTableModule } from 'ng2-smart-table' | 14 | import { DataTableModule, SharedModule as PrimeSharedModule } from 'primeng/primeng' |
15 | 15 | ||
16 | import { AUTH_HTTP_PROVIDERS } from './auth' | 16 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' |
17 | import { RestExtractor, RestService } from './rest' | 17 | import { RestExtractor, RestService } from './rest' |
18 | import { SearchComponent, SearchService } from './search' | 18 | import { SearchComponent, SearchService } from './search' |
19 | import { UserService } from './users' | 19 | import { UserService } from './users' |
@@ -24,8 +24,8 @@ import { VideoAbuseService } from './video-abuse' | |||
24 | CommonModule, | 24 | CommonModule, |
25 | FormsModule, | 25 | FormsModule, |
26 | ReactiveFormsModule, | 26 | ReactiveFormsModule, |
27 | HttpModule, | ||
28 | RouterModule, | 27 | RouterModule, |
28 | HttpClientModule, | ||
29 | 29 | ||
30 | BsDropdownModule.forRoot(), | 30 | BsDropdownModule.forRoot(), |
31 | ModalModule.forRoot(), | 31 | ModalModule.forRoot(), |
@@ -33,7 +33,9 @@ import { VideoAbuseService } from './video-abuse' | |||
33 | ProgressbarModule.forRoot(), | 33 | ProgressbarModule.forRoot(), |
34 | 34 | ||
35 | FileUploadModule, | 35 | FileUploadModule, |
36 | Ng2SmartTableModule | 36 | |
37 | DataTableModule, | ||
38 | PrimeSharedModule | ||
37 | ], | 39 | ], |
38 | 40 | ||
39 | declarations: [ | 41 | declarations: [ |
@@ -46,15 +48,16 @@ import { VideoAbuseService } from './video-abuse' | |||
46 | CommonModule, | 48 | CommonModule, |
47 | FormsModule, | 49 | FormsModule, |
48 | ReactiveFormsModule, | 50 | ReactiveFormsModule, |
49 | HttpModule, | ||
50 | RouterModule, | 51 | RouterModule, |
52 | HttpClientModule, | ||
51 | 53 | ||
52 | BsDropdownModule, | 54 | BsDropdownModule, |
53 | FileUploadModule, | 55 | FileUploadModule, |
54 | ModalModule, | 56 | ModalModule, |
55 | PaginationModule, | 57 | PaginationModule, |
56 | ProgressbarModule, | 58 | ProgressbarModule, |
57 | Ng2SmartTableModule, | 59 | DataTableModule, |
60 | PrimeSharedModule, | ||
58 | BytesPipe, | 61 | BytesPipe, |
59 | KeysPipe, | 62 | KeysPipe, |
60 | 63 | ||
@@ -62,7 +65,7 @@ import { VideoAbuseService } from './video-abuse' | |||
62 | ], | 65 | ], |
63 | 66 | ||
64 | providers: [ | 67 | providers: [ |
65 | AUTH_HTTP_PROVIDERS, | 68 | AUTH_INTERCEPTOR_PROVIDER, |
66 | RestExtractor, | 69 | RestExtractor, |
67 | RestService, | 70 | RestService, |
68 | SearchService, | 71 | SearchService, |
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index 35180be4d..5c089d221 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts | |||
@@ -1,10 +1,8 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Http } from '@angular/http' | 2 | import { HttpClient } from '@angular/common/http' |
3 | import 'rxjs/add/operator/catch' | 3 | import 'rxjs/add/operator/catch' |
4 | import 'rxjs/add/operator/map' | 4 | import 'rxjs/add/operator/map' |
5 | 5 | ||
6 | import { AuthService } from '../../core' | ||
7 | import { AuthHttp } from '../auth' | ||
8 | import { RestExtractor } from '../rest' | 6 | import { RestExtractor } from '../rest' |
9 | import { UserCreate, UserUpdateMe } from '../../../../../shared' | 7 | import { UserCreate, UserUpdateMe } from '../../../../../shared' |
10 | 8 | ||
@@ -13,9 +11,7 @@ export class UserService { | |||
13 | static BASE_USERS_URL = API_URL + '/api/v1/users/' | 11 | static BASE_USERS_URL = API_URL + '/api/v1/users/' |
14 | 12 | ||
15 | constructor ( | 13 | constructor ( |
16 | private http: Http, | 14 | private authHttp: HttpClient, |
17 | private authHttp: AuthHttp, | ||
18 | private authService: AuthService, | ||
19 | private restExtractor: RestExtractor | 15 | private restExtractor: RestExtractor |
20 | ) {} | 16 | ) {} |
21 | 17 | ||
@@ -34,7 +30,7 @@ export class UserService { | |||
34 | 30 | ||
35 | return this.authHttp.put(url, body) | 31 | return this.authHttp.put(url, body) |
36 | .map(this.restExtractor.extractDataBool) | 32 | .map(this.restExtractor.extractDataBool) |
37 | .catch((res) => this.restExtractor.handleError(res)) | 33 | .catch(res => this.restExtractor.handleError(res)) |
38 | } | 34 | } |
39 | 35 | ||
40 | updateMyDetails (details: UserUpdateMe) { | 36 | updateMyDetails (details: UserUpdateMe) { |
@@ -42,12 +38,12 @@ export class UserService { | |||
42 | 38 | ||
43 | return this.authHttp.put(url, details) | 39 | return this.authHttp.put(url, details) |
44 | .map(this.restExtractor.extractDataBool) | 40 | .map(this.restExtractor.extractDataBool) |
45 | .catch((res) => this.restExtractor.handleError(res)) | 41 | .catch(res => this.restExtractor.handleError(res)) |
46 | } | 42 | } |
47 | 43 | ||
48 | signup (userCreate: UserCreate) { | 44 | signup (userCreate: UserCreate) { |
49 | return this.http.post(UserService.BASE_USERS_URL + 'register', userCreate) | 45 | return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate) |
50 | .map(this.restExtractor.extractDataBool) | 46 | .map(this.restExtractor.extractDataBool) |
51 | .catch(this.restExtractor.handleError) | 47 | .catch(res => this.restExtractor.handleError(res)) |
52 | } | 48 | } |
53 | } | 49 | } |
diff --git a/client/src/app/shared/utils.ts b/client/src/app/shared/utils.ts index c3189a570..7c8ae2e3e 100644 --- a/client/src/app/shared/utils.ts +++ b/client/src/app/shared/utils.ts | |||
@@ -2,15 +2,7 @@ import { DatePipe } from '@angular/common' | |||
2 | 2 | ||
3 | export class Utils { | 3 | export class Utils { |
4 | 4 | ||
5 | static dateToHuman (date: String) { | 5 | static dateToHuman (date: Date) { |
6 | return new DatePipe('en').transform(date, 'medium') | 6 | return new DatePipe('en').transform(date, 'medium') |
7 | } | 7 | } |
8 | |||
9 | static getRowDeleteButton () { | ||
10 | return '<span class="glyphicon glyphicon-remove glyphicon-black"></span>' | ||
11 | } | ||
12 | |||
13 | static getRowEditButton () { | ||
14 | return '<span class="glyphicon glyphicon-pencil glyphicon-black"></span>' | ||
15 | } | ||
16 | } | 8 | } |
diff --git a/client/src/app/shared/video-abuse/video-abuse.service.ts b/client/src/app/shared/video-abuse/video-abuse.service.ts index 636a02084..984581114 100644 --- a/client/src/app/shared/video-abuse/video-abuse.service.ts +++ b/client/src/app/shared/video-abuse/video-abuse.service.ts | |||
@@ -1,42 +1,53 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Http } from '@angular/http' | 2 | import { HttpClient, HttpParams } from '@angular/common/http' |
3 | import { Observable } from 'rxjs/Observable' | ||
4 | import 'rxjs/add/operator/catch' | 3 | import 'rxjs/add/operator/catch' |
5 | import 'rxjs/add/operator/map' | 4 | import 'rxjs/add/operator/map' |
5 | import { Observable } from 'rxjs/Observable' | ||
6 | |||
7 | import { SortMeta } from 'primeng/primeng' | ||
6 | 8 | ||
7 | import { AuthService } from '../core' | 9 | import { AuthService } from '../core' |
8 | import { AuthHttp } from '../auth' | 10 | import { RestExtractor, RestPagination, RestService } from '../rest' |
9 | import { RestDataSource, RestExtractor, ResultList } from '../rest' | 11 | import { Utils } from '../utils' |
10 | import { VideoAbuse } from '../../../../../shared' | 12 | import { ResultList, VideoAbuse } from '../../../../../shared' |
11 | 13 | ||
12 | @Injectable() | 14 | @Injectable() |
13 | export class VideoAbuseService { | 15 | export class VideoAbuseService { |
14 | private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/' | 16 | private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/' |
15 | 17 | ||
16 | constructor ( | 18 | constructor ( |
17 | private authHttp: AuthHttp, | 19 | private authHttp: HttpClient, |
20 | private restService: RestService, | ||
18 | private restExtractor: RestExtractor | 21 | private restExtractor: RestExtractor |
19 | ) {} | 22 | ) {} |
20 | 23 | ||
21 | getDataSource () { | 24 | getVideoAbuses (pagination: RestPagination, sort: SortMeta): Observable<ResultList<VideoAbuse>> { |
22 | return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse') | 25 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse' |
26 | |||
27 | let params = new HttpParams() | ||
28 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
29 | |||
30 | return this.authHttp.get<ResultList<VideoAbuse>>(url, { params }) | ||
31 | .map(res => this.restExtractor.convertResultListDateToHuman(res)) | ||
32 | .map(res => this.restExtractor.applyToResultListData(res, this.formatVideoAbuse.bind(this))) | ||
33 | .catch(res => this.restExtractor.handleError(res)) | ||
23 | } | 34 | } |
24 | 35 | ||
25 | reportVideo (id: number, reason: string) { | 36 | reportVideo (id: number, reason: string) { |
37 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse' | ||
26 | const body = { | 38 | const body = { |
27 | reason | 39 | reason |
28 | } | 40 | } |
29 | const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse' | ||
30 | 41 | ||
31 | return this.authHttp.post(url, body) | 42 | return this.authHttp.post(url, body) |
32 | .map(this.restExtractor.extractDataBool) | 43 | .map(this.restExtractor.extractDataBool) |
33 | .catch((res) => this.restExtractor.handleError(res)) | 44 | .catch(res => this.restExtractor.handleError(res)) |
34 | } | 45 | } |
35 | 46 | ||
36 | private extractVideoAbuses (result: ResultList) { | 47 | private formatVideoAbuse (videoAbuse: VideoAbuse) { |
37 | const videoAbuses: VideoAbuse[] = result.data | 48 | return Object.assign(videoAbuse, { |
38 | const totalVideoAbuses = result.total | 49 | createdAt: Utils.dateToHuman(videoAbuse.createdAt) |
39 | 50 | }) | |
40 | return { videoAbuses, totalVideoAbuses } | ||
41 | } | 51 | } |
52 | |||
42 | } | 53 | } |
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts index 97d795321..8168e3bfd 100644 --- a/client/src/app/videos/shared/index.ts +++ b/client/src/app/videos/shared/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './sort-field.type' | 1 | export * from './sort-field.type' |
2 | export * from './video.model' | 2 | export * from './video.model' |
3 | export * from './video.service' | 3 | export * from './video.service' |
4 | export * from './video-pagination.model' | ||
diff --git a/client/src/app/videos/shared/video-pagination.model.ts b/client/src/app/videos/shared/video-pagination.model.ts new file mode 100644 index 000000000..9e71769cb --- /dev/null +++ b/client/src/app/videos/shared/video-pagination.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export interface VideoPagination { | ||
2 | currentPage: number | ||
3 | itemsPerPage: number | ||
4 | totalItems: number | ||
5 | } | ||
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts index 1a413db9d..17f41059d 100644 --- a/client/src/app/videos/shared/video.model.ts +++ b/client/src/app/videos/shared/video.model.ts | |||
@@ -46,7 +46,7 @@ export class Video implements VideoServerModel { | |||
46 | 46 | ||
47 | constructor (hash: { | 47 | constructor (hash: { |
48 | author: string, | 48 | author: string, |
49 | createdAt: string, | 49 | createdAt: Date | string, |
50 | categoryLabel: string, | 50 | categoryLabel: string, |
51 | category: number, | 51 | category: number, |
52 | licenceLabel: string, | 52 | licenceLabel: string, |
@@ -70,7 +70,7 @@ export class Video implements VideoServerModel { | |||
70 | files: VideoFile[] | 70 | files: VideoFile[] |
71 | }) { | 71 | }) { |
72 | this.author = hash.author | 72 | this.author = hash.author |
73 | this.createdAt = new Date(hash.createdAt) | 73 | this.createdAt = new Date(hash.createdAt.toString()) |
74 | this.categoryLabel = hash.categoryLabel | 74 | this.categoryLabel = hash.categoryLabel |
75 | this.category = hash.category | 75 | this.category = hash.category |
76 | this.licenceLabel = hash.licenceLabel | 76 | this.licenceLabel = hash.licenceLabel |
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts index 67091a8d8..b6d2a0666 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/videos/shared/video.service.ts | |||
@@ -1,27 +1,26 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { Http, Headers, RequestOptions } from '@angular/http' | ||
3 | import { Observable } from 'rxjs/Observable' | 2 | import { Observable } from 'rxjs/Observable' |
4 | import 'rxjs/add/operator/catch' | 3 | import 'rxjs/add/operator/catch' |
5 | import 'rxjs/add/operator/map' | 4 | import 'rxjs/add/operator/map' |
5 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
6 | 6 | ||
7 | import { Search } from '../../shared' | 7 | import { Search } from '../../shared' |
8 | import { SortField } from './sort-field.type' | 8 | import { SortField } from './sort-field.type' |
9 | import { AuthService } from '../../core' | ||
10 | import { | 9 | import { |
11 | AuthHttp, | ||
12 | RestExtractor, | 10 | RestExtractor, |
13 | RestPagination, | ||
14 | RestService, | 11 | RestService, |
15 | ResultList, | ||
16 | UserService | 12 | UserService |
17 | } from '../../shared' | 13 | } from '../../shared' |
18 | import { Video } from './video.model' | 14 | import { Video } from './video.model' |
15 | import { VideoPagination } from './video-pagination.model' | ||
19 | import { | 16 | import { |
20 | UserVideoRate, | 17 | UserVideoRate, |
21 | VideoRateType, | 18 | VideoRateType, |
22 | VideoUpdate, | 19 | VideoUpdate, |
23 | VideoAbuseCreate, | 20 | VideoAbuseCreate, |
24 | UserVideoRateUpdate | 21 | UserVideoRateUpdate, |
22 | Video as VideoServerModel, | ||
23 | ResultList | ||
25 | } from '../../../../../shared' | 24 | } from '../../../../../shared' |
26 | 25 | ||
27 | @Injectable() | 26 | @Injectable() |
@@ -33,9 +32,7 @@ export class VideoService { | |||
33 | videoLanguages: Array<{ id: number, label: string }> = [] | 32 | videoLanguages: Array<{ id: number, label: string }> = [] |
34 | 33 | ||
35 | constructor ( | 34 | constructor ( |
36 | private authService: AuthService, | 35 | private authHttp: HttpClient, |
37 | private authHttp: AuthHttp, | ||
38 | private http: Http, | ||
39 | private restExtractor: RestExtractor, | 36 | private restExtractor: RestExtractor, |
40 | private restService: RestService | 37 | private restService: RestService |
41 | ) {} | 38 | ) {} |
@@ -52,11 +49,10 @@ export class VideoService { | |||
52 | return this.loadVideoAttributeEnum('languages', this.videoLanguages) | 49 | return this.loadVideoAttributeEnum('languages', this.videoLanguages) |
53 | } | 50 | } |
54 | 51 | ||
55 | getVideo (uuid: string): Observable<Video> { | 52 | getVideo (uuid: string) { |
56 | return this.http.get(VideoService.BASE_VIDEO_URL + uuid) | 53 | return this.authHttp.get<VideoServerModel>(VideoService.BASE_VIDEO_URL + uuid) |
57 | .map(this.restExtractor.extractDataGet) | 54 | .map(videoHash => new Video(videoHash)) |
58 | .map(videoHash => new Video(videoHash)) | 55 | .catch((res) => this.restExtractor.handleError(res)) |
59 | .catch((res) => this.restExtractor.handleError(res)) | ||
60 | } | 56 | } |
61 | 57 | ||
62 | updateVideo (video: Video) { | 58 | updateVideo (video: Video) { |
@@ -72,38 +68,41 @@ export class VideoService { | |||
72 | nsfw: video.nsfw | 68 | nsfw: video.nsfw |
73 | } | 69 | } |
74 | 70 | ||
75 | const headers = new Headers({ 'Content-Type': 'application/json' }) | 71 | return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body) |
76 | const options = new RequestOptions({ headers: headers }) | ||
77 | |||
78 | return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options) | ||
79 | .map(this.restExtractor.extractDataBool) | 72 | .map(this.restExtractor.extractDataBool) |
80 | .catch(this.restExtractor.handleError) | 73 | .catch(this.restExtractor.handleError) |
81 | } | 74 | } |
82 | 75 | ||
83 | getVideos (pagination: RestPagination, sort: SortField) { | 76 | getVideos (videoPagination: VideoPagination, sort: SortField) { |
84 | const params = this.restService.buildRestGetParams(pagination, sort) | 77 | const pagination = this.videoPaginationToRestPagination(videoPagination) |
85 | 78 | ||
86 | return this.http.get(VideoService.BASE_VIDEO_URL, { search: params }) | 79 | let params = new HttpParams() |
87 | .map(res => res.json()) | 80 | params = this.restService.addRestGetParams(params, pagination, sort) |
88 | .map(this.extractVideos) | ||
89 | .catch((res) => this.restExtractor.handleError(res)) | ||
90 | } | ||
91 | 81 | ||
92 | removeVideo (id: number) { | 82 | return this.authHttp.get(VideoService.BASE_VIDEO_URL, { params }) |
93 | return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) | 83 | .map(this.extractVideos) |
94 | .map(this.restExtractor.extractDataBool) | ||
95 | .catch((res) => this.restExtractor.handleError(res)) | 84 | .catch((res) => this.restExtractor.handleError(res)) |
96 | } | 85 | } |
97 | 86 | ||
98 | searchVideos (search: Search, pagination: RestPagination, sort: SortField) { | 87 | searchVideos (search: Search, videoPagination: VideoPagination, sort: SortField) { |
99 | const params = this.restService.buildRestGetParams(pagination, sort) | 88 | const url = VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value) |
89 | |||
90 | const pagination = this.videoPaginationToRestPagination(videoPagination) | ||
91 | |||
92 | let params = new HttpParams() | ||
93 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
100 | 94 | ||
101 | if (search.field) params.set('field', search.field) | 95 | if (search.field) params.set('field', search.field) |
102 | 96 | ||
103 | return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params }) | 97 | return this.authHttp.get<ResultList<VideoServerModel>>(url, { params }) |
104 | .map(this.restExtractor.extractDataList) | 98 | .map(this.extractVideos) |
105 | .map(this.extractVideos) | 99 | .catch((res) => this.restExtractor.handleError(res)) |
106 | .catch((res) => this.restExtractor.handleError(res)) | 100 | } |
101 | |||
102 | removeVideo (id: number) { | ||
103 | return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) | ||
104 | .map(this.restExtractor.extractDataBool) | ||
105 | .catch((res) => this.restExtractor.handleError(res)) | ||
107 | } | 106 | } |
108 | 107 | ||
109 | reportVideo (id: number, reason: string) { | 108 | reportVideo (id: number, reason: string) { |
@@ -114,7 +113,7 @@ export class VideoService { | |||
114 | 113 | ||
115 | return this.authHttp.post(url, body) | 114 | return this.authHttp.post(url, body) |
116 | .map(this.restExtractor.extractDataBool) | 115 | .map(this.restExtractor.extractDataBool) |
117 | .catch((res) => this.restExtractor.handleError(res)) | 116 | .catch(res => this.restExtractor.handleError(res)) |
118 | } | 117 | } |
119 | 118 | ||
120 | setVideoLike (id: number) { | 119 | setVideoLike (id: number) { |
@@ -129,14 +128,20 @@ export class VideoService { | |||
129 | const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating' | 128 | const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating' |
130 | 129 | ||
131 | return this.authHttp.get(url) | 130 | return this.authHttp.get(url) |
132 | .map(this.restExtractor.extractDataGet) | 131 | .catch(res => this.restExtractor.handleError(res)) |
133 | .catch((res) => this.restExtractor.handleError(res)) | ||
134 | } | 132 | } |
135 | 133 | ||
136 | blacklistVideo (id: number) { | 134 | blacklistVideo (id: number) { |
137 | return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {}) | 135 | return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {}) |
138 | .map(this.restExtractor.extractDataBool) | 136 | .map(this.restExtractor.extractDataBool) |
139 | .catch((res) => this.restExtractor.handleError(res)) | 137 | .catch(res => this.restExtractor.handleError(res)) |
138 | } | ||
139 | |||
140 | private videoPaginationToRestPagination (videoPagination: VideoPagination) { | ||
141 | const start: number = (videoPagination.currentPage - 1) * videoPagination.itemsPerPage | ||
142 | const count: number = videoPagination.itemsPerPage | ||
143 | |||
144 | return { start, count } | ||
140 | } | 145 | } |
141 | 146 | ||
142 | private setVideoRate (id: number, rateType: VideoRateType) { | 147 | private setVideoRate (id: number, rateType: VideoRateType) { |
@@ -147,13 +152,14 @@ export class VideoService { | |||
147 | 152 | ||
148 | return this.authHttp.put(url, body) | 153 | return this.authHttp.put(url, body) |
149 | .map(this.restExtractor.extractDataBool) | 154 | .map(this.restExtractor.extractDataBool) |
150 | .catch((res) => this.restExtractor.handleError(res)) | 155 | .catch(res => this.restExtractor.handleError(res)) |
151 | } | 156 | } |
152 | 157 | ||
153 | private extractVideos (result: ResultList) { | 158 | private extractVideos (result: ResultList<VideoServerModel>) { |
154 | const videosJson = result.data | 159 | const videosJson = result.data |
155 | const totalVideos = result.total | 160 | const totalVideos = result.total |
156 | const videos = [] | 161 | const videos = [] |
162 | |||
157 | for (const videoJson of videosJson) { | 163 | for (const videoJson of videosJson) { |
158 | videos.push(new Video(videoJson)) | 164 | videos.push(new Video(videoJson)) |
159 | } | 165 | } |
@@ -162,15 +168,14 @@ export class VideoService { | |||
162 | } | 168 | } |
163 | 169 | ||
164 | private loadVideoAttributeEnum (attributeName: 'categories' | 'licences' | 'languages', hashToPopulate: { id: number, label: string }[]) { | 170 | private loadVideoAttributeEnum (attributeName: 'categories' | 'licences' | 'languages', hashToPopulate: { id: number, label: string }[]) { |
165 | return this.http.get(VideoService.BASE_VIDEO_URL + attributeName) | 171 | return this.authHttp.get(VideoService.BASE_VIDEO_URL + attributeName) |
166 | .map(this.restExtractor.extractDataGet) | 172 | .subscribe(data => { |
167 | .subscribe(data => { | 173 | Object.keys(data).forEach(dataKey => { |
168 | Object.keys(data).forEach(dataKey => { | 174 | hashToPopulate.push({ |
169 | hashToPopulate.push({ | 175 | id: parseInt(dataKey, 10), |
170 | id: parseInt(dataKey, 10), | 176 | label: data[dataKey] |
171 | label: data[dataKey] | 177 | }) |
178 | }) | ||
172 | }) | 179 | }) |
173 | }) | ||
174 | }) | ||
175 | } | 180 | } |
176 | } | 181 | } |
diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts index 4ac539960..590632063 100644 --- a/client/src/app/videos/video-list/video-list.component.ts +++ b/client/src/app/videos/video-list/video-list.component.ts | |||
@@ -8,11 +8,12 @@ import { NotificationsService } from 'angular2-notifications' | |||
8 | import { | 8 | import { |
9 | SortField, | 9 | SortField, |
10 | Video, | 10 | Video, |
11 | VideoService | 11 | VideoService, |
12 | VideoPagination | ||
12 | } from '../shared' | 13 | } from '../shared' |
13 | import { AuthService, AuthUser } from '../../core' | 14 | import { AuthService, AuthUser } from '../../core' |
14 | import { RestPagination, Search, SearchField } from '../../shared' | 15 | import { Search, SearchField, SearchService } from '../../shared' |
15 | import { SearchService } from '../../shared' | 16 | import { } from '../../shared' |
16 | 17 | ||
17 | @Component({ | 18 | @Component({ |
18 | selector: 'my-videos-list', | 19 | selector: 'my-videos-list', |
@@ -21,7 +22,7 @@ import { SearchService } from '../../shared' | |||
21 | }) | 22 | }) |
22 | export class VideoListComponent implements OnInit, OnDestroy { | 23 | export class VideoListComponent implements OnInit, OnDestroy { |
23 | loading: BehaviorSubject<boolean> = new BehaviorSubject(false) | 24 | loading: BehaviorSubject<boolean> = new BehaviorSubject(false) |
24 | pagination: RestPagination = { | 25 | pagination: VideoPagination = { |
25 | currentPage: 1, | 26 | currentPage: 1, |
26 | itemsPerPage: 25, | 27 | itemsPerPage: 25, |
27 | totalItems: null | 28 | totalItems: null |
@@ -152,6 +153,6 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
152 | 153 | ||
153 | private navigateToNewParams () { | 154 | private navigateToNewParams () { |
154 | const routeParams = this.buildRouteParams() | 155 | const routeParams = this.buildRouteParams() |
155 | this.router.navigate(['/videos/list', routeParams]) | 156 | this.router.navigate([ '/videos/list', routeParams ]) |
156 | } | 157 | } |
157 | } | 158 | } |
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 285339d42..cd573841d 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -1,4 +1,6 @@ | |||
1 | @import '../../node_modules/video.js/dist/video-js.css'; | 1 | @import '~primeng/resources/themes/bootstrap/theme.css'; |
2 | @import '~primeng/resources/primeng.css'; | ||
3 | @import '~video.js/dist/video-js.css'; | ||
2 | @import './video-js-custom.scss'; | 4 | @import './video-js-custom.scss'; |
3 | 5 | ||
4 | [hidden] { | 6 | [hidden] { |
@@ -45,23 +47,13 @@ input.readonly { | |||
45 | } | 47 | } |
46 | } | 48 | } |
47 | 49 | ||
48 | /* some fixes for ng2-smart-table */ | 50 | /* ngprime data table customizations */ |
49 | ng2-smart-table { | 51 | p-datatable { |
50 | thead tr { | 52 | .action-cell { |
51 | border-top: 1px solid rgb(233, 235, 236) | 53 | text-align: center; |
52 | } | ||
53 | |||
54 | td, th { | ||
55 | padding: 8px !important; | ||
56 | color: #333333 !important; | ||
57 | font-size: 14px !important; | ||
58 | } | ||
59 | 54 | ||
60 | .ng2-smart-pagination-nav .page-link { | 55 | .glyphicon { |
61 | font-size: 11px !important; | 56 | cursor: pointer; |
62 | } | 57 | } |
63 | |||
64 | .glyphicon { | ||
65 | font-family: 'Glyphicons Halflings' !important; | ||
66 | } | 58 | } |
67 | } | 59 | } |
diff --git a/client/yarn.lock b/client/yarn.lock index 9478e23b2..3552dbf99 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -4436,10 +4436,6 @@ ng-router-loader@^2.0.0: | |||
4436 | loader-utils "^0.2.16" | 4436 | loader-utils "^0.2.16" |
4437 | recast "^0.11.20" | 4437 | recast "^0.11.20" |
4438 | 4438 | ||
4439 | ng2-completer@^1.2.2: | ||
4440 | version "1.6.1" | ||
4441 | resolved "https://registry.yarnpkg.com/ng2-completer/-/ng2-completer-1.6.1.tgz#62bad1a0a1d99c62b15f6723911ee0a3a00c91bb" | ||
4442 | |||
4443 | ng2-file-upload@^1.1.4-2: | 4439 | ng2-file-upload@^1.1.4-2: |
4444 | version "1.2.1" | 4440 | version "1.2.1" |
4445 | resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.2.1.tgz#5563c5dfd6f43fbfbe815c206e343464a0a6a197" | 4441 | resolved "https://registry.yarnpkg.com/ng2-file-upload/-/ng2-file-upload-1.2.1.tgz#5563c5dfd6f43fbfbe815c206e343464a0a6a197" |
@@ -4448,13 +4444,6 @@ ng2-material-dropdown@0.7.10: | |||
4448 | version "0.7.10" | 4444 | version "0.7.10" |
4449 | resolved "https://registry.yarnpkg.com/ng2-material-dropdown/-/ng2-material-dropdown-0.7.10.tgz#093471f2a9cadd726cbcb120b0ad7818a54fa5ed" | 4445 | resolved "https://registry.yarnpkg.com/ng2-material-dropdown/-/ng2-material-dropdown-0.7.10.tgz#093471f2a9cadd726cbcb120b0ad7818a54fa5ed" |
4450 | 4446 | ||
4451 | ng2-smart-table@1.2.1: | ||
4452 | version "1.2.1" | ||
4453 | resolved "https://registry.yarnpkg.com/ng2-smart-table/-/ng2-smart-table-1.2.1.tgz#b25102c1a8b0588c508cf913c539ddf0f0b3341d" | ||
4454 | dependencies: | ||
4455 | lodash "^4.17.4" | ||
4456 | ng2-completer "^1.2.2" | ||
4457 | |||
4458 | ngc-webpack@3.2.2: | 4447 | ngc-webpack@3.2.2: |
4459 | version "3.2.2" | 4448 | version "3.2.2" |
4460 | resolved "https://registry.yarnpkg.com/ngc-webpack/-/ngc-webpack-3.2.2.tgz#1905c40e3c7d30c86fe029c7a7fda71cb4dc59df" | 4449 | resolved "https://registry.yarnpkg.com/ngc-webpack/-/ngc-webpack-3.2.2.tgz#1905c40e3c7d30c86fe029c7a7fda71cb4dc59df" |
@@ -5340,6 +5329,10 @@ pretty-error@^2.0.2: | |||
5340 | renderkid "^2.0.1" | 5329 | renderkid "^2.0.1" |
5341 | utila "~0.4" | 5330 | utila "~0.4" |
5342 | 5331 | ||
5332 | primeng@^4.2.0: | ||
5333 | version "4.2.0" | ||
5334 | resolved "https://registry.yarnpkg.com/primeng/-/primeng-4.2.0.tgz#49c8c99de26d254f41d3fbb8759227fe1d269772" | ||
5335 | |||
5343 | private@^0.1.6, private@^0.1.7, private@~0.1.5: | 5336 | private@^0.1.6, private@^0.1.7, private@~0.1.5: |
5344 | version "0.1.7" | 5337 | version "0.1.7" |
5345 | resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" | 5338 | resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" |
diff --git a/server/lib/friends.ts b/server/lib/friends.ts index ea9ddbe8d..c0dd24c53 100644 --- a/server/lib/friends.ts +++ b/server/lib/friends.ts | |||
@@ -190,6 +190,7 @@ function quitFriends () { | |||
190 | .catch(err => { | 190 | .catch(err => { |
191 | logger.error('Some errors while quitting friends.', err) | 191 | logger.error('Some errors while quitting friends.', err) |
192 | // Don't stop the process | 192 | // Don't stop the process |
193 | return pods | ||
193 | }) | 194 | }) |
194 | }) | 195 | }) |
195 | .then(pods => { | 196 | .then(pods => { |
diff --git a/shared/models/users/index.ts b/shared/models/users/index.ts index 45dbc7b8f..efb58c320 100644 --- a/shared/models/users/index.ts +++ b/shared/models/users/index.ts | |||
@@ -1,5 +1,7 @@ | |||
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-login.model' | ||
4 | export * from './user-refresh-token.model' | ||
3 | export * from './user-update.model' | 5 | export * from './user-update.model' |
4 | export * from './user-update-me.model' | 6 | export * from './user-update-me.model' |
5 | export * from './user-role.type' | 7 | export * from './user-role.type' |
diff --git a/shared/models/users/user-login.model.ts b/shared/models/users/user-login.model.ts new file mode 100644 index 000000000..b0383c695 --- /dev/null +++ b/shared/models/users/user-login.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export interface UserLogin { | ||
2 | access_token: string | ||
3 | refresh_token: string | ||
4 | token_type: string | ||
5 | } \ No newline at end of file | ||
diff --git a/shared/models/users/user-refresh-token.model.ts b/shared/models/users/user-refresh-token.model.ts new file mode 100644 index 000000000..f528dd961 --- /dev/null +++ b/shared/models/users/user-refresh-token.model.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export interface UserRefreshToken { | ||
2 | access_token: string | ||
3 | refresh_token: string | ||
4 | } | ||
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index 6277dbe59..75070bfd6 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts | |||
@@ -9,8 +9,8 @@ export interface Video { | |||
9 | id: number | 9 | id: number |
10 | uuid: string | 10 | uuid: string |
11 | author: string | 11 | author: string |
12 | createdAt: Date | 12 | createdAt: Date | string |
13 | updatedAt: Date | 13 | updatedAt: Date | string |
14 | categoryLabel: string | 14 | categoryLabel: string |
15 | category: number | 15 | category: number |
16 | licenceLabel: string | 16 | licenceLabel: string |