From de59c48f5f317018e3f746bbe4a7b7efe00109f2 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Aug 2016 16:54:21 +0200 Subject: [PATCH] Client: centralize http res extraction in a service --- client/src/app/account/account.service.ts | 12 +++- .../admin/friends/shared/friend.service.ts | 22 +++---- .../app/admin/users/shared/user.service.ts | 28 ++++----- client/src/app/app.component.ts | 4 +- client/src/app/login/login.component.ts | 6 +- client/src/app/shared/auth/auth.service.ts | 22 +++---- client/src/app/shared/index.ts | 1 + client/src/app/shared/rest/index.ts | 3 + .../app/shared/rest/rest-extractor.service.ts | 46 ++++++++++++++ .../rest/rest-pagination.ts} | 4 +- client/src/app/shared/rest/rest.service.ts | 27 ++++++++ client/src/app/videos/shared/index.ts | 1 - client/src/app/videos/shared/video.service.ts | 62 +++++++------------ .../videos/video-list/video-list.component.ts | 5 +- client/src/main.ts | 3 +- client/tsconfig.json | 10 +++ 16 files changed, 160 insertions(+), 96 deletions(-) create mode 100644 client/src/app/shared/rest/index.ts create mode 100644 client/src/app/shared/rest/rest-extractor.service.ts rename client/src/app/{videos/shared/pagination.model.ts => shared/rest/rest-pagination.ts} (65%) create mode 100644 client/src/app/shared/rest/rest.service.ts diff --git a/client/src/app/account/account.service.ts b/client/src/app/account/account.service.ts index 19b4e0624..355bcef74 100644 --- a/client/src/app/account/account.service.ts +++ b/client/src/app/account/account.service.ts @@ -1,12 +1,16 @@ import { Injectable } from '@angular/core'; -import { AuthHttp, AuthService } from '../shared'; +import { AuthHttp, AuthService, RestExtractor } from '../shared'; @Injectable() export class AccountService { private static BASE_USERS_URL = '/api/v1/users/'; - constructor(private authHttp: AuthHttp, private authService: AuthService) { } + constructor( + private authHttp: AuthHttp, + private authService: AuthService, + private restExtractor: RestExtractor + ) {} changePassword(newPassword: string) { const url = AccountService.BASE_USERS_URL + this.authService.getUser().id; @@ -14,6 +18,8 @@ export class AccountService { password: newPassword }; - return this.authHttp.put(url, body); + return this.authHttp.put(url, body) + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); } } diff --git a/client/src/app/admin/friends/shared/friend.service.ts b/client/src/app/admin/friends/shared/friend.service.ts index e4e680c29..75826fc17 100644 --- a/client/src/app/admin/friends/shared/friend.service.ts +++ b/client/src/app/admin/friends/shared/friend.service.ts @@ -1,9 +1,8 @@ import { Injectable } from '@angular/core'; -import { Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Friend } from './friend.model'; -import { AuthHttp, AuthService } from '../../../shared'; +import { AuthHttp, RestExtractor } from '../../../shared'; @Injectable() export class FriendService { @@ -11,13 +10,15 @@ export class FriendService { constructor ( private authHttp: AuthHttp, - private authService: AuthService + private restExtractor: RestExtractor ) {} getFriends(): Observable { return this.authHttp.get(FriendService.BASE_FRIEND_URL) - .map(res => res.json()) - .catch(this.handleError); + // Not implemented as a data list by the server yet + // .map(this.restExtractor.extractDataList) + .map((res) => res.json()) + .catch((res) => this.restExtractor.handleError(res)); } makeFriends(notEmptyUrls) { @@ -26,18 +27,13 @@ export class FriendService { }; return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body) - .map(res => res.status) - .catch(this.handleError); + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); } quitFriends() { return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') .map(res => res.status) - .catch(this.handleError); - } - - private handleError (error: Response) { - console.error(error); - return Observable.throw(error.json().error || 'Server error'); + .catch((res) => this.restExtractor.handleError(res)); } } diff --git a/client/src/app/admin/users/shared/user.service.ts b/client/src/app/admin/users/shared/user.service.ts index be433f0a1..d96db4575 100644 --- a/client/src/app/admin/users/shared/user.service.ts +++ b/client/src/app/admin/users/shared/user.service.ts @@ -1,15 +1,16 @@ import { Injectable } from '@angular/core'; -import { Response } from '@angular/http'; -import { Observable } from 'rxjs/Observable'; -import { AuthHttp, User } from '../../../shared'; +import { AuthHttp, RestExtractor, ResultList, User } from '../../../shared'; @Injectable() export class UserService { // TODO: merge this constant with account private static BASE_USERS_URL = '/api/v1/users/'; - constructor(private authHttp: AuthHttp) {} + constructor( + private authHttp: AuthHttp, + private restExtractor: RestExtractor + ) {} addUser(username: string, password: string) { const body = { @@ -17,23 +18,25 @@ export class UserService { password }; - return this.authHttp.post(UserService.BASE_USERS_URL, body); + return this.authHttp.post(UserService.BASE_USERS_URL, body) + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); } getUsers() { return this.authHttp.get(UserService.BASE_USERS_URL) - .map(res => res.json()) + .map(this.restExtractor.extractDataList) .map(this.extractUsers) - .catch(this.handleError); + .catch((res) => this.restExtractor.handleError(res)); } removeUser(user: User) { return this.authHttp.delete(UserService.BASE_USERS_URL + user.id); } - private extractUsers(body: any) { - const usersJson = body.data; - const totalUsers = body.total; + private extractUsers(result: ResultList) { + const usersJson = result.data; + const totalUsers = result.total; const users = []; for (const userJson of usersJson) { users.push(new User(userJson)); @@ -41,9 +44,4 @@ export class UserService { return { users, totalUsers }; } - - private handleError(error: Response) { - console.error(error); - return Observable.throw(error.json().error || 'Server error'); - } } diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 2e0fd13f1..9d05c272f 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -3,7 +3,7 @@ import { Router, ROUTER_DIRECTIVES } from '@angular/router'; import { MenuAdminComponent } from './admin'; import { MenuComponent } from './menu.component'; -import { SearchComponent, SearchService } from './shared'; +import { RestExtractor, RestService, SearchComponent, SearchService } from './shared'; import { VideoService } from './videos'; @Component({ @@ -11,7 +11,7 @@ import { VideoService } from './videos'; template: require('./app.component.html'), styles: [ require('./app.component.scss') ], directives: [ MenuAdminComponent, MenuComponent, ROUTER_DIRECTIVES, SearchComponent ], - providers: [ VideoService, SearchService ] + providers: [ RestExtractor, RestService, VideoService, SearchService ] }) export class AppComponent { diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index fe867b7b4..1e0ba0fe8 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts @@ -37,12 +37,12 @@ export class LoginComponent implements OnInit { this.router.navigate(['/videos/list']); }, error => { - console.error(error); + console.error(error.json); - if (error.error === 'invalid_grant') { + if (error.json.error === 'invalid_grant') { this.error = 'Credentials are invalid.'; } else { - this.error = `${error.error}: ${error.error_description}`; + this.error = `${error.json.error}: ${error.json.error_description}`; } } ); diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 8eea0c4bf..2273048c8 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts @@ -1,10 +1,11 @@ import { Injectable } from '@angular/core'; -import { Headers, Http, Response, URLSearchParams } from '@angular/http'; +import { Headers, Http, URLSearchParams } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import { AuthStatus } from './auth-status.model'; import { AuthUser } from './auth-user.model'; +import { RestExtractor } from '../rest'; @Injectable() export class AuthService { @@ -19,15 +20,15 @@ export class AuthService { private loginChanged: Subject; private user: AuthUser = null; - constructor(private http: Http) { + constructor(private http: Http, private restExtractor: RestExtractor) { 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(res => res.json()) - .catch(this.handleError) + .map(this.restExtractor.extractDataGet) + .catch((res) => this.restExtractor.handleError(res)) .subscribe( result => { this.clientId = result.client_id; @@ -101,14 +102,14 @@ export class AuthService { }; return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(res => res.json()) + .map(this.restExtractor.extractDataGet) .map(res => { res.username = username; return res; }) .flatMap(res => this.fetchUserInformations(res)) .map(res => this.handleLogin(res)) - .catch(this.handleError); + .catch((res) => this.restExtractor.handleError(res)); } logout() { @@ -139,9 +140,9 @@ export class AuthService { }; return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) - .map(res => res.json()) + .map(this.restExtractor.extractDataGet) .map(res => this.handleRefreshToken(res)) - .catch(this.handleError); + .catch((res) => this.restExtractor.handleError(res)); } private fetchUserInformations (obj: any) { @@ -160,11 +161,6 @@ export class AuthService { ); } - private handleError (error: Response) { - console.error(error); - return Observable.throw(error.json() || { error: 'Server error' }); - } - private handleLogin (obj: any) { const id = obj.id; const username = obj.username; diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts index 9edf9b4a0..c362a0e4a 100644 --- a/client/src/app/shared/index.ts +++ b/client/src/app/shared/index.ts @@ -1,4 +1,5 @@ export * from './auth'; export * from './form-validators'; +export * from './rest'; export * from './search'; export * from './users'; diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts new file mode 100644 index 000000000..3c9509dc7 --- /dev/null +++ b/client/src/app/shared/rest/index.ts @@ -0,0 +1,3 @@ +export * from './rest-extractor.service'; +export * from './rest-pagination'; +export * from './rest.service'; diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts new file mode 100644 index 000000000..aa44799af --- /dev/null +++ b/client/src/app/shared/rest/rest-extractor.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { Response } from '@angular/http'; +import { Observable } from 'rxjs/Observable'; + +export interface ResultList { + data: any[]; + total: number; +} + +@Injectable() +export class RestExtractor { + + constructor () { ; } + + extractDataBool(res: Response) { + return true; + } + + extractDataList(res: Response) { + const body = res.json(); + + const ret: ResultList = { + data: body.data, + total: body.total + }; + + return ret; + } + + extractDataGet(res: Response) { + return res.json(); + } + + handleError(res: Response) { + let text = 'Server error: '; + text += res.text(); + let json = res.json(); + + const error = { + json, + text + }; + + return Observable.throw(error); + } +} diff --git a/client/src/app/videos/shared/pagination.model.ts b/client/src/app/shared/rest/rest-pagination.ts similarity index 65% rename from client/src/app/videos/shared/pagination.model.ts rename to client/src/app/shared/rest/rest-pagination.ts index eda44ebfb..0cfa4f468 100644 --- a/client/src/app/videos/shared/pagination.model.ts +++ b/client/src/app/shared/rest/rest-pagination.ts @@ -1,5 +1,5 @@ -export interface Pagination { +export interface RestPagination { currentPage: number; itemsPerPage: number; totalItems: number; -} +}; diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts new file mode 100644 index 000000000..16b47e957 --- /dev/null +++ b/client/src/app/shared/rest/rest.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { URLSearchParams } from '@angular/http'; + +import { RestPagination } from './rest-pagination'; + +@Injectable() +export class RestService { + + buildRestGetParams(pagination?: RestPagination, sort?: string) { + const params = new URLSearchParams(); + + 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 (sort) { + params.set('sort', sort); + } + + return params; + } + +} diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts index a54120f5d..67d16ead1 100644 --- a/client/src/app/videos/shared/index.ts +++ b/client/src/app/videos/shared/index.ts @@ -1,5 +1,4 @@ export * from './loader'; -export * from './pagination.model'; export * from './sort-field.type'; export * from './video.model'; export * from './video.service'; diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts index b4396f767..ad8557533 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/videos/shared/video.service.ts @@ -1,11 +1,10 @@ import { Injectable } from '@angular/core'; -import { Http, Response, URLSearchParams } from '@angular/http'; +import { Http } from '@angular/http'; import { Observable } from 'rxjs/Observable'; -import { Pagination } from './pagination.model'; import { Search } from '../../shared'; import { SortField } from './sort-field.type'; -import { AuthHttp, AuthService } from '../../shared'; +import { AuthHttp, AuthService, RestExtractor, RestPagination, RestService, ResultList } from '../../shared'; import { Video } from './video.model'; @Injectable() @@ -15,68 +14,51 @@ export class VideoService { constructor( private authService: AuthService, private authHttp: AuthHttp, - private http: Http + private http: Http, + private restExtractor: RestExtractor, + private restService: RestService ) {} - getVideo(id: string) { + getVideo(id: string): Observable