aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2019-03-09 21:36:55 +0800
committercrupest <crupest@outlook.com>2019-03-09 21:36:55 +0800
commit14a799cc17f16ab93ee652bd9a2973c60cb3697c (patch)
tree9c34cb2354dd7fb90994c3659ae592f6b616c898 /Timeline/ClientApp/src
parentf65ac5d592e4e449dd513ad01cfd2b980324f240 (diff)
downloadtimeline-14a799cc17f16ab93ee652bd9a2973c60cb3697c.tar.gz
timeline-14a799cc17f16ab93ee652bd9a2973c60cb3697c.tar.bz2
timeline-14a799cc17f16ab93ee652bd9a2973c60cb3697c.zip
Seperate internal and public user service.
Diffstat (limited to 'Timeline/ClientApp/src')
-rw-r--r--Timeline/ClientApp/src/app/app.component.ts9
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/errors.ts25
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts17
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts (renamed from Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts)20
-rw-r--r--Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts93
-rw-r--r--Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts22
-rw-r--r--Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts10
-rw-r--r--Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts9
-rw-r--r--Timeline/ClientApp/src/app/user/user-login/user-login.component.ts7
-rw-r--r--Timeline/ClientApp/src/app/user/user-service/user.service.ts118
-rw-r--r--Timeline/ClientApp/src/app/user/user.service.ts33
11 files changed, 206 insertions, 157 deletions
diff --git a/Timeline/ClientApp/src/app/app.component.ts b/Timeline/ClientApp/src/app/app.component.ts
index 0e2a9799..ee02f833 100644
--- a/Timeline/ClientApp/src/app/app.component.ts
+++ b/Timeline/ClientApp/src/app/app.component.ts
@@ -1,6 +1,5 @@
import { Component } from '@angular/core';
-import { MatDialog } from '@angular/material';
-import { UserDialogComponent } from './user/user-dialog/user-dialog.component';
+import { UserService } from './user/user.service';
@Component({
selector: 'app-root',
@@ -9,11 +8,9 @@ import { UserDialogComponent } from './user/user-dialog/user-dialog.component';
})
export class AppComponent {
- constructor(private dialog: MatDialog) { }
+ constructor(private userService: UserService) { }
openUserDialog() {
- this.dialog.open(UserDialogComponent, {
- width: '300px'
- });
+ this.userService.openUserDialog();
}
}
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts b/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts
new file mode 100644
index 00000000..22e44dd6
--- /dev/null
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/errors.ts
@@ -0,0 +1,25 @@
+export abstract class LoginError extends Error { }
+
+export class BadNetworkError extends LoginError {
+ constructor() {
+ super('Network is bad.');
+ }
+}
+
+export class AlreadyLoginError extends LoginError {
+ constructor() {
+ super('Internal logical error. There is already a token saved. Please call validateUserLoginState first.');
+ }
+}
+
+export class BadCredentialsError extends LoginError {
+ constructor() {
+ super('Username or password is wrong.');
+ }
+}
+
+export class UnknownError extends LoginError {
+ constructor(public internalError?: any) {
+ super('Sorry, unknown error occured!');
+ }
+}
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts b/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts
new file mode 100644
index 00000000..1335b407
--- /dev/null
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/http-entities.ts
@@ -0,0 +1,17 @@
+import { UserCredentials, UserInfo } from '../entities';
+
+export type CreateTokenRequest = UserCredentials;
+
+export interface CreateTokenResponse {
+ token: string;
+ userInfo: UserInfo;
+}
+
+export interface ValidateTokenRequest {
+ token: string;
+}
+
+export interface ValidateTokenResponse {
+ isValid: boolean;
+ userInfo?: UserInfo;
+}
diff --git a/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts
index 9effe000..4a2c78f8 100644
--- a/Timeline/ClientApp/src/app/user/user-service/user.service.spec.ts
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.spec.ts
@@ -4,9 +4,9 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/
import { UserInfo, UserCredentials } from '../entities';
import {
- UserService, CreateTokenResult,
+ InternalUserService, CreateTokenResult,
UserLoginState, TokenValidationRequest, TokenValidationResult
-} from './user.service';
+} from './internal-user.service';
describe('UserService', () => {
const tokenCreateUrl = '/api/User/CreateToken';
@@ -21,19 +21,19 @@ describe('UserService', () => {
}));
it('should be created', () => {
- const service: UserService = TestBed.get(UserService);
+ const service: InternalUserService = TestBed.get(InternalUserService);
expect(service).toBeTruthy();
});
it('should be nologin at first', () => {
- const service: UserService = TestBed.get(UserService);
- service.validateUserLoginState().subscribe(result => {
+ const service: InternalUserService = TestBed.get(InternalUserService);
+ service.refreshAndGetUserState().subscribe(result => {
expect(result.state).toBe('nologin');
});
});
it('login should work well', () => {
- const service: UserService = TestBed.get(UserService);
+ const service: InternalUserService = TestBed.get(InternalUserService);
const mockUserInfo: UserInfo = {
username: 'user',
@@ -58,7 +58,7 @@ describe('UserService', () => {
});
describe('validateUserLoginState', () => {
- let service: UserService;
+ let service: InternalUserService;
let httpController: HttpTestingController;
const mockUserInfo: UserInfo = {
@@ -73,7 +73,7 @@ describe('UserService', () => {
};
beforeEach(() => {
- service = TestBed.get(UserService);
+ service = TestBed.get(InternalUserService);
httpController = TestBed.get(HttpTestingController);
service.tryLogin(mockUserCredentials).subscribe(); // subscribe to activate login
@@ -85,7 +85,7 @@ describe('UserService', () => {
});
it('success should work well', () => {
- service.validateUserLoginState().subscribe((result: UserLoginState) => {
+ service.refreshAndGetUserState().subscribe((result: UserLoginState) => {
expect(result).toEqual(<UserLoginState>{
state: 'success',
userInfo: mockUserInfo
@@ -101,7 +101,7 @@ describe('UserService', () => {
});
it('invalid should work well', () => {
- service.validateUserLoginState().subscribe((result: UserLoginState) => {
+ service.refreshAndGetUserState().subscribe((result: UserLoginState) => {
expect(result).toEqual(<UserLoginState>{
state: 'invalidlogin'
});
diff --git a/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts
new file mode 100644
index 00000000..f6987d7d
--- /dev/null
+++ b/Timeline/ClientApp/src/app/user/internal-user-service/internal-user.service.ts
@@ -0,0 +1,93 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
+import { Router } from '@angular/router';
+
+import { Observable, of, throwError, BehaviorSubject } from 'rxjs';
+import { map, catchError, retry } from 'rxjs/operators';
+
+import { AlreadyLoginError, BadCredentialsError, BadNetworkError, UnknownError } from './errors';
+import { CreateTokenRequest, CreateTokenResponse, ValidateTokenRequest, ValidateTokenResponse } from './http-entities';
+import { UserCredentials, UserInfo } from '../entities';
+
+
+export type UserLoginState = 'nologin' | 'invalidlogin' | 'success';
+
+/**
+ * This service is only used internal in user module.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class InternalUserService {
+
+ private token: string;
+ private userInfoSubject = new BehaviorSubject<UserInfo | null>(null);
+
+ get currentUserInfo(): UserInfo | null {
+ return this.userInfoSubject.value;
+ }
+
+ get userInfo$(): Observable<UserInfo | null> {
+ return this.userInfoSubject;
+ }
+
+ constructor(private httpClient: HttpClient, private router: Router) { }
+
+ userRouteNavigate(commands: any[]) {
+ this.router.navigate([{
+ outlets: {
+ user: commands
+ }
+ }]);
+ }
+
+ refreshAndGetUserState(): Observable<UserLoginState> {
+ if (this.token === undefined || this.token === null) {
+ return of(<UserLoginState>'nologin');
+ }
+
+ return this.httpClient.post<ValidateTokenResponse>('/api/User/ValidateToken', <ValidateTokenRequest>{ token: this.token }).pipe(
+ retry(3),
+ catchError(error => {
+ console.error('Failed to validate token.');
+ return throwError(error);
+ }),
+ map(result => {
+ if (result.isValid) {
+ this.userInfoSubject.next(result.userInfo);
+ return <UserLoginState>'success';
+ } else {
+ this.token = null;
+ this.userInfoSubject.next(null);
+ return <UserLoginState>'invalidlogin';
+ }
+ })
+ );
+ }
+
+ tryLogin(credentials: UserCredentials): Observable<UserInfo> {
+ if (this.token) {
+ return throwError(new AlreadyLoginError());
+ }
+
+ return this.httpClient.post<CreateTokenResponse>('/api/User/CreateToken', <CreateTokenRequest>credentials).pipe(
+ catchError((error: HttpErrorResponse) => {
+ if (error.error instanceof ErrorEvent) {
+ console.error('An error occurred when login: ' + error.error.message);
+ return throwError(new BadNetworkError());
+ } else if (error.status === 400) {
+ console.error('An error occurred when login: wrong credentials.');
+ return throwError(new BadCredentialsError());
+ } else {
+ console.error('An unknown error occurred when login: ' + error);
+ return throwError(new UnknownError(error));
+ }
+ }),
+ map(result => {
+ this.token = result.token;
+ this.userInfoSubject.next(result.userInfo);
+ return result.userInfo;
+ })
+ );
+ }
+}
diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts
index d24c0cd2..dd7af6ca 100644
--- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts
+++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.spec.ts
@@ -4,9 +4,9 @@ import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';
-import { UserInfo } from '../user-info';
+import { UserInfo } from '../entities';
import { UserDialogComponent } from './user-dialog.component';
-import { UserService, UserLoginState } from '../user-service/user.service';
+import { InternalUserService, UserLoginState } from '../internal-user-service/internal-user.service';
import { LoginEvent } from '../user-login/user-login.component';
@Component({
@@ -38,7 +38,7 @@ class UserLoginSuccessStubComponent { }
describe('UserDialogComponent', () => {
let component: UserDialogComponent;
let fixture: ComponentFixture<UserDialogComponent>;
- let mockUserService: jasmine.SpyObj<UserService>;
+ let mockUserService: jasmine.SpyObj<InternalUserService>;
beforeEach(async(() => {
mockUserService = jasmine.createSpyObj('UserService', ['validateUserLoginState', 'tryLogin']);
@@ -46,7 +46,7 @@ describe('UserDialogComponent', () => {
TestBed.configureTestingModule({
declarations: [UserDialogComponent, MatProgressSpinnerStubComponent,
UserLoginStubComponent, UserLoginSuccessStubComponent],
- providers: [{ provide: UserService, useValue: mockUserService }]
+ providers: [{ provide: InternalUserService, useValue: mockUserService }]
})
.compileComponents();
}));
@@ -57,7 +57,7 @@ describe('UserDialogComponent', () => {
});
it('progress spinner should work well', fakeAsync(() => {
- mockUserService.validateUserLoginState.and.returnValue(of(<UserLoginState>{ state: 'nologin' }).pipe(delay(10)));
+ mockUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>{ state: 'nologin' }).pipe(delay(10)));
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('mat-progress-spinner'))).toBeTruthy();
tick(10);
@@ -66,30 +66,30 @@ describe('UserDialogComponent', () => {
}));
it('nologin should work well', () => {
- mockUserService.validateUserLoginState.and.returnValue(of(<UserLoginState>{ state: 'nologin' }));
+ mockUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>{ state: 'nologin' }));
fixture.detectChanges();
- expect(mockUserService.validateUserLoginState).toHaveBeenCalled();
+ expect(mockUserService.refreshAndGetUserState).toHaveBeenCalled();
expect(fixture.debugElement.query(By.css('app-user-login'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('app-user-login-success'))).toBeFalsy();
});
it('success should work well', () => {
- mockUserService.validateUserLoginState.and.returnValue(of(<UserLoginState>{ state: 'success', userInfo: {} }));
+ mockUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>{ state: 'success', userInfo: {} }));
fixture.detectChanges();
- expect(mockUserService.validateUserLoginState).toHaveBeenCalled();
+ expect(mockUserService.refreshAndGetUserState).toHaveBeenCalled();
expect(fixture.debugElement.query(By.css('app-user-login'))).toBeFalsy();
expect(fixture.debugElement.query(By.css('app-user-login-success'))).toBeTruthy();
});
it('login should work well', () => {
- mockUserService.validateUserLoginState.and.returnValue(of(<UserLoginState>{ state: 'nologin' }));
+ mockUserService.refreshAndGetUserState.and.returnValue(of(<UserLoginState>{ state: 'nologin' }));
fixture.detectChanges();
- expect(mockUserService.validateUserLoginState).toHaveBeenCalled();
+ expect(mockUserService.refreshAndGetUserState).toHaveBeenCalled();
expect(fixture.debugElement.query(By.css('app-user-login'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('app-user-login-success'))).toBeFalsy();
diff --git a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts
index 0edde924..498ffaa1 100644
--- a/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts
+++ b/Timeline/ClientApp/src/app/user/user-dialog/user-dialog.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
-import { UserService } from '../user-service/user.service';
+import { InternalUserService } from '../internal-user-service/internal-user.service';
import { RouterOutlet, Router, ActivationStart } from '@angular/router';
@Component({
@@ -9,7 +9,7 @@ import { RouterOutlet, Router, ActivationStart } from '@angular/router';
})
export class UserDialogComponent implements OnInit, OnDestroy {
- constructor(private userService: UserService, private router: Router) { }
+ constructor(private userService: InternalUserService, private router: Router) { }
@ViewChild(RouterOutlet) outlet: RouterOutlet;
@@ -24,12 +24,12 @@ export class UserDialogComponent implements OnInit, OnDestroy {
});
- this.userService.validateUserLoginState().subscribe(result => {
+ this.userService.refreshAndGetUserState().subscribe(result => {
this.isLoading = false;
- if (result.state === 'success') {
+ if (result === 'success') {
this.userService.userRouteNavigate(['success', { reason: 'already' }]);
} else {
- this.userService.userRouteNavigate(['login', { reason: result.state }]);
+ this.userService.userRouteNavigate(['login', { reason: result }]);
}
});
}
diff --git a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts
index d141b3b6..48e331d6 100644
--- a/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts
+++ b/Timeline/ClientApp/src/app/user/user-login-success/user-login-success.component.ts
@@ -1,8 +1,9 @@
import { Component, OnInit, Input } from '@angular/core';
-import { UserInfo } from '../entities';
-import { UserService } from '../user-service/user.service';
import { ActivatedRoute } from '@angular/router';
+import { UserInfo } from '../entities';
+import { InternalUserService } from '../internal-user-service/internal-user.service';
+
@Component({
selector: 'app-user-login-success',
templateUrl: './user-login-success.component.html',
@@ -14,10 +15,10 @@ export class UserLoginSuccessComponent implements OnInit {
userInfo: UserInfo;
- constructor(private route: ActivatedRoute, private userService: UserService) { }
+ constructor(private route: ActivatedRoute, private userService: InternalUserService) { }
ngOnInit() {
- this.userInfo = this.userService.userInfo;
+ this.userInfo = this.userService.currentUserInfo;
this.displayLoginSuccessMessage = this.route.snapshot.paramMap.get('reason') === 'login';
}
}
diff --git a/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts
index 971d57ce..082f879c 100644
--- a/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts
+++ b/Timeline/ClientApp/src/app/user/user-login/user-login.component.ts
@@ -1,8 +1,9 @@
import { Component, Output, OnInit, EventEmitter } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
-import { UserService } from '../user-service/user.service';
import { ActivatedRoute } from '@angular/router';
+import { InternalUserService } from '../internal-user-service/internal-user.service';
+
export type LoginMessage = 'nologin' | 'invalidlogin' | string;
export class LoginEvent {
@@ -17,9 +18,9 @@ export class LoginEvent {
})
export class UserLoginComponent implements OnInit {
- constructor(private route: ActivatedRoute, private userService: UserService) { }
+ constructor(private route: ActivatedRoute, private userService: InternalUserService) { }
- message: string;
+ message: LoginMessage;
form = new FormGroup({
username: new FormControl(''),
diff --git a/Timeline/ClientApp/src/app/user/user-service/user.service.ts b/Timeline/ClientApp/src/app/user/user-service/user.service.ts
deleted file mode 100644
index e535537d..00000000
--- a/Timeline/ClientApp/src/app/user/user-service/user.service.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Injectable } from '@angular/core';
-import { HttpClient, HttpErrorResponse } from '@angular/common/http';
-import { Observable, of, throwError } from 'rxjs';
-import { map, catchError, retry } from 'rxjs/operators';
-
-import { UserCredentials, UserInfo } from '../entities';
-import { Router } from '@angular/router';
-
-export interface CreateTokenResult {
- token: string;
- userInfo: UserInfo;
-}
-
-export interface TokenValidationRequest {
- token: string;
-}
-
-export interface TokenValidationResult {
- isValid: boolean;
- userInfo?: UserInfo;
-}
-
-export interface UserLoginState {
- state: 'nologin' | 'invalidlogin' | 'success';
- userInfo?: UserInfo;
-}
-
-export class BadNetworkException extends Error {
- constructor() {
- super('Network is bad.');
- }
-}
-
-export class AlreadyLoginException extends Error {
- constructor() {
- super('There is already a token saved. Please call validateUserLoginState first.');
- }
-}
-
-export class BadCredentialsException extends Error {
- constructor() {
- super(`Username or password is wrong.`);
- }
-}
-
-@Injectable({
- providedIn: 'root'
-})
-export class UserService {
-
- private token: string;
- userInfo: UserInfo;
-
- constructor(private httpClient: HttpClient, private router: Router) { }
-
- userRouteNavigate(commands: any[]) {
- this.router.navigate([{
- outlets: {
- user: commands
- }
- }]);
- }
-
- validateUserLoginState(): Observable<UserLoginState> {
- if (this.token === undefined || this.token === null) {
- return of(<UserLoginState>{ state: 'nologin' });
- }
-
- return this.httpClient.post<TokenValidationResult>('/api/User/ValidateToken', <TokenValidationRequest>{ token: this.token }).pipe(
- retry(3),
- catchError(error => {
- console.error('Failed to validate token.');
- return throwError(error);
- }),
- map(result => {
- if (result.isValid) {
- this.userInfo = result.userInfo;
- return <UserLoginState>{
- state: 'success',
- userInfo: result.userInfo
- };
- } else {
- this.token = null;
- this.userInfo = null;
- return <UserLoginState>{
- state: 'invalidlogin'
- };
- }
- })
- );
- }
-
- tryLogin(credentials: UserCredentials): Observable<UserInfo> {
- if (this.token) {
- return throwError(new AlreadyLoginException());
- }
-
- return this.httpClient.post<CreateTokenResult>('/api/User/CreateToken', credentials).pipe(
- catchError((error: HttpErrorResponse) => {
- if (error.error instanceof ErrorEvent) {
- console.error('An error occurred when login: ' + error.error.message);
- return throwError(new BadNetworkException());
- } else if (error.status === 400) {
- console.error('An error occurred when login: wrong credentials.');
- return throwError(new BadCredentialsException());
- } else {
- console.error('An unknown error occurred when login: ' + error);
- return throwError(error);
- }
- }),
- map(result => {
- this.token = result.token;
- this.userInfo = result.userInfo;
- return result.userInfo;
- })
- );
- }
-}
diff --git a/Timeline/ClientApp/src/app/user/user.service.ts b/Timeline/ClientApp/src/app/user/user.service.ts
new file mode 100644
index 00000000..e876706c
--- /dev/null
+++ b/Timeline/ClientApp/src/app/user/user.service.ts
@@ -0,0 +1,33 @@
+import { Injectable } from '@angular/core';
+import { MatDialog } from '@angular/material';
+
+import { Observable } from 'rxjs';
+
+import { UserInfo } from './entities';
+import { InternalUserService } from './internal-user-service/internal-user.service';
+import { UserDialogComponent } from './user-dialog/user-dialog.component';
+
+
+/**
+ * This service provides public api of user module.
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class UserService {
+ constructor(private dialog: MatDialog, private internalService: InternalUserService) { }
+
+ get currentUserInfo(): UserInfo | null {
+ return this.internalService.currentUserInfo;
+ }
+
+ get userInfo$(): Observable<UserInfo | null> {
+ return this.internalService.userInfo$;
+ }
+
+ openUserDialog() {
+ this.dialog.open(UserDialogComponent, {
+ width: '300px'
+ });
+ }
+}