Add services.

- Add block sync service.
- Add mock token service.
- Add mock user service.
- Add user info to transactions.
This commit is contained in:
Spencer Ofwiti 2020-11-25 10:51:15 +03:00
parent 9d6217558c
commit f228a9866b
9 changed files with 397 additions and 8 deletions

View File

@ -0,0 +1,22 @@
import { TestBed } from '@angular/core/testing';
import { BlockSyncService } from './block-sync.service';
import {TransactionService} from './transaction.service';
import {TransactionServiceStub} from '../../testing';
describe('BlockSyncService', () => {
let service: BlockSyncService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{ provide: TransactionService, useClass: TransactionServiceStub }
]
});
service = TestBed.inject(BlockSyncService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,108 @@
import {Injectable} from '@angular/core';
import {Settings} from '../_models';
import Web3 from 'web3';
import {abi, Registry, TransactionHelper} from 'cic-client';
import {first} from 'rxjs/operators';
import {TransactionService} from './transaction.service';
@Injectable({
providedIn: 'root'
})
export class BlockSyncService {
registryAddress: string = '0xb708175e3f6Cd850643aAF7B32212AFad50e2549';
readyStateTarget: number = 3;
readyState: number = 0;
constructor(private transactionService: TransactionService) { }
blockSync(): any {
const settings = new Settings(this.scan);
const provider = 'ws://localhost:8545';
const readyStateElements = {
token: 1,
network: 2,
};
settings.w3.provider = provider;
settings.w3.engine = new Web3(provider);
settings.registry = new Registry(settings.w3.engine, this.registryAddress, abi);
settings.txHelper = new TransactionHelper(settings.registry);
settings.txHelper.ontransfer = async (transaction: any): Promise<void> => {
window.dispatchEvent(this.newTransferEvent(transaction));
};
settings.txHelper.onconversion = async (transaction: any): Promise<any> => {
window.dispatchEvent(this.newConversionEvent(transaction));
};
settings.registry.ontokensload = (tokenCount: number): void => {
// console.debug('loaded tokens', tokenCount);
this.readyStateProcessor(settings, readyStateElements.token);
};
settings.registry.onregistryload = (addressReturned: number): void => {
// console.debug('loaded network contracts', addressReturned);
this.readyStateProcessor(settings, readyStateElements.network);
};
settings.registry.load();
}
readyStateProcessor(settings, bit): void {
this.readyState |= bit;
if (this.readyStateTarget === this.readyState && this.readyStateTarget) {
// console.log('reached readyState target', this.readyStateTarget);
this.fetcher(settings);
}
}
newTransferEvent(tx): any {
return new CustomEvent('cic_transfer', {
detail: {
tx,
},
});
}
newConversionEvent(tx): any {
return new CustomEvent('cic_convert', {
detail: {
tx,
},
});
}
async scan(settings, lo, hi, bloomBlockBytes, bloomBlocktxBytes, bloomRounds): Promise<void> {
const w = new Worker('./../assets/js/worker.js');
w.onmessage = (m) => {
settings.txHelper.processReceipt(m.data);
};
w.postMessage({
w3_provider: settings.w3.provider,
lo,
hi,
filters: [
bloomBlockBytes,
bloomBlocktxBytes,
],
filter_rounds: bloomRounds,
});
}
fetcher(settings: any, offset: number = 0, limit: number = 100): void {
this.transactionService.getAllTransactions(offset, limit).pipe(first()).subscribe(data => {
const blockFilterBinstr = window.atob(data.block_filter);
const bOne = new Uint8Array(blockFilterBinstr.length);
bOne.map((e, i, v) => v[i] = blockFilterBinstr.charCodeAt(i));
const blocktxFilterBinstr = window.atob(data.blocktx_filter);
const bTwo = new Uint8Array(blocktxFilterBinstr.length);
bTwo.map((e, i, v) => v[i] = blocktxFilterBinstr.charCodeAt(i));
for (let i = 0; i < blockFilterBinstr.length; i++) {
if (bOne[i] > 0 ) {
// console.debug('blocktx value on', i);
}
}
settings.scanFilter(settings, data.low, data.high, bOne, bTwo, data.filter_rounds);
});
}
}

View File

@ -0,0 +1,4 @@
export * from './transaction.service';
export * from './user.service';
export * from './token.service';
export * from './block-sync.service';

View File

@ -0,0 +1,24 @@
import { TestBed } from '@angular/core/testing';
import { TokenService } from './token.service';
describe('TokenService', () => {
let service: TokenService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TokenService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return token for available token', () => {
expect(service.getBySymbol('RSV')).toEqual({ name: 'Reserve', symbol: 'RSV' });
});
it('should not return token for unavailable token', () => {
expect(service.getBySymbol('ABC')).toBeUndefined();
});
});

