hopjs
Version:
A RESTful declarative API framework, with stub generators for Shell, and Android
346 lines (307 loc) • 11.7 kB
JavaScript
var Hop = require('./api');
var Fire = require('fire-ts');
var fs = require('fs');
var path = require('path');
var util = require('hopjs-common');
var url = require('url');
Hop.convertValue=function(value){
if(value===null){
return "";
} else if(value instanceof Array){
var r = [];
for(var i in value){
r.push(Hop.convertValue(value[i]));
}
return r;
} else if(typeof value=="object"){
var r = {};
for(var v in value){
r[v]=Hop.convertValue(value[v]);
}
return r;
} else {
if(value==null)
return "";
else return value.toString();
}
}
/**
Inserts the Hop calls into express
@param {string} basePath
the path which the Hop will hang off of
@param {object} app
the express app
@for Hop
@method apiHook
**/
Hop.apiHook=function(basePath,app,options){
try {
var packageJSON = fs.readFileSync(path.resolve(__dirname,path.join("..","package.json")))
Hop.version = JSON.parse(packageJSON.toString()).version;
} catch(e){
Hop.error("Failed to discover hope version");
Hop.error(e.stack);
}
options=options||{};
Hop.basePath=basePath;
for(var objectName in Hop.Objects){
var obj = Hop.Objects[objectName];
for(var methodName in obj.methods){
var method = obj.methods[methodName];
Hop.log(method.method,method.getPath());
(function Hop_apiHook(obj,method){
app[method.method](method.getPath(),function Hop_apiHook_hook(req,res){
Hop.log("-",method.method,method.getPath());
if(!Hop._host){
Hop._host = req.get("host");
}
var input = {};
if(req.body._hopRedirect || req.query._hopRedirect){
var redirect = req.body._hopRedirect || req.query._hopRedirect;
}
if(req.body._hopTemplate || req.query._hopTemplate){
var template = req.body._hopTemplate || req.query._hopTemplate;
}
for(var paramName in method.params){
var param = method.params[paramName];
var value=undefined;
if(method.method=="get" || method.method=="del" || method.method=="delete")
value = req.params[paramName] || req.query[paramName];
else if(method.method=="post" || method.method=="put" || method.method=="patch") {
if(req.files && (param.demandFile || param.optionalFile)){
value = req.files[paramName];
} else if(req.body){
value = req.params[paramName] || req.body[paramName];
} else {
value = req.params[paramName];
}
}
if(param.demand && typeof value=="undefined"){
return res.send(400,"Missing parameter:"+paramName);
}
//value=Hop.convertValue(value);
input[paramName]=value;
}
//try {
req._response = res;
req.getResponse=function(){ return res; };
var called=false;
Hop.call(obj.name+"."+method.name,input,function(err,result,statusCode){
if(typeof statusCode!="undefined"){
res.status(statusCode);
}
var m = obj.name+"."+method.name;
if(called){
Hop.logCall(m,"warn","called onComplete twice");
} else {
called=true;
try {
if(redirect)
return res.redirect(redirect);
if(template)
return res.render(template,{ result: result, error: err, request: req});
if(err){
err=obj.callOnError(method,req,input,err);
if(typeof err.send=="function"){
err.send(res);
} else {
res.send(statusCode||500,err);
}
} else {
if(result && result.send && typeof result.send=="function"){
result.send(res);
} else {
if(result===null || result===undefined){
res.send(statusCode||404,"Not found");
} else {
if(method.input && method.input.model){
res.setHeader("X-Input-Schema",util.webpath.join(basePath,"/schema/"+method.input.model));
}
if(method.output && method.output.model){
res.setHeader('Content-Type','application/json; profile='+util.webpath.join(basePath,"/schema/"+method.output.model));
res.send(JSON.stringify(result));
} else {
res.json(result);
}
}
}
}
} catch(e){
Hop.error(e.stack);
}
}
},req,res);
/*
} catch(e){
res.send(500,e.toString()+"\n"+e.stack());
}*/
});
})(obj,method);
}
}
var cwd = path.join(path.dirname(module.filename),"../");
Hop.docURL = util.webpath.join(basePath,"/");
app.get(util.webpath.join(basePath,"/"),function(req,res){
var apiChecksum = (Hop.checksum());
if(req.get("If-Non-Match")==apiChecksum){
return res.send("Not modified",304);
}
fs.readFile(path.join(cwd,"static/doc.comb"),function(err,data){
try {
var template = Fire.compile(data.toString());
res.set('Content-Type', 'text/html');
res.set("ETag",apiChecksum);
res.send(template({ Hop: Hop, _csrf: (req.session?req.session._csrf:undefined) }));
} catch(e){
Hop.error("Error parsing doc template",e.stack);
res.send(500,"Error:"+e.toString());
}
});
});
Hop.hopjsURL = util.webpath.join(basePath,"hopjs.js");
app.get(util.webpath.join(basePath,"hopjs.js"),function(req,res){
if(req.get("If-Non-Match")==Hop.version){
return res.send("Not modified",304);
}
var file = path.join(cwd,"static/hopjs.comb");
fs.readFile(file,function(err,data){
try {
var template = Fire.compile(data.toString());
res.set('Content-Type', 'application/javascript');
res.set("ETag",Hop.version);
res.send(template({ Hop: Hop, _csrf: (req.session?req.session._csrf:null)}));
} catch (e){
Hop.error("Error parsing hopjs template",e.stack);
res.send(500,"Error:"+e.msg);
}
});
});
Hop.basePath = basePath;
Hop.apiURL = util.webpath.join(basePath,"api.js");
app.get(util.webpath.join(basePath,"api.js"),function(req,res){
var apiChecksum = (Hop.checksum());
var srcURL="";
//Allow the requestor to specify the root URL to use when making calls
if(req.query.url){
var _url = url.parse(req.query.url);
srcURL = url.format({host: _url.host, protocol: _url.protocol, auth: _url.auth });
}
if(req.get("If-Non-Match")==apiChecksum){
return res.send("Not modified",304);
}
var file = path.join(cwd,"static/api.comb");
fs.readFile(file,function(err,data){
try {
var template = Fire.compile(data.toString());
res.set('Content-Type', 'application/javascript');
res.set("ETag",apiChecksum);
res.set("Hop-Checksum",apiChecksum);
res.send(template({ Hop: Hop, Objects: Hop.Objects, Models: Hop.Models, _csrf: (req.session?req.session._csrf:null), srcURL:srcURL}));
} catch (e){
Hop.error("Error parsing api template",e.stack);
res.send(500,"Error:"+e.msg);
}
});
});
Hop.testURL=util.webpath.join(basePath,"test.js");
if(Hop.enableUnitTests){
app.get(util.webpath.join(basePath,"test.js"),function(req,res){
var apiChecksum = (Hop.checksum());
if(req.get("If-Non-Match")==apiChecksum){
return res.send("Not modified",304);
}
fs.readFile(path.join(cwd,"static/test.comb"),function(err,data){
try {
var template = Fire.compile(data.toString());
res.set('Content-Type', 'application/javascript');
res.set("ETag",apiChecksum);
res.send(template(Hop));
} catch(e){
Hop.error("Error parsing test template",e.stack);
res.send(500,"Error:"+e.toString());
}
});
});
}
app.get("/_hopjs/",function(req,res){
res.redirect(basePath);
});
app.get(util.webpath.join(basePath,"/schema/:model"),function(req,res){
if(Hop.Models && Hop.Models[req.params.model]){
res.send(Hop.Models[req.params.model].toJSONSchema());
} else return res.send(404,"Not found");
});
var files = {
"api.json":function(req,res){
res.contentType("application/json");
res.send(Hop.toJSON());
},
"apitest.json":function(req,res){
if(Hop.enableUnitTests){
res.contentType("application/json");
res.send(JSON.stringify(Hop.TestCases));
} else res.send(404,"Not Found");
},
"apitest.js":function(req,res){
if(Hop.enableUnitTests){
res.contentType("text/javascript");
res.send(Hop.TestCase.toStandAlone());
} else res.send(404,"Not Found");
}
}
for(var file in files){
app.get(util.webpath.join(basePath,file),files[file])
app.get(util.webpath.join("/_hopjs",file),files[file])
}
app.get(util.webpath.join(basePath,"*"),function(req,res){
if(req.params[0].indexOf("..")!=-1)
return res.send(404,"Not found");
var file = path.join(__dirname,"../static/_hopjs",req.params[0]);
if(/\.comb$/.test(file) || /\.html\.fts/.test(file)){
Fire.parse(file,{},function(err,templ){
if(err) return res.send(500,err.toString());
if(req.query.name){
res.send(req.query.name+"="+templ.toString());
} else {
res.send(templ.toString());
}
});
} else {
res.sendfile(file);
}
});
}
var cwd = path.join(path.dirname(module.filename),"../");
/**
Render a template using the cumbustion template engine
@param {string} filename The filename of the template relative to the "./static" path
@param {object} input The inputs to the template
@for Hop
@method renderTemplate
**/
Hop.renderTemplate=function(filename,input){
input=input||{};
try {
var file = path.join(cwd,"static",filename);
if(fs.existsSync(file)){
var data = fs.readFileSync(file);
} else {
var data = fs.readFileSync(filename);
}
if(!data){
return "Invalid template specified:"+filename;
}
var _input={};
for(var i in input){
_input[i]=input[i];
}
_input.Hop=Hop;
var template = Fire.compile(data.toString());
return template;
} catch(e){
Hop.error(e);
Hop.error(e.stack);
return (file+":"+e.toString());
}
}
module.exports=Hop;