json-object-editor
Version:
JOE the Json Object Editor | Platform Edition
255 lines (241 loc) • 11 kB
JavaScript
var fs = require('fs');
//const request = require('request');
function Storage(specs){
var moduleName = colorize('[storage] ','module');
var self = this;
var specs = specs || {mongo:true};
this.Api = {
get:function(collection,specs){
var specs = specs || {};
var callback = specs.callback;
var query = specs.query;
var path = specs.path;
//make http request with querystring
console.log('api for collection -'+collection);
}
}
this.File = {
get:function(collection,specs){
var specs = specs || {};
var callback = specs.callback;
var query = specs.query || {};
if(query._id && !query._id.isCuid()){
query._id = mongojs.ObjectId(query._id);
}
query[JOE.webconfig.deleted] = {$in:[false,'false','False',undefined]};
fs.readFile(JOE.dataDir+collection+'.json', 'utf8', function(err,data){
if(err){
console.log(err);
callback([]);
}
callback(data);
});
}
}
JOE.dataDir = dataDir = JOE.appDir+'/'+JOE.webconfig.dataDir+'/';
//JOE.schemaDir = schemaDir = JOE.appDir+'/'+JOE.webconfig.schemaDir+'/';
// function setupFolder(dir,name,watchHandler){
// if (!fs.existsSync(dir)){
// fs.mkdirSync(dir);
// }
// var watchHandler = watchHandler ||
// function(event,filename){
// console.log(name+' dir updated >'+filename||'no filename')
// }
// fs.watch(dir,watchHandler);
// console.log(name+' stored in: '+dir);
// }
//setupFolder(dataDir,'data');
JOE.Utils.setupFileFolder(dataDir,'data');
this.load = function(collection,query,callback){
if(JOE.Schemas.schema[collection] && JOE.Schemas.schema[collection].storage && JOE.Schemas.schema[collection].storage.type){
var schema_storage = JOE.Schemas.schema[collection].storage;
var query = schema_storage.query || query || {};
var path = schema_storage.path || '';
//console.log('mysql query',query);
console.log('collection: '+collection,schema_storage);
switch(schema_storage.type.toLowerCase()){
case 'mongodb':
case 'mongo':
JOE.Mongo.get(collection,{query:query,callback:callback});
break;
case 'mysql':
case 'sql':
JOE.MySQL.get(collection,{query:query,callback:callback});
break;
case 'file':
self.File.get(collection,{query:query,callback:callback});
break;
case 'api':
self.Api.get(collection,{path:path,query:query,callback:callback});
break;
}
}else{
var query = query || {};
if(JOE.Mongo){
JOE.Mongo.get(collection,{query:query,callback:callback});
}else{
self.File.get(collection,{query:query,callback:callback});
}
}
}
this.save = function(data,collection,callback,specs){
var callback = callback || function(err,data){
if(err){
console.log(JOE.Utils.color('[error] ','red')+err);
}
logit(colorize(data,'gray'));
};
try{
var specs = $c.merge({history:true},(specs||{}));
var user = specs.user || {name:'anonymous'};
// Ensure timestamps: always set joeUpdated; set created only on create when missing
try{
if(!data.joeUpdated){ data.joeUpdated = new Date().toISOString(); }
if(!data.created){
var cachedBefore = null;
// Only use Cache.findByID when _id is a string; ObjectId instances can cause internal split() errors.
if (data && data._id && typeof data._id === 'string' && JOE && JOE.Cache && JOE.Cache.findByID){
try {
cachedBefore = JOE.Cache.findByID(collection, data._id);
} catch(__e){ cachedBefore = null; }
}
if(!cachedBefore){ data.created = new Date().toISOString(); }
}
}catch(_e){}
if(JOE.Mongo){
logit(moduleName+' mongo saving -> '+colorize(collection,'schema'));
var save_callback = function(err,data){
callback(err,data);
if (!specs || specs.push !== false) {
JOE.io.emit('item_updated', { results: [data] });
}
if(!err){
var ts = new Date().toISOString();
var cached = JOE.Cache.findByID(data.itemtype,data._id);
var events = 'save';
if(!cached){
events = 'create,save';
}
var event_specs = {timestamp:ts};
var history_payload = {
itemid:data._id,
collection:collection,
user:{_id:user._id,name:user.name},
timestamp:ts,
historical:data
};
//var cached = JOE.Cache.findByID(data.itemtype,data._id);
if(cached){
history_payload.changes = $c.changes(cached,data);
//sanitize
for(var hvar in history_payload.changes){
if(hvar.indexOf('$') != -1){
history_payload.changes[hvar.replace(/\$/g,'__')] = history_payload.changes[hvar];
delete history_payload.changes[hvar];
}
}
event_specs.cached = cached;
}else{
history_payload.created = true;
}
if(history_payload.changes.status){
events+= ',status';
event_specs.status =JOE.Cache.findByID('status',history_payload.changes.status)||{};
}
if(
history_payload.changes[JOE.webconfig.deleted] &&
data[JOE.webconfig.deleted]
){
events+= ',delete';
history_payload.deleted = true;
}
event_specs.historical_info = history_payload;
JOE.Schemas.events(data,events,event_specs);
JOE.Mongo.saveHistory(history_payload);
}
}
if(!specs.history){
save_callback=function(err,data){
var ts = new Date().toISOString();
callback(err,data);
if(data.itemtype == "comments"){
//TODO events for comments.
return;
}
var ts = new Date().toISOString();
var cached = JOE.Cache.findByID(data.itemtype,data._id);
var events = 'save';
if(!cached){
events = 'create,save';
}
JOE.Schemas.events(data,events,{timestamp:ts});
}
}
JOE.Mongo.save(data,collection,save_callback);
}else{
console.log('JOE.Mongo not connected');
}
}catch(e){
callback(colorize('[storage]','error')+' error:' + e);
}
};
return self;
}
module.exports = new Storage();
/**
* Save data to the specified collection.
*
* This function handles saving data to a MongoDB collection or a file-based storage system.
* It supports history tracking, event triggering, and user attribution for the saved data.
*
* @param {Object} data - The data object to be saved. Must include at least the `itemtype` property.
* @param {String} collection - The name of the collection where the data will be saved.
* @param {Function} [callback] - Optional callback function to handle the result of the save operation.
* The callback receives two arguments: `err` (error) and `data` (saved data).
* @param {Object} [specs] - Additional specifications for the save operation.
* - `history` (Boolean): Whether to track history for the saved data (default: true).
* - `user` (Object): The user performing the save operation. Should include `_id` and `name`.
*
* ### Features:
* - **MongoDB Integration**: Saves data to MongoDB if connected.
* - **File-Based Fallback**: If MongoDB is not connected, it falls back to file-based storage.
* - **History Tracking**: Tracks changes to the data and saves historical records.
* - **Event Triggering**: Triggers events for `create`, `save`, `status`, and `delete` actions.
* - **User Attribution**: Associates the save operation with a user for auditing purposes.
*
* ### Example Usage:
*
* ```javascript
* const data = {
* _id: "12345",
* itemtype: "campaign",
* name: "New Campaign",
* status: "active"
* };
*
* const collection = "campaigns";
*
* const callback = function(err, savedData) {
* if (err) {
* console.error("Error saving data:", err);
* } else {
* console.log("Data saved successfully:", savedData);
* }
* };
*
* const specs = {
* history: true,
* user: { _id: "67890", name: "John Doe" }
* };
*
* JOE.Storage.save(data, collection, callback, specs);
* ```
*
* ### Error Handling:
* - If an error occurs during the save operation, it is logged to the console and passed to the callback.
*
* ### Notes:
* - The `itemtype` property in the `data` object is required for proper event handling.
* - If `history` is disabled in `specs`, historical tracking and event triggering are skipped.
*/