View File

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class TokenService {
data = [
{ name: 'Reserve', symbol: 'RSV' },
{ name: 'Bert', symbol: 'BRT' },
{ name: 'Ernie', symbol: 'ERN' },
{ name: 'Reserve', symbol: 'RSV' },
{ name: 'Bert', symbol: 'BRT' },
{ name: 'Ernie', symbol: 'ERN' },
{ name: 'Reserve', symbol: 'RSV' },
{ name: 'Bert', symbol: 'BRT' },
{ name: 'Ernie', symbol: 'ERN' },
{ name: 'Reserve', symbol: 'RSV' },
{ name: 'Bert', symbol: 'BRT' },
{ name: 'Ernie', symbol: 'ERN' },
];
constructor() { }
getBySymbol(symbol: string): any {
return this.data.find(token => token.symbol === symbol);
}
}

View File

@ -1,16 +1,32 @@
import { TestBed } from '@angular/core/testing';
import {fakeAsync, TestBed, tick} from '@angular/core/testing';
import { TransactionService } from './transaction.service';
import {HttpClient} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
describe('TransactionService', () => {
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
let service: TransactionService;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(TransactionService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
// it('#getUser() should fetch userInfo', fakeAsync(() => {
// expect(service.userInfo).toBeUndefined();
// service.getUser('0x9D7c284907acbd4a0cE2dDD0AA69147A921a573D').then(
// );
// tick();
// expect(service.userInfo).toBe('Hey');
// }));
});

View File

@ -1,9 +1,12 @@
import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {map} from 'rxjs/operators';
import {Observable} from 'rxjs';
import {first, map} from 'rxjs/operators';
import {BehaviorSubject, Observable} from 'rxjs';
import {environment} from '../../environments/environment';
import {Conversion, Transaction} from '../_models';
import {User} from 'cic-client-meta';
import {UserService} from './user.service';
import {parse} from '../../assets/js/parse-vcard';
@Injectable({
providedIn: 'root'
@ -11,8 +14,16 @@ import {Conversion, Transaction} from '../_models';
export class TransactionService {
transactions: Transaction[] = [];
conversions: Conversion[] = [];
private transactionList = new BehaviorSubject<Transaction[]>(this.transactions);
transactionsSubject = this.transactionList.asObservable();
private conversionList = new BehaviorSubject<Conversion[]>(this.conversions);
conversionsSubject = this.conversionList.asObservable();
userInfo: any;
constructor(private http: HttpClient) { }
constructor(
private http: HttpClient,
private userService: UserService
) { }
getAllTransactions(offset: number, limit: number): Observable<any> {
return this.http.get(`${environment.cicCacheUrl}/tx/${offset}/${limit}`)
@ -28,11 +39,39 @@ export class TransactionService {
}));
}
setTransaction(transaction): void {
this.transactions.push(transaction);
setTransaction(transaction, cacheSize: number): void {
const cachedTransaction = this.transactions.find(cachedTx => cachedTx.tx.txHash === transaction.tx.txHash);
if (cachedTransaction) { return; }
this.getUser(transaction.from).then(() => {
transaction.sender = this.userInfo;
this.getUser(transaction.to).then(() => {
transaction.recipient = this.userInfo;
this.transactions.unshift(transaction);
if (this.transactions.length > cacheSize) {
this.transactions.length = cacheSize;
}
this.transactionList.next(this.transactions);
});
});
}
setConversion(conversion): void {
const cachedConversion = this.conversions.find(cachedTx => cachedTx.tx.txHash === conversion.tx.txHash);
if (cachedConversion) { return; }
this.getUser(conversion.trader).then(() => {
conversion.user = this.userInfo;
this.conversions.push(conversion);
this.conversionList.next(this.conversions);
});
}
async getUser(address: string): Promise<void> {
this.userService.getUser(await User.toKey(address)).pipe(first()).subscribe(res => {
const vcard = parse(atob(res.vcard));
res.vcard = vcard;
this.userInfo = res;
}, error => {
console.log(error);
});
}
}

View File

@ -0,0 +1,80 @@
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
import {HttpClient} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
describe('UserService', () => {
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
service = TestBed.inject(UserService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should return user for available id', () => {
expect(service.getUserById('1')).toEqual({
id: 1,
name: 'John Doe',
phone: '+25412345678',
address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865',
type: 'user',
created: '08/16/2020',
balance: '12987',
failedPinAttempts: 1,
status: 'approved',
bio: 'Bodaboda',
gender: 'male'
});
});
it('should not return user for unavailable id', () => {
expect(service.getUserById('9999999999')).toBeUndefined();
});
it('should return action for available id', () => {
expect(service.getActionById('1')).toEqual({
id: 1,
user: 'Tom',
role: 'enroller',
action: 'Disburse RSV 100',
approval: false
});
});
it('should not return action for unavailable id', () => {
expect(service.getActionById('9999999999')).toBeUndefined();
});
it('should switch action approval from false to true', () => {
service.approveAction('1');
expect(service.getActionById('1')).toEqual({
id: 1,
user: 'Tom',
role: 'enroller',
action: 'Disburse RSV 100',
approval: true
});
});
it('should switch action approval from true to false', () => {
service.revokeAction('2');
expect(service.getActionById('2')).toEqual({
id: 2,
user: 'Christine',
role: 'admin',
action: 'Change user phone number',
approval: false
});
});
});

View File

@ -0,0 +1,69 @@
import { Injectable } from '@angular/core';
import {Observable} from 'rxjs';
import {HttpClient, HttpParams} from '@angular/common/http';
import {environment} from '../../environments/environment';
import {map} from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class UserService {
users = [
{id: 1, name: 'John Doe', phone: '+25412345678', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '08/16/2020', balance: '12987', failedPinAttempts: 1, status: 'approved', bio: 'Bodaboda', gender: 'male'},
{id: 2, name: 'Jane Buck', phone: '+25412341234', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'vendor', created: '04/02/2020', balance: '56281', failedPinAttempts: 0, status: 'approved', bio: 'Groceries', gender: 'female'},
{id: 3, name: 'Mc Donald', phone: '+25498765432', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'group', created: '11/16/2020', balance: '450', failedPinAttempts: 2, status: 'unapproved', bio: 'Food', gender: 'male'},
{id: 4, name: 'Hera Cles', phone: '+25498769876', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '05/28/2020', balance: '5621', failedPinAttempts: 3, status: 'approved', bio: 'Shop', gender: 'female'},
{id: 5, name: 'Silver Fia', phone: '+25462518374', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'token agent', created: '10/10/2020', balance: '817', failedPinAttempts: 0, status: 'unapproved', bio: 'Electronics', gender: 'male'},
{id: 1, name: 'John Doe', phone: '+25412345678', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '08/16/2020', balance: '12987', failedPinAttempts: 1, status: 'approved', bio: 'Bodaboda', gender: 'male'},
{id: 2, name: 'Jane Buck', phone: '+25412341234', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'vendor', created: '04/02/2020', balance: '56281', failedPinAttempts: 0, status: 'approved', bio: 'Groceries', gender: 'female'},
{id: 3, name: 'Mc Donald', phone: '+25498765432', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'group', created: '11/16/2020', balance: '450', failedPinAttempts: 2, status: 'unapproved', bio: 'Food', gender: 'male'},
{id: 4, name: 'Hera Cles', phone: '+25498769876', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '05/28/2020', balance: '5621', failedPinAttempts: 3, status: 'approved', bio: 'Shop', gender: 'female'},
{id: 5, name: 'Silver Fia', phone: '+25462518374', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'token agent', created: '10/10/2020', balance: '817', failedPinAttempts: 0, status: 'unapproved', bio: 'Electronics', gender: 'male'},
{id: 1, name: 'John Doe', phone: '+25412345678', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '08/16/2020', balance: '12987', failedPinAttempts: 1, status: 'approved', bio: 'Bodaboda', gender: 'male'},
{id: 2, name: 'Jane Buck', phone: '+25412341234', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'vendor', created: '04/02/2020', balance: '56281', failedPinAttempts: 0, status: 'approved', bio: 'Groceries', gender: 'female'},
{id: 3, name: 'Mc Donald', phone: '+25498765432', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'group', created: '11/16/2020', balance: '450', failedPinAttempts: 2, status: 'unapproved', bio: 'Food', gender: 'male'},
{id: 4, name: 'Hera Cles', phone: '+25498769876', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '05/28/2020', balance: '5621', failedPinAttempts: 3, status: 'approved', bio: 'Shop', gender: 'female'},
{id: 5, name: 'Silver Fia', phone: '+25462518374', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'token agent', created: '10/10/2020', balance: '817', failedPinAttempts: 0, status: 'unapproved', bio: 'Electronics', gender: 'male'},
{id: 1, name: 'John Doe', phone: '+25412345678', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '08/16/2020', balance: '12987', failedPinAttempts: 1, status: 'approved', bio: 'Bodaboda', gender: 'male'},
{id: 2, name: 'Jane Buck', phone: '+25412341234', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'vendor', created: '04/02/2020', balance: '56281', failedPinAttempts: 0, status: 'approved', bio: 'Groceries', gender: 'female'},
{id: 3, name: 'Mc Donald', phone: '+25498765432', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'group', created: '11/16/2020', balance: '450', failedPinAttempts: 2, status: 'unapproved', bio: 'Food', gender: 'male'},
{id: 4, name: 'Hera Cles', phone: '+25498769876', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'user', created: '05/28/2020', balance: '5621', failedPinAttempts: 3, status: 'approved', bio: 'Shop', gender: 'female'},
{id: 5, name: 'Silver Fia', phone: '+25462518374', address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865', type: 'token agent', created: '10/10/2020', balance: '817', failedPinAttempts: 0, status: 'unapproved', bio: 'Electronics', gender: 'male'},
];
actions = [
{ id: 1, user: 'Tom', role: 'enroller', action: 'Disburse RSV 100', approval: false },
{ id: 2, user: 'Christine', role: 'admin', action: 'Change user phone number', approval: true },
{ id: 3, user: 'Will', role: 'superadmin', action: 'Reclaim RSV 1000', approval: true },
{ id: 4, user: 'Vivian', role: 'enroller', action: 'Complete user profile', approval: true },
{ id: 5, user: 'Jack', role: 'enroller', action: 'Reclaim RSV 200', approval: false },
{ id: 6, user: 'Patience', role: 'enroller', action: 'Change user information', approval: false }
];
constructor(private http: HttpClient) { }
getUser(userKey: string): Observable<any> {
const params = new HttpParams().set('userKey', '0970c6e9cdad650ba9006e5a1caf090a13da312792389a147263c98ac78cd037');
return this.http.get(`${environment.cicScriptsUrl}`, { params })
.pipe(map(response => {
return response;
}));
}
getUserById(id: string): any {
return this.users.find(user => user.id === parseInt(id, 10));
}
getActionById(id: string): any {
return this.actions.find(action => action.id === parseInt(id, 10));
}
approveAction(id: string): void {
const action = this.actions.find(queriedAction => queriedAction.id === parseInt(id, 10));
action.approval = true;
}
revokeAction(id: string): void {
const action = this.actions.find(queriedAction => queriedAction.id === parseInt(id, 10));
action.approval = false;
}
}