From fefd0c930c49bd39674714aba74f39dd5d74ff12 Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Sun, 19 May 2013 18:51:27 -0300 Subject: [PATCH 01/10] better logging in dev mode --- app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 4c148b908d..511c256c99 100644 --- a/app.js +++ b/app.js @@ -25,7 +25,8 @@ ghost.app().configure('development', function () { ghost.app().use(express.favicon(__dirname + '/content/images/favicon.ico')); - ghost.app().use(express.errorHandler()); + ghost.app().use(express.errorHandler({ dumpExceptions: true, showStack: true })); + ghost.app().use(express.logger('dev')); ghost.app().use(I18n.load(ghost)); ghost.app().use(express.bodyParser()); ghost.app().use(express.cookieParser('try-ghost')); From b808f73effc848c49451a61b93bd453ea81bfb19 Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Sun, 19 May 2013 18:52:12 -0300 Subject: [PATCH 02/10] First steps towards saving settings --- core/admin/assets/js/settings.js | 17 +++++++++++++++++ core/admin/views/settings.hbs | 4 ++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/admin/assets/js/settings.js b/core/admin/assets/js/settings.js index d28d068ed0..98fec36207 100644 --- a/core/admin/assets/js/settings.js +++ b/core/admin/assets/js/settings.js @@ -30,6 +30,23 @@ $('input').iCheck({ checkboxClass: 'icheckbox_square-grey' }); + + $('.button-save').click(function(e){ + e.preventDefault(); + console.log(getSettings()); + }) }); + var defaultSettings = { + title: 'My Blog', + description: '' + }; + + function getSettings () { + return $.extend(defaultSettings, { + title : $('#blog-title').val(), + description : $('#blog-description').val() + }); + } + }(jQuery)); \ No newline at end of file diff --git a/core/admin/views/settings.hbs b/core/admin/views/settings.hbs index b8ad64409f..3124d827d5 100644 --- a/core/admin/views/settings.hbs +++ b/core/admin/views/settings.hbs @@ -128,13 +128,13 @@ From 4860cccef94152210d6cd6ebd2e269d23abbba49 Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Sun, 19 May 2013 18:52:53 -0300 Subject: [PATCH 03/10] Define Settings models --- core/shared/models/models.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/core/shared/models/models.js b/core/shared/models/models.js index 6592ce54c8..56dcabd16d 100644 --- a/core/shared/models/models.js +++ b/core/shared/models/models.js @@ -12,7 +12,8 @@ Post, Posts, User, - Setting; + Setting, + Settings; Post = Bookshelf.Model.extend({ @@ -68,15 +69,19 @@ }); Setting = Bookshelf.Model.extend({ + tableName: 'settings', + hasTimestamps: true + }); - tableName: 'settings' - + Settings = Bookshelf.Collection.extend({ + model: Setting }); module.exports = { Post: Post, Posts: Posts, User: User, - Setting: Setting + Setting: Setting, + Settings: Settings }; }()); \ No newline at end of file From 6f12870e708eda5fcae9cb4cff70ca989c6239c2 Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Mon, 20 May 2013 01:22:00 -0300 Subject: [PATCH 04/10] Add task for API tests --- Gruntfile.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0e42681150..827b78654b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -22,7 +22,8 @@ // Unit test all the things! nodeunit: { - all: ['core/test/ghost/**/test-*.js'] + all: ['core/test/ghost/**/test-*.js'], + api: ['core/test/ghost/test-api.js'] }, // Compile all the SASS! @@ -45,6 +46,9 @@ // TODO: Git submodule init/update (https://github.com/jaubourg/grunt-update-submodules)? grunt.registerTask("init", ["compass:admin"]); + // Run API tests only + grunt.registerTask("test-api", ["nodeunit:api"]); + // Run tests and lint code grunt.registerTask("validate", ["jslint", "nodeunit:all"]); }; From a49565c6c2935e6478ec9f5f4a0f0846eda1f161 Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Mon, 20 May 2013 01:22:21 -0300 Subject: [PATCH 05/10] settings' API routes --- app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.js b/app.js index 511c256c99..e43a627f11 100644 --- a/app.js +++ b/app.js @@ -67,6 +67,9 @@ ghost.app().post('/api/v0.1/posts/create', auth, api.requestHandler(api.posts.add)); ghost.app().put('/api/v0.1/posts/edit', auth, api.requestHandler(api.posts.edit)); ghost.app()['delete']('/api/v0.1/posts/:id', auth, api.requestHandler(api.posts.destroy)); + ghost.app().get('/api/v0.1/settings', auth, api.requestHandler(api.settings.browse)); + ghost.app().get('/api/v0.1/settings/:key', auth, api.requestHandler(api.settings.read)); + ghost.app().put('/api/v0.1/settings/edit', auth, api.requestHandler(api.settings.edit)); /** * Admin routes.. From 245421a512acedebd952d3e8921a95c7edf68793 Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Mon, 20 May 2013 01:22:55 -0300 Subject: [PATCH 06/10] settings data provider and api --- core/shared/api.js | 21 ++++++++++++-- core/shared/models/dataProvider.bookshelf.js | 29 ++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/core/shared/api.js b/core/shared/api.js index 6c15dfe9ef..61f48dc0dc 100644 --- a/core/shared/api.js +++ b/core/shared/api.js @@ -16,6 +16,7 @@ ghost = new Ghost(), posts, users, + settings, requestHandler; // # Posts @@ -56,9 +57,22 @@ return when.call(ghost.dataProvider().users.check, postData); } }; -// settings: {}, -// categories: {}, -// post_categories: {} + + // # Settings + settings = { + browse: function (options) { + return when.call(ghost.dataProvider().settings.browse, options); + }, + read: function (key) { + return when.call(ghost.dataProvider().settings.read, key); + }, + edit: function (key, value) { + return when.call(ghost.dataProvider().settings.edit, key, value); + } + }; + + // categories: {}; + // post_categories: {}; // requestHandler @@ -78,5 +92,6 @@ module.exports.posts = posts; module.exports.users = users; + module.exports.settings = settings; module.exports.requestHandler = requestHandler; }()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.bookshelf.js b/core/shared/models/dataProvider.bookshelf.js index a42a145e2e..ef9ad56c85 100644 --- a/core/shared/models/dataProvider.bookshelf.js +++ b/core/shared/models/dataProvider.bookshelf.js @@ -9,6 +9,7 @@ var knex = require('./knex_init'), models = require('./models'), bcrypt = require('bcrypt'), + when = require("when"), DataProvider, instance; @@ -121,6 +122,34 @@ callback(null, _user); }); }); + + // ## Settings + DataProvider.prototype.settings = function () { }; + + DataProvider.prototype.settings.browse = function (_args, callback) { + models.Settings.forge(_args).fetch().then(function(settings){ + callback(null, settings); + }, callback); + }; + + DataProvider.prototype.settings.read = function (_key, callback) { + models.Settings.forge({ key: _key }).fetch().then(function(setting){ + callback(null, setting); + }, callback); + }; + + DataProvider.prototype.settings.edit = function (_key, _value, callback) { + var promise; + if (typeof _key === 'object') { + promise = when.all(_.map(settings, function (value, key) { + models.Setting.forge({ key: _key }).set('value', _value).save(); + })); + } else { + promise = models.Setting.forge({ key: _key }).set('value', _value).save(); + } + promise.then(function (settings) { + callback(null, settings); + }, callback); }; module.exports = DataProvider; From 9e77d32043578bdb45b8c365beca31def8a14b84 Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Mon, 20 May 2013 01:23:23 -0300 Subject: [PATCH 07/10] Add tests for API/settings --- config.js | 7 ++++ core/test/ghost/test-api.js | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 core/test/ghost/test-api.js diff --git a/config.js b/config.js index d6f263fdb6..5e6813f00a 100644 --- a/config.js +++ b/config.js @@ -56,6 +56,13 @@ config.homepage.posts = 4; config.database = { + testing: { + client: 'sqlite3', + connection: { + filename: './core/shared/data/tests.db' + } + }, + development: { client: 'sqlite3', connection: { diff --git a/core/test/ghost/test-api.js b/core/test/ghost/test-api.js new file mode 100644 index 0000000000..94df16fb6c --- /dev/null +++ b/core/test/ghost/test-api.js @@ -0,0 +1,66 @@ +/*global require, module */ +(function () { + "use strict"; + + // Use 'testing' Ghost config + process.env.NODE_ENV = 'testing'; + + var fs = require('fs'), + path = require('path'), + _ = require('underscore'), + assert = require('assert'), + delay = require('when/delay'), + config = require('../../../config'), + fixtures = require('../../shared/data/fixtures/001'), + api; + + function fail (err) { + process.nextTick(function(){ + assert.ifError(err); + }); + } + + module.exports = { + setUp: function (done) { + // Clear database + var dbpath = path.resolve(__dirname, '../../../', config.database.testing.connection.filename); + fs.unlink(dbpath, function(){ + // There is currently no way to tell when Ghost is loaded. api instantiates it's own `Ghost` + // which will run migrations without making the promise externally accessible + api = require('../../shared/api'); + // So we just sit for a while :/ + setTimeout(done, 3000); + }); + }, + + 'settings:browse': function (test) { + test.expect(1); + api.settings.browse().then(function (settings) { + settings = _.map(settings.toJSON(), function (item) { + return _.omit(item, 'id', 'updated_at', 'created_at') + }); + test.deepEqual(settings, fixtures.settings); + test.done(); + }).then(null, fail); + }, + + // 'settings:read': function (test) { + // api.settings.read('title', function (setting) { + // console.log(setting); + // test.done(); + // }).then(null, fail); + // }, + + // 'settings:edit': function (test) { + // test.expect(2); + // api.settings.edit('title', "Jenna O'Neil").then(function (title) { + // title = title.toJSON(); + // console.log('got title') + // test.equal(title.key, 'title'); + // test.equal(title.value, "Jenna O'Neil"); + // test.done(); + // }).then(null, fail); + // } + }; + +}()); \ No newline at end of file From 28bfa8feeb75b4f1a3a02f184a6309304460357d Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Thu, 23 May 2013 09:30:38 -0300 Subject: [PATCH 08/10] missing brace --- core/shared/models/dataProvider.bookshelf.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/shared/models/dataProvider.bookshelf.js b/core/shared/models/dataProvider.bookshelf.js index ef9ad56c85..3ec16a7d61 100644 --- a/core/shared/models/dataProvider.bookshelf.js +++ b/core/shared/models/dataProvider.bookshelf.js @@ -122,6 +122,7 @@ callback(null, _user); }); }); + }; // ## Settings DataProvider.prototype.settings = function () { }; From 1af28bc2ac7ec2fd5701d92e0e3254eda852d64f Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Thu, 23 May 2013 10:57:37 -0300 Subject: [PATCH 09/10] Working settings --- core/admin/assets/js/settings.js | 38 ++++++++++++-------- core/admin/controllers/index.js | 14 +++++--- core/admin/views/settings.hbs | 5 +-- core/shared/api.js | 8 ++--- core/shared/models/dataProvider.bookshelf.js | 21 +++++------ 5 files changed, 49 insertions(+), 37 deletions(-) diff --git a/core/admin/assets/js/settings.js b/core/admin/assets/js/settings.js index 98fec36207..2152915afa 100644 --- a/core/admin/assets/js/settings.js +++ b/core/admin/assets/js/settings.js @@ -13,6 +13,18 @@ $('.settings-content').fadeOut().delay(250); $(newPage).fadeIn(); + }, + + defaultSettings = { + title: 'My Blog', + description: '' + }, + + getSettings = function () { + return $.extend(defaultSettings, { + title : $('#blog-title').val(), + description : $('#blog-description').val() + }); }; $(document).ready(function () { @@ -31,22 +43,18 @@ checkboxClass: 'icheckbox_square-grey' }); - $('.button-save').click(function(e){ + $('.button-save').click(function (e) { e.preventDefault(); - console.log(getSettings()); - }) + var data = getSettings(); + $.ajax({ + method: 'PUT', + url: '/api/v0.1/settings/edit', + data: data, + success: function (res, xhr, c) { + console.log(xhr, c); + } + }); + }); }); - var defaultSettings = { - title: 'My Blog', - description: '' - }; - - function getSettings () { - return $.extend(defaultSettings, { - title : $('#blog-title').val(), - description : $('#blog-description').val() - }); - } - }(jQuery)); \ No newline at end of file diff --git a/core/admin/controllers/index.js b/core/admin/controllers/index.js index 9f8aec81b9..9182951679 100644 --- a/core/admin/controllers/index.js +++ b/core/admin/controllers/index.js @@ -131,10 +131,16 @@ }); }, 'settings': function (req, res) { - res.render('settings', { - bodyClass: 'settings', - adminNav: setSelected(adminNavbar, 'settings') - }); + api.settings.browse() + .then(function (settings) { + settings = settings.toJSON(); + settings = _.object(_.pluck(settings, 'key'), _.pluck(settings, 'value')); + res.render('settings', { + bodyClass: 'settings', + adminNav: setSelected(adminNavbar, 'settings'), + settings: settings + }); + }); }, 'debug': { /* ugly temporary stuff for managing the app before it's properly finished */ index: function (req, res) { diff --git a/core/admin/views/settings.hbs b/core/admin/views/settings.hbs index 3124d827d5..8ecdbf52b3 100644 --- a/core/admin/views/settings.hbs +++ b/core/admin/views/settings.hbs @@ -27,11 +27,12 @@
+ {{#with settings}}
@@ -81,7 +82,7 @@
- + {{/with}}
diff --git a/core/shared/api.js b/core/shared/api.js index 61f48dc0dc..db32e6f835 100644 --- a/core/shared/api.js +++ b/core/shared/api.js @@ -63,11 +63,11 @@ browse: function (options) { return when.call(ghost.dataProvider().settings.browse, options); }, - read: function (key) { - return when.call(ghost.dataProvider().settings.read, key); + read: function (options) { + return when.call(ghost.dataProvider().settings.read, options.key); }, - edit: function (key, value) { - return when.call(ghost.dataProvider().settings.edit, key, value); + edit: function (options) { + return when.call(ghost.dataProvider().settings.edit, options); } }; diff --git a/core/shared/models/dataProvider.bookshelf.js b/core/shared/models/dataProvider.bookshelf.js index 3ec16a7d61..5abc72d18e 100644 --- a/core/shared/models/dataProvider.bookshelf.js +++ b/core/shared/models/dataProvider.bookshelf.js @@ -10,6 +10,7 @@ models = require('./models'), bcrypt = require('bcrypt'), when = require("when"), + _ = require("underscore"), DataProvider, instance; @@ -128,27 +129,23 @@ DataProvider.prototype.settings = function () { }; DataProvider.prototype.settings.browse = function (_args, callback) { - models.Settings.forge(_args).fetch().then(function(settings){ + models.Settings.forge(_args).fetch().then(function (settings) { callback(null, settings); }, callback); }; DataProvider.prototype.settings.read = function (_key, callback) { - models.Settings.forge({ key: _key }).fetch().then(function(setting){ + models.Setting.forge({ key: _key }).fetch().then(function (setting) { callback(null, setting); }, callback); }; - DataProvider.prototype.settings.edit = function (_key, _value, callback) { - var promise; - if (typeof _key === 'object') { - promise = when.all(_.map(settings, function (value, key) { - models.Setting.forge({ key: _key }).set('value', _value).save(); - })); - } else { - promise = models.Setting.forge({ key: _key }).set('value', _value).save(); - } - promise.then(function (settings) { + DataProvider.prototype.settings.edit = function (_data, callback) { + when.all(_.map(_data, function (value, key) { + return models.Setting.forge({ key: key }).fetch().then(function (setting) { + return setting.set('value', value).save(); + }); + })).then(function (settings) { callback(null, settings); }, callback); }; From 76891afd08750805694555302080d749b417d91d Mon Sep 17 00:00:00 2001 From: Ricardo Tomasi Date: Thu, 23 May 2013 11:00:45 -0300 Subject: [PATCH 10/10] jslint --- core/admin/assets/js/settings.js | 20 ++++++++--------- core/test/ghost/test-api.js | 38 +++++++++++++++----------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/core/admin/assets/js/settings.js b/core/admin/assets/js/settings.js index 2152915afa..4668686f0b 100644 --- a/core/admin/assets/js/settings.js +++ b/core/admin/assets/js/settings.js @@ -15,17 +15,17 @@ }, - defaultSettings = { - title: 'My Blog', - description: '' - }, + defaultSettings = { + title: 'My Blog', + description: '' + }, - getSettings = function () { - return $.extend(defaultSettings, { - title : $('#blog-title').val(), - description : $('#blog-description').val() - }); - }; + getSettings = function () { + return $.extend(defaultSettings, { + title : $('#blog-title').val(), + description : $('#blog-description').val() + }); + }; $(document).ready(function () { if (location.hash) { diff --git a/core/test/ghost/test-api.js b/core/test/ghost/test-api.js index 94df16fb6c..0ec5225f46 100644 --- a/core/test/ghost/test-api.js +++ b/core/test/ghost/test-api.js @@ -14,8 +14,8 @@ fixtures = require('../../shared/data/fixtures/001'), api; - function fail (err) { - process.nextTick(function(){ + function fail(err) { + process.nextTick(function () { assert.ifError(err); }); } @@ -24,7 +24,7 @@ setUp: function (done) { // Clear database var dbpath = path.resolve(__dirname, '../../../', config.database.testing.connection.filename); - fs.unlink(dbpath, function(){ + fs.unlink(dbpath, function () { // There is currently no way to tell when Ghost is loaded. api instantiates it's own `Ghost` // which will run migrations without making the promise externally accessible api = require('../../shared/api'); @@ -37,30 +37,28 @@ test.expect(1); api.settings.browse().then(function (settings) { settings = _.map(settings.toJSON(), function (item) { - return _.omit(item, 'id', 'updated_at', 'created_at') + return _.omit(item, 'id', 'updated_at', 'created_at'); }); test.deepEqual(settings, fixtures.settings); test.done(); }).then(null, fail); }, - // 'settings:read': function (test) { - // api.settings.read('title', function (setting) { - // console.log(setting); - // test.done(); - // }).then(null, fail); - // }, + 'settings:read': function (test) { + api.settings.read('title', function (setting) { + test.done(); + }).then(null, fail); + }, - // 'settings:edit': function (test) { - // test.expect(2); - // api.settings.edit('title', "Jenna O'Neil").then(function (title) { - // title = title.toJSON(); - // console.log('got title') - // test.equal(title.key, 'title'); - // test.equal(title.value, "Jenna O'Neil"); - // test.done(); - // }).then(null, fail); - // } + 'settings:edit': function (test) { + test.expect(2); + api.settings.edit('title', "Jenna O'Neil").then(function (title) { + title = title.toJSON(); + test.equal(title.key, 'title'); + test.equal(title.value, "Jenna O'Neil"); + test.done(); + }).then(null, fail); + } }; }()); \ No newline at end of file