first version of sample.component finished
This commit is contained in:
53
src/app/services/api.service.spec.ts
Normal file
53
src/app/services/api.service.spec.ts
Normal file
@ -0,0 +1,53 @@
|
||||
// import { TestBed } from '@angular/core/testing';
|
||||
// import { ApiService } from './api.service';
|
||||
// import {HttpClient} from '@angular/common/http';
|
||||
// import {LocalStorageService} from 'angular-2-local-storage';
|
||||
// import {Observable} from 'rxjs';
|
||||
//
|
||||
// let apiService: ApiService;
|
||||
// let httpClientSpy: jasmine.SpyObj<HttpClient>;
|
||||
// let localStorageServiceSpy: jasmine.SpyObj<LocalStorageService>;
|
||||
//
|
||||
// describe('ApiService', () => {
|
||||
// beforeEach(() => {
|
||||
// const httpSpy = jasmine.createSpyObj('HttpClient', ['get']);
|
||||
// const localStorageSpy = jasmine.createSpyObj('LocalStorageService', ['get']);
|
||||
//
|
||||
// TestBed.configureTestingModule({
|
||||
// providers: [
|
||||
// ApiService,
|
||||
// {provide: HttpClient, useValue: httpSpy},
|
||||
// {provide: LocalStorageService, useValue: localStorageSpy}
|
||||
// ]
|
||||
// });
|
||||
//
|
||||
// apiService = TestBed.inject(ApiService);
|
||||
// httpClientSpy = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;
|
||||
// localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj<LocalStorageService>;
|
||||
// });
|
||||
//
|
||||
// it('should be created', () => {
|
||||
// expect(apiService).toBeTruthy();
|
||||
// });
|
||||
//
|
||||
// it('should do get requests without auth if not available', () => {
|
||||
// const getReturn = new Observable();
|
||||
// httpClientSpy.get.and.returnValue(getReturn);
|
||||
// localStorageServiceSpy.get.and.returnValue(undefined);
|
||||
//
|
||||
// const result = apiService.get('/testurl');
|
||||
// expect(result).toBe(getReturn);
|
||||
// expect(httpClientSpy.get).toHaveBeenCalledWith('/testurl', {});
|
||||
// expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth');
|
||||
// });
|
||||
// it('should do get requests with basic auth if available', () => {
|
||||
// const getReturn = new Observable();
|
||||
// httpClientSpy.get.and.returnValue(getReturn);
|
||||
// localStorageServiceSpy.get.and.returnValue('basicAuth');
|
||||
//
|
||||
// const result = apiService.get('/testurl');
|
||||
// expect(result).toBe(getReturn);
|
||||
// expect(httpClientSpy.get).toHaveBeenCalledWith('/testurl', jasmine.any(Object)); // could not test http headers better
|
||||
// expect(localStorageServiceSpy.get).toHaveBeenCalledWith('basicAuth');
|
||||
// });
|
||||
// });
|
55
src/app/services/api.service.ts
Normal file
55
src/app/services/api.service.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||
import {LocalStorageService} from 'angular-2-local-storage';
|
||||
import {Observable} from 'rxjs';
|
||||
import {ErrorComponent} from '../error/error.component';
|
||||
import {ModalService} from '@inst-iot/bosch-angular-ui-components';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
|
||||
private host = '/api';
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private storage: LocalStorageService,
|
||||
private modalService: ModalService
|
||||
) { }
|
||||
|
||||
get<T>(url, f: (data?: T, err?) => void = () => {}) {
|
||||
this.requestErrorHandler<T>(this.http.get(this.host + url, this.authOptions()), f);
|
||||
}
|
||||
|
||||
post<T>(url, data = null, f: (data?: T, err?) => void = () => {}) {
|
||||
this.requestErrorHandler<T>(this.http.post(this.host + url, data, this.authOptions()), f);
|
||||
}
|
||||
|
||||
put<T>(url, data = null, f: (data?: T, err?) => void = () => {}) {
|
||||
this.requestErrorHandler<T>(this.http.put(this.host + url, data, this.authOptions()), f);
|
||||
}
|
||||
|
||||
delete<T>(url, f: (data?: T, err?) => void = () => {}) {
|
||||
this.requestErrorHandler<T>(this.http.delete(this.host + url, this.authOptions()), f);
|
||||
}
|
||||
|
||||
private requestErrorHandler<T>(observable: Observable<any>, f: (data?: T, err?) => void) {
|
||||
observable.subscribe(data => {
|
||||
f(data, undefined);
|
||||
}, () => {
|
||||
const modalRef = this.modalService.openComponent(ErrorComponent);
|
||||
modalRef.instance.message = 'Network request failed!';
|
||||
});
|
||||
}
|
||||
|
||||
private authOptions() {
|
||||
const auth = this.storage.get('basicAuth');
|
||||
if (auth) {
|
||||
return {headers: new HttpHeaders({Authorization: 'Basic ' + auth})};
|
||||
}
|
||||
else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
16
src/app/services/autocomplete.service.spec.ts
Normal file
16
src/app/services/autocomplete.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AutocompleteService } from './autocomplete.service';
|
||||
|
||||
describe('AutocompleteService', () => {
|
||||
let service: AutocompleteService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(AutocompleteService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
20
src/app/services/autocomplete.service.ts
Normal file
20
src/app/services/autocomplete.service.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {QuickScore} from 'quick-score';
|
||||
import {of} from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AutocompleteService {
|
||||
|
||||
constructor() { }
|
||||
|
||||
bind(ref, list) {
|
||||
return this.search.bind(ref, list);
|
||||
}
|
||||
|
||||
search(arr, str) {
|
||||
const qs = new QuickScore(arr);
|
||||
return of(str === '' ? [] : qs.search(str).map(e => e.item));
|
||||
}
|
||||
}
|
81
src/app/services/login.service.spec.ts
Normal file
81
src/app/services/login.service.spec.ts
Normal file
@ -0,0 +1,81 @@
|
||||
// import { TestBed } from '@angular/core/testing';
|
||||
//
|
||||
// import { LoginService } from './login.service';
|
||||
// import {LocalStorageService} from 'angular-2-local-storage';
|
||||
// import {ApiService} from './api.service';
|
||||
// import {Observable} from 'rxjs';
|
||||
//
|
||||
// let loginService: LoginService;
|
||||
// let apiServiceSpy: jasmine.SpyObj<ApiService>;
|
||||
// let localStorageServiceSpy: jasmine.SpyObj<LocalStorageService>;
|
||||
//
|
||||
// describe('LoginService', () => {
|
||||
// beforeEach(() => {
|
||||
// const apiSpy = jasmine.createSpyObj('ApiService', ['get']);
|
||||
// const localStorageSpy = jasmine.createSpyObj('LocalStorageService', ['set', 'remove']);
|
||||
//
|
||||
// TestBed.configureTestingModule({
|
||||
// providers: [
|
||||
// LoginService,
|
||||
// {provide: ApiService, useValue: apiSpy},
|
||||
// {provide: LocalStorageService, useValue: localStorageSpy}
|
||||
// ]
|
||||
// });
|
||||
// loginService = TestBed.inject(LoginService);
|
||||
// apiServiceSpy = TestBed.inject(ApiService) as jasmine.SpyObj<ApiService>;
|
||||
// localStorageServiceSpy = TestBed.inject(LocalStorageService) as jasmine.SpyObj<LocalStorageService>;
|
||||
// });
|
||||
//
|
||||
// it('should be created', () => {
|
||||
// expect(loginService).toBeTruthy();
|
||||
// });
|
||||
//
|
||||
// describe('login', () => {
|
||||
// it('should store the basic auth', () => {
|
||||
// localStorageServiceSpy.set.and.returnValue(true);
|
||||
// apiServiceSpy.get.and.returnValue(new Observable());
|
||||
// loginService.login('username', 'password');
|
||||
// expect(localStorageServiceSpy.set).toHaveBeenCalledWith('basicAuth', 'dXNlcm5hbWU6cGFzc3dvcmQ=');
|
||||
// });
|
||||
//
|
||||
// it('should remove the basic auth if login fails', () => {
|
||||
// localStorageServiceSpy.set.and.returnValue(true);
|
||||
// localStorageServiceSpy.remove.and.returnValue(true);
|
||||
// apiServiceSpy.get.and.returnValue(new Observable(o => o.error()));
|
||||
// loginService.login('username', 'password');
|
||||
// expect(localStorageServiceSpy.remove.calls.count()).toBe(1);
|
||||
// expect(localStorageServiceSpy.remove).toHaveBeenCalledWith('basicAuth');
|
||||
// });
|
||||
//
|
||||
// it('should resolve true when login succeeds', async () => {
|
||||
// localStorageServiceSpy.set.and.returnValue(true);
|
||||
// apiServiceSpy.get.and.returnValue(new Observable(o => o.next({status: 'Authorization successful', method: 'basic'})));
|
||||
// expect(await loginService.login('username', 'password')).toBeTruthy();
|
||||
// });
|
||||
//
|
||||
// it('should resolve false when a wrong result comes in', async () => {
|
||||
// localStorageServiceSpy.set.and.returnValue(true);
|
||||
// apiServiceSpy.get.and.returnValue(new Observable(o => o.next({status: 'xxx', method: 'basic'})));
|
||||
// expect(await loginService.login('username', 'password')).toBeFalsy();
|
||||
// });
|
||||
//
|
||||
// it('should resolve false on an error', async () => {
|
||||
// localStorageServiceSpy.set.and.returnValue(true);
|
||||
// apiServiceSpy.get.and.returnValue(new Observable(o => o.error()));
|
||||
// expect(await loginService.login('username', 'password')).toBeFalsy();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// describe('canActivate', () => {
|
||||
// it('should return false at first', () => {
|
||||
// expect(loginService.canActivate(null, null)).toBeFalsy();
|
||||
// });
|
||||
//
|
||||
// it('returns true if login was successful', async () => {
|
||||
// localStorageServiceSpy.set.and.returnValue(true);
|
||||
// apiServiceSpy.get.and.returnValue(new Observable(o => o.next({status: 'Authorization successful', method: 'basic'})));
|
||||
// await loginService.login('username', 'password');
|
||||
// expect(loginService.canActivate(null, null)).toBeTruthy();
|
||||
// });
|
||||
// });
|
||||
// });
|
59
src/app/services/login.service.ts
Normal file
59
src/app/services/login.service.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {ApiService} from './api.service';
|
||||
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from '@angular/router';
|
||||
import {LocalStorageService} from 'angular-2-local-storage';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LoginService implements CanActivate {
|
||||
|
||||
private loggedIn;
|
||||
|
||||
constructor(
|
||||
private api: ApiService,
|
||||
private storage: LocalStorageService
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
login(username = '', password = '') {
|
||||
return new Promise(resolve => {
|
||||
if (username !== '') {
|
||||
this.storage.set('basicAuth', btoa(username + ':' + password));
|
||||
}
|
||||
this.api.get('/authorized', (data: any, error) => {
|
||||
if (!error) {
|
||||
if (data.status === 'Authorization successful') {
|
||||
this.loggedIn = true;
|
||||
resolve(true);
|
||||
} else {
|
||||
this.loggedIn = false;
|
||||
this.storage.remove('basicAuth');
|
||||
resolve(false);
|
||||
}
|
||||
} else {
|
||||
this.loggedIn = false;
|
||||
this.storage.remove('basicAuth');
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot = null, state: RouterStateSnapshot = null): Observable<boolean> {
|
||||
return new Observable<boolean>(observer => {
|
||||
if (this.loggedIn === undefined) {
|
||||
this.login().then(res => {
|
||||
observer.next(res as any);
|
||||
observer.complete();
|
||||
});
|
||||
}
|
||||
else {
|
||||
observer.next(this.loggedIn);
|
||||
observer.complete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
35
src/app/services/validation.service.spec.ts
Normal file
35
src/app/services/validation.service.spec.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ValidationService } from './validation.service';
|
||||
|
||||
let validationService: ValidationService;
|
||||
|
||||
describe('ValidationService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ValidationService]
|
||||
});
|
||||
|
||||
validationService = TestBed.inject(ValidationService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(validationService).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return true on a correct username', () => {
|
||||
expect(validationService.username('abc')).toEqual({ok: true, error: ''});
|
||||
});
|
||||
|
||||
it('should return an error on an incorrect username', () => {
|
||||
expect(validationService.username('abc#')).toEqual({ok: false, error: 'username must only contain a-z0-9-_.'});
|
||||
});
|
||||
|
||||
it('should return true on a correct password', () => {
|
||||
expect(validationService.password('Abc123!#')).toEqual({ok: true, error: ''});
|
||||
});
|
||||
|
||||
it('should return an error on an incorrect password', () => {
|
||||
expect(validationService.password('Abc123')).toEqual({ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'});
|
||||
});
|
||||
|
||||
});
|
124
src/app/services/validation.service.ts
Normal file
124
src/app/services/validation.service.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import Joi from '@hapi/joi';
|
||||
import {AbstractControl} from '@angular/forms';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ValidationService {
|
||||
|
||||
private vUsername = Joi.string()
|
||||
.lowercase()
|
||||
.pattern(new RegExp('^[a-z0-9-_.]+$'))
|
||||
.min(1)
|
||||
.max(128);
|
||||
|
||||
private vPassword = Joi.string()
|
||||
.pattern(/^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~])(?=\S+$)[a-zA-Z0-9!"#%&'()*+,\-.\/:;<=>?@[\]^_`{|}~]{8,}$/)
|
||||
.max(128);
|
||||
|
||||
constructor() { }
|
||||
|
||||
generate(method, args) { // generate a Validator function
|
||||
return (control: AbstractControl): {[key: string]: any} | null => {
|
||||
let ok;
|
||||
let error;
|
||||
if (args) {
|
||||
({ok, error} = this[method](control.value, ...args));
|
||||
}
|
||||
else {
|
||||
({ok, error} = this[method](control.value));
|
||||
}
|
||||
return ok ? null : { failure: error };
|
||||
};
|
||||
}
|
||||
|
||||
username(data) {
|
||||
const {ignore, error} = this.vUsername.validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: 'username must only contain a-z0-9-_.'};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
password(data) {
|
||||
const {ignore, error} = this.vPassword.validate(data);
|
||||
if (error) {
|
||||
if (Joi.string().min(8).validate(data).error) {
|
||||
return {ok: false, error: 'password must have at least 8 characters'};
|
||||
}
|
||||
else if (Joi.string().pattern(/[a-z]+/).validate(data).error) {
|
||||
return {ok: false, error: 'password must have at least one lowercase character'};
|
||||
}
|
||||
else if (Joi.string().pattern(/[A-Z]+/).validate(data).error) {
|
||||
return {ok: false, error: 'password must have at least one uppercase character'};
|
||||
}
|
||||
else if (Joi.string().pattern(/[0-9]+/).validate(data).error) {
|
||||
return {ok: false, error: 'password must have at least one number'};
|
||||
}
|
||||
else if (Joi.string().pattern(/[!"#%&'()*+,-.\/:;<=>?@[\]^_`{|}~]+/).validate(data).error) {
|
||||
return {ok: false, error: 'password must have at least one of the following characters !"#%&\'()*+,-.\\/:;<=>?@[]^_`{|}~'};
|
||||
}
|
||||
else {
|
||||
return {ok: false, error: 'password must only contain a-zA-Z0-9!"#%&\'()*+,-./:;<=>?@[]^_`{|}~'};
|
||||
}
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
string(data) {
|
||||
const {ignore, error} = Joi.string().max(128).allow('').validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: 'must contain max 128 characters'};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
stringOf(data, list) {
|
||||
const {ignore, error} = Joi.string().allow('').valid(...list.map(e => e.toString())).validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: 'must be one of ' + list.join(', ')};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
stringLength(data, length) {
|
||||
const {ignore, error} = Joi.string().max(length).allow('').validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: 'must contain max ' + length + ' characters'};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
minMax(data, min, max) {
|
||||
const {ignore, error} = Joi.number().allow('').min(min).max(max).validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: `must be between ${min} and ${max}`};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
min(data, min) {
|
||||
const {ignore, error} = Joi.number().allow('').min(min).validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: `must not be below ${min}`};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
max(data, max) {
|
||||
const {ignore, error} = Joi.number().allow('').min(max).validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: `must not be above ${max}`};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
|
||||
unique(data, list) {
|
||||
const {ignore, error} = Joi.string().allow('').invalid(...list.map(e => e.toString())).validate(data);
|
||||
if (error) {
|
||||
return {ok: false, error: `values must be unique`};
|
||||
}
|
||||
return {ok: true, error: ''};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user