mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-13 07:42:55 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
20
packages/noodl-runtime/src/api/cloudfile.js
Normal file
20
packages/noodl-runtime/src/api/cloudfile.js
Normal file
@@ -0,0 +1,20 @@
|
||||
class CloudFile {
|
||||
constructor({ name, url }) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.url;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CloudFile;
|
||||
613
packages/noodl-runtime/src/api/cloudstore.js
Normal file
613
packages/noodl-runtime/src/api/cloudstore.js
Normal file
@@ -0,0 +1,613 @@
|
||||
const NoodlRuntime = require('../../noodl-runtime');
|
||||
const Model = require('../model');
|
||||
const Collection = require('../collection');
|
||||
const CloudFile = require('./cloudfile');
|
||||
const EventEmitter = require('../events');
|
||||
|
||||
const _protectedFields = {
|
||||
_common: ['_createdAt', '_updatedAt', 'objectId'],
|
||||
_User: ['_email_verify_token']
|
||||
};
|
||||
|
||||
function _removeProtectedFields(data, className) {
|
||||
const _data = Object.assign({}, data);
|
||||
_protectedFields._common.forEach((f) => delete _data[f]);
|
||||
if (className && _protectedFields[className]) _protectedFields[className].forEach((f) => delete _data[f]);
|
||||
|
||||
return _data;
|
||||
}
|
||||
|
||||
class CloudStore {
|
||||
constructor(modelScope) {
|
||||
this._initCloudServices();
|
||||
|
||||
this.events = new EventEmitter();
|
||||
this.events.setMaxListeners(10000);
|
||||
this.modelScope = modelScope;
|
||||
|
||||
this._fromJSON = (item, collectionName) => CloudStore._fromJSON(item, collectionName, modelScope);
|
||||
this._deserializeJSON = (data, type) => CloudStore._deserializeJSON(data, type, modelScope);
|
||||
this._serializeObject = (data, collectionName) => CloudStore._serializeObject(data, collectionName, modelScope);
|
||||
}
|
||||
|
||||
_initCloudServices() {
|
||||
_collections = undefined; // clear collection cache, so it's refetched
|
||||
const cloudServices = NoodlRuntime.instance.getMetaData('cloudservices');
|
||||
|
||||
if (cloudServices) {
|
||||
this.appId = cloudServices.appId;
|
||||
this.endpoint = cloudServices.endpoint;
|
||||
}
|
||||
}
|
||||
|
||||
on() {
|
||||
this.events.on.apply(this.events, arguments);
|
||||
}
|
||||
|
||||
off() {
|
||||
this.events.off.apply(this.events, arguments);
|
||||
}
|
||||
|
||||
_makeRequest(path, options) {
|
||||
if (typeof _noodl_cloud_runtime_version === 'undefined') {
|
||||
// Running in browser
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
var json;
|
||||
try {
|
||||
// In SSR, we dont have xhr.response
|
||||
json = JSON.parse(xhr.response || xhr.responseText);
|
||||
} catch (e) {}
|
||||
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
options.success(json);
|
||||
} else options.error(json || { error: xhr.responseText, status: xhr.status });
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open(options.method || 'GET', this.endpoint + path, true);
|
||||
|
||||
xhr.setRequestHeader('X-Parse-Application-Id', this.appId);
|
||||
if (typeof _noodl_cloudservices !== 'undefined')
|
||||
xhr.setRequestHeader('X-Parse-Master-Key', _noodl_cloudservices.masterKey);
|
||||
|
||||
// Check for current users
|
||||
var _cu = localStorage['Parse/' + this.appId + '/currentUser'];
|
||||
if (_cu !== undefined) {
|
||||
try {
|
||||
const currentUser = JSON.parse(_cu);
|
||||
xhr.setRequestHeader('X-Parse-Session-Token', currentUser.sessionToken);
|
||||
} catch (e) {
|
||||
// Failed to extract session token
|
||||
}
|
||||
}
|
||||
|
||||
if (options.onUploadProgress) {
|
||||
xhr.upload.onprogress = (pe) => options.onUploadProgress(pe);
|
||||
}
|
||||
|
||||
if (options.content instanceof File) {
|
||||
xhr.send(options.content);
|
||||
} else {
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.send(JSON.stringify(options.content));
|
||||
}
|
||||
} else {
|
||||
// Running in cloud runtime
|
||||
const endpoint = typeof _noodl_cloudservices !== 'undefined' ? _noodl_cloudservices.endpoint : this.endpoint;
|
||||
const appId = typeof _noodl_cloudservices !== 'undefined' ? _noodl_cloudservices.appId : this.appId;
|
||||
const masterKey = typeof _noodl_cloudservices !== 'undefined' ? _noodl_cloudservices.masterKey : undefined;
|
||||
|
||||
fetch(endpoint + path, {
|
||||
method: options.method || 'GET',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(options.content)
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.status === 200 || r.status === 201) {
|
||||
if (options.method === 'DELETE') {
|
||||
options.success(undefined);
|
||||
} else {
|
||||
r.json()
|
||||
.then((json) => options.success(json))
|
||||
.catch((e) =>
|
||||
options.error({
|
||||
error: 'CloudStore: Failed to get json result.'
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (options.method === 'DELETE') {
|
||||
options.error({ error: 'Failed to delete.' });
|
||||
} else {
|
||||
r.json()
|
||||
.then((json) => options.error(json))
|
||||
.catch((e) => options.error({ error: 'Failed to fetch.' }));
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
options.error({ error: e.message });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
query(options) {
|
||||
this._makeRequest('/classes/' + options.collection, {
|
||||
method: 'POST',
|
||||
content: {
|
||||
_method: 'GET',
|
||||
where: options.where,
|
||||
limit: options.limit,
|
||||
skip: options.skip,
|
||||
include: Array.isArray(options.include) ? options.include.join(',') : options.include,
|
||||
keys: Array.isArray(options.select) ? options.select.join(',') : options.select,
|
||||
order: Array.isArray(options.sort) ? options.sort.join(',') : options.sort,
|
||||
count: options.count
|
||||
},
|
||||
success: function (response) {
|
||||
options.success(response.results, response.count);
|
||||
},
|
||||
error: function () {
|
||||
options.error();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
aggregate(options) {
|
||||
const args = [];
|
||||
|
||||
if (!options.group) {
|
||||
options.error('You need to provide group option.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.where) args.push('match=' + encodeURIComponent(JSON.stringify(options.where)));
|
||||
if (options.limit) args.push('limit=' + options.limit);
|
||||
if (options.skip) args.push('skip=' + options.skip);
|
||||
|
||||
const grouping = {
|
||||
objectId: null
|
||||
};
|
||||
|
||||
Object.keys(options.group).forEach((k) => {
|
||||
const _g = {};
|
||||
const group = options.group[k];
|
||||
if (group['avg'] !== undefined) _g['$avg'] = '$' + group['avg'];
|
||||
else if (group['sum'] !== undefined) _g['$sum'] = '$' + group['sum'];
|
||||
else if (group['max'] !== undefined) _g['$max'] = '$' + group['max'];
|
||||
else if (group['min'] !== undefined) _g['$min'] = '$' + group['min'];
|
||||
else if (group['distinct'] !== undefined) _g['$addToSet'] = '$' + group['distinct'];
|
||||
|
||||
grouping[k] = _g;
|
||||
});
|
||||
|
||||
args.push('group=' + JSON.stringify(grouping));
|
||||
|
||||
this._makeRequest('/aggregate/' + options.collection + (args.length > 0 ? '?' + args.join('&') : ''), {
|
||||
success: function (response) {
|
||||
const res = {};
|
||||
|
||||
if (!response.results || response.results.length !== 1) {
|
||||
options.success({}); // No result
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(options.group).forEach((k) => {
|
||||
res[k] = response.results[0][k];
|
||||
});
|
||||
|
||||
options.success(res);
|
||||
},
|
||||
error: function () {
|
||||
options.error();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
count(options) {
|
||||
const args = [];
|
||||
|
||||
if (options.where) args.push('where=' + encodeURIComponent(JSON.stringify(options.where)));
|
||||
args.push('limit=0');
|
||||
args.push('count=1');
|
||||
|
||||
this._makeRequest('/classes/' + options.collection + (args.length > 0 ? '?' + args.join('&') : ''), {
|
||||
success: function (response) {
|
||||
options.success(response.count);
|
||||
},
|
||||
error: function () {
|
||||
options.error();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
distinct(options) {
|
||||
const args = [];
|
||||
|
||||
if (options.where) args.push('where=' + encodeURIComponent(JSON.stringify(options.where)));
|
||||
args.push('distinct=' + options.property);
|
||||
|
||||
this._makeRequest('/aggregate/' + options.collection + (args.length > 0 ? '?' + args.join('&') : ''), {
|
||||
success: function (response) {
|
||||
options.success(response.results);
|
||||
},
|
||||
error: function () {
|
||||
options.error();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fetch(options) {
|
||||
const args = [];
|
||||
|
||||
if (options.include)
|
||||
args.push('include=' + (Array.isArray(options.include) ? options.include.join(',') : options.include));
|
||||
|
||||
this._makeRequest(
|
||||
'/classes/' + options.collection + '/' + options.objectId + (args.length > 0 ? '?' + args.join('&') : ''),
|
||||
{
|
||||
method: 'GET',
|
||||
success: (response) => {
|
||||
options.success(response);
|
||||
this.events.emit('fetch', {
|
||||
type: 'fetch',
|
||||
objectId: options.objectId,
|
||||
object: response,
|
||||
collection: options.collection
|
||||
});
|
||||
},
|
||||
error: function (res) {
|
||||
options.error(res.error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
create(options) {
|
||||
this._makeRequest('/classes/' + options.collection, {
|
||||
method: 'POST',
|
||||
content: Object.assign(
|
||||
_removeProtectedFields(_serializeObject(options.data, options.collection), options.collection),
|
||||
{ ACL: options.acl }
|
||||
),
|
||||
success: (response) => {
|
||||
const _obj = Object.assign({}, options.data, response);
|
||||
options.success(_obj);
|
||||
this.events.emit('create', {
|
||||
type: 'create',
|
||||
objectId: options.objectId,
|
||||
object: _obj,
|
||||
collection: options.collection
|
||||
});
|
||||
},
|
||||
error: function (res) {
|
||||
options.error(res.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
increment(options) {
|
||||
const data = {};
|
||||
|
||||
for (let key in options.properties) {
|
||||
data[key] = { __op: 'Increment', amount: options.properties[key] };
|
||||
}
|
||||
|
||||
this._makeRequest('/classes/' + options.collection + '/' + options.objectId, {
|
||||
method: 'PUT',
|
||||
content: data,
|
||||
success: (response) => {
|
||||
options.success(response);
|
||||
},
|
||||
error: function (res) {
|
||||
options.error(res.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
save(options) {
|
||||
const _data = Object.assign({}, options.data);
|
||||
delete _data.createdAt;
|
||||
delete _data.updatedAt;
|
||||
|
||||
this._makeRequest('/classes/' + options.collection + '/' + options.objectId, {
|
||||
method: 'PUT',
|
||||
content: Object.assign(_removeProtectedFields(_serializeObject(_data, options.collection), options.collection), {
|
||||
ACL: options.acl
|
||||
}),
|
||||
success: (response) => {
|
||||
options.success(response);
|
||||
this.events.emit('save', {
|
||||
type: 'save',
|
||||
objectId: options.objectId,
|
||||
object: Object.assign({}, options.data, response),
|
||||
collection: options.collection
|
||||
});
|
||||
},
|
||||
error: function (res) {
|
||||
options.error(res.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete(options) {
|
||||
this._makeRequest('/classes/' + options.collection + '/' + options.objectId, {
|
||||
method: 'DELETE',
|
||||
success: () => {
|
||||
options.success();
|
||||
this.events.emit('delete', {
|
||||
type: 'delete',
|
||||
objectId: options.objectId,
|
||||
collection: options.collection
|
||||
});
|
||||
},
|
||||
error: function (res) {
|
||||
options.error(res.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addRelation(options) {
|
||||
const _content = {};
|
||||
_content[options.key] = {
|
||||
__op: 'AddRelation',
|
||||
objects: [
|
||||
{
|
||||
__type: 'Pointer',
|
||||
objectId: options.targetObjectId,
|
||||
className: options.targetClass
|
||||
}
|
||||
]
|
||||
};
|
||||
this._makeRequest('/classes/' + options.collection + '/' + options.objectId, {
|
||||
method: 'PUT',
|
||||
content: _content,
|
||||
success: function (response) {
|
||||
options.success(response);
|
||||
},
|
||||
error: function (res) {
|
||||
options.error(res.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
removeRelation(options) {
|
||||
const _content = {};
|
||||
_content[options.key] = {
|
||||
__op: 'RemoveRelation',
|
||||
objects: [
|
||||
{
|
||||
__type: 'Pointer',
|
||||
objectId: options.targetObjectId,
|
||||
className: options.targetClass
|
||||
}
|
||||
]
|
||||
};
|
||||
this._makeRequest('/classes/' + options.collection + '/' + options.objectId, {
|
||||
method: 'PUT',
|
||||
content: _content,
|
||||
success: function (response) {
|
||||
options.success(response);
|
||||
},
|
||||
error: function (res) {
|
||||
options.error(res.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uploadFile(options) {
|
||||
this._makeRequest('/files/' + options.file.name, {
|
||||
method: 'POST',
|
||||
content: options.file,
|
||||
contentType: options.file.type,
|
||||
success: (response) => options.success(Object.assign({}, options.data, response)),
|
||||
error: (err) => options.error(err),
|
||||
onUploadProgress: options.onUploadProgress
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Users holding the master key are allowed to delete files
|
||||
*
|
||||
* @param {{
|
||||
* file: {
|
||||
* name: string;
|
||||
* }
|
||||
* }} options
|
||||
*/
|
||||
deleteFile(options) {
|
||||
this._makeRequest('/files/' + options.file.name, {
|
||||
method: 'DELETE',
|
||||
success: (response) => options.success(Object.assign({}, options.data, response)),
|
||||
error: (err) => options.error(err)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function _isArrayOfObjects(a) {
|
||||
if (!Array.isArray(a)) return false;
|
||||
for (var i = 0; i < a.length; i++) if (typeof a[i] !== 'object' || a[i] === null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function _toJSON(obj) {
|
||||
if (obj instanceof Model) {
|
||||
var res = {};
|
||||
for (var key in obj.data) {
|
||||
res[key] = _toJSON(obj.data[key]);
|
||||
}
|
||||
return res;
|
||||
} else if (obj instanceof Collection) {
|
||||
var res = [];
|
||||
obj.items.forEach((m) => {
|
||||
res.push(_toJSON(m));
|
||||
});
|
||||
return res;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function _serializeObject(data, collectionName, modelScope) {
|
||||
if (CloudStore._collections[collectionName]) var schema = CloudStore._collections[collectionName].schema;
|
||||
|
||||
for (var key in data) {
|
||||
var _type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined;
|
||||
|
||||
if (data[key] === undefined || data[key] === null) {
|
||||
// Keep null and undefined as is
|
||||
} else if (_type === 'Pointer' && typeof data[key] === 'string') {
|
||||
// This is a string pointer to an object
|
||||
data[key] = {
|
||||
__type: 'Pointer',
|
||||
className: schema.properties[key].targetClass,
|
||||
objectId: data[key]
|
||||
};
|
||||
} else if (_type === 'Pointer' && typeof data[key] === 'object' && (modelScope || Model).instanceOf(data[key])) {
|
||||
// This is an embedded object that should be stored as pointer
|
||||
data[key] = {
|
||||
__type: 'Pointer',
|
||||
className: schema.properties[key].targetClass,
|
||||
objectId: data[key].getId()
|
||||
};
|
||||
} else if (_type === 'Date' && (typeof data[key] === 'string' || data[key] instanceof Date)) {
|
||||
data[key] = {
|
||||
__type: 'Date',
|
||||
iso: data[key] instanceof Date ? data[key].toISOString() : data[key]
|
||||
};
|
||||
} else if (_type === 'File' && data[key] instanceof CloudFile) {
|
||||
const cloudFile = data[key];
|
||||
data[key] = {
|
||||
__type: 'File',
|
||||
url: cloudFile.getUrl(),
|
||||
name: cloudFile.getName()
|
||||
};
|
||||
} else if (_type === 'Array' && typeof data[key] === 'string' && Collection.exists(data[key])) {
|
||||
data[key] = _toJSON(Collection.get(data[key]));
|
||||
} else if (_type === 'Object' && typeof data[key] === 'string' && (modelScope || Model).exists(data[key])) {
|
||||
data[key] = _toJSON((modelScope || Model).get(data[key]));
|
||||
} else if (_type === 'GeoPoint' && typeof data[key] === 'object') {
|
||||
data[key] = {
|
||||
__type: 'GeoPoint',
|
||||
latitude: Number(data[key].latitude),
|
||||
longitude: Number(data[key].longitude)
|
||||
};
|
||||
} else data[key] = _toJSON(data[key]);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function _deserializeJSON(data, type, modelScope) {
|
||||
if (data === undefined) return;
|
||||
if (data === null) return null;
|
||||
|
||||
if (type === 'Relation' && data.__type === 'Relation') {
|
||||
return undefined; // Ignore relation fields
|
||||
} else if (type === 'Pointer' && data.__type === 'Pointer') {
|
||||
// This is a pointer type, resolve into id
|
||||
return data.objectId;
|
||||
} else if (type === 'Date' && data.__type === 'Date') {
|
||||
return new Date(data.iso);
|
||||
} else if (type === 'Date' && typeof data === 'string') {
|
||||
return new Date(data);
|
||||
} else if (type === 'File' && data.__type === 'File') {
|
||||
return new CloudFile(data);
|
||||
} else if (type === 'GeoPoint' && data.__type === 'GeoPoint') {
|
||||
return {
|
||||
latitude: data.latitude,
|
||||
longitude: data.longitude
|
||||
};
|
||||
} else if (_isArrayOfObjects(data)) {
|
||||
var a = [];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
a.push(_deserializeJSON(data[i], undefined, modelScope));
|
||||
}
|
||||
var c = Collection.get();
|
||||
c.set(a);
|
||||
return c;
|
||||
} else if (Array.isArray(data)) return data;
|
||||
// This is an array with mixed data, just return it
|
||||
else if (data && data.__type === 'Object' && data.className !== undefined && data.objectId !== undefined) {
|
||||
const _data = Object.assign({}, data);
|
||||
delete _data.className;
|
||||
delete _data.__type;
|
||||
return _fromJSON(_data, data.className, modelScope);
|
||||
} else if (typeof data === 'object' && data !== null) {
|
||||
var m = (modelScope || Model).get();
|
||||
for (var key in data) {
|
||||
m.set(key, _deserializeJSON(data[key], undefined, modelScope));
|
||||
}
|
||||
return m;
|
||||
} else return data;
|
||||
}
|
||||
|
||||
function _fromJSON(item, collectionName, modelScope) {
|
||||
const m = (modelScope || Model).get(item.objectId);
|
||||
m._class = collectionName;
|
||||
|
||||
if (collectionName !== undefined && CloudStore._collections[collectionName] !== undefined)
|
||||
var schema = CloudStore._collections[collectionName].schema;
|
||||
|
||||
for (var key in item) {
|
||||
if (key === 'objectId' || key === 'ACL') continue;
|
||||
|
||||
var _type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined;
|
||||
|
||||
m.set(key, _deserializeJSON(item[key], _type, modelScope));
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
CloudStore._fromJSON = _fromJSON;
|
||||
CloudStore._deserializeJSON = _deserializeJSON;
|
||||
CloudStore._serializeObject = _serializeObject;
|
||||
|
||||
CloudStore.forScope = (modelScope) => {
|
||||
if (modelScope === undefined) return CloudStore.instance;
|
||||
if (modelScope._cloudStore) return modelScope._cloudStore;
|
||||
|
||||
modelScope._cloudStore = new CloudStore(modelScope);
|
||||
return modelScope._cloudStore;
|
||||
};
|
||||
|
||||
var _instance;
|
||||
Object.defineProperty(CloudStore, 'instance', {
|
||||
get: function () {
|
||||
if (_instance === undefined) _instance = new CloudStore();
|
||||
return _instance;
|
||||
}
|
||||
});
|
||||
|
||||
var _collections;
|
||||
Object.defineProperty(CloudStore, '_collections', {
|
||||
get: function () {
|
||||
if (_collections === undefined) {
|
||||
_collections = {};
|
||||
const dbCollections = NoodlRuntime.instance.getMetaData('dbCollections') || [];
|
||||
dbCollections.forEach((c) => {
|
||||
_collections[c.name] = c;
|
||||
});
|
||||
|
||||
const systemCollections = NoodlRuntime.instance.getMetaData('systemCollections') || [];
|
||||
systemCollections.forEach((c) => {
|
||||
_collections[c.name] = c;
|
||||
});
|
||||
}
|
||||
return _collections;
|
||||
}
|
||||
});
|
||||
|
||||
CloudStore.invalidateCollections = () => {
|
||||
_collections = undefined;
|
||||
};
|
||||
|
||||
module.exports = CloudStore;
|
||||
112
packages/noodl-runtime/src/api/configservice.js
Normal file
112
packages/noodl-runtime/src/api/configservice.js
Normal file
@@ -0,0 +1,112 @@
|
||||
const NoodlRuntime = require('../../../noodl-runtime');
|
||||
|
||||
class ConfigService {
|
||||
constructor() {
|
||||
this.cacheDuration = 15 * 60 * 1000; // 15 min cache
|
||||
}
|
||||
|
||||
_makeRequest(path, options) {
|
||||
if (typeof _noodl_cloud_runtime_version === 'undefined') {
|
||||
// Running in browser
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
var json;
|
||||
try {
|
||||
json = JSON.parse(xhr.response);
|
||||
} catch (e) {}
|
||||
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
options.success(json);
|
||||
} else options.error(json || { error: xhr.responseText, status: xhr.status });
|
||||
}
|
||||
};
|
||||
|
||||
const cloudServices = NoodlRuntime.instance.getMetaData('cloudservices');
|
||||
const appId = cloudServices.appId;
|
||||
const endpoint = cloudServices.endpoint;
|
||||
xhr.open('GET', endpoint + path, true);
|
||||
|
||||
xhr.setRequestHeader('X-Parse-Application-Id', appId);
|
||||
|
||||
xhr.send();
|
||||
} else {
|
||||
// Running in cloud runtime
|
||||
const endpoint = typeof _noodl_cloudservices !== 'undefined' ? _noodl_cloudservices.endpoint : this.endpoint;
|
||||
const appId = typeof _noodl_cloudservices !== 'undefined' ? _noodl_cloudservices.appId : this.appId;
|
||||
const masterKey = typeof _noodl_cloudservices !== 'undefined' ? _noodl_cloudservices.masterKey : undefined;
|
||||
|
||||
fetch(endpoint + path, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey
|
||||
}
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.status === 200 || r.status === 201) {
|
||||
r.json()
|
||||
.then((json) => options.success(json))
|
||||
.catch((e) =>
|
||||
options.error({
|
||||
error: 'Config: Failed to get json result.'
|
||||
})
|
||||
);
|
||||
} else {
|
||||
r.json()
|
||||
.then((json) => options.error(json))
|
||||
.catch((e) => options.error({ error: 'Failed to fetch.' }));
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
options.error({ error: e.message });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_getConfig() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._makeRequest('/config', {
|
||||
success: (config) => {
|
||||
resolve(config.params || {});
|
||||
},
|
||||
error: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getConfig() {
|
||||
if (this.configCachePending) return this.configCachePending;
|
||||
|
||||
if (!this.configCache) {
|
||||
this.configCachePending = this._getConfig();
|
||||
|
||||
this.configCache = await this.configCachePending;
|
||||
delete this.configCachePending;
|
||||
this.ttl = Date.now() + this.cacheDuration;
|
||||
return this.configCache;
|
||||
} else {
|
||||
// Update cache if ttl has passed
|
||||
if (Date.now() > this.ttl) {
|
||||
this._getConfig().then((config) => {
|
||||
this.configCache = config;
|
||||
this.ttl = Date.now() + this.cacheDuration;
|
||||
});
|
||||
}
|
||||
|
||||
// But return currently cached
|
||||
return this.configCache;
|
||||
}
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
delete this.configCache;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigService.instance = new ConfigService();
|
||||
|
||||
module.exports = ConfigService;
|
||||
310
packages/noodl-runtime/src/api/queryutils.js
Normal file
310
packages/noodl-runtime/src/api/queryutils.js
Normal file
@@ -0,0 +1,310 @@
|
||||
const CloudStore = require('./cloudstore');
|
||||
const Model = require('../model');
|
||||
|
||||
function convertVisualFilter(query, options) {
|
||||
var inputs = options.queryParameters;
|
||||
|
||||
if (query.combinator !== undefined && query.rules !== undefined) {
|
||||
if (query.rules.length === 0) return;
|
||||
else if (query.rules.length === 1) return convertVisualFilter(query.rules[0], options);
|
||||
else {
|
||||
const _res = {};
|
||||
const _op = '$' + query.combinator;
|
||||
_res[_op] = [];
|
||||
query.rules.forEach((r) => {
|
||||
var cond = convertVisualFilter(r, options);
|
||||
if (cond !== undefined) _res[_op].push(cond);
|
||||
});
|
||||
|
||||
return _res;
|
||||
}
|
||||
} else if (query.operator === 'related to') {
|
||||
var value = query.input !== undefined ? inputs[query.input] : undefined;
|
||||
if (value === undefined) return;
|
||||
|
||||
return {
|
||||
$relatedTo: {
|
||||
object: {
|
||||
__type: 'Pointer',
|
||||
objectId: value,
|
||||
className: query.relatedTo
|
||||
},
|
||||
key: query.relationProperty
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const _res = {};
|
||||
var cond;
|
||||
var value = query.input !== undefined ? inputs[query.input] : query.value;
|
||||
|
||||
if (query.operator === 'exist') {
|
||||
_res[query.property] = { $exists: true };
|
||||
return _res;
|
||||
}
|
||||
else if (query.operator === 'not exist') {
|
||||
_res[query.property] = { $exists: false };
|
||||
return _res;
|
||||
}
|
||||
|
||||
if (value === undefined) return;
|
||||
|
||||
if (CloudStore._collections[options.collectionName])
|
||||
var schema = CloudStore._collections[options.collectionName].schema;
|
||||
|
||||
var propertyType =
|
||||
schema && schema.properties && schema.properties[query.property]
|
||||
? schema.properties[query.property].type
|
||||
: undefined;
|
||||
|
||||
if (propertyType === 'Date') {
|
||||
if (!(value instanceof Date)) value = new Date(value.toString());
|
||||
value = { __type: 'Date', iso: value.toISOString() };
|
||||
}
|
||||
|
||||
if (query.operator === 'greater than') cond = { $gt: value };
|
||||
else if (query.operator === 'greater than or equal to') cond = { $gte: value };
|
||||
else if (query.operator === 'less than') cond = { $lt: value };
|
||||
else if (query.operator === 'less than or equal to') cond = { $lte: value };
|
||||
else if (query.operator === 'equal to') cond = { $eq: value };
|
||||
else if (query.operator === 'not equal to') cond = { $ne: value };
|
||||
else if (query.operator === 'points to') {
|
||||
var targetClass =
|
||||
schema && schema.properties && schema.properties[query.property]
|
||||
? schema.properties[query.property].targetClass
|
||||
: undefined;
|
||||
|
||||
cond = {
|
||||
$eq: { __type: 'Pointer', objectId: value, className: targetClass }
|
||||
};
|
||||
} else if (query.operator === 'contain') {
|
||||
cond = { $regex: value, $options: 'i' };
|
||||
}
|
||||
|
||||
|
||||
_res[query.property] = cond;
|
||||
|
||||
return _res;
|
||||
}
|
||||
}
|
||||
|
||||
function matchesQuery(model, query) {
|
||||
var match = true;
|
||||
|
||||
if (query === undefined) return true;
|
||||
|
||||
if (query['$and'] !== undefined) {
|
||||
query['$and'].forEach((q) => {
|
||||
match &= matchesQuery(model, q);
|
||||
});
|
||||
} else if (query['$or'] !== undefined) {
|
||||
match = false;
|
||||
query['$or'].forEach((q) => {
|
||||
match |= matchesQuery(model, q);
|
||||
});
|
||||
} else {
|
||||
var keys = Object.keys(query);
|
||||
keys.forEach((k) => {
|
||||
if (k === 'objectId') {
|
||||
if (query[k]['$eq'] !== undefined) match &= model.getId() === query[k]['$eq'];
|
||||
else if (query[k]['$in'] !== undefined) match &= query[k]['$in'].indexOf(model.getId()) !== -1;
|
||||
} else if (k === '$relatedTo') {
|
||||
match = false; // cannot resolve relation queries locally
|
||||
} else {
|
||||
var value = model.get(k);
|
||||
if (query[k]['$eq'] !== undefined && query[k]['$eq'].__type === 'Pointer')
|
||||
match &= value === query[k]['$eq'].objectId;
|
||||
else if (query[k]['$eq'] !== undefined) match &= value == query[k]['$eq'];
|
||||
else if (query[k]['$ne'] !== undefined) match &= value != query[k]['$ne'];
|
||||
else if (query[k]['$lt'] !== undefined) match &= value < query[k]['$lt'];
|
||||
else if (query[k]['$lte'] !== undefined) match &= value <= query[k]['$lt'];
|
||||
else if (query[k]['$gt'] !== undefined) match &= value > query[k]['$gt'];
|
||||
else if (query[k]['$gte'] !== undefined) match &= value >= query[k]['$gte'];
|
||||
else if (query[k]['$exists'] !== undefined) match &= value !== undefined;
|
||||
else if (query[k]['$in'] !== undefined) match &= query[k]['$in'].indexOf(value) !== -1;
|
||||
else if (query[k]['$nin'] !== undefined) match &= query[k]['$in'].indexOf(value) === -1;
|
||||
else if (query[k]['$regex'] !== undefined)
|
||||
match &= new RegExp(query[k]['$regex'], query[k]['$options']).test(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
function compareObjects(sort, a, b) {
|
||||
for (var i = 0; i < sort.length; i++) {
|
||||
let _s = sort[i];
|
||||
if (_s[0] === '-') {
|
||||
// Descending
|
||||
let prop = _s.substring(1);
|
||||
if (a.get(prop) > b.get(prop)) return -1;
|
||||
else if (a.get(prop) < b.get(prop)) return 1;
|
||||
} else {
|
||||
// Ascending
|
||||
if (a.get(_s) > b.get(_s)) return 1;
|
||||
else if (a.get(_s) < b.get(_s)) return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function convertVisualSorting(sorting) {
|
||||
return sorting.map((s) => {
|
||||
return (s.order === 'descending' ? '-' : '') + s.property;
|
||||
});
|
||||
}
|
||||
|
||||
function _value(v) {
|
||||
if (v instanceof Date && typeof v.toISOString === 'function') {
|
||||
return {
|
||||
__type: 'Date',
|
||||
iso: v.toISOString()
|
||||
};
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
function convertFilterOp(filter, options) {
|
||||
const keys = Object.keys(filter);
|
||||
if (keys.length === 0) return {};
|
||||
if (keys.length !== 1) return options.error('Filter must only have one key found ' + keys.join(','));
|
||||
|
||||
const res = {};
|
||||
const key = keys[0];
|
||||
if (filter['and'] !== undefined && Array.isArray(filter['and'])) {
|
||||
res['$and'] = filter['and'].map((f) => convertFilterOp(f, options));
|
||||
} else if (filter['or'] !== undefined && Array.isArray(filter['or'])) {
|
||||
res['$or'] = filter['or'].map((f) => convertFilterOp(f, options));
|
||||
} else if (filter['idEqualTo'] !== undefined) {
|
||||
res['objectId'] = { $eq: filter['idEqualTo'] };
|
||||
} else if (filter['idContainedIn'] !== undefined) {
|
||||
res['objectId'] = { $in: filter['idContainedIn'] };
|
||||
} else if (filter['relatedTo'] !== undefined) {
|
||||
var modelId = filter['relatedTo']['id'];
|
||||
if (modelId === undefined) return options.error('Must provide id in relatedTo filter');
|
||||
|
||||
var relationKey = filter['relatedTo']['key'];
|
||||
if (relationKey === undefined) return options.error('Must provide key in relatedTo filter');
|
||||
|
||||
var m = (options.modelScope || Model).get(modelId);
|
||||
res['$relatedTo'] = {
|
||||
object: {
|
||||
__type: 'Pointer',
|
||||
objectId: modelId,
|
||||
className: m._class
|
||||
},
|
||||
key: relationKey
|
||||
};
|
||||
} else if (typeof filter[key] === 'object') {
|
||||
const opAndValue = filter[key];
|
||||
if (opAndValue['equalTo'] !== undefined) res[key] = { $eq: _value(opAndValue['equalTo']) };
|
||||
else if (opAndValue['notEqualTo'] !== undefined) res[key] = { $ne: _value(opAndValue['notEqualTo']) };
|
||||
else if (opAndValue['lessThan'] !== undefined) res[key] = { $lt: _value(opAndValue['lessThan']) };
|
||||
else if (opAndValue['greaterThan'] !== undefined) res[key] = { $gt: _value(opAndValue['greaterThan']) };
|
||||
else if (opAndValue['lessThanOrEqualTo'] !== undefined)
|
||||
res[key] = { $lte: _value(opAndValue['lessThanOrEqualTo']) };
|
||||
else if (opAndValue['greaterThanOrEqualTo'] !== undefined)
|
||||
res[key] = { $gte: _value(opAndValue['greaterThanOrEqualTo']) };
|
||||
else if (opAndValue['exists'] !== undefined) res[key] = { $exists: opAndValue['exists'] };
|
||||
else if (opAndValue['containedIn'] !== undefined) res[key] = { $in: opAndValue['containedIn'] };
|
||||
else if (opAndValue['notContainedIn'] !== undefined) res[key] = { $nin: opAndValue['notContainedIn'] };
|
||||
else if (opAndValue['pointsTo'] !== undefined) {
|
||||
var m = (options.modelScope || Model).get(opAndValue['pointsTo']);
|
||||
if (CloudStore._collections[options.collectionName])
|
||||
var schema = CloudStore._collections[options.collectionName].schema;
|
||||
|
||||
var targetClass =
|
||||
schema && schema.properties && schema.properties[key] ? schema.properties[key].targetClass : undefined;
|
||||
var type = schema && schema.properties && schema.properties[key] ? schema.properties[key].type : undefined;
|
||||
|
||||
if (type === 'Relation') {
|
||||
res[key] = {
|
||||
__type: 'Pointer',
|
||||
objectId: opAndValue['pointsTo'],
|
||||
className: targetClass
|
||||
};
|
||||
} else {
|
||||
if (Array.isArray(opAndValue['pointsTo']))
|
||||
res[key] = {
|
||||
$in: opAndValue['pointsTo'].map((v) => {
|
||||
return { __type: 'Pointer', objectId: v, className: targetClass };
|
||||
})
|
||||
};
|
||||
else
|
||||
res[key] = {
|
||||
$eq: {
|
||||
__type: 'Pointer',
|
||||
objectId: opAndValue['pointsTo'],
|
||||
className: targetClass
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (opAndValue['matchesRegex'] !== undefined) {
|
||||
res[key] = {
|
||||
$regex: opAndValue['matchesRegex'],
|
||||
$options: opAndValue['options']
|
||||
};
|
||||
} else if (opAndValue['text'] !== undefined && opAndValue['text']['search'] !== undefined) {
|
||||
var _v = opAndValue['text']['search'];
|
||||
if (typeof _v === 'string') res[key] = { $text: { $search: { $term: _v, $caseSensitive: false } } };
|
||||
else
|
||||
res[key] = {
|
||||
$text: {
|
||||
$search: {
|
||||
$term: _v.term,
|
||||
$language: _v.language,
|
||||
$caseSensitive: _v.caseSensitive,
|
||||
$diacriticSensitive: _v.diacriticSensitive
|
||||
}
|
||||
}
|
||||
};
|
||||
// Geo points
|
||||
} else if (opAndValue['nearSphere'] !== undefined) {
|
||||
var _v = opAndValue['nearSphere'];
|
||||
res[key] = {
|
||||
$nearSphere: {
|
||||
__type: "GeoPoint",
|
||||
latitude: _v.latitude,
|
||||
longitude: _v.longitude,
|
||||
},
|
||||
$maxDistanceInMiles:_v.$maxDistanceInMiles,
|
||||
$maxDistanceInKilometers:_v.maxDistanceInKilometers,
|
||||
$maxDistanceInRadians:_v.maxDistanceInRadians
|
||||
};
|
||||
} else if (opAndValue['withinBox'] !== undefined) {
|
||||
var _v = opAndValue['withinBox'];
|
||||
res[key] = {
|
||||
$within:{
|
||||
$box: _v.map(gp => ({
|
||||
__type:"GeoPoint",
|
||||
latitude:gp.latitude,
|
||||
longitude:gp.longitude
|
||||
}))
|
||||
}
|
||||
};
|
||||
} else if (opAndValue['withinPolygon'] !== undefined) {
|
||||
var _v = opAndValue['withinPolygon'];
|
||||
res[key] = {
|
||||
$geoWithin:{
|
||||
$polygon: _v.map(gp => ({
|
||||
__type:"GeoPoint",
|
||||
latitude:gp.latitude,
|
||||
longitude:gp.longitude
|
||||
}))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
} else {
|
||||
options.error('Unrecognized filter keys ' + keys.join(','));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
convertVisualFilter,
|
||||
compareObjects,
|
||||
matchesQuery,
|
||||
convertVisualSorting,
|
||||
convertFilterOp
|
||||
};
|
||||
276
packages/noodl-runtime/src/api/records.js
Normal file
276
packages/noodl-runtime/src/api/records.js
Normal file
@@ -0,0 +1,276 @@
|
||||
const CloudStore = require('./cloudstore');
|
||||
const QueryUtils = require('./queryutils');
|
||||
const Model = require('../model');
|
||||
|
||||
function createRecordsAPI(modelScope) {
|
||||
let _cloudstore;
|
||||
const cloudstore = () => {
|
||||
// We must create the cloud store just in time so all meta data is loaded
|
||||
if (!_cloudstore) _cloudstore = new CloudStore(modelScope);
|
||||
return _cloudstore;
|
||||
};
|
||||
|
||||
return {
|
||||
async query(className, query, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().query({
|
||||
collection: className,
|
||||
where: QueryUtils.convertFilterOp(query || {}, {
|
||||
collectionName: className,
|
||||
error: (e) => reject(e),
|
||||
modelScope
|
||||
}),
|
||||
limit: options ? options.limit : undefined,
|
||||
sort: options ? options.sort : undefined,
|
||||
skip: options ? options.skip : undefined,
|
||||
include: options ? options.include : undefined,
|
||||
select: options ? options.select : undefined,
|
||||
count: options ? options.count : undefined,
|
||||
success: (results,count) => {
|
||||
const _results = results.map((r) => cloudstore()._fromJSON(r, className));
|
||||
if(count !== undefined) resolve({results:_results,count});
|
||||
else resolve(_results);
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to query.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async count(className, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().count({
|
||||
collection: className,
|
||||
where: query
|
||||
? QueryUtils.convertFilterOp(query || {}, {
|
||||
collectionName: className,
|
||||
error: (e) => reject(e),
|
||||
modelScope
|
||||
})
|
||||
: undefined,
|
||||
success: (count) => {
|
||||
resolve(count);
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to query.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async distinct(className, property, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().distinct({
|
||||
collection: className,
|
||||
property,
|
||||
where: query
|
||||
? QueryUtils.convertFilterOp(query || {}, {
|
||||
collectionName: className,
|
||||
error: (e) => reject(e),
|
||||
modelScope
|
||||
})
|
||||
: undefined,
|
||||
success: (results) => {
|
||||
resolve(results);
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to query.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async aggregate(className, group, query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().aggregate({
|
||||
collection: className,
|
||||
group,
|
||||
where: query
|
||||
? QueryUtils.convertFilterOp(query || {}, {
|
||||
collectionName: className,
|
||||
error: (e) => reject(e),
|
||||
modelScope
|
||||
})
|
||||
: undefined,
|
||||
success: (results) => {
|
||||
resolve(results);
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to aggregate.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async fetch(objectOrId, options) {
|
||||
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
|
||||
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!className) return reject('No class name specified');
|
||||
|
||||
cloudstore().fetch({
|
||||
collection: className,
|
||||
objectId: objectOrId,
|
||||
include: options ? options.include : undefined,
|
||||
success: function (response) {
|
||||
var record = cloudstore()._fromJSON(response, className);
|
||||
resolve(record);
|
||||
},
|
||||
error: function (err) {
|
||||
reject(Error(err || 'Failed to fetch.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async increment(objectOrId, properties, options) {
|
||||
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
|
||||
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!className) return reject('No class name specified');
|
||||
|
||||
cloudstore().increment({
|
||||
collection: className,
|
||||
objectId: objectOrId,
|
||||
properties,
|
||||
success: (response) => {
|
||||
cloudstore()._fromJSON(Object.assign({ objectId: objectOrId }, response), className); // Update values
|
||||
|
||||
resolve();
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to increment.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async save(objectOrId, properties, options) {
|
||||
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
|
||||
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
|
||||
|
||||
const model = (modelScope || Model).get(objectOrId);
|
||||
if (properties) {
|
||||
Object.keys(properties).forEach((p) => {
|
||||
model.set(p, properties[p]);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!className) return reject('No class name specified');
|
||||
|
||||
cloudstore().save({
|
||||
collection: className,
|
||||
objectId: objectOrId,
|
||||
data: properties || model.data,
|
||||
acl: options ? options.acl : undefined,
|
||||
success: (response) => {
|
||||
cloudstore()._fromJSON(Object.assign({ objectId: objectOrId }, response), className); // Assign updated at
|
||||
resolve();
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to save.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async create(className, properties, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
cloudstore().create({
|
||||
collection: className,
|
||||
data: properties,
|
||||
acl: options ? options.acl : undefined,
|
||||
success: (data) => {
|
||||
// Successfully created
|
||||
const m = cloudstore()._fromJSON(data, className);
|
||||
resolve(m);
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to insert.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async delete(objectOrId, options) {
|
||||
if (typeof objectOrId !== 'string') objectOrId = objectOrId.getId();
|
||||
const className = (options ? options.className : undefined) || (modelScope || Model).get(objectOrId)._class;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!className) return reject('No class name specified');
|
||||
|
||||
cloudstore().delete({
|
||||
collection: className,
|
||||
objectId: objectOrId,
|
||||
success: () => {
|
||||
(modelScope || Model).get(objectOrId).notify('delete');
|
||||
resolve();
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to delete.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async addRelation(options) {
|
||||
const recordId = options.recordId || options.record.getId();
|
||||
const className = options.className || (modelScope || Model).get(recordId)._class;
|
||||
|
||||
const targetRecordId = options.targetRecordId || options.targetRecord.getId();
|
||||
const targetClassName = options.targetClassName || (modelScope || Model).get(targetRecordId)._class;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!className) return reject('No class name specified');
|
||||
if (!targetClassName) return reject('No target class name specified');
|
||||
|
||||
cloudstore().addRelation({
|
||||
collection: className,
|
||||
objectId: recordId,
|
||||
key: options.key,
|
||||
targetObjectId: targetRecordId,
|
||||
targetClass: targetClassName,
|
||||
success: (response) => {
|
||||
resolve();
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(err || 'Failed to add relation.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
async removeRelation(options) {
|
||||
const recordId = options.recordId || options.record.getId();
|
||||
const className = options.className || (modelScope || Model).get(recordId)._class;
|
||||
|
||||
const targetRecordId = options.targetRecordId || options.targetRecord.getId();
|
||||
const targetClassName = options.targetClassName || (modelScope || Model).get(targetRecordId)._class;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!className) return reject('No class name specified');
|
||||
if (!targetClassName) return reject('No target class name specified');
|
||||
|
||||
cloudstore().removeRelation({
|
||||
collection: className,
|
||||
objectId: recordId,
|
||||
key: options.key,
|
||||
targetObjectId: targetRecordId,
|
||||
targetClass: targetClassName,
|
||||
success: (response) => {
|
||||
resolve();
|
||||
},
|
||||
error: (err) => {
|
||||
reject(Error(rr || 'Failed to add relation.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = createRecordsAPI;
|
||||
Reference in New Issue
Block a user