From e7b37f8671ef93f26150d8db441c624c4ccdca4f Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Thu, 16 May 2013 11:29:02 +0100 Subject: [PATCH 1/5] Cleaning up global statement --- core/ghost.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/ghost.js b/core/ghost.js index d227f84a0d..154f3d9c5f 100644 --- a/core/ghost.js +++ b/core/ghost.js @@ -1,11 +1,7 @@ // # Ghost Module // Defines core methods required to build the frontend -/** - * global module, - * require, - * __dirname - **/ +/*global module, require, __dirname */ (function () { "use strict"; From bb6880ea4986a7c1c95b7001063dfd1ebeea1ee9 Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Thu, 16 May 2013 12:21:13 +0100 Subject: [PATCH 2/5] closes #33 - api example Moving towards using an API which we can both expose publicly, and use internally. Due to issues with JugglingDB, this breaks updating contentHTML on edit Also, language, status, featured etc are all no long set / updated. --- app.js | 13 ++-- core/admin/assets/js/editor.js | 4 +- core/admin/controllers/index.js | 74 +++++--------------- core/shared/api.js | 77 +++++++++++++++++++++ core/shared/models/dataProvider.juggling.js | 12 +++- core/shared/models/schema.js | 24 ++++--- package.json | 3 +- 7 files changed, 129 insertions(+), 78 deletions(-) create mode 100644 core/shared/api.js diff --git a/app.js b/app.js index b8663819c9..96bf00053d 100644 --- a/app.js +++ b/app.js @@ -6,9 +6,9 @@ // Module dependencies. var express = require('express'), - fs = require('fs'), admin = require('./core/admin/controllers'), frontend = require('./core/frontend/controllers'), + api = require('./core/shared/api'), flash = require('connect-flash'), Ghost = require('./core/ghost'), I18n = require('./core/lang/i18n'), @@ -46,11 +46,13 @@ /** * API routes.. - * @todo convert these into a RESTful, public, authenticated API! + * @todo auth should be public auth not user auth */ - ghost.app().post('/api/v0.1/posts/create', auth, admin.posts.create); - ghost.app().post('/api/v0.1/posts/edit', auth, admin.posts.edit); - ghost.app().get('/api/v0.1/posts', auth, admin.posts.index); + ghost.app().get('/api/v0.1/posts', auth, api.requestHandler(api.posts.browse)); + ghost.app().get('/api/v0.1/posts/:id', auth, api.requestHandler(api.posts.read)); + 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)); /** * Admin routes.. @@ -75,6 +77,5 @@ ghost.app().listen(3333, function () { console.log("Express server listening on port " + 3333); - console.log('process: ', process.env); }); }()); \ No newline at end of file diff --git a/core/admin/assets/js/editor.js b/core/admin/assets/js/editor.js index 1a56d386e4..31b1ab13e0 100644 --- a/core/admin/assets/js/editor.js +++ b/core/admin/assets/js/editor.js @@ -56,7 +56,7 @@ function save() { var entry = { title: document.getElementById('entry-title').value, - markdown: editor.getValue() + content: editor.getValue() }, urlSegments = window.location.pathname.split('/'); @@ -64,7 +64,7 @@ entry.id = urlSegments[3]; $.ajax({ url: '/api/v0.1/posts/edit', - method: 'POST', + method: 'PUT', data: entry, success: function (data) { console.log('response', data); diff --git a/core/admin/controllers/index.js b/core/admin/controllers/index.js index c5ad0705c7..8db690013d 100644 --- a/core/admin/controllers/index.js +++ b/core/admin/controllers/index.js @@ -1,11 +1,12 @@ +/*global require, module */ (function () { "use strict"; var Ghost = require('../../ghost'), _ = require('underscore'), fs = require('fs'), - Showdown = require('showdown'), - converter = new Showdown.converter(), + when = require('when/node/function'), + api = require('../../shared/api'), ghost = new Ghost(), adminNavbar, @@ -60,14 +61,15 @@ }, 'editor': function (req, res) { if (req.params.id !== undefined) { - ghost.dataProvider().posts.findOne({'id': parseInt(req.params.id, 10)}, function (error, post) { - res.render('editor', { - bodyClass: 'editor', - adminNav: setSelected(adminNavbar, 'blog'), - title: post.title, - content: post.content + api.posts.read(parseInt(req.params.id, 10)) + .then(function (post) { + res.render('editor', { + bodyClass: 'editor', + adminNav: setSelected(adminNavbar, 'blog'), + title: post.title, + content: post.content + }); }); - }); } else { res.render('editor', { bodyClass: 'editor', @@ -76,13 +78,14 @@ } }, 'blog': function (req, res) { - ghost.dataProvider().posts.findAll(function (error, posts) { - res.render('blog', { - bodyClass: 'manage', - adminNav: setSelected(adminNavbar, 'blog'), - posts: posts + api.posts.browse() + .then(function (posts) { + res.render('blog', { + bodyClass: 'manage', + adminNav: setSelected(adminNavbar, 'blog'), + posts: posts + }); }); - }); }, 'settings': function (req, res) { res.render('settings', { @@ -119,47 +122,6 @@ res.redirect('/ghost/debug'); }); } - }, - 'posts': { - 'index': function (req, res) { - - }, - 'create': function (req, res) { - var entry = { - title: req.body.title, - content: req.body.markdown, - contentHtml: '', - language: ghost.config().defaultLang, - status: ghost.statuses().draft, - featured: false - }; - - entry.contentHtml = converter.makeHtml(entry.content); - - ghost.dataProvider().posts.add(entry, function (error, post) { - if (!error) { - console.log('added', post); - res.json({id: post.id}); - } else { - res.json(400, {error: post.errors}); - } - }); - }, - 'edit': function (req, res) { - var entry = { - id: parseInt(req.body.id, 10), - title: req.body.title, - content: req.body.markdown, - contentHtml: '' - }; - - entry.contentHtml = converter.makeHtml(entry.content); - - ghost.dataProvider().posts.edit(entry, function (error, post) { - console.log('edited', post); - res.json({id: parseInt(post.id, 10)}); - }); - } } }; diff --git a/core/shared/api.js b/core/shared/api.js new file mode 100644 index 0000000000..5cc766cad5 --- /dev/null +++ b/core/shared/api.js @@ -0,0 +1,77 @@ +// # Ghost Data API +// Provides access to the data model + +/** + * This is intended to replace the old dataProvider files and should access & manipulate the models directly + */ + +/*global module, require */ +(function () { + "use strict"; + + var Ghost = require('../ghost'), + when = require('when/node/function'), + _ = require('underscore'), + + ghost = new Ghost(), + posts, + users, + requestHandler; + + // # Posts + posts = { + // takes filter / pagination parameters + // returns a list of posts in a json response + browse: function (options) { + return when.call(ghost.dataProvider().posts.findAll); + }, + // takes an identifier (id or slug?) + // returns a single post in a json response + read: function (id) { + return when.call(ghost.dataProvider().posts.findOne, {id: id}); + }, + // takes a json object with all the properties which should be updated + // returns the resulting post in a json response + edit: function (postData) { + console.log('edit data', postData); + return when.call(ghost.dataProvider().posts.edit, postData); + }, + // takes a json object representing a post, + // returns the resulting post in a json response + add: function (postData) { + console.log('data', postData); + return when.call(ghost.dataProvider().posts.add, postData); + }, + // takes an identifier (id or slug?) + // returns a json response with the id of the deleted post + destroy: function (id) { + return when.call(ghost.dataProvider().posts.destroy, id); + } + }; + + // # Users + users = {}; +// settings: {}, +// categories: {}, +// post_categories: {} + + + // requestHandler + // decorator for api functions which are called via an HTTP request + // takes the API method and wraps it so that it gets data from the request and returns a sensible JSON response + requestHandler = function (apiMethod) { + return function (req, res) { + var options = _.extend(req.body, req.params); + return apiMethod(options).then(function (result) { + res.json(result); + }, function (error) { + res.json(400, {error: error}); + }); + }; + }; + + + module.exports.posts = posts; + module.exports.users = users; + module.exports.requestHandler = requestHandler; +}()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.juggling.js b/core/shared/models/dataProvider.juggling.js index e5a147c5da..320eb821bc 100644 --- a/core/shared/models/dataProvider.juggling.js +++ b/core/shared/models/dataProvider.juggling.js @@ -101,6 +101,7 @@ /** * Naive find one where args match + * @param args * @param callback */ DataProvider.prototype.posts.findOne = function (args, callback) { @@ -109,7 +110,7 @@ /** * Naive add - * @param post + * @param _post * @param callback */ DataProvider.prototype.posts.add = function (_post, callback) { @@ -122,7 +123,7 @@ /** * Naive edit - * @param post + * @param _post * @param callback */ DataProvider.prototype.posts.edit = function (_post, callback) { @@ -133,6 +134,13 @@ }); }; + + DataProvider.prototype.posts.destroy = function (_identifier, callback) { + schema.models.Post.findOne({where: {id: _identifier}}, function (error, post) { + schema.models.Post.destroy(post.id, callback); + }); + }; + DataProvider.prototype.populateData = populateData; module.exports = DataProvider; diff --git a/core/shared/models/schema.js b/core/shared/models/schema.js index 4fe9b49b46..a8867f7d85 100644 --- a/core/shared/models/schema.js +++ b/core/shared/models/schema.js @@ -4,7 +4,7 @@ * Vastly incomplete! */ -/*globals module, require */ +/*global module, require */ (function () { "use strict"; @@ -12,6 +12,8 @@ schema = new Schema('sqlite3', { database: __dirname + '/../data/datastore.db' }), + Showdown = require('showdown'), + converter = new Showdown.converter(), Post, User, Setting; @@ -39,16 +41,15 @@ }; Post.prototype.preCreate = function (next) { - console.log('pre create 1', this); + //console.log('pre create 1', this); - if (this.createdAt === undefined) { - this.createdAt = new Date(); - } - if (this.slug === undefined) { - this.slug = this.generateSlug(); - } + this.createdAt = this.createdAt || new Date(); + this.slug = this.slug || this.generateSlug(); +// this.language = this.language || ghost.config().defaultLang; +// this.status = this.status || ghost.statuses().draft + this.featured = false; - console.log('pre create 2', this); + // console.log('pre create 2', this); next(); }; @@ -59,11 +60,12 @@ //Post.validatesUniquenessOf('slug'); //Post.validatesLengthOf('language', {min: 2, max: 5}, "The language code should be between 2 and 5 chars long, E.g. 'en' or 'en_GB' "); - - Post.beforeSave = function (next, data) { + // doesn't get run on update + Post.beforeSave = Post.beforeUpdate = function (next, data) { console.log('before s1', data); // set updated data.updatedAt = new Date(); + data.contentHtml = converter.makeHtml(data.content); next(); }; diff --git a/package.json b/package.json index dfbc2bdea3..04a16aa511 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,9 @@ "moment": "*", "underscore": "*", "showdown": "*", + "when": "*", "sqlite3": "2.1.7", - "jugglingdb": "0.2.x", + "jugglingdb": "0.2.0-29", "jugglingdb-sqlite3": "git+https://github.com/jugglingdb/sqlite3-adapter.git#master" }, "devDependencies": { From 58926d1ce4e97b2eb3b2ff6254efaae2759b35bd Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Thu, 16 May 2013 21:56:26 +0100 Subject: [PATCH 3/5] Updating controllers to use the api + some minor changes to the api calls --- core/admin/controllers/index.js | 8 ++++---- core/frontend/controllers/index.js | 30 ++++++------------------------ core/shared/api.js | 6 +++--- 3 files changed, 13 insertions(+), 31 deletions(-) diff --git a/core/admin/controllers/index.js b/core/admin/controllers/index.js index 8db690013d..c8b9decd43 100644 --- a/core/admin/controllers/index.js +++ b/core/admin/controllers/index.js @@ -61,13 +61,13 @@ }, 'editor': function (req, res) { if (req.params.id !== undefined) { - api.posts.read(parseInt(req.params.id, 10)) + api.posts.read({id: parseInt(req.params.id, 10)}) .then(function (post) { res.render('editor', { bodyClass: 'editor', adminNav: setSelected(adminNavbar, 'blog'), - title: post.title, - content: post.content + title: post.get('title'), + content: post.get('content') }); }); } else { @@ -83,7 +83,7 @@ res.render('blog', { bodyClass: 'manage', adminNav: setSelected(adminNavbar, 'blog'), - posts: posts + posts: posts.toJSON() }); }); }, diff --git a/core/frontend/controllers/index.js b/core/frontend/controllers/index.js index cbe4581e42..cf42783778 100644 --- a/core/frontend/controllers/index.js +++ b/core/frontend/controllers/index.js @@ -7,45 +7,27 @@ 'use strict'; var Ghost = require('../../ghost'), - _ = require('underscore'), + api = require('../../shared/api'), ghost = new Ghost(), frontendControllers; frontendControllers = { 'homepage': function (req, res) { - var featureCount = 0, - postCount = 0, - data; - - ghost.dataProvider().posts.findAll(function (error, posts) { - data = _.groupBy(posts, function (post) { - var group = null; - if (post.featured === true && featureCount < ghost.config().homepage.features) { - featureCount += 1; - group = 'features'; - } else if (postCount < ghost.config().homepage.posts) { - postCount += 1; - group = 'posts'; - } - - return group; - }); - - ghost.doFilter('prepostsRender', data.posts, function (posts) { - res.render('index', {features: data.features, posts: posts, ghostGlobals: ghost.globals()}); + api.posts.browse().then(function (posts) { + ghost.doFilter('prePostsRender', posts.toJSON(), function (posts) { + res.render('index', {posts: posts, ghostGlobals: ghost.globals()}); }); }); }, 'single': function (req, res) { - ghost.dataProvider().posts.findOne({'slug': req.params.slug}, function (error, post) { - ghost.doFilter('prePostsRender', post, function (post) { + api.posts.read({'slug': req.params.slug}).then(function (post) { + ghost.doFilter('prePostsRender', post.toJSON(), function (post) { res.render('single', {post: post, ghostGlobals: ghost.globals()}); }); }); } }; - module.exports = frontendControllers; }()); \ No newline at end of file diff --git a/core/shared/api.js b/core/shared/api.js index 5cc766cad5..cfcb888626 100644 --- a/core/shared/api.js +++ b/core/shared/api.js @@ -23,12 +23,12 @@ // takes filter / pagination parameters // returns a list of posts in a json response browse: function (options) { - return when.call(ghost.dataProvider().posts.findAll); + return when.call(ghost.dataProvider().posts.findAll, options); }, // takes an identifier (id or slug?) // returns a single post in a json response - read: function (id) { - return when.call(ghost.dataProvider().posts.findOne, {id: id}); + read: function (args) { + return when.call(ghost.dataProvider().posts.findOne, args); }, // takes a json object with all the properties which should be updated // returns the resulting post in a json response From ef94f3b77833280d9d31300e862241f475c69cba Mon Sep 17 00:00:00 2001 From: Hannah Wolfe Date: Thu, 16 May 2013 22:16:09 +0100 Subject: [PATCH 4/5] closes #28 - reimplements posts with bookshelf This involves switching column names to snake_case which requires template updates in both the admin and in casper --- app.js | 22 +++-- config.js | 13 +++ core/admin/views/blog.hbs | 2 +- core/ghost.js | 9 +- core/shared/api.js | 2 - core/shared/data/fixtures/001.js | 67 +++++++++++++++ core/shared/data/migration/001.js | 75 +++++++++++++++++ core/shared/models/dataProvider.bookshelf.js | 86 ++++++++++++++++++++ core/shared/models/models.js | 86 ++++++++++++++++++++ package.json | 4 +- 10 files changed, 352 insertions(+), 14 deletions(-) create mode 100644 core/shared/data/fixtures/001.js create mode 100644 core/shared/data/migration/001.js create mode 100644 core/shared/models/dataProvider.bookshelf.js create mode 100644 core/shared/models/models.js diff --git a/app.js b/app.js index 96bf00053d..bf87e283f5 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,6 @@ // # Ghost main app file -/*global require */ +/*global require, __dirname */ (function () { "use strict"; @@ -12,14 +12,17 @@ flash = require('connect-flash'), Ghost = require('./core/ghost'), I18n = require('./core/lang/i18n'), - helpers = require('./core/frontend/helpers'), - auth, + helpers = require('./core/frontend/helpers'); + + + + var auth, // ## Variables - /** - * Create new Ghost object - * @type {Ghost} - */ + /** + * Create new Ghost object + * @type {Ghost} + */ ghost = new Ghost(); ghost.app().configure('development', function () { @@ -78,4 +81,9 @@ ghost.app().listen(3333, function () { console.log("Express server listening on port " + 3333); }); +// }, function (e) { +// console.log(e.toString()); +// }).then(null, function (e) { +// console.log(e.stack); +// }); }()); \ No newline at end of file diff --git a/config.js b/config.js index 28875e22f6..1362072b76 100644 --- a/config.js +++ b/config.js @@ -55,6 +55,19 @@ */ config.homepage.posts = 4; + config.database = { + development: { + client: 'sqlite3', + connection: { + filename: './core/shared/data/testdb.db' + } + }, + + staging: {}, + + production: {} + }; + /** * @property {Object} exports */ diff --git a/core/admin/views/blog.hbs b/core/admin/views/blog.hbs index 39c36da632..f0aa9bbe0e 100644 --- a/core/admin/views/blog.hbs +++ b/core/admin/views/blog.hbs @@ -18,7 +18,7 @@
    {{#each posts}} {{! #if featured class="featured"{{/if}} -
  1. +