aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/package.json2
-rw-r--r--client/src/app/+admin/friends/friend-add/friend-add.component.ts5
-rw-r--r--client/src/app/+admin/friends/friend-list/friend-list.component.html15
-rw-r--r--client/src/app/+admin/friends/friend-list/friend-list.component.ts88
-rw-r--r--client/src/app/+admin/friends/shared/friend.service.ts24
-rw-r--r--client/src/app/+admin/request-schedulers/shared/request-schedulers.service.ts16
-rw-r--r--client/src/app/+admin/users/shared/user.service.ts56
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html27
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts91
-rw-r--r--client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.html18
-rw-r--r--client/src/app/+admin/video-abuses/video-abuse-list/video-abuse-list.component.ts84
-rw-r--r--client/src/app/account/account-details/account-details.component.ts2
-rw-r--r--client/src/app/app.component.ts2
-rw-r--r--client/src/app/app.module.ts2
-rw-r--r--client/src/app/core/auth/auth.service.ts207
-rw-r--r--client/src/app/core/config/config.service.ts15
-rw-r--r--client/src/app/shared/auth/auth-http.service.ts93
-rw-r--r--client/src/app/shared/auth/auth-interceptor.service.ts62
-rw-r--r--client/src/app/shared/auth/index.ts2
-rw-r--r--client/src/app/shared/rest/index.ts1
-rw-r--r--client/src/app/shared/rest/rest-data-source.ts98
-rw-r--r--client/src/app/shared/rest/rest-extractor.service.ts66
-rw-r--r--client/src/app/shared/rest/rest-pagination.ts5
-rw-r--r--client/src/app/shared/rest/rest-table.ts27
-rw-r--r--client/src/app/shared/rest/rest.service.ts31
-rw-r--r--client/src/app/shared/shared.module.ts19
-rw-r--r--client/src/app/shared/users/user.service.ts16
-rw-r--r--client/src/app/shared/utils.ts10
-rw-r--r--client/src/app/shared/video-abuse/video-abuse.service.ts41
-rw-r--r--client/src/app/videos/shared/index.ts1
-rw-r--r--client/src/app/videos/shared/video-pagination.model.ts5
-rw-r--r--client/src/app/videos/shared/video.model.ts4
-rw-r--r--client/src/app/videos/shared/video.service.ts111
-rw-r--r--client/src/app/videos/video-list/video-list.component.ts11
-rw-r--r--client/src/sass/application.scss28
-rw-r--r--client/yarn.lock15
36 files changed, 606 insertions, 694 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 @@
1import { Component } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications' 3import { NotificationsService } from 'angular2-notifications'
4import { ServerDataSource } from 'ng2-smart-table'
5 4
6import { ConfirmService } from '../../../core' 5import { ConfirmService } from '../../../core'
7import { Utils } from '../../../shared'
8import { FriendService } from '../shared' 6import { FriendService } from '../shared'
9import { Pod } from '../../../../../../shared' 7import { 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})
16export class FriendListComponent { 14export 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { Observable } from 'rxjs/Observable' 2import { HttpClient } from '@angular/common/http'
3import 'rxjs/add/operator/catch' 3import 'rxjs/add/operator/catch'
4import 'rxjs/add/operator/map' 4import 'rxjs/add/operator/map'
5 5
6import { ServerDataSource } from 'ng2-smart-table' 6import { RestExtractor, } from '../../../shared'
7 7import { Pod, ResultList } from '../../../../../../shared'
8import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared'
9import { Pod } from '../../../../../../shared'
10 8
11@Injectable() 9@Injectable()
12export class FriendService { 10export 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { HttpClient } from '@angular/common/http'
2import { Observable } from 'rxjs/Observable' 3import { Observable } from 'rxjs/Observable'
3import 'rxjs/add/operator/catch' 4import 'rxjs/add/operator/catch'
4import 'rxjs/add/operator/map' 5import 'rxjs/add/operator/map'
5 6
6import { RequestSchedulerStats } from '../../../../../../shared' 7import { RequestSchedulerStats } from '../../../../../../shared'
7import { AuthHttp, RestExtractor } from '../../../shared' 8import { RestExtractor } from '../../../shared'
8import { RequestSchedulerStatsAttributes } from './request-schedulers-stats-attributes.model' 9import { 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Observable } from 'rxjs/Observable'
2import 'rxjs/add/operator/catch' 4import 'rxjs/add/operator/catch'
3import 'rxjs/add/operator/map' 5import 'rxjs/add/operator/map'
4 6
7import { SortMeta } from 'primeng/primeng'
5import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' 8import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'
6 9
7import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' 10import { RestExtractor, User, RestPagination, RestService } from '../../../shared'
8import { UserCreate, UserUpdate } from '../../../../../../shared' 11import { UserCreate, UserUpdate, ResultList } from '../../../../../../shared'
9 12
10@Injectable() 13@Injectable()
11export class UserService { 14export 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 @@
1import { Component } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { SortMeta } from 'primeng/primeng'
2 3
3import { NotificationsService } from 'angular2-notifications' 4import { NotificationsService } from 'angular2-notifications'
4 5
5import { ConfirmService } from '../../../core' 6import { ConfirmService } from '../../../core'
6import { RestDataSource, User, Utils } from '../../../shared' 7import { RestTable, RestPagination, User } from '../../../shared'
7import { UserService } from '../shared' 8import { UserService } from '../shared'
8import { 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})
15export class UserListComponent { 15export 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 @@
1import { Component } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications' 3import { NotificationsService } from 'angular2-notifications'
4import { SortMeta } from 'primeng/primeng'
4 5
5import { Utils, VideoAbuseService } from '../../../shared' 6import { RestTable, RestPagination, VideoAbuseService } from '../../../shared'
6import { VideoAbuse } from '../../../../../shared' 7import { 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})
12export class VideoAbuseListComponent { 13export 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
9import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' 9import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core'
10// TODO: remove, we need this to avoid error in ng2-smart-table
11import 'rxjs/add/operator/toPromise'
12import 'bootstrap-loader' 10import 'bootstrap-loader'
13 11
14import { ENV_PROVIDERS } from './environment' 12import { 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { Headers, Http, Response, URLSearchParams } from '@angular/http'
3import { Router } from '@angular/router' 2import { Router } from '@angular/router'
4import { Observable } from 'rxjs/Observable' 3import { Observable } from 'rxjs/Observable'
5import { Subject } from 'rxjs/Subject' 4import { Subject } from 'rxjs/Subject'
5import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
6import 'rxjs/add/operator/map' 6import 'rxjs/add/operator/map'
7import 'rxjs/add/operator/mergeMap' 7import 'rxjs/add/operator/mergeMap'
8import 'rxjs/add/observable/throw' 8import 'rxjs/add/observable/throw'
@@ -11,15 +11,35 @@ import { NotificationsService } from 'angular2-notifications'
11 11
12import { AuthStatus } from './auth-status.model' 12import { AuthStatus } from './auth-status.model'
13import { AuthUser } from './auth-user.model' 13import { AuthUser } from './auth-user.model'
14import { OAuthClientLocal, UserRole } from '../../../../../shared' 14import { OAuthClientLocal, UserRole, UserRefreshToken } from '../../../../../shared'
15// Do not use the barrel (dependency loop) 15// Do not use the barrel (dependency loop)
16import { RestExtractor } from '../../shared/rest' 16import { RestExtractor } from '../../shared/rest'
17import { UserLogin } from '../../../../../shared/models/users/user-login.model'
18import { User } from '../../shared/users/user.model'
19
20interface UserLoginWithUsername extends UserLogin {
21 access_token: string
22 refresh_token: string
23 token_type: string
24 username: string
25}
26
27interface 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()
19export class AuthService { 39export 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { Http } from '@angular/http' 2import { HttpClient } from '@angular/common/http'
3 3
4import { RestExtractor } from '../../shared/rest'
5import { ServerConfig } from '../../../../../shared' 4import { 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 @@
1import { Injectable } from '@angular/core'
2import {
3 ConnectionBackend,
4 Headers,
5 Http,
6 Request,
7 RequestMethod,
8 RequestOptions,
9 RequestOptionsArgs,
10 Response,
11 XHRBackend
12} from '@angular/http'
13import { Observable } from 'rxjs/Observable'
14
15import { AuthService } from '../../core'
16
17@Injectable()
18export 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
83export function useFactory (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) {
84 return new AuthHttp(backend, defaultOptions, authService)
85}
86
87export 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 @@
1import { Injectable, Injector } from '@angular/core'
2import {
3 HttpInterceptor,
4 HttpRequest,
5 HttpEvent,
6 HttpHandler, HTTP_INTERCEPTORS
7} from '@angular/common/http'
8import { Observable } from 'rxjs/Observable'
9
10import { AuthService } from '../../core'
11import 'rxjs/add/operator/switchMap'
12
13@Injectable()
14export 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
58export 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'
2export * from './rest-extractor.service' 2export * from './rest-extractor.service'
3export * from './rest-pagination' 3export * from './rest-pagination'
4export * from './rest.service' 4export * from './rest.service'
5export * 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 @@
1import { Http, RequestOptionsArgs, URLSearchParams, Response } from '@angular/http' 1export class RestDataSource {
2 2 // protected addSortRequestOptions (requestOptions: RequestOptionsArgs) {
3import { ServerDataSource } from 'ng2-smart-table' 3 // const searchParams = requestOptions.params as URLSearchParams
4 4 //
5export 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { Response } from '@angular/http'
3import { Observable } from 'rxjs/Observable' 2import { Observable } from 'rxjs/Observable'
3import { HttpErrorResponse } from '@angular/common/http'
4 4
5export interface ResultList { 5import { Utils } from '../utils'
6 data: any[] 6import { ResultList } from '../../../../../shared'
7 total: number
8}
9 7
10@Injectable() 8@Injectable()
11export class RestExtractor { 9export 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 @@
1export interface RestPagination { 1export 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 @@
1import { LazyLoadEvent, SortMeta } from 'primeng/primeng'
2
3import { RestPagination } from './rest-pagination'
4
5export 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { URLSearchParams } from '@angular/http' 2import { HttpParams } from '@angular/common/http'
3import { SortMeta } from 'primeng/primeng'
3 4
4import { RestPagination } from './rest-pagination' 5import { RestPagination } from './rest-pagination'
5 6
6@Injectable() 7@Injectable()
7export class RestService { 8export 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 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { HttpClientModule } from '@angular/common/http'
2import { CommonModule } from '@angular/common' 3import { CommonModule } from '@angular/common'
3import { HttpModule } from '@angular/http'
4import { FormsModule, ReactiveFormsModule } from '@angular/forms' 4import { FormsModule, ReactiveFormsModule } from '@angular/forms'
5import { RouterModule } from '@angular/router' 5import { RouterModule } from '@angular/router'
6 6
@@ -11,9 +11,9 @@ import { ProgressbarModule } from 'ngx-bootstrap/progressbar'
11import { PaginationModule } from 'ngx-bootstrap/pagination' 11import { PaginationModule } from 'ngx-bootstrap/pagination'
12import { ModalModule } from 'ngx-bootstrap/modal' 12import { ModalModule } from 'ngx-bootstrap/modal'
13import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload' 13import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'
14import { Ng2SmartTableModule } from 'ng2-smart-table' 14import { DataTableModule, SharedModule as PrimeSharedModule } from 'primeng/primeng'
15 15
16import { AUTH_HTTP_PROVIDERS } from './auth' 16import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
17import { RestExtractor, RestService } from './rest' 17import { RestExtractor, RestService } from './rest'
18import { SearchComponent, SearchService } from './search' 18import { SearchComponent, SearchService } from './search'
19import { UserService } from './users' 19import { 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { Http } from '@angular/http' 2import { HttpClient } from '@angular/common/http'
3import 'rxjs/add/operator/catch' 3import 'rxjs/add/operator/catch'
4import 'rxjs/add/operator/map' 4import 'rxjs/add/operator/map'
5 5
6import { AuthService } from '../../core'
7import { AuthHttp } from '../auth'
8import { RestExtractor } from '../rest' 6import { RestExtractor } from '../rest'
9import { UserCreate, UserUpdateMe } from '../../../../../shared' 7import { 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
3export class Utils { 3export 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { Http } from '@angular/http' 2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Observable } from 'rxjs/Observable'
4import 'rxjs/add/operator/catch' 3import 'rxjs/add/operator/catch'
5import 'rxjs/add/operator/map' 4import 'rxjs/add/operator/map'
5import { Observable } from 'rxjs/Observable'
6
7import { SortMeta } from 'primeng/primeng'
6 8
7import { AuthService } from '../core' 9import { AuthService } from '../core'
8import { AuthHttp } from '../auth' 10import { RestExtractor, RestPagination, RestService } from '../rest'
9import { RestDataSource, RestExtractor, ResultList } from '../rest' 11import { Utils } from '../utils'
10import { VideoAbuse } from '../../../../../shared' 12import { ResultList, VideoAbuse } from '../../../../../shared'
11 13
12@Injectable() 14@Injectable()
13export class VideoAbuseService { 15export 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 @@
1export * from './sort-field.type' 1export * from './sort-field.type'
2export * from './video.model' 2export * from './video.model'
3export * from './video.service' 3export * from './video.service'
4export * 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 @@
1export 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 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { Http, Headers, RequestOptions } from '@angular/http'
3import { Observable } from 'rxjs/Observable' 2import { Observable } from 'rxjs/Observable'
4import 'rxjs/add/operator/catch' 3import 'rxjs/add/operator/catch'
5import 'rxjs/add/operator/map' 4import 'rxjs/add/operator/map'
5import { HttpClient, HttpParams } from '@angular/common/http'
6 6
7import { Search } from '../../shared' 7import { Search } from '../../shared'
8import { SortField } from './sort-field.type' 8import { SortField } from './sort-field.type'
9import { AuthService } from '../../core'
10import { 9import {
11 AuthHttp,
12 RestExtractor, 10 RestExtractor,
13 RestPagination,
14 RestService, 11 RestService,
15 ResultList,
16 UserService 12 UserService
17} from '../../shared' 13} from '../../shared'
18import { Video } from './video.model' 14import { Video } from './video.model'
15import { VideoPagination } from './video-pagination.model'
19import { 16import {
20 UserVideoRate, 17UserVideoRate,
21 VideoRateType, 18VideoRateType,
22 VideoUpdate, 19VideoUpdate,
23 VideoAbuseCreate, 20VideoAbuseCreate,
24 UserVideoRateUpdate 21UserVideoRateUpdate,
22Video as VideoServerModel,
23ResultList
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'
8import { 8import {
9 SortField, 9 SortField,
10 Video, 10 Video,
11 VideoService 11 VideoService,
12 VideoPagination
12} from '../shared' 13} from '../shared'
13import { AuthService, AuthUser } from '../../core' 14import { AuthService, AuthUser } from '../../core'
14import { RestPagination, Search, SearchField } from '../../shared' 15import { Search, SearchField, SearchService } from '../../shared'
15import { SearchService } from '../../shared' 16import { } 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})
22export class VideoListComponent implements OnInit, OnDestroy { 23export 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 */
49ng2-smart-table { 51p-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
4439ng2-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
4443ng2-file-upload@^1.1.4-2: 4439ng2-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
4451ng2-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
4458ngc-webpack@3.2.2: 4447ngc-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
5332primeng@^4.2.0:
5333 version "4.2.0"
5334 resolved "https://registry.yarnpkg.com/primeng/-/primeng-4.2.0.tgz#49c8c99de26d254f41d3fbb8759227fe1d269772"
5335
5343private@^0.1.6, private@^0.1.7, private@~0.1.5: 5336private@^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"