json-object-editor
Version:
JOE the Json Object Editor | Platform Edition
817 lines (731 loc) • 27.1 kB
JavaScript
console.time('Server on '+JOE.webconfig.port);
var express = require('express');
var https = require('https');
var fs = require('fs');
var compress = require('compression');
var serverPath = '../';
var server = express();
var http = require('http').Server(server);
var https = require('https');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var basicAuth = require('basic-auth');
var path = require('path');
var pem = require('pem');
var os = require('os');
const $J = require('./UniversalShorthand');
var rd = require('renderizer')({templatesDir:path.resolve(JOE.joedir,JOE.webconfig.templatesDir)});
function coloredLog(message) {
console.log(JOE.Utils.color('[server]', 'module'), message);
}
var authBool = JOE.isAuthorized = function(req,res){
var users = (JOE.Data && JOE.Data.user) || [];
if(req.cookies._j_user && req.cookies._j_token){
var User = users.where({name:req.cookies._j_user,token:req.cookies._j_token})[0]||false;
if(User){
req.User = User;
return true;
}
}
var user = basicAuth(req);
var ip = req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress;
if (!user || !user.name || !user.pass) {
return false;
};
//console.log('a user',user);
var User = users.where({name:user.name,password:user.pass})[0]||false;
//console.log(User);
if(User || !users.length){
req.User = User||{};
res.cookie('_j_user',User.name);
res.cookie('_j_token',User.token);
return true;
}
else {
return false;
}
}
var auth = JOE.auth = function (req, res, next) {
function unauthorized(res) {
if(JOE.webconfig.authorization && JOE.webconfig.authorization.url && !req.query.noSSO){
var state = req.originalUrl;
let url = JOE.webconfig.authorization.url+'&state='+state;
res.redirect(url);
return;
}
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.send(401);
};
var authorized = authBool(req,res);
if(authorized){
return next();
}else{
return unauthorized(res);
}
};
function stringFunctions(propObject){
for(var p in propObject){
if(typeof propObject[p] == 'function'){
//propObject[p] = '(' + propObject[p].toString() + ')';
propObject[p] = '(' + propObject[p].toString().replace(/\\n/g,'\\n') + ')';
}else if(typeof propObject[p] == "object"){
stringFunctions(propObject[p]);
}
}
return propObject;
}
server.use(cookieParser());
server.use( bodyParser.json({limit: '50mb'}) ); // to support JSON-encoded bodies
// to support URL-encoded bodies
server.use(bodyParser.urlencoded({
extended: true, limit: '50mb'
}));
server.use(compress());
server.get(JOE.webconfig.joepath+'server/*',function(req,res,next){
res.send('unauthorized');
});
server.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
server.use(JOE.webconfig.joepath,express.static(JOE.joedir));
//USER
server.get(['/API/user/:method'],auth,function(req,res,next){
var users = (JOE.Data && JOE.Data.user) || [];
var method = req.params.method;
var User;
if(!req.User){
/* if(req.cookies._j_user && req.cookies._j_token){
User = users.where({name:req.cookies._j_user,token:req.cookies._j_token})[0]||false;
}
if (!User){
var user = basicAuth(req);
if (user.name && user.pass) {
User = users.where({name: user.name, password: user.pass})[0] || false;
}
}*/
res.jsonp({error:'user or method not found'});
return {error:'user or method not found'};
}else{
User = req.User;
}
if (User) {
switch (method) {
case 'current':
//get current user from creds
var payload = $c.merge({}, User);
delete payload.password;
res.jsonp({user: payload});
return payload;
break;
case 'logout':
logit('logging out user '+req.User.name);
req.User = null;
res.jsonp({logout:true});
return next();
break;
case 'apps':
logit('getting apps for '+req.User.name);
userApps = [];
if (req.User.apps && req.User.apps.length) {
req.User.apps.map(function (app) {
if (JOE.Apps.cache[app]) {
userApps.push({
name: app,
title: JOE.Apps.cache[app].title,
description: JOE.Apps.cache[app].description
});
}
});
}
res.jsonp({userApps});
return;//return next();
break;
}
}
});
//SEARCH
server.get(['/API/search/','/API/search/:query'],auth,function(req,res,next){
var bm = new Benchmarker();
var query = req.params.query || req.query.query || {};
var limit = req.query.limit || 0;
var queryObj = (typeof query == "object")?query:eval('('+query+')');
var payload = {query:queryObj};
payload.results = JOE.Cache.search(queryObj);
/*payload.results.map(function(res){
res = $c.copyObject(res);
if(res.password || res.token){
delete res.password;
delete res.token;
}
})*/
payload.count = payload.results.length;
if(req.query.limit){
payload.results = payload.results.slice(0,limit)
}
payload.results = cleanPayload(payload.results);
payload.benchmark = bm.stop();
res.jsonp(payload);
});
function cleanPayload(results){
results = JSON.parse(JSON.stringify(results));
results.map(r=>{
if(r.password){
r.password = null;
}
})
return results;
}
//ITEM QUERY - secured, preferred
server.get(['/API/item/:collection/fields/:fields','/API/item/:collection/:key/:value','/API/item/:collection'],auth,function(req,res,next){
var st = new Date().getTime();
var collection = req.params.collection || req.query.collection;
var key = req.params.key || req.query.key;
var value = req.params.value || req.query.value;
var fields = req.params.fields || req.query.fields;
var filter = {};
if(key && value){
filter[key] = value;
}
var payload = {collection:collection,filter:filter};
payload.item = (JOE.Data[collection] ||[]).where(filter);
if(key && value){
payload.item = payload.item[0] || false;
}
else if(fields){
// fields = fields.split(',');
// var finalItems = [];
// payload.item.map(i=>{
// let copy = {};
// fields.map(f=>{
// if(f){
// copy[f]=i[f];
// }
// })
// finalItems.push(copy);
// })
payload.item = JOE.Utils.ProjectedItemsFromFields(fields,payload.item);
}
payload.item = cleanPayload(payload.item);
payload.elapsed = new Date().getTime() - st;
res.jsonp(payload);
});
//OBJECT SINGLE ITEM QUERY
server.get(['/API/object/:collection/:key/:value',
'/API/object/:collection/:key/:value/field/:field',
'/API/object/:collection/:key/:value/fields/:fields'
],auth,function(req,res,next){
var collection = req.params.collection || req.query.collection;
var key = req.params.key || req.query.key;
var value = req.params.value || req.query.value;
var field = req.params.field || req.query.field;
var fields = req.params.fields || req.query.fields;
var filter = {};
if(key && value){
filter[key] = value;
}
var object = (JOE.Data[collection] ||[]).where(filter)[0] || false;
if(!object){
res.jsonp({error:'object not found'})
return;
}
if(field){
var pl = {};
pl[field] = object[field];
res.jsonp(pl);
return;
}
if(fields){
fields = fields.split(',');
var pl = {};
fields.map(f=>{
if(f){
pl[f]=object[f];
}
})
res.jsonp(pl);
return;
}
res.jsonp(object);
});
//HISTORY
server.get(['/API/history/:key/:itemid'],auth,function(req,res,next){
var st = new Date().getTime();
var elapsed;
var key = req.params.key;
var id = req.params.itemid || req.query.itemid;
var quick = req.params.quick || false;
var fields = req.params.fields || req.query.fields;
var query = {};
query[key] = id;
if(!id){
res.jsonp({error:'no itemid specified'});
return;
}
JOE.Storage.load('_history',query,function(err,data){
if(err){
res.jsonp({error:err});
return;
}
if(!fields){
elapsed = new Date().getTime() - st;
res.jsonp({query:query,results:data,elapsed})
}else{
var finalItems = JOE.Utils.ProjectedItemsFromFields(fields,data);
elapsed = new Date().getTime() - st;
res.jsonp({query:query,results:finalItems,elapsed})
}
})
});
//COMMENTS
server.get(['/API/comments/:mode/:id'],auth,function(req,res,next){
var mode = req.params.mode;
var id = req.params.id;
var query = false;
var payload={error:'comments request syntax'};
switch(mode){
case 'user':
query = {'user._id':id};
break;
case 'item':
query = {'item._id':id};
break;
}
if(query){
JOE.Storage.load('comments',query,function(err,data){
if(err){
res.jsonp({error:err});
return;
}
res.jsonp({
query:query,
length:data.length,
comments:data
});
})
}else{
res.jsonp(payload);
}
});
//CACHE
server.get(['/API/cache/update','/API/cache/update/:collections'],auth,function(req,res,next){
var collections = (req.params.collections||'').split(',');
try{
JOE.Cache.update(function(){
console.log('cache updated manually');
res.jsonp({event:'cache updated'});
})
}catch(e){
res.jsonp({error:e});
}
});
//DATASET
server.get(['/API/dataset/:names','/API/datasets/:names'],auth,function(req,res,next){
var uBM = new Benchmarker();
var collections = (req.params.names||'').split(',');
var payload = {
datasets:{},
counts:{total:0}
};
/*queryness*/
var query = req.query.query;
if(query){
var queryObj = (typeof query == "object")?query:eval('('+query+')');
var payload = {query:queryObj};
}
var permitted = [];
var isSuper = false;
var isNew = false;
if(!$c.isEmpty(req.User)){
isSuper = (req.User.role == "super");
permitted = req.User.schemas || [];
}else if(!JOE.Data.user || !JOE.Data.user.length){
isNew = true;
}
collections.map(function(c){
if(c){
if(isSuper || isNew || permitted.indexOf(c) != -1){
payload.datasets[c] = JOE.Data[c] || [];
}else{payload.datasets[c] = [];}
payload.counts[c] = payload.datasets[c].length;
payload.counts.total+= payload.datasets[c].length;
}
});
payload.benchmark = uBM.stop();
res.jsonp(payload);
});
//SCHEMA
server.get(['/API/schema/:names','/API/schemas/:names'],auth,function(req,res,next){
var sBM = new Benchmarker();
var schemaname = (req.params.names||'').split(',');
var format = req.query.format;
if(!schemaname || !schemaname.length){
res.jsonp({error:'no schema sent'});
}
var payload = {};
var fields = {};
//'./server/';
var schemapath,fieldspath;
var permitted = [];
var isSuper = false;
var isNew = false;
if(!$c.isEmpty(req.User)){
isSuper = (req.User.role == "super");
permitted = JSON.parse(JSON.stringify(req.User.schemas)) || [];
if(req.User.items && req.User.items.length && req.query.mode=="additionalItems"){
JOE.Cache.search({_id:{$in:req.User.items}}).map(i=>{
permitted.push(i.itemtype);
})
}
permitted = Array.from(new Set(permitted));
}else if(!JOE.Data || !JOE.Data.user || !JOE.Data.user.length){
isNew = true;
}
schemaname.map(function(s){
if(isSuper || isNew || permitted.indexOf(s) != -1){
payload[s] = JOE.Schemas.schema[s];
}
});
var F = (req.query.fields||req.params.fields||'core').split(',');
F.map(function(f){
try {
fieldspath = serverPath+'fields/' + f + '.js';
delete require.cache[require.resolve(fieldspath)];
fields = require(fieldspath);
stringFunctions(fields);
JOE.Fields[f]=fields;
}catch(e){
fields ={error:'field '+f+' not found'};
}
});
var bm = sBM.stop();
switch(format){
case 'js':
res.set('Content-Type', 'application/javascript');
var js_payload =JSON.stringify({schemas:payload,fields:fields,benchmark:bm},null, ' ');
res.send('var joe_info = '+js_payload);
break;
default:
res.jsonp({schemas:payload,fields:fields,benchmark:bm});
break;
}
});
//PLUGINS
async function pluginHandling(req,res,next){
var pBM = new Benchmarker();
var plugin = req.params.plugin;
var data = $c.merge(req.query,req.body) || {};
//var app = req.params.app;
var method = req.params.method || 'default';
var errors = [];
// if(!plugin || !app){
// errors.push('plugin or app name not supplied');
// }
// var app_obj = JOE.Apps.cache[app];
// if(!app_obj){
// errors.push('app "'+app+'" not found');
// }
// if(!app_obj.plugins || !app_obj.plugins.contains(plugin)){
// errors.push('plugin "'+plugin+'" not found');
// }
var pluginClass = JOE.Apps.plugins[plugin];
var protected;
if(!pluginClass){
errors.push('plugin "'+plugin+'" not initialized')
}
else if(method == "protected" || !pluginClass[method]
|| (pluginClass.protected && pluginClass.protected.indexOf(method) != -1)){
errors.push('method '+method+' not found');
}
if(errors.length){
res.jsonp({errors:errors,failedat:'Server'});
console.log('[plugin] errors:');
console.log(errors);
return;
}
try{
if(typeof pluginClass[method] == "string"){
var response = pluginClass[method];
}else{
if(pluginClass.async && pluginClass.async[method]){
var response = await pluginClass[method](data,req,res);
}else{
var response = pluginClass[method](data,req,res);
}
}
if(response && response.use_callback){
return;
}
if(response && response.unauthorized){
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.send(401);
}
if(response && (response.errors || response.error)){
var errstr = (response.errors || response.error);
logit(errstr)
res.jsonp(response);
return;
}
if(typeof response == "string"){
res.send(response);
}else{
if(response && response['content-type']){
try{
console.log(response);
res.set('Content-Type', response['content-type']+';charset=utf-8');
res.set('Content-Disposition', 'attachment; filename="'+response.filename+'"');
res.send(response.content);
}catch(e){
console.log('error',e);
res.jsonp({error:e});
}
}else{
res.jsonp(response);
}
}
}catch(e){
console.log(e);
res.jsonp({errors:e});
}
logit(JOE.Utils.color('[plugins]','plugin')+' ran '+plugin+' > '+method+' in '+pBM.stop()+' secs');
}
server.get(['/API/plugin/:plugin','/API/plugin/:plugin/:method'],pluginHandling);
server.post(['/API/plugin/:plugin','/API/plugin/:plugin/:method'],pluginHandling);
//SAVE
server.get(['/API/save/','/API/save/:itemid'],auth,function(req,res,next){
var object,prev_object,message = '';
if(req.params.itemid){
prev_object = $J.get(req.params.itemid);
if(!prev_object){
res.jsonp({status:'error',error:"object not found"});
return;
}
object = Object.assign({},prev_object,req.query);
message = `${object.itemtype||'item'} updated`;
}else{
object = req.query;
}
object.name = decodeURI(object.name);
for(prop in object){
if(typeof object[prop] == "string" && ["true","false"].indexOf(object[prop].toLowerCase()) != -1){
object[prop] = $c.parseBoolean(object[prop]);
}
}
object.joeUpdated = new Date();
//TODO: check to make sure schema idprop is present
JOE.Storage.save(object,object.itemtype,function(err,results){
//var object = merge({},results);
var itemtype = results.itemtype;
var _id = results._id;
/*
JOE.Storage.load(itemtype,{'_id':_id},function(err,data){
if(err){
res.jsonp({status:'error',error:err,results:data});
}else{
res.jsonp({status:'success',message:'',error:err,results:data});
JOE.io.emit('item_updated',{results:data})
}
})
*/
JOE.Cache.update(function(){
var updated = $J.get(_id);
JOE.Storage.load(itemtype,{'_id':_id},function(err,data){
if(err){
res.jsonp({status:'error',error:err});
}else{
res.jsonp({status:'success',message:'',error:err,results:updated});
JOE.io.emit('item_updated',{results:updated})
}
})
},[object.itemtype])
},{user:req.User});
});
/*
//UPDATE
server.get('/API/update/:itemid',auth,function(req,res,next){
var object = $J.get(req.params.itemid);
res.jsonp({status:'test',object:object});
if(item.)
//var object = req.query;
return;
//TODO: check to make sure schema idprop is present
JOE.Storage.save(object,object.itemtype,function(err,results){
//var object = merge({},results);
var itemtype = results.itemtype;
var _id = results._id;
JOE.Storage.load(itemtype,{'_id':_id},function(err,data){
if(err){
res.jsonp({status:'error',error:err,results:data});
}else{
res.jsonp({status:'success',error:err,results:data});
io.emit('item_updated',{results:data})
}
});
},{user:req.User});
});
*/
server.get('/',function(req,res,next){
logit('blank request');
if(JOE.Data.site.where({url:''})[0]){
JOE.Sites.parseRoute(req,res,next);
}else{
next();
}
})
server.get(['/'],function(req,res){
fs.readFile(JOE.joedir+'/pages/joe.html', 'utf8', function (err,data) {
var payload={
webconfig:JOE.webconfig
}
res.send(fillTemplate(data,payload));
})
})
//JOE APP
server.get(['/JOE/','/JOE/:appname'],auth,function(req,res,next){
var nonAppExtensions = ['jpeg','jpg','gif','ico','pdf','png','tif','tiff'];
var appname = req.params.appname||'joe';
var ext = appname.substr(appname.lastIndexOf('.')+1);
if(nonAppExtensions.indexOf(ext) != -1){
res.status(404).send('file "'+appname+'"['+ext+'] not found.');
return;
}
var agent = req.headers["user-agent"];
var templatePage = JOE.joedir+'/pages/template.html';
if(agent.indexOf('Trident') != -1 || agent.indexOf('MSIE') != -1|| agent.indexOf('Edge') != -1){
templatePage = JOE.joedir+'/pages/template_ie.html';
}
//console.log(agent);
fs.readFile(templatePage, 'utf8', function (err,data) {
if (err) {
res.json({error:err});
return console.log(err);
}
try{
var apps = [];
//var appspath = serverPath+'Apps.js';
//delete require.cache[require.resolve(appspath)];
//JOE.Apps = require(appspath);//JOE.webconfig.apps;
var Apps = JOE.Apps.cache;
// if(!JOE[appname]){
// res.send({'error':'no app found: '+appname});
// //return;
// }
var currentApp = Apps[appname];
if(!currentApp){
res.send({'error':'app not found'})
return;
}
var currentAppCollections = JOE.Utils.propAsFuncOrValue(currentApp.collections);
if(!$c.isEmpty(req.User)){
if(req.User.apps && req.User.apps.indexOf(appname) == -1){
if(req.User.role !="super"){
res.send({'error':'invalid permission, talk to your admin'});
return;
}
}
}else if(JOE.Data.user && JOE.Data.user.length){
res.send({'error':'invalid permission, try refreshing'});
return;
}
for(var app in Apps){
apps.push(app);
}
var sites = (JOE.Data.site || []).sortBy('url');
var plugins = [];
for(var pl in JOE.Apps.plugins){
plugins.push(pl);
}
var settings ={};
var data_settings = JOE.Data.setting||[];
for(var s = 0, tot = data_settings.length; s <tot;s++){
settings[data_settings[s].name]= data_settings[s].value;
}
var USER = { };
if(!$c.isEmpty(req.User)){
USER.name = req.User.name;
USER.schemas = (req.User.schemas||[]).join(',');
USER.role = req.User.role;
USER.apps = req.User.apps;
USER.items = (req.User.items||[]).join(',');
USER.styles =req.User.styles;
}
//var collections = [];
let uappsObj = [];
USER.apps.map(a=>{
let aa = Apps[a]||{};
uappsObj.push({name:a,title:aa.title,description:aa.description});
});
var payload = {
APPNAME:currentApp.title || appname,
APPINFO:JSON.stringify(stringFunctions(currentApp)),
APPS:apps.join(','),
USERAPPS:(USER.apps||apps||[]).join(','),
USERAPPSOBJ:JSON.stringify(uappsObj),
webconfig:JOE.webconfig,
settings:settings,
JOEPATH:JOE.webconfig.joepath,
COLLECTIONS:currentAppCollections.join(),
ALL_COLLECTIONS:JOE.Apps.collections.join(),
DEFAULT_SCHEMAS:JOE.webconfig.default_schemas.join(','),
PLUGINS:plugins.join(),
USER:USER,
STARTDATE:JOE.STARTDATE.toISOString(),
//SCHEMALIST:JOE.Schemas.schemaList.join(','),
//ALL_COLLECTIONS:Apps['joe'].collections.join(),
SITES:sites,
STATS:{
memory:{
current:process.memoryUsage(),
freemem:os.freemem(),
totalmem:os.totalmem()
}
}
};
//logit(console.log('memory',Math.round((process.memoryUsage().rss/1024/1024*100)/100)));
res.send(fillTemplate(data,payload));
}catch(e){
res.json({error:e});
console.log(e);
return console.log('error loading app '+appname+': '+e);
}
});
});
server.get(['/RENDER/:contentType/:templateName'],function(req,res){
var contentType = req.params.contentType;
var templateName = req.params.templateName;
if(!Renderizer.TEMPLATES[contentType] || !Renderizer.TEMPLATES[contentType][templateName]){
res.status(404).send({error:`${contentType} '${templateName}' not found`})
return;
}
var params = Object.assign(req.params,req.query);
res.send(Renderizer.TEMPLATES[contentType][templateName](params,req));
});
JOE.webDir = webDir = JOE.appDir+'/'+JOE.webconfig.webDir+'/';
JOE.Utils.setupFileFolder(webDir,'web');
coloredLog('listening on '+JOE.webconfig.port);
server.use('/',express.static(JOE.webDir));
http.listen(JOE.webconfig.port,function(){
//console.log('joe listening on '+JOE.webconfig.port);
coloredLog('using webDir: '+JOE.webDir);
});
JOE.httpServer = http;
var httpsPort = JOE.webconfig.httpsPort;
if(httpsPort){
var httpsServer = https.Server({key: JOE.Pem.serviceKey, cert: JOE.Pem.certificate}, server);
httpsServer.listen(httpsPort);
coloredLog(' https on '+httpsPort);
JOE.httpsServer = httpsServer;
// pem.createCertificate({selfSigned:true}, function(err, keys){
// if(err){
// console.log(err);
// }
// https.Server({key: keys.serviceKey, cert: keys.certificate}, server).listen(httpsPort);
// console.log(JOE.Utils.color('[server]','module')+' https on '+httpsPort);
// JOE.httpsServer = https;
// });
}
module.exports = server;