From d592e0a9b2931c7c9cbedb27fb8efc9aaacad9bb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 14 Sep 2017 11:57:49 +0200 Subject: [PATCH] Move to HttpClient and PrimeNG data table --- client/package.json | 2 +- .../friend-add/friend-add.component.ts | 5 +- .../friend-list/friend-list.component.html | 15 +- .../friend-list/friend-list.component.ts | 88 +++----- .../+admin/friends/shared/friend.service.ts | 24 +-- .../shared/request-schedulers.service.ts | 16 +- .../app/+admin/users/shared/user.service.ts | 56 ++--- .../users/user-list/user-list.component.html | 27 ++- .../users/user-list/user-list.component.ts | 91 +++----- .../video-abuse-list.component.html | 18 +- .../video-abuse-list.component.ts | 84 +++----- .../account-details.component.ts | 2 +- client/src/app/app.component.ts | 2 + client/src/app/app.module.ts | 2 - client/src/app/core/auth/auth.service.ts | 201 ++++++++---------- client/src/app/core/config/config.service.ts | 15 +- .../src/app/shared/auth/auth-http.service.ts | 93 -------- .../shared/auth/auth-interceptor.service.ts | 62 ++++++ client/src/app/shared/auth/index.ts | 2 +- client/src/app/shared/rest/index.ts | 1 + .../src/app/shared/rest/rest-data-source.ts | 98 +++------ .../app/shared/rest/rest-extractor.service.ts | 68 +++--- client/src/app/shared/rest/rest-pagination.ts | 5 +- client/src/app/shared/rest/rest-table.ts | 27 +++ client/src/app/shared/rest/rest.service.ts | 31 +-- client/src/app/shared/shared.module.ts | 19 +- client/src/app/shared/users/user.service.ts | 16 +- client/src/app/shared/utils.ts | 10 +- .../shared/video-abuse/video-abuse.service.ts | 41 ++-- client/src/app/videos/shared/index.ts | 1 + .../videos/shared/video-pagination.model.ts | 5 + client/src/app/videos/shared/video.model.ts | 4 +- client/src/app/videos/shared/video.service.ts | 117 +++++----- .../videos/video-list/video-list.component.ts | 11 +- client/src/sass/application.scss | 28 +-- client/yarn.lock | 15 +- server/lib/friends.ts | 1 + shared/models/users/index.ts | 2 + shared/models/users/user-login.model.ts | 5 + .../models/users/user-refresh-token.model.ts | 4 + shared/models/videos/video.model.ts | 4 +- 41 files changed, 621 insertions(+), 697 deletions(-) delete mode 100644 client/src/app/shared/auth/auth-http.service.ts create mode 100644 client/src/app/shared/auth/auth-interceptor.service.ts create mode 100644 client/src/app/shared/rest/rest-table.ts create mode 100644 client/src/app/videos/shared/video-pagination.model.ts create mode 100644 shared/models/users/user-login.model.ts create mode 100644 shared/models/users/user-refresh-token.model.ts 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 @@ "json-loader": "^0.5.4", "ng-router-loader": "^2.0.0", "ng2-file-upload": "^1.1.4-2", - "ng2-smart-table": "1.2.1", "ngc-webpack": "3.2.2", "ngx-bootstrap": "1.9.1", "ngx-chips": "1.5.3", @@ -97,6 +96,7 @@ "add-asset-html-webpack-plugin": "^2.0.1", "codelyzer": "^3.0.0-beta.4", "extract-text-webpack-plugin": "^3.0.0", + "primeng": "^4.2.0", "purify-css": "^1.2.5", "purifycss-webpack": "^0.7.0", "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 { this.friendService.makeFriends(notEmptyHosts).subscribe( status => { - this.notificationsService.success('Sucess', 'Make friends request sent!') - this.router.navigate([ '/admin/friends/list' ]) + this.notificationsService.success('Success', 'Make friends request sent!') + // Wait requests between pods + setTimeout(() => this.router.navigate([ '/admin/friends/list' ]), 1000) }, 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 @@

Friends list

