From 237af40f2e4c1aee982f5b988a32771347708c75 Mon Sep 17 00:00:00 2001 From: ErisDS Date: Thu, 30 May 2013 23:34:53 +0100 Subject: [PATCH] Revert "Merge branch 'fat-models' of github.com:tgriesser/Ghost into tgriesser-fat-models" This reverts commit 611e6a49efb267a12f6a1b238eeb9a4212ef65a2, reversing changes made to a89dc1d123ff1ef0a287a928c12886cec05d9cab. --- app.js | 4 +- config.js | 10 -- core/admin/assets/sass/modules/mixins.scss | 9 +- core/admin/controllers/index.js | 2 +- core/frontend/controllers/index.js | 4 +- core/frontend/helpers/ghostNav.js | 6 +- core/ghost.js | 18 ++- core/shared/api.js | 23 ++- core/shared/data/migration/001.js | 3 +- core/shared/models/base.js | 96 ------------- .../models/dataProvider.bookshelf.base.js | 74 ++++++++++ core/shared/models/dataProvider.bookshelf.js | 52 +++++++ .../models/dataProvider.bookshelf.posts.js | 91 ++++++++++++ .../models/dataProvider.bookshelf.settings.js | 37 +++++ .../models/dataProvider.bookshelf.users.js | 69 +++++++++ core/shared/models/dataProvider.json.js | 51 +++++++ core/shared/models/index.js | 26 ---- core/shared/models/knex_init.js | 11 ++ core/shared/models/models.js | 95 +++++++++++++ core/shared/models/post.js | 134 ------------------ core/shared/models/setting.js | 45 ------ core/shared/models/user.js | 72 ---------- core/test/ghost/api_posts_spec.js | 33 ++--- core/test/ghost/api_settings_spec.js | 31 ++-- core/test/ghost/api_users_spec.js | 27 ++-- core/test/ghost/dataProvider.json_spec.js | 20 +++ core/test/ghost/helpers.js | 2 +- 27 files changed, 584 insertions(+), 461 deletions(-) delete mode 100644 core/shared/models/base.js create mode 100644 core/shared/models/dataProvider.bookshelf.base.js create mode 100644 core/shared/models/dataProvider.bookshelf.js create mode 100644 core/shared/models/dataProvider.bookshelf.posts.js create mode 100644 core/shared/models/dataProvider.bookshelf.settings.js create mode 100644 core/shared/models/dataProvider.bookshelf.users.js create mode 100644 core/shared/models/dataProvider.json.js delete mode 100644 core/shared/models/index.js create mode 100644 core/shared/models/knex_init.js create mode 100644 core/shared/models/models.js delete mode 100644 core/shared/models/post.js delete mode 100644 core/shared/models/setting.js delete mode 100644 core/shared/models/user.js create mode 100644 core/test/ghost/dataProvider.json_spec.js diff --git a/app.js b/app.js index 94fe2da918..02709deebc 100755 --- a/app.js +++ b/app.js @@ -77,8 +77,8 @@ * Expose the standard locals that every external page should have available; * path, navItems and ghostGlobals */ - ghostLocals = function (req, res, next) { - ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function (navData) { + ghostLocals = function(req, res, next) { + ghost.doFilter('ghostNavItems', {path: req.path, navItems: []}, function(navData) { // Make sure we have a locals value. res.locals = res.locals || {}; diff --git a/config.js b/config.js index 49580d6bfa..7792c4a6f6 100644 --- a/config.js +++ b/config.js @@ -85,16 +85,6 @@ production: {} }; - config.globals = { - - url: 'http://localhost:3333', //'http://john.onolan.org', - - title: "John O'Nolan", - - description: "Interactive designer, public speaker, startup advisor and writer. Living in Austria, attempting world domination via keyboard." - - }; - /** * @property {Array} nav */ diff --git a/core/admin/assets/sass/modules/mixins.scss b/core/admin/assets/sass/modules/mixins.scss index 3d03a0a3a1..57019e46a9 100644 --- a/core/admin/assets/sass/modules/mixins.scss +++ b/core/admin/assets/sass/modules/mixins.scss @@ -182,11 +182,4 @@ $green: #9FBB58; text-decoration: none; } } -} - -/* ============================================================================= - Widgets - ============================================================================= */ - -$widget-base-height: 300px; -$widget-base-width: 340px; \ No newline at end of file +} \ No newline at end of file diff --git a/core/admin/controllers/index.js b/core/admin/controllers/index.js index e8c8779857..c9b888f123 100755 --- a/core/admin/controllers/index.js +++ b/core/admin/controllers/index.js @@ -43,7 +43,7 @@ } }; - ghost.doFilter('messWithAdmin', adminNavbar, function () { + ghost.doFilter('messWithAdmin', adminNavbar, function() { console.log('the dofilter hook called in /core/admin/controllers/index.js'); }); diff --git a/core/frontend/controllers/index.js b/core/frontend/controllers/index.js index 7e2def4a92..8213e550ff 100644 --- a/core/frontend/controllers/index.js +++ b/core/frontend/controllers/index.js @@ -16,14 +16,14 @@ 'homepage': function (req, res) { api.posts.browse().then(function (posts) { ghost.doFilter('prePostsRender', posts.toJSON(), function (posts) { - res.render('index', {posts: posts, ghostGlobals: ghost.globalConfig, navItems: res.locals.navItems}); + res.render('index', {posts: posts, ghostGlobals: res.locals.ghostGlobals, navItems: res.locals.navItems}); }); }); }, 'single': function (req, res) { api.posts.read({'slug': req.params.slug}).then(function (post) { ghost.doFilter('prePostsRender', post.toJSON(), function (post) { - res.render('single', {post: post, ghostGlobals: ghost.globalConfig, navItems: res.locals.navItems}); + res.render('single', {post: post, ghostGlobals: res.locals.ghostGlobals, navItems: res.locals.navItems}); }); }); } diff --git a/core/frontend/helpers/ghostNav.js b/core/frontend/helpers/ghostNav.js index 6a0ef8975c..f1ae8bd9a7 100644 --- a/core/frontend/helpers/ghostNav.js +++ b/core/frontend/helpers/ghostNav.js @@ -25,7 +25,7 @@ // Allow people to overwrite the navTemplatePath templatePath = templatePath || this.navTemplatePath; - return nodefn.call(fs.readFile, templatePath).then(function (navTemplateContents) { + return nodefn.call(fs.readFile, templatePath).then(function(navTemplateContents) { // TODO: Can handlebars compile async? self.navTemplateFunc = handlebars.compile(navTemplateContents.toString()); }); @@ -40,11 +40,11 @@ }; // A static helper method for registering with ghost - GhostNavHelper.registerWithGhost = function (ghost) { + GhostNavHelper.registerWithGhost = function(ghost) { var templatePath = path.join(ghost.paths().frontendViews, 'nav.hbs'), ghostNavHelper = new GhostNavHelper(templatePath); - return ghostNavHelper.compileTemplate().then(function () { + return ghostNavHelper.compileTemplate().then(function() { ghost.registerThemeHelper("ghostNav", ghostNavHelper.renderNavItems); }); }; diff --git a/core/ghost.js b/core/ghost.js index e0a1a95b57..6912bb7aeb 100644 --- a/core/ghost.js +++ b/core/ghost.js @@ -12,7 +12,11 @@ hbs = require('express-hbs'), _ = require('underscore'), Polyglot = require('node-polyglot'), - models = require('./shared/models'), + + JsonDataProvider = require('./shared/models/dataProvider.json'), + jsonDataProvider = new JsonDataProvider(), + BookshelfDataProvider = require('./shared/models/dataProvider.bookshelf'), + bookshelfDataProvider = new BookshelfDataProvider(), ExampleFilter = require('../content/plugins/exampleFilters'), Ghost, @@ -40,6 +44,7 @@ */ Ghost = function () { var app, + globals, plugin, polyglot; @@ -47,6 +52,11 @@ instance = this; plugin = new ExampleFilter(instance).init(); + // Temporary loading of settings + jsonDataProvider.globals.findAll(function (error, data) { + globals = data; + }); + app = express(); polyglot = new Polyglot(); @@ -58,11 +68,11 @@ _.extend(instance, { app: function () { return app; }, config: function () { return config; }, - globalConfig: config.globals, - dataProvider: models, + globals: function () { return globals; }, // there's no management here to be sure this has loaded + dataProvider: function () { return bookshelfDataProvider; }, statuses: function () { return statuses; }, polyglot: function () { return polyglot; }, - plugin: function () { return plugin; }, + plugin: function() { return plugin; }, paths: function () { return { 'activeTheme': __dirname + '/../content/' + config.themeDir + '/' + config.activeTheme + '/', diff --git a/core/shared/api.js b/core/shared/api.js index 7959822818..98f913b81e 100644 --- a/core/shared/api.js +++ b/core/shared/api.js @@ -13,7 +13,6 @@ _ = require('underscore'), ghost = new Ghost(), - dataProvider = ghost.dataProvider, posts, users, settings, @@ -24,57 +23,57 @@ // takes filter / pagination parameters // returns a list of posts in a json response browse: function (options) { - return dataProvider.Post.findAll(options); + return ghost.dataProvider().posts.findAll(options); }, // takes an identifier (id or slug?) // returns a single post in a json response read: function (args) { - return dataProvider.Post.findOne(args); + return 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 edit: function (postData) { - return dataProvider.Post.edit(postData); + return ghost.dataProvider().posts.edit(postData); }, // takes a json object representing a post, // returns the resulting post in a json response add: function (postData) { - return dataProvider.Post.add(postData); + return ghost.dataProvider().posts.add(postData); }, // takes an identifier (id or slug?) // returns a json response with the id of the deleted post destroy: function (args) { - return dataProvider.Post.destroy(args.id); + return ghost.dataProvider().posts.destroy(args.id); } }; // # Users users = { add: function (postData) { - return dataProvider.Users.add(postData); + return ghost.dataProvider().users.add(postData); }, check: function (postData) { - return dataProvider.Users.check(postData); + return ghost.dataProvider().users.check(postData); } }; // # Settings settings = { browse: function (options) { - return dataProvider.Settings.browse(options); + return ghost.dataProvider().settings.browse(options); }, read: function (options) { - return dataProvider.Settings.read(options.key); + return ghost.dataProvider().settings.read(options.key); }, edit: function (options) { - return dataProvider.Settings.edit(options); + return ghost.dataProvider().settings.edit(options); } }; // 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 diff --git a/core/shared/data/migration/001.js b/core/shared/data/migration/001.js index de0e7a14c6..86630a1575 100644 --- a/core/shared/data/migration/001.js +++ b/core/shared/data/migration/001.js @@ -3,8 +3,9 @@ (function () { "use strict"; + var when = require('when'), - knex = require('../../models/base').Knex, + knex = require('../../models/knex_init'), fixtures = require('../fixtures/001'), up, down; diff --git a/core/shared/models/base.js b/core/shared/models/base.js deleted file mode 100644 index 0133a335b1..0000000000 --- a/core/shared/models/base.js +++ /dev/null @@ -1,96 +0,0 @@ -(function () { - - "use strict"; - - var GhostBookshelf, - Bookshelf = require('bookshelf'), - config = require('../../../config'); - - // Initializes Bookshelf as its own instance, so we can modify the Models and not mess up - // others' if they're using the library outside of ghost. - GhostBookshelf = Bookshelf.Initialize('ghost', config.database[process.env.NODE_ENV || 'development']); - - // The Base Model which other Ghost objects will inherit from, - // including some convenience functions as static properties on the model. - GhostBookshelf.Model = GhostBookshelf.Model.extend({ - - // Base prototype properties will go here - - }, { - - /** - * Naive find all - * @param options (optional) - */ - findAll: function (options) { - options = options || {}; - return GhostBookshelf.Collection.forge([], {model: this}).fetch(options); - }, - - browse: function () { - return this.findAll.apply(this, arguments); - }, - - /** - * Naive find one where args match - * @param args - * @param options (optional) - */ - findOne: function (args, options) { - options = options || {}; - return this.forge(args).fetch(options); - }, - - read: function () { - return this.findOne.apply(this, arguments); - }, - - /** - * Naive edit - * @param editedObj - * @param options (optional) - */ - edit: function (editedObj, options) { - options = options || {}; - return this.forge({id: editedObj.id}).fetch(options).then(function (foundObj) { - return foundObj.set(editedObj).save(); - }); - }, - - update: function () { - return this.edit.apply(this, arguments); - }, - - /** - * Naive create - * @param editedObj - * @param options (optional) - */ - add: function (newObj, options) { - options = options || {}; - return this.forge(newObj).save(options); - }, - - create: function () { - return this.add.apply(this, arguments); - }, - - /** - * Naive destroy - * @param _identifier - * @param options (optional) - */ - destroy: function (_identifier, options) { - options = options || {}; - return this.forge({id: _identifier}).destroy(options); - }, - - 'delete': function () { - return this.destroy.apply(this, arguments); - } - - }); - - module.exports = GhostBookshelf; - -}()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.bookshelf.base.js b/core/shared/models/dataProvider.bookshelf.base.js new file mode 100644 index 0000000000..6f44c41771 --- /dev/null +++ b/core/shared/models/dataProvider.bookshelf.base.js @@ -0,0 +1,74 @@ +/*global require, module */ +(function () { + "use strict"; + + var _ = require('underscore'), + BookshelfBase; + + /** + * The base class for interacting with bookshelf models/collections. + * Provides naive implementations of CRUD/BREAD operations. + */ + BookshelfBase = function (model, collection) { + // Bind the 'this' value for all our functions since they get messed + // up by the when.call + _.bindAll(this, 'findAll', 'browse', 'findOne', 'read', 'edit', 'add', 'destroy'); + + this.model = model; + this.collection = collection; + }; + + /** + * Naive find all + * @param args (optional) + * @param opts (optional) + */ + BookshelfBase.prototype.findAll = BookshelfBase.prototype.browse = function (opts) { + opts = opts || {}; + return this.collection.forge().fetch(opts); + }; + + /** + * Naive find one where args match + * @param args + * @param opts (optional) + */ + BookshelfBase.prototype.findOne = BookshelfBase.prototype.read = function (args, opts) { + opts = opts || {}; + return this.model.forge(args).fetch(opts); + }; + + /** + * Naive edit + * @param editedObj + * @param opts (optional) + */ + BookshelfBase.prototype.edit = BookshelfBase.prototype.update = function (editedObj, opts) { + opts = opts || {}; + return this.model.forge({id: editedObj.id}).fetch(opts).then(function (foundObj) { + return foundObj.set(editedObj).save(); + }); + }; + + /** + * Naive add + * @param newObj + * @param opts (optional) + */ + BookshelfBase.prototype.add = BookshelfBase.prototype.create = function (newObj, opts) { + opts = opts || {}; + return this.model.forge(newObj).save(opts); + }; + + /** + * Naive destroy + * @param _identifier + * @param opts (optional) + */ + BookshelfBase.prototype.destroy = BookshelfBase.prototype['delete'] = function (_identifier, opts) { + opts = opts || {}; + return this.model.forge({id: _identifier}).destroy(opts); + }; + + module.exports = BookshelfBase; +}()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.bookshelf.js b/core/shared/models/dataProvider.bookshelf.js new file mode 100644 index 0000000000..8923e59dd0 --- /dev/null +++ b/core/shared/models/dataProvider.bookshelf.js @@ -0,0 +1,52 @@ +/** + * Provides access to data via the Bookshelf ORM + */ + +/*globals module, require, process */ +(function () { + "use strict"; + + var _ = require('underscore'), + knex = require('./knex_init'), + PostsProvider = require('./dataProvider.bookshelf.posts'), + UsersProvider = require('./dataProvider.bookshelf.users'), + SettingsProvider = require('./dataProvider.bookshelf.settings'), + DataProvider, + instance, + defaultOptions = { + autoInit: false + }; + + DataProvider = function (options) { + options = _.defaults(options || {}, defaultOptions); + + if (!instance) { + instance = this; + + if (options.autoInit) { + this.init(); + } + } + + return instance; + }; + + DataProvider.prototype.init = function () { + return knex.Schema.hasTable('posts').then(null, function () { + // Simple bootstraping of the data model for now. + var migration = require('../data/migration/001'); + + return migration.down().then(function () { + return migration.up(); + }); + }).then(function () { + console.log('DataProvider ready'); + }); + }; + + DataProvider.prototype.posts = new PostsProvider(); + DataProvider.prototype.users = new UsersProvider(); + DataProvider.prototype.settings = new SettingsProvider(); + + module.exports = DataProvider; +}()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.bookshelf.posts.js b/core/shared/models/dataProvider.bookshelf.posts.js new file mode 100644 index 0000000000..e7c02c7f7c --- /dev/null +++ b/core/shared/models/dataProvider.bookshelf.posts.js @@ -0,0 +1,91 @@ +(function () { + "use strict"; + + var _ = require('underscore'), + util = require('util'), + models = require('./models'), + Bookshelf = require('bookshelf'), + BaseProvider = require('./dataProvider.bookshelf.base'), + PostsProvider; + + /** + * The Posts data provider implementation for Bookshelf. + */ + PostsProvider = function () { + BaseProvider.call(this, models.Post, models.Posts); + }; + + util.inherits(PostsProvider, BaseProvider); + + /** + * Find results by page - returns an object containing the + * information about the request (page, limit), along with the + * info needed for pagination (pages, total). + * + * { + * posts: [ + * {...}, {...}, {...} + * ], + * page: __, + * limit: __, + * pages: __, + * total: __ + * } + * + * @params opts + */ + PostsProvider.prototype.findPage = function (opts) { + var postCollection; + + // Allow findPage(n) + if (!_.isObject(opts)) { + opts = {page: opts}; + } + + opts = _.defaults(opts || {}, { + page: 1, + limit: 15, + where: null + }); + postCollection = this.collection.forge(); + + // If there are where conditionals specified, add those + // to the query. + if (opts.where) { + postCollection.query('where', opts.where); + } + + // Set the limit & offset for the query, fetching + // with the opts (to specify any eager relations, etc.) + // Omitting the `page`, `limit`, `where` just to be sure + // aren't used for other purposes. + return postCollection + .query('limit', opts.limit) + .query('offset', opts.limit * (opts.page - 1)) + .fetch(_.omit(opts, 'page', 'limit', 'where')) + .then(function (collection) { + var qb; + + // After we're done, we need to figure out what + // the limits are for the pagination values. + qb = Bookshelf.Knex(_.result(collection, 'tableName')); + + if (opts.where) { + qb.where(opts.where); + } + + return qb.count(_.result(collection, 'idAttribute')).then(function (resp) { + var totalPosts = resp[0].aggregate; + return { + posts: collection.toJSON(), + page: opts.page, + limit: opts.limit, + pages: Math.ceil(totalPosts / opts.limit), + total: totalPosts + }; + }); + }); + }; + + module.exports = PostsProvider; +}()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.bookshelf.settings.js b/core/shared/models/dataProvider.bookshelf.settings.js new file mode 100644 index 0000000000..e8a6538a84 --- /dev/null +++ b/core/shared/models/dataProvider.bookshelf.settings.js @@ -0,0 +1,37 @@ +(function () { + "use strict"; + + var _ = require('underscore'), + when = require('when'), + util = require('util'), + models = require('./models'), + BaseProvider = require('./dataProvider.bookshelf.base'), + SettingsProvider; + + /** + * The Posts data provider implementation for Bookshelf. + */ + SettingsProvider = function () { + BaseProvider.call(this, models.Setting, models.Settings); + }; + + util.inherits(SettingsProvider, BaseProvider); + + SettingsProvider.prototype.read = function (_key) { + // Allow for just passing the key instead of attributes + if (_.isString(_key)) { + _key = { key: _key }; + } + return BaseProvider.prototype.read.call(this, _key); + }; + + SettingsProvider.prototype.edit = function (_data) { + return when.all(_.map(_data, function (value, key) { + return this.model.forge({ key: key }).fetch().then(function (setting) { + return setting.set('value', value).save(); + }); + }, this)); + }; + + module.exports = SettingsProvider; +}()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.bookshelf.users.js b/core/shared/models/dataProvider.bookshelf.users.js new file mode 100644 index 0000000000..eeaf9b443e --- /dev/null +++ b/core/shared/models/dataProvider.bookshelf.users.js @@ -0,0 +1,69 @@ +(function () { + "use strict"; + + var util = require('util'), + _ = require('underscore'), + bcrypt = require('bcrypt-nodejs'), + models = require('./models.js'), + when = require('when'), + nodefn = require('when/node/function'), + BaseProvider = require('./dataProvider.bookshelf.base.js'), + UsersProvider; + + /** + * The Users data provider implementation for Bookshelf. + */ + UsersProvider = function () { + BaseProvider.call(this, models.User, models.Users); + }; + + util.inherits(UsersProvider, BaseProvider); + + /** + * Naive user add + * @param _user + * + * Hashes the password provided before saving to the database. + */ + UsersProvider.prototype.add = function (_user) { + var self = this, + // Clone the _user so we don't expose the hashed password unnecessarily + userData = _.extend({}, _user); + + return self.model.forge({email_address: userData.email_address}).fetch().then(function (user) { + if (!!user.attributes.email_address) { + return when.reject(new Error('A user with that email address already exists.')); + } + + return nodefn.call(bcrypt.hash, _user.password, null, null).then(function (hash) { + userData.password = hash; + return BaseProvider.prototype.add.call(self, userData); + }); + }); + + }; + + /** + * User check + * @param _userdata + * + * Finds the user by email, and check's the password + */ + UsersProvider.prototype.check = function (_userdata) { + return this.model.forge({ + email_address: _userdata.email + }).fetch().then(function (user) { + if (!!user.attributes.email_address) { + return nodefn.call(bcrypt.compare, _userdata.pw, user.get('password')).then(function (matched) { + if (!matched) { + return when.reject(new Error('Passwords do not match')); + } + return user; + }); + } + return when.reject(new Error('We do not have a record for such user.')); + }); + }; + + module.exports = UsersProvider; +}()); \ No newline at end of file diff --git a/core/shared/models/dataProvider.json.js b/core/shared/models/dataProvider.json.js new file mode 100644 index 0000000000..993f911ffa --- /dev/null +++ b/core/shared/models/dataProvider.json.js @@ -0,0 +1,51 @@ +/** + * Dummy dataProvider returns hardcoded JSON data until we finish migrating settings data to a datastore + */ + +/*globals module, require */ +(function () { + "use strict"; + + var _ = require('underscore'), + when = require('when'), + DataProvider, + blogData, + instance, + d; + + blogData = { + url: 'http://localhost:3333', //'http://john.onolan.org', + title: "John O'Nolan", + description: "Interactive designer, public speaker, startup advisor and writer. Living in Austria, attempting world domination via keyboard." + }; + + DataProvider = function () { + if (!instance) { + instance = this; + } + return instance; + }; + DataProvider.prototype.globals = {}; + DataProvider.prototype.globals.data = []; + + + DataProvider.prototype.globals.findAll = function () { + return when(this.data); + }; + + DataProvider.prototype.globals.save = function (globals) { + _.each(globals, function (global, key) { + this.data[key] = global; + }, this); + + return when(globals); + }; + + /* Lets bootstrap with dummy data */ + d = new DataProvider(); + d.globals.save(blogData, function (error) { + if (error) { throw error; } + }); + + module.exports = DataProvider; +}()); \ No newline at end of file diff --git a/core/shared/models/index.js b/core/shared/models/index.js deleted file mode 100644 index f52150cb8e..0000000000 --- a/core/shared/models/index.js +++ /dev/null @@ -1,26 +0,0 @@ -/*global require, module */ - -(function () { - "use strict"; - - var GhostBookshelf = require('./base'), - knex = GhostBookshelf.Knex; - - module.exports = { - Post: require('./post').Post, - User: require('./user').User, - Setting: require('./setting').Setting, - init: function () { - return knex.Schema.hasTable('posts').then(null, function () { - // Simple bootstraping of the data model for now. - var migration = require('../data/migration/001'); - return migration.down().then(function () { - return migration.up(); - }); - }).then(function () { - console.log('models loaded'); - }); - } - }; - -}()); \ No newline at end of file diff --git a/core/shared/models/knex_init.js b/core/shared/models/knex_init.js new file mode 100644 index 0000000000..7a310fce4a --- /dev/null +++ b/core/shared/models/knex_init.js @@ -0,0 +1,11 @@ +/*global require, module, process */ +(function () { + "use strict"; + + var knex = require('knex'), + config = require('../../../config'); + + knex.Initialize(config.database[process.env.NODE_ENV || 'development']); + + module.exports = knex; +}()); \ No newline at end of file diff --git a/core/shared/models/models.js b/core/shared/models/models.js new file mode 100644 index 0000000000..262f61d708 --- /dev/null +++ b/core/shared/models/models.js @@ -0,0 +1,95 @@ +/*global require, module */ + +(function () { + "use strict"; + + // We should just be able to require bookshelf and have it reference + // the `Knex` instance bootstraped at the app initialization. + var Bookshelf = require('bookshelf'), + Showdown = require('showdown'), + converter = new Showdown.converter(), + + Post, + Posts, + User, + Users, + Setting, + Settings; + + Post = Bookshelf.Model.extend({ + + tableName: 'posts', + + hasTimestamps: true, + + initialize: function () { + this.on('creating', this.creating, this); + this.on('saving', this.saving, this); + }, + + saving: function () { + if (!this.get('title')) { + throw new Error('Post title cannot be blank'); + } + this.set('content_html', converter.makeHtml(this.get('content'))); + + // refactoring of ghost required in order to make these details available here + // this.set('language', this.get('language') || ghost.config().defaultLang); + // this.set('status', this.get('status') || ghost.statuses().draft); + + }, + + creating: function () { + if (!this.get('slug')) { + this.generateSlug(); + } + }, + + generateSlug: function () { + return this.set('slug', this.get('title').replace(/\:/g, '').replace(/\s/g, '-').toLowerCase()); + }, + + user: function () { + return this.belongsTo(User, 'created_by'); + } + + }); + + Posts = Bookshelf.Collection.extend({ + + model: Post + + }); + + User = Bookshelf.Model.extend({ + tableName: 'users', + hasTimestamps: true, + posts: function () { + return this.hasMany(Posts, 'created_by'); + } + }); + + Users = Bookshelf.Collection.extend({ + + model: User + + }); + + Setting = Bookshelf.Model.extend({ + tableName: 'settings', + hasTimestamps: true + }); + + Settings = Bookshelf.Collection.extend({ + model: Setting + }); + + module.exports = { + Post: Post, + Posts: Posts, + User: User, + Users: Users, + Setting: Setting, + Settings: Settings + }; +}()); \ No newline at end of file diff --git a/core/shared/models/post.js b/core/shared/models/post.js deleted file mode 100644 index 115a875792..0000000000 --- a/core/shared/models/post.js +++ /dev/null @@ -1,134 +0,0 @@ -(function () { - - "use strict"; - - var Post, - Posts, - _ = require('underscore'), - Showdown = require('showdown'), - converter = new Showdown.converter(), - User = require('./user').User, - GhostBookshelf = require('./base'); - - Post = GhostBookshelf.Model.extend({ - - tableName: 'posts', - - hasTimestamps: true, - - initialize: function () { - this.on('creating', this.creating, this); - this.on('saving', this.saving, this); - }, - - saving: function () { - if (!this.get('title')) { - throw new Error('Post title cannot be blank'); - } - this.set('content_html', converter.makeHtml(this.get('content'))); - - // refactoring of ghost required in order to make these details available here - // this.set('language', this.get('language') || ghost.config().defaultLang); - // this.set('status', this.get('status') || ghost.statuses().draft); - }, - - creating: function () { - if (!this.get('slug')) { - this.generateSlug(); - } - }, - - generateSlug: function () { - return this.set('slug', this.get('title').replace(/\:/g, '').replace(/\s/g, '-').toLowerCase()); - }, - - user: function () { - return this.belongsTo(User, 'created_by'); - } - - }, { - - /** - * Find results by page - returns an object containing the - * information about the request (page, limit), along with the - * info needed for pagination (pages, total). - * - * { - * posts: [ - * {...}, {...}, {...} - * ], - * page: __, - * limit: __, - * pages: __, - * total: __ - * } - * - * @params opts - */ - findPage: function (opts) { - var postCollection; - - // Allow findPage(n) - if (!_.isObject(opts)) { - opts = {page: opts}; - } - - opts = _.defaults(opts || {}, { - page: 1, - limit: 15, - where: null - }); - postCollection = Posts.forge(); - - // If there are where conditionals specified, add those - // to the query. - if (opts.where) { - postCollection.query('where', opts.where); - } - - // Set the limit & offset for the query, fetching - // with the opts (to specify any eager relations, etc.) - // Omitting the `page`, `limit`, `where` just to be sure - // aren't used for other purposes. - return postCollection - .query('limit', opts.limit) - .query('offset', opts.limit * (opts.page - 1)) - .fetch(_.omit(opts, 'page', 'limit', 'where')) - .then(function (collection) { - var qb; - - // After we're done, we need to figure out what - // the limits are for the pagination values. - qb = GhostBookshelf.Knex(_.result(collection, 'tableName')); - - if (opts.where) { - qb.where(opts.where); - } - - return qb.count(_.result(collection, 'idAttribute')).then(function (resp) { - var totalPosts = resp[0].aggregate; - return { - posts: collection.toJSON(), - page: opts.page, - limit: opts.limit, - pages: Math.ceil(totalPosts / opts.limit), - total: totalPosts - }; - }); - }); - } - - }); - - Posts = GhostBookshelf.Collection.extend({ - - model: Post - - }); - - module.exports = { - Post: Post, - Posts: Posts - }; - -}()); \ No newline at end of file diff --git a/core/shared/models/setting.js b/core/shared/models/setting.js deleted file mode 100644 index e27f0612b4..0000000000 --- a/core/shared/models/setting.js +++ /dev/null @@ -1,45 +0,0 @@ -(function () { - "use strict"; - - var Setting, - Settings, - GhostBookshelf = require('./base'), - _ = require('underscore'), - when = require('when'); - - Setting = GhostBookshelf.Model.extend({ - - tableName: 'settings', - - hasTimestamps: true - - }, { - - read: function (_key) { - // Allow for just passing the key instead of attributes - if (!_.isObject(_key)) { - _key = { key: _key }; - } - return GhostBookshelf.Model.read.call(this, _key); - }, - - edit: function (_data) { - return when.all(_.map(_data, function (value, key) { - return this.forge({ key: key }).fetch().then(function (setting) { - return setting.set('value', value).save(); - }); - }, this)); - } - - }); - - Settings = GhostBookshelf.Collection.extend({ - model: Setting - }); - - module.exports = { - Setting: Setting, - Settings: Settings - }; - -}()); \ No newline at end of file diff --git a/core/shared/models/user.js b/core/shared/models/user.js deleted file mode 100644 index b05bad283b..0000000000 --- a/core/shared/models/user.js +++ /dev/null @@ -1,72 +0,0 @@ -(function () { - "use strict"; - - var User, - Users, - _ = require('underscore'), - when = require('when'), - nodefn = require('when/node/function'), - bcrypt = require('bcrypt-nodejs'), - Posts = require('./post').Posts, - GhostBookshelf = require('./base'); - - User = GhostBookshelf.Model.extend({ - - tableName: 'users', - - hasTimestamps: true, - - posts: function () { - return this.hasMany(Posts, 'created_by'); - } - - }, { - - /** - * Naive user add - * @param _user - * - * Hashes the password provided before saving to the database. - */ - add: function (_user) { - var User = this, - // Clone the _user so we don't expose the hashed password unnecessarily - userData = _.extend({}, _user); - - return nodefn.call(bcrypt.hash, _user.password, null, null).then(function (hash) { - userData.password = hash; - return GhostBookshelf.Model.add.call(User, userData); - }); - }, - - /** - * User check - * @param _userdata - * - * Finds the user by email, and check's the password - */ - check: function (_userdata) { - return this.model.forge({ - email_address: _userdata.email - }).fetch().then(function (user) { - return nodefn.call(bcrypt.compare, _userdata.pw, user.get('password')).then(function (matched) { - if (!matched) { - return when.reject(new Error('Password does not match')); - } - return user; - }); - }); - } - - }); - - Users = GhostBookshelf.Collection.extend({ - model: User - }); - - module.exports = { - User: User, - Users: Users - }; - -}()); \ No newline at end of file diff --git a/core/test/ghost/api_posts_spec.js b/core/test/ghost/api_posts_spec.js index 95524025f1..36f9084a30 100644 --- a/core/test/ghost/api_posts_spec.js +++ b/core/test/ghost/api_posts_spec.js @@ -6,20 +6,21 @@ var _ = require("underscore"), should = require('should'), helpers = require('./helpers'), - Models = require('../../shared/models'); + PostProvider = require('../../shared/models/dataProvider.bookshelf.posts'); - describe('Bookshelf Post Model', function () { + describe('Bookshelf PostsProvider', function () { - var PostModel = Models.Post; + var posts; beforeEach(function (done) { helpers.resetData().then(function () { + posts = new PostProvider(); done(); }, done); }); it('can browse', function (done) { - PostModel.browse().then(function (results) { + posts.browse().then(function (results) { should.exist(results); results.length.should.equal(2); @@ -31,14 +32,14 @@ it('can read', function (done) { var firstPost; - PostModel.browse().then(function (results) { + posts.browse().then(function (results) { should.exist(results); results.length.should.be.above(0); firstPost = results.models[0]; - return PostModel.read({slug: firstPost.attributes.slug}); + return posts.read({slug: firstPost.attributes.slug}); }).then(function (found) { should.exist(found); @@ -51,7 +52,7 @@ it('can edit', function (done) { var firstPost; - PostModel.browse().then(function (results) { + posts.browse().then(function (results) { should.exist(results); @@ -59,7 +60,7 @@ firstPost = results.models[0]; - return PostModel.edit({id: firstPost.id, title: "new title"}); + return posts.edit({id: firstPost.id, title: "new title"}); }).then(function (edited) { @@ -78,7 +79,7 @@ content: 'Test Content 1' }; - PostModel.add(newPost).then(function (createdPost) { + posts.add(newPost).then(function (createdPost) { should.exist(createdPost); createdPost.attributes.title.should.equal(newPost.title, "title is correct"); @@ -91,7 +92,7 @@ it('can delete', function (done) { var firstPostId; - PostModel.browse().then(function (results) { + posts.browse().then(function (results) { should.exist(results); @@ -99,11 +100,11 @@ firstPostId = results.models[0].id; - return PostModel.destroy(firstPostId); + return posts.destroy(firstPostId); }).then(function () { - return PostModel.browse(); + return posts.browse(); }).then(function (newResults) { var ids, hasDeletedId; @@ -125,7 +126,7 @@ helpers.insertMorePosts().then(function () { - return PostModel.findPage({page: 2}); + return posts.findPage({page: 2}); }).then(function (paginationResult) { @@ -137,7 +138,7 @@ paginationResult.pages.should.equal(4); - return PostModel.findPage({page: 5}); + return posts.findPage({page: 5}); }).then(function (paginationResult) { @@ -149,7 +150,7 @@ paginationResult.pages.should.equal(4); - return PostModel.findPage({limit: 30}); + return posts.findPage({limit: 30}); }).then(function (paginationResult) { @@ -161,7 +162,7 @@ paginationResult.pages.should.equal(2); - return PostModel.findPage({limit: 10, page: 2, where: {language: 'fr'}}); + return posts.findPage({limit: 10, page: 2, where: {language: 'fr'}}); }).then(function (paginationResult) { diff --git a/core/test/ghost/api_settings_spec.js b/core/test/ghost/api_settings_spec.js index 8f6de03be1..5bb9fd5e49 100644 --- a/core/test/ghost/api_settings_spec.js +++ b/core/test/ghost/api_settings_spec.js @@ -6,20 +6,21 @@ var _ = require("underscore"), should = require('should'), helpers = require('./helpers'), - Models = require('../../shared/models'); + SettingProvider = require('../../shared/models/dataProvider.bookshelf.settings'); - describe('Bookshelf Setting Model', function () { + describe('Bookshelf SettingsProvider', function () { - var SettingModel = Models.Setting; + var settings; beforeEach(function (done) { helpers.resetData().then(function () { + settings = new SettingProvider(); done(); - }, done); + }); }); it('can browse', function (done) { - SettingModel.browse().then(function (results) { + settings.browse().then(function (results) { should.exist(results); @@ -32,7 +33,7 @@ it('can read', function (done) { var firstSetting; - SettingModel.browse().then(function (results) { + settings.browse().then(function (results) { should.exist(results); @@ -40,7 +41,7 @@ firstSetting = results.models[0]; - return SettingModel.read(firstSetting.attributes.key); + return settings.read(firstSetting.attributes.key); }).then(function (found) { @@ -57,7 +58,7 @@ var firstPost, toEdit = {}; - SettingModel.browse().then(function (results) { + settings.browse().then(function (results) { should.exist(results); @@ -69,7 +70,7 @@ // key/value pairs toEdit[firstPost.attributes.key] = "new value"; - return SettingModel.edit(toEdit); + return settings.edit(toEdit); }).then(function (edited) { @@ -93,7 +94,7 @@ editedPost, toEdit = {}; - SettingModel.browse().then(function (results) { + settings.browse().then(function (results) { should.exist(results); @@ -107,7 +108,7 @@ toEdit[firstPost.attributes.key] = "new value1"; toEdit[secondPost.attributes.key] = "new value2"; - return SettingModel.edit(toEdit); + return settings.edit(toEdit); }).then(function (edited) { @@ -136,7 +137,7 @@ value: 'Test Content 1' }; - SettingModel.add(newSetting).then(function (createdSetting) { + settings.add(newSetting).then(function (createdSetting) { should.exist(createdSetting); @@ -150,7 +151,7 @@ it('can delete', function (done) { var firstSettingId; - SettingModel.browse().then(function (results) { + settings.browse().then(function (results) { should.exist(results); @@ -158,11 +159,11 @@ firstSettingId = results.models[0].id; - return SettingModel.destroy(firstSettingId); + return settings.destroy(firstSettingId); }).then(function () { - return SettingModel.browse(); + return settings.browse(); }).then(function (newResults) { diff --git a/core/test/ghost/api_users_spec.js b/core/test/ghost/api_users_spec.js index 32be67ed5e..7311f90e03 100644 --- a/core/test/ghost/api_users_spec.js +++ b/core/test/ghost/api_users_spec.js @@ -6,21 +6,22 @@ var _ = require('underscore'), should = require('should'), helpers = require('./helpers'), - Models = require('../../shared/models'); + UserProvider = require('../../shared/models/dataProvider.bookshelf.users'); - describe('Bookshelf User Model', function () { + describe('Bookshelf UsersProvider', function () { - var UserModel = Models.User; + var users; beforeEach(function (done) { helpers.resetData().then(function () { + users = new UserProvider(); done(); - }, done); + }); }); it('can browse', function (done) { - UserModel.browse().then(function (results) { + users.browse().then(function (results) { should.exist(results); @@ -34,7 +35,7 @@ it('can read', function (done) { var firstUser; - UserModel.browse().then(function (results) { + users.browse().then(function (results) { should.exist(results); @@ -42,7 +43,7 @@ firstUser = results.models[0]; - return UserModel.read({email_address: firstUser.attributes.email_address}); + return users.read({email_address: firstUser.attributes.email_address}); }).then(function (found) { @@ -59,7 +60,7 @@ it('can edit', function (done) { var firstUser; - UserModel.browse().then(function (results) { + users.browse().then(function (results) { should.exist(results); @@ -67,7 +68,7 @@ firstUser = results.models[0]; - return UserModel.edit({id: firstUser.id, url: "some.newurl.com"}); + return users.edit({id: firstUser.id, url: "some.newurl.com"}); }).then(function (edited) { @@ -86,7 +87,7 @@ email_address: "test@test1.com" }; - UserModel.add(userData).then(function (createdUser) { + users.add(userData).then(function (createdUser) { should.exist(createdUser); @@ -100,7 +101,7 @@ it('can delete', function (done) { var firstUserId; - UserModel.browse().then(function (results) { + users.browse().then(function (results) { should.exist(results); @@ -108,11 +109,11 @@ firstUserId = results.models[0].id; - return UserModel.destroy(firstUserId); + return users.destroy(firstUserId); }).then(function () { - return UserModel.browse(); + return users.browse(); }).then(function (newResults) { var ids, hasDeletedId; diff --git a/core/test/ghost/dataProvider.json_spec.js b/core/test/ghost/dataProvider.json_spec.js new file mode 100644 index 0000000000..c264427f5f --- /dev/null +++ b/core/test/ghost/dataProvider.json_spec.js @@ -0,0 +1,20 @@ +/*globals describe, beforeEach, it*/ + +(function () { + "use strict"; + + var should = require('should'), + DataProvider = require('../../shared/models/dataProvider.json'); + + describe("dataProvider.json", function () { + + it("is a singleton", function () { + var provider1 = new DataProvider(), + provider2 = new DataProvider(); + + should.strictEqual(provider1, provider2); + }); + + }); + +}()); \ No newline at end of file diff --git a/core/test/ghost/helpers.js b/core/test/ghost/helpers.js index 7d1d422ad2..11557800eb 100644 --- a/core/test/ghost/helpers.js +++ b/core/test/ghost/helpers.js @@ -4,7 +4,7 @@ // Use 'testing' Ghost config; unless we are running on travis (then show queries for debugging) process.env.NODE_ENV = process.env.TRAVIS ? 'travis' : 'testing'; - var knex = require('../../shared/models/base').Knex, + var knex = require('knex'), migrations = { one: require("../../shared/data/migration/001") },