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
|
||||
*~
|
||||
*.*.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,
|
||||
"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"
|
||||
}
|
||||
|
@ -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",
|
||||
"url" : "TODO",
|
||||
"author" : "Meutel <meutel@meutel.net>",
|
||||
"dependencies" : {},
|
||||
"private" : true,
|
||||
"dependencies" : {
|
||||
"express": "3.x"
|
||||
},
|
||||
"main" : "./lib/bouquins",
|
||||
"bin" : { "bootstrap" : "./bin/bootstrap" },
|
||||
"version" : "0.1.0"
|
||||
|
Loading…
Reference in New Issue
Block a user