Initiate angular project with firebase and base components
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ErrorHandler, NgModule } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
@ -7,6 +7,8 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { AngularFireModule } from '@angular/fire';
|
||||
import { AngularFireDatabaseModule } from '@angular/fire/database';
|
||||
import { environment } from '@env/environment';
|
||||
import { GlobalErrorHandler } from '@app/core/global-error-handler/global-error-handler.service';
|
||||
import { LogService } from '@app/core/services';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -18,7 +20,13 @@ import { environment } from '@env/environment';
|
||||
AngularFireModule.initializeApp(environment.firebaseConfig),
|
||||
AngularFireDatabaseModule,
|
||||
],
|
||||
providers: [],
|
||||
providers: [
|
||||
LogService,
|
||||
{
|
||||
provide: ErrorHandler,
|
||||
useClass: GlobalErrorHandler
|
||||
},
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule {
|
||||
|
@ -0,0 +1,16 @@
|
||||
/* tslint:disable:no-unused-variable */
|
||||
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
import { GlobalErrorHandler } from './global-error-handler.service';
|
||||
|
||||
describe( 'Service: GlobalErrorHandler', () => {
|
||||
beforeEach( () => {
|
||||
TestBed.configureTestingModule( {
|
||||
providers: [ GlobalErrorHandler ]
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'should ...', inject( [ GlobalErrorHandler ], ( service: GlobalErrorHandler ) => {
|
||||
expect( service ).toBeTruthy();
|
||||
} ) );
|
||||
} );
|
@ -0,0 +1,35 @@
|
||||
import { ErrorHandler, Injectable, Injector } from '@angular/core';
|
||||
import * as StackTrace from 'stacktrace-js';
|
||||
|
||||
import { LogService } from '@app/core/services';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalErrorHandler extends ErrorHandler {
|
||||
constructor( private injector: Injector ) {
|
||||
super();
|
||||
}
|
||||
|
||||
public handleError( error ) {
|
||||
const logService: LogService = this.injector.get( LogService );
|
||||
const message = error.message ? error.message : error.toString();
|
||||
|
||||
if ( error.status ) {
|
||||
error = new Error( message );
|
||||
}
|
||||
|
||||
StackTrace.fromError( error ).then( ( stackframes ) => {
|
||||
const stackString = stackframes
|
||||
.splice( 0, 10 )
|
||||
.map( ( sf ) => {
|
||||
return sf.toString();
|
||||
} )
|
||||
.toString();
|
||||
|
||||
const errorTraceStr = `Error message: ${ message }. Stack trace: ${ stackString }`;
|
||||
|
||||
logService.logError( errorTraceStr );
|
||||
|
||||
throw error;
|
||||
} );
|
||||
}
|
||||
}
|
2
src/app/core/model/index.ts
Normal file
2
src/app/core/model/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './storage/storage';
|
||||
export * from './user/user';
|
5
src/app/core/model/storage/storage.ts
Normal file
5
src/app/core/model/storage/storage.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class Storage {
|
||||
pageData: object;
|
||||
sessionData: object;
|
||||
localData: object;
|
||||
}
|
36
src/app/core/model/user/user.ts
Normal file
36
src/app/core/model/user/user.ts
Normal file
@ -0,0 +1,36 @@
|
||||
export class User {
|
||||
timeGetToken: number;
|
||||
token: string;
|
||||
dataUser: DataUser;
|
||||
}
|
||||
|
||||
export class DataUser {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
address: string;
|
||||
postalCode: string;
|
||||
city: string;
|
||||
country: string;
|
||||
phoneNumber: string;
|
||||
email: string;
|
||||
birthday: number;
|
||||
licenseNumber: string;
|
||||
licenseValidity: number;
|
||||
licenseCategories: string[];
|
||||
|
||||
constructor() {
|
||||
this.firstName = null;
|
||||
this.lastName = null;
|
||||
this.address = null;
|
||||
this.postalCode = null;
|
||||
this.city = null;
|
||||
this.country = null;
|
||||
this.phoneNumber = null;
|
||||
this.email = null;
|
||||
this.birthday = null;
|
||||
this.licenseNumber = null;
|
||||
this.licenseValidity = null;
|
||||
this.licenseCategories = null;
|
||||
}
|
||||
|
||||
}
|
12
src/app/core/services/auth/auth.service.spec.ts
Normal file
12
src/app/core/services/auth/auth.service.spec.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe( 'AuthService', () => {
|
||||
beforeEach( () => TestBed.configureTestingModule( {} ) );
|
||||
|
||||
it( 'should be created', () => {
|
||||
const service: AuthService = TestBed.get( AuthService );
|
||||
expect( service ).toBeTruthy();
|
||||
} );
|
||||
} );
|
90
src/app/core/services/auth/auth.service.ts
Normal file
90
src/app/core/services/auth/auth.service.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import * as sha512 from 'js-sha512';
|
||||
|
||||
// import * as config from '@assets/config.json';
|
||||
|
||||
import { User } from '@app/core/model';
|
||||
import { StorageService } from '@app/core/services/storage/storage.service';
|
||||
import { LogService } from '@app/core/services/log/log.service';
|
||||
|
||||
@Injectable( { providedIn: 'root' } )
|
||||
export class AuthService {
|
||||
|
||||
public currentUser: Observable<User>;
|
||||
// public emailRegex = new RegExp( );
|
||||
private currentUserSubject: BehaviorSubject<User>;
|
||||
|
||||
constructor( private http: HttpClient, private router: Router, private stor: StorageService, private looger: LogService ) {
|
||||
this.currentUserSubject = new BehaviorSubject<User>( stor.getLocal( 'user' ) as User );
|
||||
this.currentUser = this.currentUserSubject.asObservable();
|
||||
}
|
||||
|
||||
public get currentUserValue(): User {
|
||||
if ( this.currentUserSubject.value && this.currentUserSubject.value.timeGetToken + 60 * 60 * 1000 < new Date().getTime() ) {
|
||||
const data = new FormData();
|
||||
|
||||
data.append( 'auth_token', this.currentUserSubject.value.token );
|
||||
// this.http.post<any>( environment.baseUrl + config.url.upauth, data ).subscribe( ( res ) => {
|
||||
// if ( res.statusCode !== 200 ) {
|
||||
// this.logout();
|
||||
// } else {
|
||||
// this.currentUserSubject.value.timeGetToken = new Date().getTime();
|
||||
// this.stor.setLocal( 'user', this.currentUserSubject.value );
|
||||
// }
|
||||
// } );
|
||||
}
|
||||
return this.currentUserSubject.value;
|
||||
}
|
||||
|
||||
public login( username: string, password: string ): any {
|
||||
const data: FormData = new FormData();
|
||||
|
||||
data.append( 'email', username );
|
||||
data.append( 'password', sha512.sha512( password ) );
|
||||
|
||||
// return this.http.post<any>( environment.baseUrl + config.url.auth, data )
|
||||
// .pipe( map( result => {
|
||||
// let user = new User();
|
||||
//
|
||||
// this.looger.logInfo( result );
|
||||
// // login successful if there's a jwt token in the response
|
||||
// if ( result && result.body && result.statusCode === 200 ) {
|
||||
//
|
||||
// user.token = result.body;
|
||||
// user.timeGetToken = new Date().getTime();
|
||||
//
|
||||
// // store user details and jwt token in local storage to keep user logged in between page refreshes
|
||||
// this.stor.setLocal( 'user', user );
|
||||
// this.currentUserSubject.next( user );
|
||||
// } else {
|
||||
// user = result.statusCode;
|
||||
// }
|
||||
//
|
||||
// return user;
|
||||
// } ) );
|
||||
}
|
||||
|
||||
public logout(): void {
|
||||
// remove user from local storage to log user out
|
||||
this.stor.setLocal( 'user' );
|
||||
this.currentUserSubject.next( null );
|
||||
this.router.navigate( [ '/login' ] );
|
||||
}
|
||||
|
||||
public createAccount( email: string, password: string, repeat: string ): any {
|
||||
// const data: FormData = new FormData();
|
||||
|
||||
// if ( password === repeat && this.emailRegex.test( email ) ) {
|
||||
// data.append( 'email', email );
|
||||
// data.append( 'password', sha512.sha512( password ) );
|
||||
|
||||
// return this.http.post( environment.baseUrl + config.url.register, data );
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
2
src/app/core/services/index.ts
Normal file
2
src/app/core/services/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './auth/auth.service';
|
||||
export * from './log/log.service';
|
8
src/app/core/services/log/log-data.interface.ts
Normal file
8
src/app/core/services/log/log-data.interface.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface LogFields {
|
||||
userId?: string;
|
||||
elapsedTime?: number;
|
||||
requestPath?: string;
|
||||
environment?: string;
|
||||
appVersion?: string;
|
||||
url?: string;
|
||||
}
|
20
src/app/core/services/log/log.service.spec.ts
Normal file
20
src/app/core/services/log/log.service.spec.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/* tslint:disable:no-unused-variable */
|
||||
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
import { LogService } from './log.service';
|
||||
|
||||
describe( 'Service: Log', () => {
|
||||
beforeEach( () => {
|
||||
TestBed.configureTestingModule( {
|
||||
imports: [],
|
||||
providers: [ LogService ]
|
||||
} );
|
||||
} );
|
||||
|
||||
it(
|
||||
'should ...',
|
||||
inject( [ LogService ], ( service: LogService ) => {
|
||||
expect( service ).toBeTruthy();
|
||||
} )
|
||||
);
|
||||
} );
|
63
src/app/core/services/log/log.service.ts
Normal file
63
src/app/core/services/log/log.service.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '@env/environment';
|
||||
|
||||
import { LogFields } from './log-data.interface';
|
||||
import { Logger } from './logger';
|
||||
|
||||
@Injectable( {
|
||||
providedIn: 'root'
|
||||
} )
|
||||
export class LogService {
|
||||
|
||||
private logger: Logger;
|
||||
|
||||
constructor() {
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
this.logger = new Logger( environment.appName );
|
||||
}
|
||||
|
||||
public logHttpInfo( info: any, elapsedTime: number, requestPath: string ) {
|
||||
// TODO: create and set correlation id
|
||||
const url = location.href;
|
||||
const logFields: LogFields = {
|
||||
environment: environment.env,
|
||||
// userId: this.userId,
|
||||
requestPath,
|
||||
elapsedTime,
|
||||
url,
|
||||
};
|
||||
|
||||
this.logger.log( 'Information', `${ info }`, logFields );
|
||||
}
|
||||
|
||||
public logError( errorMsg: string ) {
|
||||
const url = location.href;
|
||||
|
||||
const logFields: LogFields = {
|
||||
environment: environment.env,
|
||||
// userId: this.userId,
|
||||
requestPath: '',
|
||||
elapsedTime: 0,
|
||||
url: url,
|
||||
};
|
||||
|
||||
this.logger.log( 'Error', errorMsg, logFields );
|
||||
}
|
||||
|
||||
public logInfo( info: any ) {
|
||||
const url = location.href;
|
||||
|
||||
const logFields: LogFields = {
|
||||
environment: environment.env,
|
||||
// userId: this.userId,
|
||||
requestPath: '',
|
||||
elapsedTime: 0,
|
||||
url,
|
||||
};
|
||||
|
||||
this.logger.log( 'Information', info, logFields );
|
||||
}
|
||||
}
|
140
src/app/core/services/log/logger.ts
Normal file
140
src/app/core/services/log/logger.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { environment } from '@env/environment';
|
||||
import * as moment from 'moment';
|
||||
import { Subject } from 'rxjs';
|
||||
import { debounceTime, filter } from 'rxjs/operators';
|
||||
import { LogFields } from './log-data.interface';
|
||||
|
||||
export type LogType = 'Error' | 'Information';
|
||||
|
||||
interface LogEntry {
|
||||
type: LogType;
|
||||
message: string;
|
||||
data: LogFields;
|
||||
}
|
||||
|
||||
enum LoggerEvents {
|
||||
Flush = 1
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private readonly APP_FIELD = 'Application';
|
||||
private readonly ENV_FIELD = 'Environment';
|
||||
private readonly VERSION_FIELD = 'Version';
|
||||
private readonly USER_NAME_FIELD = 'UserName';
|
||||
private readonly ELAPSED_MS_FIELD = 'ElapsedMilliseconds';
|
||||
private readonly REQUEST_PATH_FIELD = 'RequestPath';
|
||||
private readonly URL_FIELD = 'Url';
|
||||
private readonly APP_STATE_FIELD = 'AppState';
|
||||
|
||||
private buffer: LogEntry[] = [];
|
||||
private flush = new Subject<LoggerEvents>();
|
||||
|
||||
constructor( private appName: string, private logEndpoint?: string ) {
|
||||
this.flush
|
||||
.pipe( debounceTime( 5000 ), filter( ( event ) => event === LoggerEvents.Flush ) )
|
||||
.subscribe( () => this.flushBuffer() );
|
||||
}
|
||||
|
||||
public log( type: LogType, message: string, data: LogFields ) {
|
||||
this.buffer.push( {
|
||||
type,
|
||||
message,
|
||||
data
|
||||
} );
|
||||
this.flush.next( LoggerEvents.Flush );
|
||||
}
|
||||
|
||||
private flushBuffer() {
|
||||
const data = this.buffer.splice( 0 );
|
||||
|
||||
if ( data.length === 0 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const body = data
|
||||
.map( ( entry ) => this.buildLogString( entry ) )
|
||||
.reduce( ( sum, entry ) => ( sum += entry ), '' );
|
||||
|
||||
if ( !environment.production ) {
|
||||
// This is nested to make sure we always end up in here when running locally
|
||||
// as in do not && this to the above if...
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log( {
|
||||
body,
|
||||
data
|
||||
} );
|
||||
} else {
|
||||
const xobj = new XMLHttpRequest();
|
||||
// tslint:disable-next-line:no-console
|
||||
if ( this.logEndpoint ) {
|
||||
xobj.onerror = ( err ) => console.error( err );
|
||||
xobj.open( 'POST', this.logEndpoint, true );
|
||||
xobj.send( body );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildLogString( entry: LogEntry ): string {
|
||||
const index = this.buildIndexChunk();
|
||||
const body = this.buildBodyChunk( entry );
|
||||
|
||||
return `${ index }\n${ body }\n`;
|
||||
}
|
||||
|
||||
private buildIndexChunk() {
|
||||
const date = moment();
|
||||
const index = {
|
||||
index: {
|
||||
_index: `logstash-${ date.format( 'YYYY.M.D' ) }`,
|
||||
_type: 'logevent'
|
||||
}
|
||||
};
|
||||
|
||||
return JSON.stringify( index );
|
||||
}
|
||||
|
||||
private buildBodyChunk( entry: LogEntry ) {
|
||||
const { type, message, data } = entry;
|
||||
const level = type;
|
||||
const date = moment();
|
||||
const messageTemplate = this.getMessageTemplate();
|
||||
const fields = this.getFields( data );
|
||||
const body = {
|
||||
'@timestamp': `${ date.toISOString() }`,
|
||||
level,
|
||||
messageTemplate,
|
||||
message,
|
||||
fields
|
||||
};
|
||||
|
||||
return JSON.stringify( body );
|
||||
}
|
||||
|
||||
private getMessageTemplate() {
|
||||
const fields: string[] = [
|
||||
this.APP_FIELD,
|
||||
this.ENV_FIELD,
|
||||
this.VERSION_FIELD,
|
||||
this.USER_NAME_FIELD,
|
||||
this.ELAPSED_MS_FIELD,
|
||||
this.REQUEST_PATH_FIELD,
|
||||
this.URL_FIELD,
|
||||
this.APP_STATE_FIELD
|
||||
];
|
||||
const template = fields.map( ( field ) => `{${ field }}` ).join( ' - ' );
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
private getFields( data: LogFields ) {
|
||||
return {
|
||||
[ this.APP_FIELD ]: this.appName,
|
||||
[ this.ENV_FIELD ]: data.environment,
|
||||
[ this.VERSION_FIELD ]: data.appVersion,
|
||||
[ this.USER_NAME_FIELD ]: data.userId,
|
||||
[ this.ELAPSED_MS_FIELD ]: data.elapsedTime,
|
||||
[ this.REQUEST_PATH_FIELD ]: data.requestPath,
|
||||
[ this.URL_FIELD ]: data.url
|
||||
};
|
||||
}
|
||||
}
|
12
src/app/core/services/storage/storage.service.spec.ts
Normal file
12
src/app/core/services/storage/storage.service.spec.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { StorageService } from './storage.service';
|
||||
|
||||
describe( 'StorageService', () => {
|
||||
beforeEach( () => TestBed.configureTestingModule( {} ) );
|
||||
|
||||
it( 'should be created', () => {
|
||||
const service: StorageService = TestBed.get( StorageService );
|
||||
expect( service ).toBeTruthy();
|
||||
} );
|
||||
} );
|
103
src/app/core/services/storage/storage.service.ts
Normal file
103
src/app/core/services/storage/storage.service.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Storage } from '@app/core/model';
|
||||
|
||||
@Injectable()
|
||||
export class StorageService {
|
||||
|
||||
private data: Storage;
|
||||
|
||||
constructor() {
|
||||
const localData = localStorage.getItem( 'c2a' );
|
||||
const sessionData = sessionStorage.getItem( 'c2a' );
|
||||
|
||||
this.data = new Storage();
|
||||
this.data.pageData = {};
|
||||
this.data.sessionData = ( sessionData ? JSON.parse( sessionData ) : {} );
|
||||
this.data.localData = ( localData ? JSON.parse( localData ) : {} );
|
||||
}
|
||||
|
||||
public getLocal( key: string ): string | number | boolean | object {
|
||||
const value = this.data.localData[ key ];
|
||||
|
||||
if ( !value ) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public getSession( key: string ): string | number | boolean | object {
|
||||
const value = this.data.sessionData[ key ];
|
||||
|
||||
if ( !value ) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public getPage( key: string ): string | number | boolean | object {
|
||||
const value = this.data.pageData[ key ];
|
||||
|
||||
if ( !value ) {
|
||||
return null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public setLocal( key: string, value?: any ): void {
|
||||
let val: string | number | boolean;
|
||||
|
||||
if ( typeof value === 'boolean' || 'string' || 'number' || 'object' ) {
|
||||
val = value;
|
||||
} else {
|
||||
val = null;
|
||||
}
|
||||
|
||||
if ( val === null ) {
|
||||
if ( this.data.localData[ key ] ) {
|
||||
delete this.data.localData[ key ];
|
||||
}
|
||||
} else {
|
||||
this.data.localData[ key ] = val;
|
||||
}
|
||||
localStorage.setItem( 'c2a', JSON.stringify( this.data.localData ) );
|
||||
}
|
||||
|
||||
public setSession( key: string, value?: any ): void {
|
||||
let val: string | number | boolean;
|
||||
|
||||
if ( typeof value === 'boolean' || 'string' || 'number' || 'object' ) {
|
||||
val = value;
|
||||
} else {
|
||||
val = null;
|
||||
}
|
||||
|
||||
if ( val === null ) {
|
||||
if ( this.data.sessionData[ key ] ) {
|
||||
delete this.data.sessionData[ key ];
|
||||
}
|
||||
} else {
|
||||
this.data.sessionData[ key ] = val;
|
||||
}
|
||||
sessionStorage.setItem( 'c2a', JSON.stringify( this.data.localData ) );
|
||||
}
|
||||
|
||||
public setPage( key: string, value?: any ): void {
|
||||
let val: string | number | boolean;
|
||||
|
||||
if ( typeof value === 'boolean' || 'string' || 'number' || 'object' ) {
|
||||
val = value;
|
||||
} else {
|
||||
val = null;
|
||||
}
|
||||
|
||||
if ( val === null ) {
|
||||
if ( this.data.pageData[ key ] ) {
|
||||
delete this.data.pageData[ key ];
|
||||
}
|
||||
} else {
|
||||
this.data.pageData[ key ] = val;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
0
src/assets/config.json
Normal file
0
src/assets/config.json
Normal file
@ -1,5 +1,7 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
appName: 'DiscoTrip',
|
||||
production: false,
|
||||
env: 'production',
|
||||
firebaseConfig: {
|
||||
apiKey: 'AIzaSyDe48emSyoXS3WV6vM4GjYiJwdcMhcspqY',
|
||||
authDomain: 'disco-trip.firebaseapp.com',
|
||||
|
@ -3,7 +3,9 @@
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
appName: 'DiscoTrip',
|
||||
production: false,
|
||||
env: 'local',
|
||||
firebaseConfig: {
|
||||
apiKey: 'AIzaSyDe48emSyoXS3WV6vM4GjYiJwdcMhcspqY',
|
||||
authDomain: 'disco-trip.firebaseapp.com',
|
||||
|
Reference in New Issue
Block a user