- + + + + + + + + + + + + Quit friends - + Make friends
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 @@ -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { NotificationsService } from 'angular2-notifications' -import { ServerDataSource } from 'ng2-smart-table' import { ConfirmService } from '../../../core' -import { Utils } from '../../../shared' import { FriendService } from '../shared' import { Pod } from '../../../../../../shared' @Component({ selector: 'my-friend-list', templateUrl: './friend-list.component.html', - styleUrls: [ './friend-list.component.scss' ] + styleUrls: ['./friend-list.component.scss'] }) -export class FriendListComponent { - friendsSource = null - tableSettings = { - mode: 'external', - attr: { - class: 'table-hover' - }, - hideSubHeader: true, - actions: { - position: 'right', - add: false, - edit: false, - delete: true - }, - delete: { - deleteButtonContent: Utils.getRowDeleteButton() - }, - columns: { - id: { - title: 'ID', - sort: false, - sortDirection: 'asc' - }, - host: { - title: 'Host', - sort: false - }, - email: { - title: 'Email', - sort: false - }, - score: { - title: 'Score', - sort: false - }, - createdAt: { - title: 'Created Date', - sort: false, - valuePrepareFunction: Utils.dateToHuman - } - } - } +export class FriendListComponent implements OnInit { + friends: Pod[] = [] constructor ( private notificationsService: NotificationsService, private confirmService: ConfirmService, private friendService: FriendService - ) { - this.friendsSource = this.friendService.getDataSource() + ) {} + + ngOnInit () { + this.loadData() } hasFriends () { - return this.friendsSource.count() !== 0 + return this.friends.length !== 0 } quitFriends () { @@ -77,32 +37,42 @@ export class FriendListComponent { this.friendService.quitFriends().subscribe( status => { this.notificationsService.success('Success', 'Friends left!') - this.friendsSource.refresh() + this.loadData() }, - err => this.notificationsService.error('Error', err.text) + err => this.notificationsService.error('Error', err) ) } ) } - removeFriend ({ data }) { + removeFriend (friend: Pod) { const confirmMessage = 'Do you really want to remove this friend ? All its videos will be deleted.' - const friend: Pod = data this.confirmService.confirm(confirmMessage, 'Remove').subscribe( res => { if (res === false) return this.friendService.removeFriend(friend).subscribe( - status => { - this.notificationsService.success('Success', 'Friend removed') - this.friendsSource.refresh() - }, + status => { + this.notificationsService.success('Success', 'Friend removed') + this.loadData() + }, - err => this.notificationsService.error('Error', err.text) - ) + err => this.notificationsService.error('Error', err) + ) } ) } + + private loadData () { + this.friendService.getFriends() + .subscribe( + resultList => { + this.friends = resultList.data + }, + + err => this.notificationsService.error('Error', err) + ) + } } 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 @@ import { Injectable } from '@angular/core' -import { Observable } from 'rxjs/Observable' +import { HttpClient } from '@angular/common/http' import 'rxjs/add/operator/catch' import 'rxjs/add/operator/map' -import { ServerDataSource } from 'ng2-smart-table' - -import { AuthHttp, RestExtractor, RestDataSource, ResultList } from '../../../shared' -import { Pod } from '../../../../../../shared' +import { RestExtractor, } from '../../../shared' +import { Pod, ResultList } from '../../../../../../shared' @Injectable() export class FriendService { private static BASE_FRIEND_URL = API_URL + '/api/v1/pods/' constructor ( - private authHttp: AuthHttp, + private authHttp: HttpClient, private restExtractor: RestExtractor ) {} - getDataSource () { - return new RestDataSource(this.authHttp, FriendService.BASE_FRIEND_URL) + getFriends () { + return this.authHttp.get>(FriendService.BASE_FRIEND_URL) + .map(res => this.restExtractor.convertResultListDateToHuman(res)) + .catch(res => this.restExtractor.handleError(res)) } makeFriends (notEmptyHosts: String[]) { @@ -28,18 +28,18 @@ export class FriendService { return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'make-friends', body) .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)) + .catch(res => this.restExtractor.handleError(res)) } quitFriends () { return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quit-friends') - .map(res => res.status) - .catch((res) => this.restExtractor.handleError(res)) + .map(this.restExtractor.extractDataBool) + .catch(res => this.restExtractor.handleError(res)) } removeFriend (friend: Pod) { return this.authHttp.delete(FriendService.BASE_FRIEND_URL + friend.id) .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)) + .catch(res => this.restExtractor.handleError(res)) } } 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 @@ import { Injectable } from '@angular/core' +import { HttpClient } from '@angular/common/http' import { Observable } from 'rxjs/Observable' import 'rxjs/add/operator/catch' import 'rxjs/add/operator/map' import { RequestSchedulerStats } from '../../../../../../shared' -import { AuthHttp, RestExtractor } from '../../../shared' +import { RestExtractor } from '../../../shared' import { RequestSchedulerStatsAttributes } from './request-schedulers-stats-attributes.model' @Injectable() @@ -12,19 +13,18 @@ export class RequestSchedulersService { private static BASE_REQUEST_URL = API_URL + '/api/v1/request-schedulers/' constructor ( - private authHttp: AuthHttp, + private authHttp: HttpClient, private restExtractor: RestExtractor ) {} - getStats (): Observable { - return this.authHttp.get(RequestSchedulersService.BASE_REQUEST_URL + 'stats') - .map(this.restExtractor.extractDataGet) - .map(this.buildRequestObjects) - .catch((res) => this.restExtractor.handleError(res)) + getStats () { + return this.authHttp.get(RequestSchedulersService.BASE_REQUEST_URL + 'stats') + .map(res => this.buildRequestObjects(res)) + .catch(res => this.restExtractor.handleError(res)) } private buildRequestObjects (data: RequestSchedulerStats) { - const requestSchedulers = {} + const requestSchedulers: { [ id: string ]: RequestSchedulerStatsAttributes } = {} Object.keys(data).forEach(requestSchedulerName => { 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 @@ import { Injectable } from '@angular/core' +import { HttpClient, HttpParams } from '@angular/common/http' +import { Observable } from 'rxjs/Observable' import 'rxjs/add/operator/catch' import 'rxjs/add/operator/map' +import { SortMeta } from 'primeng/primeng' import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' -import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' -import { UserCreate, UserUpdate } from '../../../../../../shared' +import { RestExtractor, User, RestPagination, RestService } from '../../../shared' +import { UserCreate, UserUpdate, ResultList } from '../../../../../../shared' @Injectable() export class UserService { @@ -13,53 +16,52 @@ export class UserService { private bytesPipe = new BytesPipe() constructor ( - private authHttp: AuthHttp, + private authHttp: HttpClient, + private restService: RestService, private restExtractor: RestExtractor ) {} addUser (userCreate: UserCreate) { return this.authHttp.post(UserService.BASE_USERS_URL, userCreate) .map(this.restExtractor.extractDataBool) - .catch(this.restExtractor.handleError) + .catch(err => this.restExtractor.handleError(err)) } updateUser (userId: number, userUpdate: UserUpdate) { return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate) - .map(this.restExtractor.extractDataBool) - .catch(this.restExtractor.handleError) + .map(this.restExtractor.extractDataBool) + .catch(err => this.restExtractor.handleError(err)) } getUser (userId: number) { - return this.authHttp.get(UserService.BASE_USERS_URL + userId) - .map(this.restExtractor.extractDataGet) - .catch(this.restExtractor.handleError) + return this.authHttp.get(UserService.BASE_USERS_URL + userId) + .catch(err => this.restExtractor.handleError(err)) } - getDataSource () { - return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this)) + getUsers (pagination: RestPagination, sort: SortMeta): Observable> { + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(UserService.BASE_USERS_URL, { params }) + .map(res => this.restExtractor.convertResultListDateToHuman(res)) + .map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))) + .catch(err => this.restExtractor.handleError(err)) } removeUser (user: User) { return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) } - private formatDataSource (users: User[]) { - const newUsers = [] + private formatUser (user: User) { + let videoQuota + if (user.videoQuota === -1) { + videoQuota = 'Unlimited' + } else { + videoQuota = this.bytesPipe.transform(user.videoQuota) + } - users.forEach(user => { - let videoQuota - if (user.videoQuota === -1) { - videoQuota = 'Unlimited' - } else { - videoQuota = this.bytesPipe.transform(user.videoQuota) - } - - const newUser = Object.assign(user, { - videoQuota - }) - newUsers.push(newUser) + return Object.assign(user, { + videoQuota }) - - return newUsers } } 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 @@

Users list

- + + + + + + + + + + + + + + + + + + + + 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 @@ -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' +import { SortMeta } from 'primeng/primeng' import { NotificationsService } from 'angular2-notifications' import { ConfirmService } from '../../../core' -import { RestDataSource, User, Utils } from '../../../shared' +import { RestTable, RestPagination, User } from '../../../shared' import { UserService } from '../shared' -import { Router } from '@angular/router' @Component({ selector: 'my-user-list', templateUrl: './user-list.component.html', styleUrls: [ './user-list.component.scss' ] }) -export class UserListComponent { - usersSource: RestDataSource = null - tableSettings = { - mode: 'external', - attr: { - class: 'table-hover' - }, - hideSubHeader: true, - actions: { - position: 'right', - add: false, - edit: true, - delete: true - }, - delete: { - deleteButtonContent: Utils.getRowDeleteButton() - }, - edit: { - editButtonContent: Utils.getRowEditButton() - }, - pager: { - display: true, - perPage: 10 - }, - columns: { - id: { - title: 'ID', - sortDirection: 'asc' - }, - username: { - title: 'Username' - }, - email: { - title: 'Email' - }, - videoQuota: { - title: 'Video quota' - }, - role: { - title: 'Role', - sort: false - }, - createdAt: { - title: 'Created Date', - valuePrepareFunction: Utils.dateToHuman - } - } - } +export class UserListComponent extends RestTable implements OnInit { + users: User[] = [] + totalRecords = 0 + rowsPerPage = 10 + sort: SortMeta = { field: 'id', order: 1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( - private router: Router, private notificationsService: NotificationsService, private confirmService: ConfirmService, private userService: UserService ) { - this.usersSource = this.userService.getDataSource() + super() } - editUser ({ data }: { data: User }) { - this.router.navigate([ '/admin', 'users', data.id, 'update' ]) + ngOnInit () { + this.loadData() } - removeUser ({ data }: { data: User }) { - const user = data - + removeUser (user: User) { if (user.username === 'root') { this.notificationsService.error('Error', 'You cannot delete root.') return @@ -89,12 +44,28 @@ export class UserListComponent { this.userService.removeUser(user).subscribe( () => { this.notificationsService.success('Success', `User ${user.username} deleted.`) - this.usersSource.refresh() + this.loadData() }, - err => this.notificationsService.error('Error', err.text) + err => this.notificationsService.error('Error', err) ) } ) } + + getRouterUserEditLink (user: User) { + return [ '/admin', 'users', user.id, 'update' ] + } + + protected loadData () { + this.userService.getUsers(this.pagination, this.sort) + .subscribe( + resultList => { + this.users = resultList.data + this.totalRecords = resultList.total + }, + + err => this.notificationsService.error('Error', err) + ) + } } 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 @@

Video abuses list

- + + + + + + + +
{{ videoAbuse.videoId }} + + + + 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 @@ -import { Component } from '@angular/core' +import { Component, OnInit } from '@angular/core' import { NotificationsService } from 'angular2-notifications' +import { SortMeta } from 'primeng/primeng' -import { Utils, VideoAbuseService } from '../../../shared' -import { VideoAbuse } from '../../../../../shared' +import { RestTable, RestPagination, VideoAbuseService } from '../../../shared' +import { VideoAbuse } from '../../../../../../shared' @Component({ selector: 'my-video-abuse-list', templateUrl: './video-abuse-list.component.html' }) -export class VideoAbuseListComponent { - videoAbusesSource = null - tableSettings = { - mode: 'external', - attr: { - class: 'table-hover' - }, - hideSubHeader: true, - actions: { - position: 'right', - add: false, - edit: false, - delete: false - }, - pager: { - display: true, - perPage: 10 - }, - columns: { - id: { - title: 'ID', - sortDirection: 'asc' - }, - reason: { - title: 'Reason', - sort: false - }, - reporterPodHost: { - title: 'Reporter pod host', - sort: false - }, - reporterUsername: { - title: 'Reporter username', - sort: false - }, - videoId: { - title: 'Video', - type: 'html', - sort: false, - valuePrepareFunction: this.buildVideoLink - }, - createdAt: { - title: 'Created Date', - valuePrepareFunction: Utils.dateToHuman - } - } - } +export class VideoAbuseListComponent extends RestTable implements OnInit { + videoAbuses: VideoAbuse[] = [] + totalRecords = 0 + rowsPerPage = 1 + sort: SortMeta = { field: 'id', order: 1 } + pagination: RestPagination = { count: this.rowsPerPage, start: 0 } constructor ( private notificationsService: NotificationsService, private videoAbuseService: VideoAbuseService ) { - this.videoAbusesSource = this.videoAbuseService.getDataSource() + super() } - buildVideoLink (videoId: string) { - // TODO: transform to routerLink - // https://github.com/akveo/ng2-smart-table/issues/57 - return `${videoId}` + ngOnInit () { + this.loadData() + } + + getRouterVideoLink (videoId: number) { + return [ '/videos', videoId ] + } + + protected loadData () { + return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort) + .subscribe( + resultList => { + this.videoAbuses = resultList.data + this.totalRecords = resultList.total + }, + + err => this.notificationsService.error('Error', err) + ) } } 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 { () => { this.notificationsService.success('Success', 'Information updated.') - this.authService.refreshUserInformations() + this.authService.refreshUserInformation() }, 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 { ) {} ngOnInit () { + this.authService.loadClientCredentials() + if (this.authService.isLoggedIn()) { // The service will automatically redirect to the login page if the token is not valid anymore 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 { } from '@angularclass/hmr' import { MetaModule, MetaLoader, MetaStaticLoader, PageTitlePositioning } from '@ngx-meta/core' -// TODO: remove, we need this to avoid error in ng2-smart-table -import 'rxjs/add/operator/toPromise' import 'bootstrap-loader' 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 @@ import { Injectable } from '@angular/core' -import { Headers, Http, Response, URLSearchParams } from '@angular/http' import { Router } from '@angular/router' import { Observable } from 'rxjs/Observable' import { Subject } from 'rxjs/Subject' +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' import 'rxjs/add/operator/map' import 'rxjs/add/operator/mergeMap' import 'rxjs/add/observable/throw' @@ -11,15 +11,35 @@ import { NotificationsService } from 'angular2-notifications' import { AuthStatus } from './auth-status.model' import { AuthUser } from './auth-user.model' -import { OAuthClientLocal, UserRole } from '../../../../../shared' +import { OAuthClientLocal, UserRole, UserRefreshToken } from '../../../../../shared' // Do not use the barrel (dependency loop) import { RestExtractor } from '../../shared/rest' +import { UserLogin } from '../../../../../shared/models/users/user-login.model' +import { User } from '../../shared/users/user.model' + +interface UserLoginWithUsername extends UserLogin { + access_token: string + refresh_token: string + token_type: string + username: string +} + +interface UserLoginWithUserInformation extends UserLogin { + access_token: string + refresh_token: string + token_type: string + username: string + id: number + role: UserRole + displayNSFW: boolean + email: string +} @Injectable() export class AuthService { private static BASE_CLIENT_URL = API_URL + '/api/v1/oauth-clients/local' private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token' - private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me' + private static BASE_USER_INFORMATION_URL = API_URL + '/api/v1/users/me' loginChangedSource: Observable @@ -29,7 +49,7 @@ export class AuthService { private user: AuthUser = null constructor ( - private http: Http, + private http: HttpClient, private notificationsService: NotificationsService, private restExtractor: RestExtractor, private router: Router @@ -37,32 +57,33 @@ export class AuthService { this.loginChanged = new Subject() this.loginChangedSource = this.loginChanged.asObservable() - // Fetch the client_id/client_secret - // FIXME: save in local storage? - this.http.get(AuthService.BASE_CLIENT_URL) - .map(this.restExtractor.extractDataGet) - .catch(res => this.restExtractor.handleError(res)) - .subscribe( - (result: OAuthClientLocal) => { - this.clientId = result.client_id - this.clientSecret = result.client_secret - console.log('Client credentials loaded.') - }, - - error => { - let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n` - errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' - - // We put a bigger timeout - // This is an important message - this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }) - } - ) - // Return null if there is nothing to load this.user = AuthUser.load() } + loadClientCredentials () { + // Fetch the client_id/client_secret + // FIXME: save in local storage? + this.http.get(AuthService.BASE_CLIENT_URL) + .catch(res => this.restExtractor.handleError(res)) + .subscribe( + res => { + this.clientId = res.client_id + this.clientSecret = res.client_secret + console.log('Client credentials loaded.') + }, + + error => { + let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n` + errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' + + // We put a bigger timeout + // This is an important message + this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }) + } + ) + } + getRefreshToken () { if (this.user === null) return null @@ -70,7 +91,11 @@ export class AuthService { } getRequestHeaderValue () { - return `${this.getTokenType()} ${this.getAccessToken()}` + const accessToken = this.getAccessToken() + + if (accessToken === null) return null + + return `${this.getTokenType()} ${accessToken}` } getAccessToken () { @@ -96,39 +121,26 @@ export class AuthService { } isLoggedIn () { - if (this.getAccessToken()) { - return true - } else { - return false - } + return !!this.getAccessToken() } login (username: string, password: string) { - let body = new URLSearchParams() - body.set('client_id', this.clientId) - body.set('client_secret', this.clientSecret) - body.set('response_type', 'code') - body.set('grant_type', 'password') - body.set('scope', 'upload') - body.set('username', username) - body.set('password', password) + // Form url encoded + const body = new HttpParams().set('client_id', this.clientId) + .set('client_secret', this.clientSecret) + .set('response_type', 'code') + .set('grant_type', 'password') + .set('scope', 'upload') + .set('username', username) + .set('password', password) - let headers = new Headers() - headers.append('Content-Type', 'application/x-www-form-urlencoded') + const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') - let options = { - headers: headers - } - - return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(this.restExtractor.extractDataGet) - .map(res => { - res.username = username - return res - }) - .flatMap(res => this.mergeUserInformations(res)) + return this.http.post(AuthService.BASE_TOKEN_URL, body, { headers }) + .map(res => Object.assign(res, { username })) + .flatMap(res => this.mergeUserInformation(res)) .map(res => this.handleLogin(res)) - .catch((res) => this.restExtractor.handleError(res)) + .catch(res => this.restExtractor.handleError(res)) } logout () { @@ -145,33 +157,26 @@ export class AuthService { const refreshToken = this.getRefreshToken() - let body = new URLSearchParams() - body.set('refresh_token', refreshToken) - body.set('client_id', this.clientId) - body.set('client_secret', this.clientSecret) - body.set('response_type', 'code') - body.set('grant_type', 'refresh_token') + // Form url encoded + const body = new HttpParams().set('refresh_token', refreshToken) + .set('client_id', this.clientId) + .set('client_secret', this.clientSecret) + .set('response_type', 'code') + .set('grant_type', 'refresh_token') - let headers = new Headers() - headers.append('Content-Type', 'application/x-www-form-urlencoded') + const headers = new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded') - let options = { - headers: headers - } - - return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(this.restExtractor.extractDataGet) + return this.http.post(AuthService.BASE_TOKEN_URL, body, { headers }) .map(res => this.handleRefreshToken(res)) - .catch((res: Response) => { + .catch(res => { // The refresh token is invalid? - if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { + if (res.status === 400 && res.error === 'invalid_grant') { console.error('Cannot refresh token -> logout...') this.logout() this.router.navigate(['/login']) return Observable.throw({ - json: () => '', - text: () => 'You need to reconnect.' + error: 'You need to reconnect.' }) } @@ -179,7 +184,7 @@ export class AuthService { }) } - refreshUserInformations () { + refreshUserInformation () { const obj = { access_token: this.user.getAccessToken(), refresh_token: null, @@ -187,7 +192,7 @@ export class AuthService { username: this.user.username } - this.mergeUserInformations (obj) + this.mergeUserInformation(obj) .subscribe( res => { this.user.displayNSFW = res.displayNSFW @@ -198,42 +203,25 @@ export class AuthService { ) } - private mergeUserInformations (obj: { - access_token: string, - refresh_token: string, - token_type: string, - username: string - }) { - // Do not call authHttp here to avoid circular dependencies headaches + private mergeUserInformation (obj: UserLoginWithUsername): Observable { + // User is not loaded yet, set manually auth header + const headers = new HttpHeaders().set('Authorization', `${obj.token_type} ${obj.access_token}`) - const headers = new Headers() - headers.set('Authorization', `Bearer ${obj.access_token}`) + return this.http.get(AuthService.BASE_USER_INFORMATION_URL, { headers }) + .map(res => { + const newProperties = { + id: res.id as number, + role: res.role as UserRole, + displayNSFW: res.displayNSFW as boolean, + email: res.email as string + } - return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) - .map(res => res.json()) - .map(res => { - const newProperties = { - id: res.id as number, - role: res.role as UserRole, - displayNSFW: res.displayNSFW as boolean, - email: res.email as string - } - - return Object.assign(obj, newProperties) - } + return Object.assign(obj, newProperties) + } ) } - private handleLogin (obj: { - access_token: string, - refresh_token: string, - token_type: string, - id: number, - username: string, - email: string, - role: UserRole, - displayNSFW: boolean - }) { + private handleLogin (obj: UserLoginWithUserInformation) { const id = obj.id const username = obj.username const role = obj.role @@ -251,7 +239,7 @@ export class AuthService { this.setStatus(AuthStatus.LoggedIn) } - private handleRefreshToken (obj: { access_token: string, refresh_token: string }) { + private handleRefreshToken (obj: UserRefreshToken) { this.user.refreshTokens(obj.access_token, obj.refresh_token) this.user.save() } @@ -259,5 +247,4 @@ export class AuthService { private setStatus (status: AuthStatus) { this.loginChanged.next(status) } - } 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 @@ import { Injectable } from '@angular/core' -import { Http } from '@angular/http' +import { HttpClient } from '@angular/common/http' -import { RestExtractor } from '../../shared/rest' import { ServerConfig } from '../../../../../shared' @Injectable() @@ -14,17 +13,11 @@ export class ConfigService { } } - constructor ( - private http: Http, - private restExtractor: RestExtractor - ) {} + constructor (private http: HttpClient) {} loadConfig () { - this.http.get(ConfigService.BASE_CONFIG_URL) - .map(this.restExtractor.extractDataGet) - .subscribe(data => { - this.config = data - }) + this.http.get(ConfigService.BASE_CONFIG_URL) + .subscribe(data => this.config = data) } 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 @@ -import { Injectable } from '@angular/core' -import { - ConnectionBackend, - Headers, - Http, - Request, - RequestMethod, - RequestOptions, - RequestOptionsArgs, - Response, - XHRBackend -} from '@angular/http' -import { Observable } from 'rxjs/Observable' - -import { AuthService } from '../../core' - -@Injectable() -export class AuthHttp extends Http { - constructor (backend: ConnectionBackend, defaultOptions: RequestOptions, private authService: AuthService) { - super(backend, defaultOptions) - } - - request (url: string | Request, options?: RequestOptionsArgs): Observable { - if (!options) options = {} - - options.headers = new Headers() - this.setAuthorizationHeader(options.headers) - - return super.request(url, options) - .catch((err) => { - if (err.status === 401) { - return this.handleTokenExpired(url, options) - } - - return Observable.throw(err) - }) - } - - delete (url: string, options?: RequestOptionsArgs): Observable { - if (!options) options = {} - options.method = RequestMethod.Delete - - return this.request(url, options) - } - - get (url: string, options?: RequestOptionsArgs): Observable { - if (!options) options = {} - options.method = RequestMethod.Get - - return this.request(url, options) - } - - post (url: string, body: any, options?: RequestOptionsArgs): Observable { - if (!options) options = {} - options.method = RequestMethod.Post - options.body = body - - return this.request(url, options) - } - - put (url: string, body: any, options?: RequestOptionsArgs): Observable { - if (!options) options = {} - options.method = RequestMethod.Put - options.body = body - - return this.request(url, options) - } - - private handleTokenExpired (url: string | Request, options: RequestOptionsArgs) { - return this.authService.refreshAccessToken() - .flatMap(() => { - this.setAuthorizationHeader(options.headers) - - return super.request(url, options) - }) - } - - private setAuthorizationHeader (headers: Headers) { - headers.set('Authorization', this.authService.getRequestHeaderValue()) - } -} - -export function useFactory (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) { - return new AuthHttp(backend, defaultOptions, authService) -} - -export const AUTH_HTTP_PROVIDERS = [ - { - provide: AuthHttp, - useFactory, - deps: [ XHRBackend, RequestOptions, AuthService ] - } -] 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 @@ +import { Injectable, Injector } from '@angular/core' +import { + HttpInterceptor, + HttpRequest, + HttpEvent, + HttpHandler, HTTP_INTERCEPTORS +} from '@angular/common/http' +import { Observable } from 'rxjs/Observable' + +import { AuthService } from '../../core' +import 'rxjs/add/operator/switchMap' + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + private authService: AuthService + + // https://github.com/angular/angular/issues/18224#issuecomment-316957213 + constructor (private injector: Injector) {} + + intercept (req: HttpRequest, next: HttpHandler): Observable> { + if (this.authService === undefined) { + this.authService = this.injector.get(AuthService) + } + + const authReq = this.cloneRequestWithAuth(req) + + // Pass on the cloned request instead of the original request + // Catch 401 errors (refresh token expired) + return next.handle(authReq) + .catch(err => { + if (err.status === 401) { + return this.handleTokenExpired(req, next) + } + + return Observable.throw(err) + }) + } + + private handleTokenExpired (req: HttpRequest, next: HttpHandler): Observable> { + return this.authService.refreshAccessToken() + .switchMap(() => { + const authReq = this.cloneRequestWithAuth(req) + + return next.handle(authReq) + }) + } + + private cloneRequestWithAuth (req: HttpRequest) { + const authHeaderValue = this.authService.getRequestHeaderValue() + + if (authHeaderValue === null) return req + + // Clone the request to add the new header + return req.clone({ headers: req.headers.set('Authorization', authHeaderValue) }) + } +} + +export const AUTH_INTERCEPTOR_PROVIDER = { + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptor, + multi: true +} 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' export * from './rest-extractor.service' export * from './rest-pagination' export * from './rest.service' +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 @@ -import { Http, RequestOptionsArgs, URLSearchParams, Response } from '@angular/http' - -import { ServerDataSource } from 'ng2-smart-table' - -export class RestDataSource extends ServerDataSource { - private updateResponse: (input: any[]) => any[] - - constructor (http: Http, endpoint: string, updateResponse?: (input: any[]) => any[]) { - const options = { - endPoint: endpoint, - sortFieldKey: 'sort', - dataKey: 'data' - } - super(http, options) - - if (updateResponse) { - this.updateResponse = updateResponse - } - } - - protected extractDataFromResponse (res: Response) { - const json = res.json() - if (!json) return [] - let data = json.data - - if (this.updateResponse !== undefined) { - data = this.updateResponse(data) - } - - return data - } - - protected extractTotalFromResponse (res: Response) { - const rawData = res.json() - return rawData ? parseInt(rawData.total, 10) : 0 - } - - protected addSortRequestOptions (requestOptions: RequestOptionsArgs) { - const searchParams = requestOptions.params as URLSearchParams - - if (this.sortConf) { - this.sortConf.forEach((fieldConf) => { - const sortPrefix = fieldConf.direction === 'desc' ? '-' : '' - - searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field) - }) - } - - return requestOptions - } - - protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) { - const searchParams = requestOptions.params as URLSearchParams - - if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { - const perPage = this.pagingConf['perPage'] - const page = this.pagingConf['page'] - - const start = (page - 1) * perPage - const count = perPage - - searchParams.set('start', start.toString()) - searchParams.set('count', count.toString()) - } - - return requestOptions - } +export class RestDataSource { + // protected addSortRequestOptions (requestOptions: RequestOptionsArgs) { + // const searchParams = requestOptions.params as URLSearchParams + // + // if (this.sortConf) { + // this.sortConf.forEach((fieldConf) => { + // const sortPrefix = fieldConf.direction === 'desc' ? '-' : '' + // + // searchParams.set(this.conf.sortFieldKey, sortPrefix + fieldConf.field) + // }) + // } + // + // return requestOptions + // } + // + // protected addPagerRequestOptions (requestOptions: RequestOptionsArgs) { + // const searchParams = requestOptions.params as URLSearchParams + // + // if (this.pagingConf && this.pagingConf['page'] && this.pagingConf['perPage']) { + // const perPage = this.pagingConf['perPage'] + // const page = this.pagingConf['page'] + // + // const start = (page - 1) * perPage + // const count = perPage + // + // searchParams.set('start', start.toString()) + // searchParams.set('count', count.toString()) + // } + // + // return requestOptions + // } } 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 @@ import { Injectable } from '@angular/core' -import { Response } from '@angular/http' import { Observable } from 'rxjs/Observable' +import { HttpErrorResponse } from '@angular/common/http' -export interface ResultList { - data: any[] - total: number -} +import { Utils } from '../utils' +import { ResultList } from '../../../../../shared' @Injectable() export class RestExtractor { - extractDataBool (res: Response) { + extractDataBool () { return true } - extractDataList (res: Response) { - const body = res.json() + applyToResultListData (result: ResultList, fun: Function, additionalArgs?: any[]): ResultList { + const data: T[] = result.data + const newData: T[] = [] - const ret: ResultList = { - data: body.data, - total: body.total + data.forEach(d => newData.push(fun.call(this, d, additionalArgs))) + + return { + total: result.total, + data: newData } - - return ret } - extractDataGet (res: Response) { - return res.json() + convertResultListDateToHuman (result: ResultList, fieldsToConvert: string[] = [ 'createdAt' ]): ResultList { + return this.applyToResultListData(result, this.convertDateToHuman, [ fieldsToConvert ]) } - handleError (res: Response) { - let text = 'Server error: ' - text += res.text() - let json = '' + convertDateToHuman (target: object, fieldsToConvert: string[]) { + const source = {} + fieldsToConvert.forEach(field => { + source[field] = Utils.dateToHuman(target[field]) + }) - try { - json = res.json() - } catch (err) { - console.error('Cannot get JSON from response.') + return Object.assign(target, source) + } + + handleError (err: HttpErrorResponse) { + let errorMessage + + if (err.error instanceof Error) { + // A client-side or network error occurred. Handle it accordingly. + errorMessage = err.error.message + console.error('An error occurred:', errorMessage) + } else if (err.status !== undefined) { + // The backend returned an unsuccessful response code. + // The response body may contain clues as to what went wrong, + errorMessage = err.error + console.error(`Backend returned code ${err.status}, body was: ${errorMessage}`) + } else { + errorMessage = err } - const error = { - json, - text - } - - console.error(error) - - return Observable.throw(error) + return Observable.throw(errorMessage) } } 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 @@ export interface RestPagination { - currentPage: number - itemsPerPage: number - totalItems: number + start: number + count: number } 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 @@ +import { LazyLoadEvent, SortMeta } from 'primeng/primeng' + +import { RestPagination } from './rest-pagination' + +export abstract class RestTable { + abstract totalRecords: number + abstract rowsPerPage: number + abstract sort: SortMeta + abstract pagination: RestPagination + + protected abstract loadData (): void + + loadLazy (event: LazyLoadEvent) { + this.sort = { + order: event.sortOrder, + field: event.sortField + } + + this.pagination = { + start: event.first, + count: this.rowsPerPage + } + + this.loadData() + } + +} 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 @@ import { Injectable } from '@angular/core' -import { URLSearchParams } from '@angular/http' +import { HttpParams } from '@angular/common/http' +import { SortMeta } from 'primeng/primeng' import { RestPagination } from './rest-pagination' @Injectable() export class RestService { - buildRestGetParams (pagination?: RestPagination, sort?: string) { - const params = new URLSearchParams() + addRestGetParams (params: HttpParams, pagination?: RestPagination, sort?: SortMeta | string) { + let newParams = params - if (pagination) { - const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage - const count: number = pagination.itemsPerPage - - params.set('start', start.toString()) - params.set('count', count.toString()) + if (pagination !== undefined) { + newParams = newParams.set('start', pagination.start.toString()) + .set('count', pagination.count.toString()) } - if (sort) { - params.set('sort', sort) + if (sort !== undefined) { + let sortString = '' + + if (typeof sort === 'string') { + sortString = sort + } else { + const sortPrefix = sort.order === 1 ? '' : '-' + sortString = sortPrefix + sort.field + } + + newParams = newParams.set('sort', sortString) } - return params + return newParams } } 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 @@ import { NgModule } from '@angular/core' +import { HttpClientModule } from '@angular/common/http' import { CommonModule } from '@angular/common' -import { HttpModule } from '@angular/http' import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { RouterModule } from '@angular/router' @@ -11,9 +11,9 @@ import { ProgressbarModule } from 'ngx-bootstrap/progressbar' import { PaginationModule } from 'ngx-bootstrap/pagination' import { ModalModule } from 'ngx-bootstrap/modal' import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload' -import { Ng2SmartTableModule } from 'ng2-smart-table' +import { DataTableModule, SharedModule as PrimeSharedModule } from 'primeng/primeng' -import { AUTH_HTTP_PROVIDERS } from './auth' +import { AUTH_INTERCEPTOR_PROVIDER } from './auth' import { RestExtractor, RestService } from './rest' import { SearchComponent, SearchService } from './search' import { UserService } from './users' @@ -24,8 +24,8 @@ import { VideoAbuseService } from './video-abuse' CommonModule, FormsModule, ReactiveFormsModule, - HttpModule, RouterModule, + HttpClientModule, BsDropdownModule.forRoot(), ModalModule.forRoot(), @@ -33,7 +33,9 @@ import { VideoAbuseService } from './video-abuse' ProgressbarModule.forRoot(), FileUploadModule, - Ng2SmartTableModule + + DataTableModule, + PrimeSharedModule ], declarations: [ @@ -46,15 +48,16 @@ import { VideoAbuseService } from './video-abuse' CommonModule, FormsModule, ReactiveFormsModule, - HttpModule, RouterModule, + HttpClientModule, BsDropdownModule, FileUploadModule, ModalModule, PaginationModule, ProgressbarModule, - Ng2SmartTableModule, + DataTableModule, + PrimeSharedModule, BytesPipe, KeysPipe, @@ -62,7 +65,7 @@ import { VideoAbuseService } from './video-abuse' ], providers: [ - AUTH_HTTP_PROVIDERS, + AUTH_INTERCEPTOR_PROVIDER, RestExtractor, RestService, 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 @@ import { Injectable } from '@angular/core' -import { Http } from '@angular/http' +import { HttpClient } from '@angular/common/http' import 'rxjs/add/operator/catch' import 'rxjs/add/operator/map' -import { AuthService } from '../../core' -import { AuthHttp } from '../auth' import { RestExtractor } from '../rest' import { UserCreate, UserUpdateMe } from '../../../../../shared' @@ -13,9 +11,7 @@ export class UserService { static BASE_USERS_URL = API_URL + '/api/v1/users/' constructor ( - private http: Http, - private authHttp: AuthHttp, - private authService: AuthService, + private authHttp: HttpClient, private restExtractor: RestExtractor ) {} @@ -34,7 +30,7 @@ export class UserService { return this.authHttp.put(url, body) .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)) + .catch(res => this.restExtractor.handleError(res)) } updateMyDetails (details: UserUpdateMe) { @@ -42,12 +38,12 @@ export class UserService { return this.authHttp.put(url, details) .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)) + .catch(res => this.restExtractor.handleError(res)) } signup (userCreate: UserCreate) { - return this.http.post(UserService.BASE_USERS_URL + 'register', userCreate) + return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate) .map(this.restExtractor.extractDataBool) - .catch(this.restExtractor.handleError) + .catch(res => this.restExtractor.handleError(res)) } } 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' export class Utils { - static dateToHuman (date: String) { + static dateToHuman (date: Date) { return new DatePipe('en').transform(date, 'medium') } - - static getRowDeleteButton () { - return '' - } - - static getRowEditButton () { - return '' - } } 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 @@ import { Injectable } from '@angular/core' -import { Http } from '@angular/http' -import { Observable } from 'rxjs/Observable' +import { HttpClient, HttpParams } from '@angular/common/http' import 'rxjs/add/operator/catch' import 'rxjs/add/operator/map' +import { Observable } from 'rxjs/Observable' + +import { SortMeta } from 'primeng/primeng' import { AuthService } from '../core' -import { AuthHttp } from '../auth' -import { RestDataSource, RestExtractor, ResultList } from '../rest' -import { VideoAbuse } from '../../../../../shared' +import { RestExtractor, RestPagination, RestService } from '../rest' +import { Utils } from '../utils' +import { ResultList, VideoAbuse } from '../../../../../shared' @Injectable() export class VideoAbuseService { private static BASE_VIDEO_ABUSE_URL = API_URL + '/api/v1/videos/' constructor ( - private authHttp: AuthHttp, + private authHttp: HttpClient, + private restService: RestService, private restExtractor: RestExtractor ) {} - getDataSource () { - return new RestDataSource(this.authHttp, VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse') + getVideoAbuses (pagination: RestPagination, sort: SortMeta): Observable> { + const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + 'abuse' + + let params = new HttpParams() + params = this.restService.addRestGetParams(params, pagination, sort) + + return this.authHttp.get>(url, { params }) + .map(res => this.restExtractor.convertResultListDateToHuman(res)) + .map(res => this.restExtractor.applyToResultListData(res, this.formatVideoAbuse.bind(this))) + .catch(res => this.restExtractor.handleError(res)) } reportVideo (id: number, reason: string) { + const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse' const body = { reason } - const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse' return this.authHttp.post(url, body) .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)) + .catch(res => this.restExtractor.handleError(res)) } - private extractVideoAbuses (result: ResultList) { - const videoAbuses: VideoAbuse[] = result.data - const totalVideoAbuses = result.total - - return { videoAbuses, totalVideoAbuses } + private formatVideoAbuse (videoAbuse: VideoAbuse) { + return Object.assign(videoAbuse, { + createdAt: Utils.dateToHuman(videoAbuse.createdAt) + }) } + } 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 @@ export * from './sort-field.type' export * from './video.model' export * from './video.service' +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 @@ +export interface VideoPagination { + currentPage: number + itemsPerPage: number + totalItems: number +} 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 { constructor (hash: { author: string, - createdAt: string, + createdAt: Date | string, categoryLabel: string, category: number, licenceLabel: string, @@ -70,7 +70,7 @@ export class Video implements VideoServerModel { files: VideoFile[] }) { this.author = hash.author - this.createdAt = new Date(hash.createdAt) + this.createdAt = new Date(hash.createdAt.toString()) this.categoryLabel = hash.categoryLabel this.category = hash.category 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 @@ import { Injectable } from '@angular/core' -import { Http, Headers, RequestOptions } from '@angular/http' import { Observable } from 'rxjs/Observable' import 'rxjs/add/operator/catch' import 'rxjs/add/operator/map' +import { HttpClient, HttpParams } from '@angular/common/http' import { Search } from '../../shared' import { SortField } from './sort-field.type' -import { AuthService } from '../../core' import { - AuthHttp, RestExtractor, - RestPagination, RestService, - ResultList, UserService } from '../../shared' import { Video } from './video.model' +import { VideoPagination } from './video-pagination.model' import { - UserVideoRate, - VideoRateType, - VideoUpdate, - VideoAbuseCreate, - UserVideoRateUpdate +UserVideoRate, +VideoRateType, +VideoUpdate, +VideoAbuseCreate, +UserVideoRateUpdate, +Video as VideoServerModel, +ResultList } from '../../../../../shared' @Injectable() @@ -33,9 +32,7 @@ export class VideoService { videoLanguages: Array<{ id: number, label: string }> = [] constructor ( - private authService: AuthService, - private authHttp: AuthHttp, - private http: Http, + private authHttp: HttpClient, private restExtractor: RestExtractor, private restService: RestService ) {} @@ -52,11 +49,10 @@ export class VideoService { return this.loadVideoAttributeEnum('languages', this.videoLanguages) } - getVideo (uuid: string): Observable