yalento
Version:
An awesome integration of Google Firebase for Angular and Node
957 lines (956 loc) • 32.7 kB
JavaScript
"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;