UNPKG

yalento

Version:

An awesome integration of Google Firebase for Angular and Node

957 lines (956 loc) 32.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Repository = void 0; const class_transformer_1 = require("class-transformer"); const guid_typescript_1 = require("guid-typescript"); const ngeohash_1 = require("ngeohash"); require("reflect-metadata"); const rxjs_1 = require("rxjs"); const FirestoreConnector_1 = require("./connector/FirestoreConnector"); const LocalStorageConnector_1 = require("./connector/LocalStorageConnector"); const QuerySubject_1 = require("./QuerySubject"); const select_1 = require("./select/select"); //eslint-disable-next-line @typescript-eslint/no-var-requires const alasql = require('alasql'); class GeoData { constructor(hash, lat, lng) { this.geohash = hash; this.latitude = lat; this.longitude = lng; } getHash() { return this.geohash; } getLat() { return this.latitude; } getLng() { return this.longitude; } } /** * Repository class * This class can be instantiated by new constructor. * You can use the class as singleton, if you share repository data, otherwise initiate new instance for every sql statement */ class Repository { /** * construct new repository instance * @param model * @param className * @param constructorArguments */ constructor(model, className, ...constructorArguments) { this._zone = { run: (callback) => { callback(); }, }; this._classProperties = []; this._subjects = []; this._selects = []; this._subscriptions = []; this._tempData = []; this._excludeSerializeProperties = ['__owner', '__distance', '__initialdata', '__timestamp']; this._connections = {}; this._className = ''; this.userUuid = null; this.userUuid$ = new rxjs_1.Subject(); this.privateMode = false; this.geoQuery = ''; this._class = model; this._creationTimestamp = new Date().getTime(); this._constructorArguments = constructorArguments; this._instanceIdentifier = guid_typescript_1.Guid.create().toString().replace(/-/g, ''); this.createDatabase(); this.initSerializer(className); } /** * */ static generateUuid() { return guid_typescript_1.Guid.create().toString(); } /** * destroy repository instance */ destroy() { Object.keys(this._connections).forEach((connectionId) => { this._connections[connectionId].disconnect(); }); this._subjects.forEach((subject) => { subject.unsubscribe(); }); this._selects.forEach((select) => { select.unsubscribe(); }); } unsubscribe() { this._subscriptions.forEach((sub) => { sub.unsubscribe(); }); } getUserUuid() { return this.userUuid; } getUserUuidObservable() { return this.userUuid$; } isPrivateMode() { return this.privateMode; } /** * * @param storage * @param options */ connectLocalStorage(storage, options) { this._connections.localStorage = new LocalStorageConnector_1.LocalStorageConnector(this, storage, options); this._connections.localStorage.select(''); } /** * * @param firestore * @param options */ connectFirestore(firestore, options) { this._connections.firestore = new FirestoreConnector_1.FirestoreConnector(this, firestore, options); this._connections.firestore.getUserUuid().subscribe((u) => { if (u) { this.userUuid$.next(u); this.userUuid = u; } }); this.privateMode = this._connections.firestore.isPrivateMode(); if (options && options.nearBy) { let timeout; this._subscriptions.push(options.nearBy.lat.subscribe(() => { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ geoLocationChanged: true }); }); }, 10); })); this._subscriptions.push(options.nearBy.long.subscribe(() => { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ geoLocationChanged: true }); }); }, 10); })); this._subscriptions.push(options.nearBy.radius.subscribe(() => { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ geoLocationChanged: true }); }); }, 10); })); } return this; } /** * set ngZone * @param ngZone */ setNgZone(ngZone) { this._zone = ngZone; return this; } /** * */ loadQueryFromConnectors(query) { Object.keys(this._connections).forEach((key) => { this._connections[key].select(query); }); } /** * * @param sql * @param paginatorDefaults */ select(sql, paginatorDefaults) { const subject = new QuerySubject_1.QuerySubject(this, sql, paginatorDefaults); this._subjects.push(subject); subject.execStatement(subject.getSql()).then(); const select = new select_1.Select(subject); this._selects.push(select); return select; } getOneByIdentifier(identifier) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (!identifier) { reject('getOneByIdentifier identifier is null for ' + this.getClassName()); } else { const existingData = yield this.exec({ where: '__uuid LIKE ?', params: [identifier], }, true).then(); if (existingData.length) { resolve(existingData[0]); return; } const promises = []; Object.keys(this._connections).forEach((key) => { promises.push(this._connections[key].selectOneByIdentifier(identifier)); }); if (promises.length === 0) { resolve({}); } Promise.all(promises).then(() => { this.exec({ where: '__uuid LIKE ?', params: [identifier] }, true).then((data) => { resolve(data.length ? data[0] : {}); }); }); } })); } exec(sql, skipGeolocation) { return new Promise((resolve) => { const subject = new QuerySubject_1.QuerySubject(this, sql); subject.execStatement(sql, true, skipGeolocation).then((results) => { resolve(results); }); }); } /** * * @param data * @param id * @param readDefaultsFromSelectStatement * @param skipConnector * @param owners */ create(data, id, readDefaultsFromSelectStatement, skipConnector, owners) { return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { const c = this.createObjectFromClass(data, id, readDefaultsFromSelectStatement, owners); const promises = []; Object.keys(this._connections).forEach((k) => { /* istanbul ignore next */ if (skipConnector !== k) { promises.push(this._connections[k].add([c])); } }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataAdded: true }); }); if (promises.length) { Promise.all(promises) .then(() => resolve(c)) .catch((e) => reject(e)); } else { resolve(c); } })); } /** * * @param data * @param skipConnector */ remove(data, skipConnector) { return new Promise((resolve, reject) => { const promises = []; this._tempData .filter((value) => value['__uuid'] === data['__uuid']) .forEach((remove) => { remove['__removed'] = true; }); Object.keys(this._connections).forEach((k) => { /* istanbul ignore next */ if (skipConnector !== k) { // @ts-ignore promises.push(this._connections[k].remove([data])); } }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataRemoved: true }); }); if (promises.length) { Promise.all(promises) .then(() => resolve(data)) .catch((e) => reject(e)); } else { resolve(data); } }); } /** * * @param data * @param skipConnector */ update(data, skipConnector) { return new Promise((resolve, reject) => { const promises = []; Object.keys(this._connections).forEach((k) => { /* istanbul ignore next */ if (skipConnector !== k) { // @ts-ignore promises.push(this._connections[k].update([data])); } }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataUpdated: true }); }); this._tempData .filter((item) => { return item.__uuid === data.getUuid(); }) .forEach((item) => { item['_ref'] = data; }); if (promises.length) { Promise.all(promises) .then(() => resolve(data)) .catch((e) => reject(e)); } else { resolve(data); } }); } /** * * @param data * @param skipConnector */ updateMultiple(data, skipConnector) { return new Promise((resolve) => { Object.keys(this._connections).forEach((k) => { /* istanbul ignore next */ if (skipConnector !== k) { // @ts-ignore this._connections[k].update(data); } }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataUpdated: true }); }); resolve(data); }); } /** * * @param data * @param skipConnector */ removeMultiple(data, skipConnector) { return new Promise((resolve) => { data.forEach((value) => { value['__removed'] = true; }); const ids = data.map((value) => value['__uuid']); this._tempData .filter((value) => ids.indexOf(value['__uuid']) >= 0) .forEach((remove) => { remove['__removed'] = true; }); Object.keys(this._connections).forEach((k) => { /* istanbul ignore next */ if (skipConnector !== k) { // @ts-ignore this._connections[k].remove(data); } }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataRemoved: true }); }); resolve(data); }); } /** * * @param data * @param readDefaultsFromSelectStatement * @param skipConnector */ createMany(data, readDefaultsFromSelectStatement, skipConnector) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { const promises = []; data.forEach((value) => { promises.push(this.createOneFromMany(value, value['__uuid'] === undefined ? undefined : value['__uuid'], readDefaultsFromSelectStatement)); }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataAdded: true }); }); Promise.all(promises).then((c) => { Object.keys(this._connections).forEach((key) => { if (skipConnector !== key) { this._connections[key].add(c); } }); resolve(c); }); })); }); } updateGeoLocations(nearByFound, currentLat, currentLng) { const idGeoHashMap = {}; nearByFound.forEach((item) => { idGeoHashMap[item['__uuid']] = { hash: item['__geohash'], distance: this.distance(item['__latitude'], item['__longitude'], currentLat, currentLng), }; }); this._tempData.map((item) => { item['__geohash'] = idGeoHashMap[item.__uuid] ? idGeoHashMap[item.__uuid].hash : null; item['_ref']['__distance'] = idGeoHashMap[item.__uuid] ? idGeoHashMap[item.__uuid].distance : 0; }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataUpdated: true }); }); } /** * INTERNAL USE ONLY: return temporary identifier */ getInstanceIdentifier() { return this._instanceIdentifier; } /** * INTERNAL USE ONLY: return temporary identifier */ getClassProperties() { if (this._classProperties.length) { return this._classProperties; } if (this._tempData.length) { Object.keys(this._tempData[0]['_ref']).forEach((property) => { if (property !== '__geohash' && property !== '__owner' && property !== '__viewer') { this._classProperties.push({ name: property }); } }); } else { const c = this._constructorArguments.length ? new this._class(...this._constructorArguments) : new this._class(); Object.keys(c).forEach((property) => { if (property !== '__owner' && property !== '__viewer') { this._classProperties.push({ name: property }); } }); } return this._classProperties; } /** * get assql table name */ getTableName() { return 'temp' + this.getInstanceIdentifier(); } /** * */ getCreationTimestamp() { return this._creationTimestamp; } /** * */ getClassName() { return this._className; } /** * count temporary data */ count() { return Object.keys(this._tempData).length; } setGeoQuery(geoQuery) { this.geoQuery = geoQuery ? geoQuery : ''; } getGeoQuery() { return this.geoQuery; } /** * * @param id * @param data */ createClassInstance(id, data) { const o = this._constructorArguments.length ? new this._class(...this._constructorArguments) : new this._class(); const c = class_transformer_1.plainToClassFromExist(o, data ? data : {}); const uuid = id === undefined ? guid_typescript_1.Guid.create().toString() : id; Object.defineProperty(c, '__uuid', { enumerable: true, configurable: false, writable: false, value: uuid, }); Object.defineProperty(c, 'isLoaded', { enumerable: false, configurable: false, writable: false, value: () => true, }); Object.defineProperty(c, '_timeoutSave', { enumerable: false, configurable: false, writable: true, value: {}, }); Object.defineProperty(c, '_lockedProperties', { enumerable: false, configurable: false, writable: true, value: {}, }); Object.defineProperty(c, '_timeoutSetProperty', { enumerable: false, configurable: false, writable: true, value: {}, }); Object.defineProperty(c, 'getModel', { enumerable: false, configurable: false, writable: false, value: () => { const plain = c['_toPlain'](); Object.keys(plain).forEach((key) => { if (key.substr(0, 2) === '__') { delete plain[key]; } }); return class_transformer_1.deserialize(this._class, JSON.stringify(plain)); }, }); Object.defineProperty(c, 'revert', { enumerable: false, configurable: false, writable: false, value: () => __awaiter(this, void 0, void 0, function* () { const e = yield this.create(JSON.parse(c['__initialdata'])); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataAdded: true }); }); return e; }), }); Object.defineProperty(c, 'isRemoved', { enumerable: false, configurable: false, writable: false, value: () => { return c['__removed']; }, }); Object.defineProperty(c, 'getDistance', { enumerable: false, configurable: false, writable: false, value: () => { return c['__distance'] ? c['__distance'] : 0; }, }); Object.defineProperty(c, 'toJson', { enumerable: false, configurable: false, writable: false, value: () => { return JSON.stringify(c.getModel()); }, }); Object.defineProperty(c, '_toPlain', { enumerable: false, configurable: false, writable: false, value: () => { return class_transformer_1.classToPlain(o, { excludePrefixes: this._excludeSerializeProperties, enableImplicitConversion: true, }); }, }); Object.defineProperty(c, 'save', { enumerable: false, configurable: false, writable: true, value: () => { this.update(c) .then(() => { c['_lockedProperties'] = {}; }) .catch(); }, }); Object.defineProperty(c, 'remove', { enumerable: false, configurable: false, writable: true, value: () => { this.remove(c).then().catch(); }, }); Object.defineProperty(c, 'getUuid', { enumerable: false, configurable: false, writable: false, value: () => { return c.__uuid; }, }); Object.defineProperty(c, 'setProperty', { enumerable: false, configurable: false, writable: false, value: (property, value) => { c['_lockedProperties'][property] = true; if (c['_timeoutSetProperty'][property] !== undefined) { clearTimeout(c['_timeoutSetProperty'][property]); } c['_timeoutSetProperty'][property] = setTimeout(() => { c[property] = value; this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataUpdated: true }); }); }, 500); return { save: () => { if (c['_timeoutSave'][property] !== undefined) { clearTimeout(c['_timeoutSave'][property]); } c['_timeoutSave'][property] = setTimeout(() => { const d = {}; d[property] = value; this.create(d, c.__uuid) .then(() => { c['_lockedProperties'][property] = false; }) .catch(); }, 100); }, }; }, }); Object.defineProperty(c, 'setGeoPoint', { enumerable: false, configurable: false, writable: false, value: (latitude, longitude) => { if (latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180) { c['__latitude'] = latitude; c['__longitude'] = longitude; c['__geohash'] = ngeohash_1.default.encode(latitude, longitude); } else { throw new Error('latitude must be between -90 and 90. longitude must be between -180 and 180'); } return { save: () => { this.updateProperties({ __latitude: c['__latitude'], __longitude: c['__longitude'], __geohash: c['__geohash'], }, c.__uuid) .then() .catch(); }, }; }, }); return c; } createObjectFromClass(data, id, readDefaultsFromSelectStatement, owners) { const exdistingId = id ? id : data && data['__uuid'] ? data['__uuid'] : null; const existingItem = this._tempData.filter((item) => { return item.__uuid === exdistingId; }); const c = existingItem.length ? existingItem[0]._ref : this.createClassInstance(id); if (data) { Object.keys(data).forEach((k) => { if ((!c['_lockedProperties'][k] && k.substr(0, 1) !== '_') || k === '__references' || k === '__geohash' || k === '__viewer') { if (data[k] && data[k].constructor.name === 'Timestamp') { c[k] = new Date(); c[k].setTime(data[k].seconds * 1000); } else if (c[k] && c[k].constructor && c[k] instanceof Date && typeof data[k] === 'string') { c[k] = new Date(data[k]); } else { c[k] = data[k]; } } }); c['__timestamp'] = data['__timestamp'] || new Date().getTime(); } c['__owner'] = {}; if (owners) { owners.forEach((o) => { c['__owner'][o] = true; }); } else { if (data && data['__owner']) { c['__owner'] = data['__owner']; } else if (this.getUserUuid() && this.getUserUuid().substr(0, 10) !== 'ANONYMOUS_') { c['__owner'][this.getUserUuid()] = true; if (!this.isPrivateMode) { c['__owner']['EVERYBODY'] = true; } } else if (!this.isPrivateMode()) { c['__owner']['EVERYBODY'] = true; } } if (data && data['__viewer']) { c['__viewer'] = data['__viewer']; } else if (!this.isPrivateMode) { c['__viewer'] = { EVERYBODY: true }; } else { c['__viewer'] = {}; } if (data) { c['__initialdata'] = JSON.stringify(data); } if (readDefaultsFromSelectStatement) { const additionalData = this.getDataFromSelectStatement(readDefaultsFromSelectStatement); Object.keys(additionalData).forEach((k) => { c[k] = additionalData[k]; }); } if (existingItem.length) { existingItem.forEach((item) => { item._ref = c; item.__removed = false; }); } else { this._tempData.push({ _ref: c, __uuid: c.__uuid, __removed: false, __owner: Object.keys(c.__owner).map((k) => (c.__owner[k] ? k : '')), __viewer: Object.keys(c.__viewer).map((k) => (c.__viewer[k] ? k : '')), }); } return c; } /** * * @param data * @param id * @param readDefaultsFromSelectStatement */ createOneFromMany(data, id, readDefaultsFromSelectStatement) { return new Promise((resolve) => { resolve(this.createObjectFromClass(data, id, readDefaultsFromSelectStatement)); }); } /** * create temporary database if not exists */ createDatabase() { this._tempData = []; alasql('CREATE TABLE IF NOT EXISTS ' + this.getTableName()); alasql.tables[this.getTableName()].data = this._tempData; } /** * * @param readDefaultsFromSelectStatement */ getDataFromSelectStatement(readDefaultsFromSelectStatement) { let where = readDefaultsFromSelectStatement; const and = []; const data = {}; const splitsLeft = [' WHERE ']; const splitsRight = [' ORDER BY ', ' HAVING ', ' GROUP BY ', ' LIMIT ']; splitsLeft.forEach((s) => { where = where.split(s)[1]; }); if (!where) { return {}; } splitsRight.forEach((s) => { where = where.split(s)[0]; }); where.split(' AND ').forEach((s) => { if (s.indexOf(' OR ') === -1) { const segment = s.replace(/(\))*$/, '').replace(/^(\()*/, ''); if (and.filter((value) => value === segment).length === 0) { and.push(segment); } } }); and.forEach((s) => { const match = s.match(/^([A-z]*) (=|LIKE) (.*)/); if (match && match[3] !== undefined) { data[match[1]] = match[3].indexOf('"') === 0 || match[3].indexOf("'") === 0 ? match[3].substr(1, match[3].length - 2) : parseFloat(match[3]); } }); return data; } createEmptyEntity() { const o = this._constructorArguments.length ? new this._class(...this._constructorArguments) : new this._class(); Object.defineProperty(o, 'isLoaded', { enumerable: false, configurable: false, writable: false, value: () => false, }); Object.defineProperty(o, 'getModel', { enumerable: false, configurable: false, writable: false, value: () => { return o; }, }); Object.defineProperty(o, 'isRemoved', { enumerable: false, configurable: false, writable: false, value: () => { return false; }, }); Object.defineProperty(o, 'getDistance', { enumerable: false, configurable: false, writable: false, value: () => 0, }); Object.defineProperty(o, 'save', { enumerable: false, configurable: false, writable: true, value: () => { return; }, }); Object.defineProperty(o, 'remove', { enumerable: false, configurable: false, writable: true, value: () => { return; }, }); Object.defineProperty(o, 'getUuid', { enumerable: false, configurable: false, writable: false, value: () => { return ''; }, }); Object.defineProperty(o, 'setProperty', { enumerable: false, configurable: false, writable: false, value: (property, value) => { o[property] = value; }, }); Object.defineProperty(o, 'setGeoPoint', { enumerable: false, configurable: false, writable: false, value: () => { return o; }, }); return o; } /** * * @param data * @param id */ updateProperties(data, id) { return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { Object.keys(this._connections).forEach((k) => { /* istanbul ignore next */ data['__uuid'] = id; Object.defineProperty(data, '_toPlain', { enumerable: false, configurable: false, writable: false, value: () => { return class_transformer_1.classToPlain(data, { excludePrefixes: this._excludeSerializeProperties, enableImplicitConversion: true, }); }, }); this._connections[k].add([data]); }); this._subjects.forEach((subject) => { subject.updateQueryCallbackChanges({ dataAdded: true }); }); resolve(); })); } /** * * @param lat1 * @param lon1 * @param lat2 * @param lon2 */ distance(lat1, lon1, lat2, lon2) { if (lat1 === lat2 && lon1 === lon2) { return 0; } else { const radlat1 = (Math.PI * lat1) / 180; const radlat2 = (Math.PI * lat2) / 180; const theta = lon1 - lon2; const radtheta = (Math.PI * theta) / 180; let dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); if (dist > 1) { dist = 1; } dist = Math.acos(dist); dist = (dist * 180) / Math.PI; dist = dist * 60 * 1.1515; return dist * 1.609344; } } /** * check properties that can to be serialized */ initSerializer(className) { const c = this.createClassInstance(); this._className = className; Object.keys(c).forEach((key) => { try { new c[key].constructor(); } catch (e) { this._excludeSerializeProperties.push(key); } }); } } exports.Repository = Repository;