123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- import * as fs from 'fs'
- import { _, isObject } from 'lodash'
- import { Observable, Subject, interval, map, of } from 'rxjs'
- export class queryService {
- private dataFromStorage: Subject<any> = new Subject()
- private filteredResult: Subject<any> = new Subject()
- public query(storageAddress: Storage, ...conditions: Entries[]): Observable<any> {
- this.loadObsData(storageAddress.address)
- this.filterFromObs(...conditions)
- return this.filteredResult.pipe()
- }
- // Data preparations: Purely Observables
- private loadObsData(location: string) {
- // Temporary version. More defined design will be implemented to cater for different storage locations
- let data = fs.readFileSync(location, 'utf-8')
- let dataJson = JSON.parse(data)
- let count = 0
- const intervalId = setInterval(() => {
- this.dataFromStorage.next(dataJson[count]);
- count++;
- if (count >= 100) {
- clearInterval(intervalId);
- this.dataFromStorage.complete();
- }
- }, 1000)
- }
- // Search and Filter: Pure Observables. To be moved out to become a separate service again.
- private filterFromObs(...conditions: Entries[]) {
- this.dataFromStorage.subscribe({
- next: element => {
- // Logic to check if data meets the conditions, if so, put it into result.next{}
- if (this.hasKeyAndValue(element, ...conditions)) {
- this.filteredResult.next(element)
- } else {
- console.log(`${element.header.messageID} does not match search criteria`)
- }
- }
- })
- }
- // Logic 1: Success. But argument must specifies header.messageID.... to search
- private hasMatchingProps(data, ...conditions): boolean {
- // Merge all condtions into searchObj
- let searchObj = Object.assign({}, ...conditions)
- let result = _.every(searchObj, (val, key) => {
- const propKeys = key.split('.');
- let nestedObj = data;
- _.forEach(propKeys, propKey => {
- nestedObj = nestedObj[propKey];
- });
- if (_.isObject(val)) {
- return this.hasMatchingProps(nestedObj, val);
- }
- return nestedObj === val;
- });
- return result
- }
- // Logic 2: Success: More superior version than Logic 1 since it can perform flat searches like {messageID : 1234} without specifying its nested properties
- private hasKeyAndValue(data, ...conditions): boolean {
- // Merge all condtions into searchObj
- let searchObj = Object.assign({}, ...conditions)
- if (typeof data !== 'object' || typeof searchObj !== 'object') {
- return false;
- }
- let matchKeys = Object.keys(searchObj);
- let isMatchingObject = (object) => {
- return matchKeys.every((key) => {
- let lodashPath = key.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
- let objectValue = _.get(object, lodashPath);
- let searchValue = searchObj[key];
- if (typeof searchValue === 'object' && typeof objectValue === 'object') {
- return isMatchingObject(objectValue);
- } else {
- return objectValue === searchValue;
- }
- });
- };
- let isObjectMatching = (object) => {
- if (typeof object !== 'object') {
- return false;
- }
- return isMatchingObject(object) || Object.values(object).some(isObjectMatching);
- };
- return isObjectMatching(data);
- /* This function first merges all the ...conditions objects into a single searchObj object using the Object.assign() method.
- It then checks whether both data and searchObj are of type object. If either one is not an object, the function returns false.
- Next, the function defines two helper functions: isMatchingObject and isObjectMatching. isMatchingObject takes an object and
- returns true if all the key-value pairs in searchObj are present in the object. isObjectMatching takes an object and returns
- true if the object itself or any of its nested objects satisfy the conditions specified in searchObj.
- The isMatchingObject function uses the every() method to iterate through each key in searchObj and check if the corresponding
- value in the object matches the value in searchObj. The function also uses the _.get() method from the Lodash library to get
- the value of nested object properties using a string path. If the value in searchObj or the object is an object itself,
- isMatchingObject calls itself recursively to check for nested properties.
- The isObjectMatching function first checks if the input object is of type object. If not, it returns false. If the object is an object,
- it checks whether the object or any of its nested objects satisfy the conditions in searchObj by calling isMatchingObject and isObjectMatching recursively.
- Finally, the hasKeyAndValue function returns the result of isObjectMatching(data), which is a boolean indicating whether data satisfies
- the conditions specified in searchObj. */
- }
- }
- // Entries that client will use. Subject to be improved later on
- export interface Entries {
- _id?: string,
- appLogLocId?: string,
- msgId?: string,
- msgLogDateTime?: Date | string,
- msgDateTime?: Date | string,
- msgTag?: string[],
- msgPayload?: string,
- messageID: string
- }
- export interface Storage {
- type: string,
- address: string
- }
|