diff --git a/todo-angular-koa/app.js b/todo-angular-koa/app.js index acb4cbf..d3f1f4c 100644 --- a/todo-angular-koa/app.js +++ b/todo-angular-koa/app.js @@ -3,8 +3,9 @@ var koa = require('koa'); // Middleware and helpers var serve = require('koa-static'); var parse = require('co-body'); -var router = require('koa-router'); -var http = require('http'); +var Router = require('koa-router'); +var logger = require('koa-logger'); +var co = require('co'); // Import rethinkdb var r = require('rethinkdb'); @@ -14,147 +15,103 @@ var config = require(__dirname+"/config.js"); var app = koa(); +// +// MIDDLEWARE +// + // Static content app.use(serve(__dirname+'/public')); -// Create a RethinkDB connection -app.use(createConnection); - -app.use(router(app)); -app.get('/todo/get', get); -app.put('/todo/new', create); -app.post('/todo/update', update); -app.post('/todo/delete', del); - -// Close the RethinkDB connection -app.use(closeConnection); - -/* - * Create a RethinkDB connection, and save it in req._rdbConn - */ -function* createConnection(next) { - try{ - var conn = yield r.connect(config.rethinkdb); - this._rdbConn = conn; - } - catch(err) { - this.status = 500; - this.body = err.message || http.STATUS_CODES[this.status]; +// Log requests +app.use(logger()); + +// Parse application/json bodies into this.request.body +app.use(function* (next) { + this.request.body = yield parse.json(this); + yield next; +}); + +// Create a RethinkDB connection, save it in the koa context, and ensure it closes +app.use(function* wrapConnection(next) { + this.state.conn = yield r.connect(config.rethinkdb); + // No matter what happens downstream, always close the connection + try { + yield next; + } finally { + this.state.conn.close(); } - yield next; -} +}); + +// +// ROUTES +// + +var router = Router(); // Retrieve all todos -function* get(next) { - try{ - var cursor = yield r.table('todos').orderBy({index: "createdAt"}).run(this._rdbConn); - var result = yield cursor.toArray(); - this.body = JSON.stringify(result); - } - catch(e) { - this.status = 500; - this.body = e.message || http.STATUS_CODES[this.status]; - } - yield next; -} +router.get('/todo/get', function* get(next) { + var cursor = yield r.table('todos').orderBy({index: "createdAt"}).run(this.state.conn); + var result = yield cursor.toArray(); + this.body = result; +}) // Create a new todo -function* create(next) { - try{ - var todo = yield parse(this); - todo.createdAt = r.now(); // Set the field `createdAt` to the current time - var result = yield r.table('todos').insert(todo, {returnChanges: true}).run(this._rdbConn); - - todo = result.changes[0].new_val; // todo now contains the previous todo + a field `id` and `createdAt` - this.body = JSON.stringify(todo); - } - catch(e) { - this.status = 500; - this.body = e.message || http.STATUS_CODES[this.status]; - } - yield next; -} +router.put('/todo/new', function* create(next) { + var todo = this.request.body; + todo.createdAt = r.now(); // Set the field `createdAt` to the current time + var result = yield r.table('todos').insert(todo, {returnChanges: true}).run(this.state.conn); + + todo = result.changes[0].new_val; // todo now contains the previous todo + a field `id` and `createdAt` + this.body = todo; +}); // Update a todo -function* update(next) { - try{ - var todo = yield parse(this); - delete todo._saving; - if ((todo == null) || (todo.id == null)) { - throw new Error("The todo must have a field `id`."); - } - - var result = yield r.table('todos').get(todo.id).update(todo, {returnChanges: true}).run(this._rdbConn); - this.body = JSON.stringify(result.changes[0].new_val); - } - catch(e) { - this.status = 500; - this.body = e.message || http.STATUS_CODES[this.status]; - } - yield next; -} +router.post('/todo/update', function* update(next) { + var todo = this.request.body; + this.assert(todo && todo.id, 400, 'todo must have field `id`'); + delete todo._saving; + var result = yield r.table('todos').get(todo.id).update(todo, {returnChanges: true}).run(this.state.conn); + this.body = result.changes[0].new_val; +}); // Delete a todo -function* del(next) { - try{ - var todo = yield parse(this); - if ((todo == null) || (todo.id == null)) { - throw new Error("The todo must have a field `id`."); - } - var result = yield r.table('todos').get(todo.id).delete().run(this._rdbConn); - this.body = ""; - } - catch(e) { - this.status = 500; - this.body = e.message || http.STATUS_CODES[this.status]; - } - yield next; -} - -/* - * Close the RethinkDB connection - */ -function* closeConnection(next) { - this._rdbConn.close(); -} - -r.connect(config.rethinkdb, function(err, conn) { - if (err) { - console.log("Could not open a connection to initialize the database"); - console.log(err.message); - process.exit(1); - } +router.post('/todo/delete', function* del(next) { + var todo = this.request.body; + this.assert(todo && todo.id, 400, 'todo must have field `id`'); + var result = yield r.table('todos').get(todo.id).delete().run(this.state.conn); + this.body = ''; +}); - r.table('todos').indexWait('createdAt').run(conn).then(function(err, result) { +// Mount our router +app.use(router.routes()); + +// Setup database and launch the koa server +var conn; +co(function* () { + try { + conn = yield r.connect(config.rethinkdb); + } catch(err) { + console.error("Could not open a connection to initialize the database"); + throw err; + } + try { + yield r.table('todos').indexWait('createdAt').run(conn); console.log("Table and index are available, starting koa..."); - startKoa(); - }).error(function(err) { + } catch(err) { // The database/table/index was not available, create them - r.dbCreate(config.rethinkdb.db).run(conn).finally(function() { - return r.tableCreate('todos').run(conn) - }).finally(function() { - r.table('todos').indexCreate('createdAt').run(conn); - }).finally(function(result) { - r.table('todos').indexWait('createdAt').run(conn) - }).then(function(result) { - console.log("Table and index are available, starting koa..."); - startKoa(); - conn.close(); - }).error(function(err) { - if (err) { - console.log("Could not wait for the completion of the index `todos`"); - console.log(err); - process.exit(1); - } - console.log("Table and index are available, starting koa..."); - startKoa(); - conn.close(); - }); + yield r.dbCreate(config.rethinkdb.db).run(conn); + yield r.tableCreate('todos').run(conn); + yield r.table('todos').indexCreate('createdAt').run(conn); + yield r.table('todos').indexWait('createdAt').run(conn) + console.log("Table and index are available, starting koa..."); + } +}).then(function () { + conn.close(); + app.listen(config.koa.port, function() { + console.log('Listening on port', config.koa.port); }); +}).catch(function (err) { + conn.close(); + console.error(err); + process.exit(1); }); - - -function startKoa() { - app.listen(config.koa.port); - console.log('Listening on port '+config.koa.port); -} diff --git a/todo-angular-koa/package.json b/todo-angular-koa/package.json index 3843343..ca83867 100644 --- a/todo-angular-koa/package.json +++ b/todo-angular-koa/package.json @@ -1,15 +1,14 @@ { - "name": "todo" - , "version": "0.0.2" - , "private": true - , "dependencies": { - "koa": "0.6.0" - , "koa-static": "1.4.2" - , "koa-router": "3.1.4" - , "co-body": "0.0.1" - , "co": "3.0.2" - , "body-parser": "1.0.2" - , "rethinkdb": ">=2.1.1" - , "co-assert-timeout": "0.0.4" + "name": "todo", + "version": "0.0.2", + "private": true, + "dependencies": { + "co": "4.6.0", + "co-body": "4.2.0", + "koa": "1.2.0", + "koa-logger": "^1.3.0", + "koa-router": "5.3.0", + "koa-static": "1.4.2", + "rethinkdb": ">=2.1.1" } } diff --git a/todo-angular-koa/public/js/controllers/todoCtrl.js b/todo-angular-koa/public/js/controllers/todoCtrl.js index 5fa4b08..f4a04ad 100644 --- a/todo-angular-koa/public/js/controllers/todoCtrl.js +++ b/todo-angular-koa/public/js/controllers/todoCtrl.js @@ -111,7 +111,7 @@ todomvc.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, todoStora }; $scope.clearCompletedTodos = function () { - $scope.todos = todos.filter(function (val) { + $scope.todos = $scope.todos.filter(function (val) { return !val.completed; }); };