restart project with express framework
This commit is contained in:
parent
055e24e68a
commit
8b9ceb7978
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
*.rej
|
*.rej
|
||||||
*~
|
*~
|
||||||
*.*.swp
|
*.*.swp
|
||||||
|
node_modules
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/env nodejs
|
|
||||||
/**
|
|
||||||
* Bouquins bootstrap.
|
|
||||||
* TODO license
|
|
||||||
*
|
|
||||||
* Load configuration
|
|
||||||
* Check configuration
|
|
||||||
* Launch http server
|
|
||||||
* Initialise router
|
|
||||||
*/
|
|
||||||
APP_NAME='Bouquins';
|
|
||||||
DEFAULT_CONF='./config/config.json';
|
|
||||||
|
|
||||||
var bouquins = require('../lib/bouquins');
|
|
||||||
|
|
||||||
console.log('Bootstraping ' + APP_NAME);
|
|
||||||
|
|
||||||
// TODO argument conf file
|
|
||||||
var configfile = DEFAULT_CONF;
|
|
||||||
bouquins.loadconfig(configfile, function(err, config) {
|
|
||||||
if (err) {
|
|
||||||
console.error('Fatal error, cannot load config: ' + err);
|
|
||||||
console.log(err.stack);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
// global config
|
|
||||||
GLOBAL.config = config;
|
|
||||||
// global logger
|
|
||||||
GLOBAL.logger = bouquins.initLogger();
|
|
||||||
logger.debug(config);
|
|
||||||
// database
|
|
||||||
bouquins.initDB(function(err) {
|
|
||||||
if (err) {
|
|
||||||
logger.error('Fatal error, cannot load database: ' + err);
|
|
||||||
logger.error(err.stack);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
start();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise application
|
|
||||||
*/
|
|
||||||
function start(){
|
|
||||||
logger.info('Starting '+APP_NAME);
|
|
||||||
|
|
||||||
var starReqListener=function(req, res) {
|
|
||||||
// defaut request listener
|
|
||||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
|
||||||
res.end(APP_NAME + ' is starting');
|
|
||||||
}
|
|
||||||
// launch http server, wait config and router
|
|
||||||
var server = require('http').createServer(starReqListener)
|
|
||||||
.listen(config.httpPort);
|
|
||||||
logger.info('Listening on '+config.httpPort);
|
|
||||||
|
|
||||||
// TODO check config: files/dir do exist, database exist, can read ...
|
|
||||||
|
|
||||||
// init router
|
|
||||||
var router = bouquins.makeRouter();
|
|
||||||
server.on('request', function(req, resp) {
|
|
||||||
router.request(req, resp);
|
|
||||||
}).removeListener('request', starReqListener);
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
|||||||
{
|
{
|
||||||
"httpPort":8080,
|
"httpPort":8080,
|
||||||
"debugLevel":"debug",
|
"debugLevel":"debug",
|
||||||
"endpoints": {
|
|
||||||
"library":"endpoint/library.js",
|
|
||||||
"book":"endpoint/book.js",
|
|
||||||
"author":"endpoint/author.js",
|
|
||||||
"serie":"endpoint/serie.js",
|
|
||||||
"tag":"endpoint/tag.js"
|
|
||||||
},
|
|
||||||
"urlPrefix": "http://127.0.0.1:8080",
|
|
||||||
"dbfile": "/home/meutel/metadata.db"
|
"dbfile": "/home/meutel/metadata.db"
|
||||||
}
|
}
|
||||||
|
@ -1,201 +0,0 @@
|
|||||||
/**
|
|
||||||
* Action.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
|
||||||
var util = require('util');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*/
|
|
||||||
function Action(name) {
|
|
||||||
logger.debug('new Action');
|
|
||||||
EventEmitter.call(this);
|
|
||||||
//
|
|
||||||
// ATTRIBUTES
|
|
||||||
//
|
|
||||||
this.name = name;
|
|
||||||
|
|
||||||
//
|
|
||||||
// INIT
|
|
||||||
//
|
|
||||||
};
|
|
||||||
// injerits EventEmitter
|
|
||||||
Action.prototype = Object.create(EventEmitter.prototype, {
|
|
||||||
|
|
||||||
//
|
|
||||||
// FUNCTIONS
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return action needs request body data.
|
|
||||||
*/
|
|
||||||
withReqData: {
|
|
||||||
value: function() {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen request data event.
|
|
||||||
*/
|
|
||||||
reqData: {
|
|
||||||
value: function(chunk) {
|
|
||||||
//TODO
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare action.
|
|
||||||
* @param callback (err, statusCode, headers)
|
|
||||||
*/
|
|
||||||
prepare: {
|
|
||||||
value: function(callback) {
|
|
||||||
logger.debug('prepare Action');
|
|
||||||
// TODO impl per action type
|
|
||||||
// default: OK, no headers
|
|
||||||
callback(null, 200, {});
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
|
|
||||||
doAction: {
|
|
||||||
value: function(){
|
|
||||||
logger.debug('doAction');
|
|
||||||
//TODO emit data end events
|
|
||||||
this.emit('end');
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action show single resource.
|
|
||||||
*/
|
|
||||||
function ShowAction() {
|
|
||||||
logger.debug('new ShowAction');
|
|
||||||
Action.call(this, 'show');
|
|
||||||
|
|
||||||
// Resource id.
|
|
||||||
this.resId = null;
|
|
||||||
// Resource.
|
|
||||||
this.res = null;
|
|
||||||
};
|
|
||||||
// inherits Action
|
|
||||||
ShowAction.prototype = Object.create(Action.prototype, {
|
|
||||||
/**
|
|
||||||
* Prepare show action.
|
|
||||||
* Headers:
|
|
||||||
* - Link: related resource.
|
|
||||||
*/
|
|
||||||
prepare: {
|
|
||||||
value: function(callback) {
|
|
||||||
var self = this;
|
|
||||||
if (this.resId) {
|
|
||||||
this.loadResource(this.resId, function(err, res) {
|
|
||||||
self.res = res;
|
|
||||||
logger.debug('resource loaded: ' + res);
|
|
||||||
//TODO err
|
|
||||||
var link = '';
|
|
||||||
var rels = self.getRelated(res, function(err, rels) {
|
|
||||||
//TODO err
|
|
||||||
logger.debug(rels);
|
|
||||||
rels.forEach(function (e) {
|
|
||||||
if (link.length > 0) link+=', ';
|
|
||||||
link += '<' + config.urlPrefix + e.path + '>; rel=' + e.type;
|
|
||||||
});
|
|
||||||
var headers = {};
|
|
||||||
if (link.length > 0) {
|
|
||||||
headers.Link = link;
|
|
||||||
}
|
|
||||||
callback(null, 200, headers);
|
|
||||||
}); // { type: 'author', path: '/author/id'}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
//TODO error?
|
|
||||||
callback(null, '5OO', {});
|
|
||||||
}
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Load resource.
|
|
||||||
* @param resource id
|
|
||||||
* @param callback <function(err, resource)>
|
|
||||||
*/
|
|
||||||
loadResource: {
|
|
||||||
value: function(resId, callback){
|
|
||||||
//implement
|
|
||||||
callback(new Error('Cannot load resource ' + resId));
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get related resources.
|
|
||||||
* @param res resource
|
|
||||||
* @param callback <function(err, rels)> related resources { type: 'author', path: '/author/id'}
|
|
||||||
*/
|
|
||||||
getRelated: {
|
|
||||||
value: function(res, callback){
|
|
||||||
callback(null, []);
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
|
|
||||||
doAction: {
|
|
||||||
value: function(){
|
|
||||||
logger.debug('doAction');
|
|
||||||
this.emit('data', this.res);
|
|
||||||
this.emit('end');
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Action show list of resources.
|
|
||||||
*/
|
|
||||||
function ListAction() {
|
|
||||||
logger.debug('new ListAction');
|
|
||||||
Action.call(this, 'list');
|
|
||||||
};
|
|
||||||
// inherits Action
|
|
||||||
ListAction.prototype = Object.create(Action.prototype, {
|
|
||||||
prepare: {
|
|
||||||
value: function(callback) {
|
|
||||||
//TODO
|
|
||||||
callback(null, 200, {});
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
doAction: {
|
|
||||||
value: function() {
|
|
||||||
logger.debug('doAction');
|
|
||||||
var self = this;
|
|
||||||
// TODO parameters
|
|
||||||
this.loadResources(function(err, res) {
|
|
||||||
// for each loaded resource
|
|
||||||
self.emit('data', res);
|
|
||||||
}, function(err) {
|
|
||||||
self.emit('end');
|
|
||||||
});
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
loadResources: {
|
|
||||||
value: function(onload, onend) {
|
|
||||||
// nothing to load
|
|
||||||
onend(null);
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
ShowAction: ShowAction,
|
|
||||||
ListAction: ListAction
|
|
||||||
};
|
|
@ -1,44 +0,0 @@
|
|||||||
/**
|
|
||||||
* TODO license
|
|
||||||
* Bouquins module.
|
|
||||||
*/
|
|
||||||
GLOBAL.PATH_RE=/\/([a-zA-Z0-9]+)(?:\/|$)([a-zA-Z0-9]+)?/;
|
|
||||||
|
|
||||||
var Config = require('./util/config'),
|
|
||||||
logger = require('./util/logger'),
|
|
||||||
Router = require('./router/router'),
|
|
||||||
sqlite3 = require('sqlite3').verbose(),
|
|
||||||
bouquins = exports;
|
|
||||||
|
|
||||||
var router = null;
|
|
||||||
/**
|
|
||||||
* Load config file.
|
|
||||||
*/
|
|
||||||
bouquins.loadconfig = function(configfile, callback) {
|
|
||||||
Config.loadconfig(configfile, callback);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Init logger.
|
|
||||||
*/
|
|
||||||
bouquins.initLogger = function() {
|
|
||||||
if (config.debugLevel) {
|
|
||||||
logger.debugLevel = config.debugLevel;
|
|
||||||
}
|
|
||||||
return logger;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Init database.
|
|
||||||
*/
|
|
||||||
bouquins.initDB = function(callback) {
|
|
||||||
logger.debug('Database: '+config.dbfile);
|
|
||||||
GLOBAL.db = new sqlite3.Database(config.dbfile, callback);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Make main router.
|
|
||||||
*/
|
|
||||||
bouquins.makeRouter = function() {
|
|
||||||
if (!router) {
|
|
||||||
router = new Router();
|
|
||||||
}
|
|
||||||
return router;
|
|
||||||
};
|
|
@ -1,73 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* Endpoint author.
|
|
||||||
*/
|
|
||||||
var Endpoint = require('./endpoint.js');
|
|
||||||
|
|
||||||
function Author() {
|
|
||||||
Endpoint.call(this);
|
|
||||||
this.authorId = null;
|
|
||||||
}
|
|
||||||
Author.prototype = Object.create(Endpoint.prototype, {
|
|
||||||
|
|
||||||
bind: {
|
|
||||||
value: function(action, params, callback) {
|
|
||||||
switch (action.name) {
|
|
||||||
case 'show':
|
|
||||||
action.resId = this.authorId;
|
|
||||||
action.getRelated = function(res, relcback){
|
|
||||||
// books of this autor
|
|
||||||
// TODO authors writing same book
|
|
||||||
relcback(null, new Array({ type: 'books', path: '/books?author='+this.resId }));
|
|
||||||
};
|
|
||||||
action.loadResource = function(resId, loadcback) {
|
|
||||||
logger.debug('loading author ' + resId);
|
|
||||||
db.get('SELECT * FROM authors WHERE id = '+ resId, function(err, row) {
|
|
||||||
loadcback(err, row);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
//TODO related
|
|
||||||
action.loadResources = function(onload, onend) {
|
|
||||||
var query = 'SELECT * FROM authors ';
|
|
||||||
if (!this.perPage) this.perPage = 30;
|
|
||||||
//TODO sanitize
|
|
||||||
if (!this.page) this.page = 0;
|
|
||||||
query += ' LIMIT ? OFFSET ?';
|
|
||||||
db.each(query, this.perPage, this.page*this.perPage, function (err, row) {
|
|
||||||
onload(err, row);
|
|
||||||
}, function(err) {
|
|
||||||
//TODO err
|
|
||||||
if (err) logger.error(err);
|
|
||||||
onend();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
callback(new Error('action not implemented'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
targetCollection : {
|
|
||||||
value: function(pathname) {
|
|
||||||
var match = PATH_RE.exec(pathname);
|
|
||||||
logger.debug('pathname ' + pathname + ' => ' + match);
|
|
||||||
if (match.length > 2 && match[2]) {
|
|
||||||
// TODO check integer
|
|
||||||
this.authorId = match[2];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exports = module.exports = new Author();
|
|
@ -1,88 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* Endpoint book.
|
|
||||||
*/
|
|
||||||
var Endpoint = require('./endpoint.js');
|
|
||||||
|
|
||||||
function Book() {
|
|
||||||
Endpoint.call(this);
|
|
||||||
this.bookId = null;
|
|
||||||
}
|
|
||||||
Book.prototype = Object.create(Endpoint.prototype, {
|
|
||||||
|
|
||||||
bind: {
|
|
||||||
value: function(action, params, callback) {
|
|
||||||
switch (action.name) {
|
|
||||||
case 'show':
|
|
||||||
action.resId = this.bookId;
|
|
||||||
action.getRelated = function(res,relcback){
|
|
||||||
var rels = new Array();
|
|
||||||
var nbrels = 3; // number of requests for rel
|
|
||||||
// call callback when no more requests running
|
|
||||||
var endrels = function(err) {
|
|
||||||
nbrels--;
|
|
||||||
if (err) logger.error(err);
|
|
||||||
if (nbrels == 0) relcback(null, rels);
|
|
||||||
};
|
|
||||||
var raq = 'SELECT * FROM books_authors_link WHERE book = ?';
|
|
||||||
db.each(raq, res.id, function (err, row) {
|
|
||||||
if (!err && row.author)
|
|
||||||
rels.push({ type: 'author', path: '/author/'+row.author });
|
|
||||||
}, endrels);
|
|
||||||
var rsq = 'SELECT * FROM books_series_link WHERE book = ?';
|
|
||||||
db.each(rsq, res.id, function (err, row) {
|
|
||||||
if (!err && row.series)
|
|
||||||
rels.push({ type: 'serie', path: '/serie/'+row.series });
|
|
||||||
}, endrels);
|
|
||||||
var rtq = 'SELECT * FROM books_tags_link WHERE book = ?';
|
|
||||||
db.each(rtq, res.id, function (err, row) {
|
|
||||||
if (!err && row.tag)
|
|
||||||
rels.push({ type: 'tag', path: '/tag/'+row.tag });
|
|
||||||
}, endrels);
|
|
||||||
};
|
|
||||||
action.loadResource = function(resId, callback) {
|
|
||||||
logger.debug('loading book ' + resId);
|
|
||||||
db.get('SELECT * FROM books WHERE id = '+ resId, function(err, row) {
|
|
||||||
callback(err, row);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
//TODO related
|
|
||||||
action.loadResources = function(onload, onend) {
|
|
||||||
var query = 'SELECT * FROM books ';
|
|
||||||
//TODO factorize
|
|
||||||
if (!this.perPage) this.perPage = 30;
|
|
||||||
//TODO sanitize
|
|
||||||
if (!this.page) this.page = 0;
|
|
||||||
query += ' LIMIT ? OFFSET ?';
|
|
||||||
db.each(query, this.perPage, this.page*this.perPage, function (err, row) {
|
|
||||||
onload(err, row);
|
|
||||||
}, function(err) {
|
|
||||||
//TODO err
|
|
||||||
if (err) logger.error(err);
|
|
||||||
onend();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
callback(new Error('action not implemented'));
|
|
||||||
}
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
targetCollection : {
|
|
||||||
value: function(pathname) {
|
|
||||||
var match = PATH_RE.exec(pathname);
|
|
||||||
logger.debug('pathname ' + pathname + ' => ' + match);
|
|
||||||
if (match.length > 2 && match[2]) {
|
|
||||||
// TODO check integer
|
|
||||||
this.bookId = match[2];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exports = module.exports = new Book();
|
|
@ -1,56 +0,0 @@
|
|||||||
/**
|
|
||||||
* Endpoint class.
|
|
||||||
*/
|
|
||||||
var Action = require('../action/action.js');
|
|
||||||
|
|
||||||
function Endpoint() {
|
|
||||||
// constructor
|
|
||||||
};
|
|
||||||
Endpoint.prototype = {
|
|
||||||
/**
|
|
||||||
* Build an action on resource endpoint.
|
|
||||||
* @param method <string> HTTP method
|
|
||||||
* @param url <url> target URL
|
|
||||||
* @param callback <function(err, action)> callback
|
|
||||||
*/
|
|
||||||
buildAction : function(method, url, callback) {
|
|
||||||
var col = this.targetCollection(url.pathname);
|
|
||||||
logger.debug('Building action ('+method+','+col+')');
|
|
||||||
var action;
|
|
||||||
if (col && method == 'POST') {
|
|
||||||
//TODO search
|
|
||||||
} else if (col && method == 'GET') {
|
|
||||||
action = new Action.ListAction();
|
|
||||||
action.page = url.query.page;
|
|
||||||
action.perPage = url.query.per_page;
|
|
||||||
} else if (!col && method == 'POST') {
|
|
||||||
//TODO edit
|
|
||||||
} else if (!col && method == 'GET') {
|
|
||||||
action = new Action.ShowAction();
|
|
||||||
}
|
|
||||||
if (action) {
|
|
||||||
this.bind(action, url.query, function(err) {
|
|
||||||
callback(err, action);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
callback(new Error('no action'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return target is a collection of resources.
|
|
||||||
*/
|
|
||||||
targetCollection : function(pathname) {
|
|
||||||
// TODO
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind action to endpoint resource.
|
|
||||||
*/
|
|
||||||
bind : function(action, params, callback) {
|
|
||||||
// implemented
|
|
||||||
callback(null, action);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
exports = module.exports = Endpoint;
|
|
@ -1,44 +0,0 @@
|
|||||||
/**
|
|
||||||
* Endpoint library.
|
|
||||||
*/
|
|
||||||
var Endpoint = require('./endpoint.js');
|
|
||||||
function Library() {
|
|
||||||
Endpoint.call(this);
|
|
||||||
}
|
|
||||||
Library.prototype = Object.create(Endpoint.prototype, {
|
|
||||||
|
|
||||||
bind: {
|
|
||||||
value: function(action, params, callback) {
|
|
||||||
if (action.name == 'show') {
|
|
||||||
action.resId = 'library';
|
|
||||||
action.loadResource = function(resId, callback) {
|
|
||||||
//TODO load from db
|
|
||||||
callback(null, {
|
|
||||||
name: 'Bibliothèque Meutel'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
action.getRelated = function(res, relcback){
|
|
||||||
relcback(null, new Array(
|
|
||||||
// authors list
|
|
||||||
{ type: 'authors', path: '/author/' },
|
|
||||||
// books list
|
|
||||||
{ type: 'books', path: '/books/' },
|
|
||||||
// tags list
|
|
||||||
{ type: 'tags', path: '/tags/' },
|
|
||||||
// series list
|
|
||||||
{ type: 'series', path: '/series/' },
|
|
||||||
// user favorites
|
|
||||||
{ type: 'favorites', path:'/favorites/'}
|
|
||||||
));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
callback(null, action);
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
exports = module.exports = new Library();
|
|
@ -1,85 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* Endpoint serie.
|
|
||||||
*/
|
|
||||||
var Endpoint = require('./endpoint.js');
|
|
||||||
|
|
||||||
function Serie() {
|
|
||||||
Endpoint.call(this);
|
|
||||||
this.serieId = null;
|
|
||||||
}
|
|
||||||
Serie.prototype = Object.create(Endpoint.prototype, {
|
|
||||||
|
|
||||||
bind: {
|
|
||||||
value: function(action, params, callback) {
|
|
||||||
switch (action.name) {
|
|
||||||
case 'show':
|
|
||||||
action.resId = this.serieId;
|
|
||||||
action.getRelated = function(res,relcback){
|
|
||||||
var rels = new Array();
|
|
||||||
var nbrels = 2; // number of requests for rel
|
|
||||||
// call callback when no more requests running
|
|
||||||
var endrels = function(err) {
|
|
||||||
nbrels--;
|
|
||||||
if (err) logger.error(err);
|
|
||||||
if (nbrels == 0) relcback(null, rels);
|
|
||||||
};
|
|
||||||
// author
|
|
||||||
var raq = 'SELECT DISTINCT author FROM books_series_link AS bsl, books_authors_link AS bal WHERE bsl.book = bal.book AND bsl.series = ?';
|
|
||||||
db.each(raq, res.id, function (err, row) {
|
|
||||||
if (!err && row.author)
|
|
||||||
rels.push({ type: 'author', path: '/author/'+row.author });
|
|
||||||
}, endrels);
|
|
||||||
// books
|
|
||||||
var rbq = 'SELECT * FROM books_series_link WHERE series = ?';
|
|
||||||
db.each(rbq, res.id, function (err, row) {
|
|
||||||
if (!err && row.book)
|
|
||||||
rels.push({ type: 'book', path: '/book/'+row.book });
|
|
||||||
}, endrels);
|
|
||||||
};
|
|
||||||
action.loadResource = function(resId, callback) {
|
|
||||||
logger.debug('loading serie ' + resId);
|
|
||||||
db.get('SELECT * FROM series WHERE id = '+ resId, function(err, row) {
|
|
||||||
callback(err, row);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
//TODO related
|
|
||||||
action.loadResources = function(onload, onend) {
|
|
||||||
var query = 'SELECT * FROM series ';
|
|
||||||
//TODO factorize
|
|
||||||
if (!this.perPage) this.perPage = 30;
|
|
||||||
//TODO sanitize
|
|
||||||
if (!this.page) this.page = 0;
|
|
||||||
query += ' LIMIT ? OFFSET ?';
|
|
||||||
db.each(query, this.perPage, this.page*this.perPage, function (err, row) {
|
|
||||||
onload(err, row);
|
|
||||||
}, function(err) {
|
|
||||||
//TODO err
|
|
||||||
if (err) logger.error(err);
|
|
||||||
onend();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
callback(new Error('action not implemented'));
|
|
||||||
}
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
targetCollection : {
|
|
||||||
value: function(pathname) {
|
|
||||||
var match = PATH_RE.exec(pathname);
|
|
||||||
logger.debug('pathname ' + pathname + ' => ' + match);
|
|
||||||
if (match.length > 2 && match[2]) {
|
|
||||||
// TODO check integer
|
|
||||||
this.serieId = match[2];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exports = module.exports = new Serie();
|
|
@ -1,61 +0,0 @@
|
|||||||
/**
|
|
||||||
* Endpoint tag.
|
|
||||||
*/
|
|
||||||
var Endpoint = require('./endpoint.js');
|
|
||||||
|
|
||||||
function Tag() {
|
|
||||||
Endpoint.call(this);
|
|
||||||
this.tagId = null;
|
|
||||||
}
|
|
||||||
Tag.prototype = Object.create(Endpoint.prototype, {
|
|
||||||
|
|
||||||
bind: {
|
|
||||||
value: function(action, params, callback) {
|
|
||||||
switch (action.name) {
|
|
||||||
case 'show':
|
|
||||||
action.resId = this.tagId;
|
|
||||||
action.loadResource = function(resId, callback) {
|
|
||||||
logger.debug('loading tag ' + resId);
|
|
||||||
db.get('SELECT * FROM tags WHERE id = '+ resId, function(err, row) {
|
|
||||||
callback(err, row);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
case 'list':
|
|
||||||
action.loadResources = function(onload, onend) {
|
|
||||||
var query = 'SELECT * FROM tags ';
|
|
||||||
//TODO factorize
|
|
||||||
if (!this.perPage) this.perPage = 30;
|
|
||||||
//TODO sanitize
|
|
||||||
if (!this.page) this.page = 0;
|
|
||||||
query += ' LIMIT ? OFFSET ?';
|
|
||||||
db.each(query, this.perPage, this.page*this.perPage, function (err, row) {
|
|
||||||
onload(err, row);
|
|
||||||
}, function(err) {
|
|
||||||
//TODO err
|
|
||||||
if (err) logger.error(err);
|
|
||||||
onend();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
callback(null, action);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
callback(new Error('action not implemented'));
|
|
||||||
}
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
targetCollection : {
|
|
||||||
value: function(pathname) {
|
|
||||||
var match = PATH_RE.exec(pathname);
|
|
||||||
logger.debug('pathname ' + pathname + ' => ' + match);
|
|
||||||
if (match.length > 2 && match[2]) {
|
|
||||||
// TODO check integer
|
|
||||||
this.tagId = match[2];
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exports = module.exports = new Tag();
|
|
@ -1,205 +0,0 @@
|
|||||||
/**
|
|
||||||
* TODO license
|
|
||||||
* Outputter.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outputter class.
|
|
||||||
* Abstract (not exported).
|
|
||||||
*/
|
|
||||||
function Outputter() {
|
|
||||||
//
|
|
||||||
// ATTRIBUTES
|
|
||||||
//
|
|
||||||
/**
|
|
||||||
* Output stream.
|
|
||||||
*/
|
|
||||||
this.out = null;
|
|
||||||
this.headers = {};
|
|
||||||
};
|
|
||||||
Outputter.prototype = {
|
|
||||||
//
|
|
||||||
// FUNCTIONS
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add header to request.
|
|
||||||
* Available before calling outputTo().
|
|
||||||
*/
|
|
||||||
addHeader: function (name, value) {},
|
|
||||||
/**
|
|
||||||
* Init before outputing.
|
|
||||||
*/
|
|
||||||
init: function () {},
|
|
||||||
/**
|
|
||||||
* Listen action 'data' event.
|
|
||||||
* Receive resource instance to output.
|
|
||||||
*/
|
|
||||||
output: function (resource) { },
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen action 'end' event.
|
|
||||||
* End of action data transmission.
|
|
||||||
*/
|
|
||||||
end: function() {
|
|
||||||
logger.debug('Action ended');
|
|
||||||
this.out.end();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set target stream and start outputting.
|
|
||||||
*/
|
|
||||||
outputTo: function(headers, stream) {
|
|
||||||
this.headers = headers;
|
|
||||||
this.out = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outputter in json format.
|
|
||||||
*/
|
|
||||||
var JSONOutputter = function() {
|
|
||||||
Outputter.call(this);
|
|
||||||
logger.debug('JSON');
|
|
||||||
this.buffer = new Array();
|
|
||||||
this.colStarted = false;
|
|
||||||
};
|
|
||||||
// inherits Outputter
|
|
||||||
JSONOutputter.prototype = Object.create(Outputter.prototype, {
|
|
||||||
output: {
|
|
||||||
value: function(resource) {
|
|
||||||
logger.debug('ressource: '+JSON.stringify(this.buffer));
|
|
||||||
logger.debug('colStarted: '+this.colStarted);
|
|
||||||
if (!this.colStarted && this.buffer.length == 0)
|
|
||||||
this.buffer.push(resource);
|
|
||||||
else {
|
|
||||||
this.buffer.push(resource);
|
|
||||||
if (!this.colStarted) {
|
|
||||||
this.out.write('[');
|
|
||||||
this.colStarted = true;
|
|
||||||
} else
|
|
||||||
this.out.write(',');
|
|
||||||
while (this.buffer.length>0) {
|
|
||||||
var r = this.buffer.shift();
|
|
||||||
this.out.write(JSON.stringify(r));
|
|
||||||
if (this.buffer.length>0)
|
|
||||||
this.out.write(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
value: function() {
|
|
||||||
if (this.buffer.length == 1) {
|
|
||||||
// single resource
|
|
||||||
this.out.write(JSON.stringify(this.buffer[0]));
|
|
||||||
}
|
|
||||||
if (this.colStarted) {
|
|
||||||
//end collection
|
|
||||||
this.out.write(']');
|
|
||||||
}
|
|
||||||
logger.debug('Action ended');
|
|
||||||
this.out.end();
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
init: {
|
|
||||||
value: function() {
|
|
||||||
this.addHeader('Content-Type', 'application/json');
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outputter in html.
|
|
||||||
*/
|
|
||||||
var HtmlOutputter = function() {
|
|
||||||
Outputter.call(this);
|
|
||||||
logger.debug('HTML');
|
|
||||||
// TODO use templates
|
|
||||||
this.buffer = new Array();
|
|
||||||
this.colStarted = false;
|
|
||||||
};
|
|
||||||
// inherits Outputter
|
|
||||||
HtmlOutputter.prototype = Object.create(Outputter.prototype, {
|
|
||||||
output: {
|
|
||||||
value: function(resource) {
|
|
||||||
var self = this;
|
|
||||||
logger.debug('colStarted: '+this.colStarted);
|
|
||||||
if (!this.colStarted && this.buffer.length == 0) {
|
|
||||||
this.buffer.push(resource);
|
|
||||||
this.out.write('<html><head><title>Bouquins</title></head>');
|
|
||||||
} else {
|
|
||||||
this.buffer.push(resource);
|
|
||||||
if (!this.colStarted) {
|
|
||||||
this.out.write('<h1>Data</h1><table><tr>');
|
|
||||||
this.colStarted = true;
|
|
||||||
} else
|
|
||||||
this.out.write('</tr>');
|
|
||||||
while (this.buffer.length>0) {
|
|
||||||
var r = this.buffer.shift();
|
|
||||||
Object.keys(r).forEach(function(key) {
|
|
||||||
self.out.write('<td title=\"'+key+'\">'+r[key]+'</td>');
|
|
||||||
});
|
|
||||||
if (this.buffer.length>0)
|
|
||||||
this.out.write('</tr><tr>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
end: {
|
|
||||||
value: function() {
|
|
||||||
var self = this;
|
|
||||||
if (this.buffer.length == 1) {
|
|
||||||
// single resource
|
|
||||||
this.out.write('<ul>');
|
|
||||||
var r = this.buffer[0];
|
|
||||||
Object.keys(r).forEach(function(key) {
|
|
||||||
self.out.write('<li>'+key+': '+r[key]+'</li>');
|
|
||||||
});
|
|
||||||
this.out.write('</ul>');
|
|
||||||
}
|
|
||||||
if (this.colStarted) {
|
|
||||||
//end collection
|
|
||||||
this.out.write('</table>');
|
|
||||||
}
|
|
||||||
// links
|
|
||||||
var links = this.headers.Link;
|
|
||||||
if (links) {
|
|
||||||
this.out.write('<h1>Links</h1><ul>')
|
|
||||||
var re = /<([^>]*)>; rel=([^,$]*)/g;
|
|
||||||
var match;
|
|
||||||
while ((match = re.exec(links)) != null) {
|
|
||||||
this.out.write('<li><a href=\"'+match[1]+'\">'+match[2]+'</a></li>');
|
|
||||||
}
|
|
||||||
this.out.write('</ul>')
|
|
||||||
}
|
|
||||||
this.out.write('</html>');
|
|
||||||
logger.debug('Action ended');
|
|
||||||
this.out.end();
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
},
|
|
||||||
init: {
|
|
||||||
value: function() {
|
|
||||||
this.addHeader('Content-Type', 'text/html');
|
|
||||||
}, enumerable: true, configurable: true, writable: true
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Outputter in text.
|
|
||||||
*/
|
|
||||||
var TextOutputter = function() {
|
|
||||||
Outputter.call(this);
|
|
||||||
};
|
|
||||||
// inherits Outputter
|
|
||||||
TextOutputter.prototype = Object.create(Outputter.prototype, {
|
|
||||||
});
|
|
||||||
|
|
||||||
//module.exports.JSONOutputter = JSONOutputter;
|
|
||||||
module.exports = {
|
|
||||||
JSONOutputter: JSONOutputter,
|
|
||||||
TextOutputter: TextOutputter,
|
|
||||||
HtmlOutputter: HtmlOutputter
|
|
||||||
};
|
|
@ -1,158 +0,0 @@
|
|||||||
/**
|
|
||||||
* TODO license
|
|
||||||
* Router class.
|
|
||||||
*/
|
|
||||||
var util = require('util')
|
|
||||||
Endpoint = require('../endpoint/endpoint.js'),
|
|
||||||
Outputter = require('../outputter/outputter');
|
|
||||||
|
|
||||||
|
|
||||||
exports = module.exports = Router;
|
|
||||||
function Router() {
|
|
||||||
// constructor
|
|
||||||
}
|
|
||||||
Router.prototype = {
|
|
||||||
|
|
||||||
//
|
|
||||||
// FUNCTIONS
|
|
||||||
//
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build the endpoint for given path.
|
|
||||||
* @param path <string> full path
|
|
||||||
* @param callback <function(error, endpoint)> callback
|
|
||||||
*/
|
|
||||||
buildEndpoint : function (path, callback) {
|
|
||||||
logger.debug('Building endpoint for ' + path);
|
|
||||||
|
|
||||||
var endpoint = null;
|
|
||||||
var error = null;
|
|
||||||
if (!config.endpoints) {
|
|
||||||
error = new Error("no endpoints defined");
|
|
||||||
} else {
|
|
||||||
var match = PATH_RE.exec(path);
|
|
||||||
var resName = match ? match[1] : null;
|
|
||||||
logger.debug('Match resource : ' + resName);
|
|
||||||
|
|
||||||
var resModule = config.endpoints[resName];
|
|
||||||
if (resModule) {
|
|
||||||
logger.debug('Loading ' + resModule);
|
|
||||||
try {
|
|
||||||
logger.debug('../' + resModule);
|
|
||||||
endpoint = require('../' + resModule);
|
|
||||||
} catch (err) {
|
|
||||||
logger.debug('Error loading module');
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error = new Error('No module for ' + resName);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
callback(error, endpoint);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build an outputter for given mime type.
|
|
||||||
* @param mime <string> requested mime type.
|
|
||||||
* @param callback <function(err)> error callback
|
|
||||||
*/
|
|
||||||
buildOutputter : function(mime, callback) {
|
|
||||||
var mimes = mime.split(',');
|
|
||||||
var outputter = null;
|
|
||||||
for (var i=0; i<mimes.length; i++) {
|
|
||||||
switch(mimes[i]) {
|
|
||||||
case 'application/json':
|
|
||||||
return new Outputter.JSONOutputter();
|
|
||||||
case 'text/html':
|
|
||||||
return new Outputter.HtmlOutputter();
|
|
||||||
case 'text/plain':
|
|
||||||
return new Outputter.TextOutputter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.error('Usupported type: '+mime);
|
|
||||||
return new Outputter.JSONOutputter();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener 'request' event.
|
|
||||||
*/
|
|
||||||
request: function(req, resp) {
|
|
||||||
// req headers available
|
|
||||||
// pause stream until action is ready
|
|
||||||
req.pause();
|
|
||||||
|
|
||||||
logger.debug('Handling request');
|
|
||||||
|
|
||||||
// build outputter
|
|
||||||
var outputter = this.buildOutputter(req.headers.accept, function(err) {
|
|
||||||
logger.error(err);
|
|
||||||
resp.writeHead(500, 'outputter failure');
|
|
||||||
resp.write(err.message);
|
|
||||||
resp.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.debug('outputter: ' + outputter);
|
|
||||||
|
|
||||||
var url = require('url').parse(req.url, true);
|
|
||||||
// TODO sanitize url.pathname
|
|
||||||
this.buildEndpoint(url.pathname, function(err, endpoint) {
|
|
||||||
if (err) {
|
|
||||||
logger.error(err);
|
|
||||||
resp.writeHead(404, 'no endpoint for ' + url.pathname);
|
|
||||||
resp.write(err.message);
|
|
||||||
resp.end();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
endpoint.buildAction(req.method, url, function(err, action) {
|
|
||||||
//TODO err
|
|
||||||
|
|
||||||
// allow outputter to set headers
|
|
||||||
outputter.addHeader = function(name, value) {
|
|
||||||
if (resp.headersSent) {
|
|
||||||
logger.error('Header already sent, ignoring: ' + name);
|
|
||||||
} else {
|
|
||||||
resp.setHeader(name, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (action.withReqData()) {
|
|
||||||
// action needs all request data
|
|
||||||
// listen data event
|
|
||||||
req.on('data', action.reqData);
|
|
||||||
}
|
|
||||||
// when request data received, prepare action, send headers, exec action
|
|
||||||
req.on('end', function() {
|
|
||||||
outputter.init();
|
|
||||||
action.prepare(function(err, statusCode, headers){
|
|
||||||
//TODO err
|
|
||||||
|
|
||||||
// TODO does it keep outputter headers?
|
|
||||||
resp.writeHead(statusCode, headers);
|
|
||||||
|
|
||||||
// wire streaming event
|
|
||||||
action.on('data', function(chunk) {
|
|
||||||
outputter.output(chunk);
|
|
||||||
});
|
|
||||||
action.on('end', function() {
|
|
||||||
outputter.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
// start outputter
|
|
||||||
outputter.outputTo(headers, resp);
|
|
||||||
|
|
||||||
// start action
|
|
||||||
action.doAction();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// resume reading request stream
|
|
||||||
req.resume();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Config module.
|
|
||||||
* TODO license
|
|
||||||
*/
|
|
||||||
|
|
||||||
var config = exports;
|
|
||||||
/**
|
|
||||||
* Loads config from config file
|
|
||||||
*/
|
|
||||||
config.loadconfig=function(configfile, callback) {
|
|
||||||
require('fs').readFile(
|
|
||||||
configfile, {encoding:'utf8'},
|
|
||||||
function(err, data) {
|
|
||||||
if (err) callback(err);
|
|
||||||
try {
|
|
||||||
var config = JSON.parse(data);
|
|
||||||
callback(null, config);
|
|
||||||
} catch (err) {
|
|
||||||
callback(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* TODO license
|
|
||||||
* Basic logger.
|
|
||||||
*/
|
|
||||||
var logger = exports;
|
|
||||||
|
|
||||||
logger.debugLevel = 'debug';
|
|
||||||
logger.log = function(level, message) {
|
|
||||||
var levels = ['fatal', 'error', 'warn', 'info', 'debug'];
|
|
||||||
if (levels.indexOf(level) <= levels.indexOf(logger.debugLevel) ) {
|
|
||||||
if (typeof message !== 'string') {
|
|
||||||
message = JSON.stringify(message);
|
|
||||||
};
|
|
||||||
console.log(new Date().toISOString() + ' [' + level+'] '+message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.debug = function(message) {
|
|
||||||
logger.log('debug', message);
|
|
||||||
}
|
|
||||||
logger.info = function(message) {
|
|
||||||
logger.log('info', message);
|
|
||||||
}
|
|
||||||
logger.error = function(message) {
|
|
||||||
logger.log('error', message);
|
|
||||||
}
|
|
||||||
logger.fatal = function(message) {
|
|
||||||
logger.log('fatal', message);
|
|
||||||
}
|
|
@ -3,7 +3,10 @@
|
|||||||
"description" : "HTTP frontend for calibre",
|
"description" : "HTTP frontend for calibre",
|
||||||
"url" : "TODO",
|
"url" : "TODO",
|
||||||
"author" : "Meutel <meutel@meutel.net>",
|
"author" : "Meutel <meutel@meutel.net>",
|
||||||
"dependencies" : {},
|
"private" : true,
|
||||||
|
"dependencies" : {
|
||||||
|
"express": "3.x"
|
||||||
|
},
|
||||||
"main" : "./lib/bouquins",
|
"main" : "./lib/bouquins",
|
||||||
"bin" : { "bootstrap" : "./bin/bootstrap" },
|
"bin" : { "bootstrap" : "./bin/bootstrap" },
|
||||||
"version" : "0.1.0"
|
"version" : "0.1.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user