From d592e0a9b2931c7c9cbedb27fb8efc9aaacad9bb Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 14 Sep 2017 11:57:49 +0200 Subject: Move to HttpClient and PrimeNG data table --- client/src/app/shared/auth/auth-http.service.ts | 93 -------------------- .../app/shared/auth/auth-interceptor.service.ts | 62 ++++++++++++++ client/src/app/shared/auth/index.ts | 2 +- client/src/app/shared/rest/index.ts | 1 + client/src/app/shared/rest/rest-data-source.ts | 98 +++++++--------------- .../src/app/shared/rest/rest-extractor.service.ts | 66 ++++++++------- 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 +-- .../app/shared/video-abuse/video-abuse.service.ts | 41 +++++---- 13 files changed, 223 insertions(+), 248 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 (limited to 'client/src/app/shared') 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 ret + return { + total: result.total, + data: newData + } } - 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) + } - const error = { - json, - text + 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 } - 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) + }) } + } -- cgit v1.2.3