From 983c171fb0692f68ff913abef6c243272ce40fc3 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Sat, 1 Jun 2013 10:47:41 -0400 Subject: [PATCH] simplifying the model structure, again --- config.js | 4 +- core/admin/controllers/index.js | 5 +- core/ghost.js | 41 ++---- core/shared/api.js | 21 +-- core/shared/data/migration/001.js | 2 +- 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 | 70 --------- core/shared/models/dataProvider.json.js | 35 ----- core/shared/models/index.js | 26 ++++ core/shared/models/knex_init.js | 11 -- core/shared/models/post.js | 134 ++++++++++++++++++ core/shared/models/setting.js | 45 ++++++ core/shared/models/user.js | 78 ++++++++++ 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/ghost_spec.js | 16 ++- core/test/ghost/helpers.js | 2 +- 23 files changed, 463 insertions(+), 488 deletions(-) create mode 100644 core/shared/models/base.js delete mode 100644 core/shared/models/dataProvider.bookshelf.base.js delete mode 100644 core/shared/models/dataProvider.bookshelf.js delete mode 100644 core/shared/models/dataProvider.bookshelf.posts.js delete mode 100644 core/shared/models/dataProvider.bookshelf.settings.js delete mode 100644 core/shared/models/dataProvider.bookshelf.users.js delete mode 100644 core/shared/models/dataProvider.json.js create mode 100644 core/shared/models/index.js delete mode 100644 core/shared/models/knex_init.js create mode 100644 core/shared/models/post.js create mode 100644 core/shared/models/setting.js create mode 100644 core/shared/models/user.js delete mode 100644 core/test/ghost/dataProvider.json_spec.js diff --git a/config.js b/config.js index 86b2d6fd04..a9f583878a 100644 --- a/config.js +++ b/config.js @@ -76,8 +76,8 @@ client: 'sqlite3', connection: { filename: './core/shared/data/tests.db' - }, - debug: true + } + // debug: true }, development: { diff --git a/core/admin/controllers/index.js b/core/admin/controllers/index.js index 12cefed076..14afb4633a 100755 --- a/core/admin/controllers/index.js +++ b/core/admin/controllers/index.js @@ -8,6 +8,7 @@ api = require('../../shared/api'), ghost = new Ghost(), + dataProvider = ghost.dataProvider, adminNavbar, adminControllers; @@ -170,7 +171,7 @@ }); }, 'dbpopulate': function (req, res) { - ghost.dataProvider().populateData(function (error) { + dataProvider.populateData(function (error) { if (error) { req.flash('error', error); } else { @@ -180,7 +181,7 @@ }); }, 'newUser': function (req, res) { - ghost.dataProvider().addNewUser(req, function (error) { + dataProvider.addNewUser(req, function (error) { if (error) { req.flash('error', error); } else { diff --git a/core/ghost.js b/core/ghost.js index 1fc09adbc0..917e4c7a2a 100644 --- a/core/ghost.js +++ b/core/ghost.js @@ -14,10 +14,7 @@ _ = require('underscore'), Polyglot = require('node-polyglot'), - JsonDataProvider = require('./shared/models/dataProvider.json'), - jsonDataProvider = new JsonDataProvider(), - BookshelfDataProvider = require('./shared/models/dataProvider.bookshelf'), - bookshelfDataProvider = new BookshelfDataProvider(), + models = require('./shared/models'), ExampleFilter = require('../content/plugins/exampleFilters'), Ghost, instance, @@ -49,8 +46,14 @@ polyglot; if (!instance) { - // this.init(); instance = this; + + // Holds the filters + this.filterCallbacks = []; + + // Holds the filter hooks (that are built in to Ghost Core) + this.filters = []; + plugin = new ExampleFilter(instance).init(); app = express(); @@ -64,8 +67,8 @@ _.extend(instance, { app: function () { return app; }, config: function () { return config; }, - globals: function () { return instance.globalsData; }, // there's no management here to be sure this has loaded - dataProvider: function () { return bookshelfDataProvider; }, + globals: function () { return instance.globalConfig; }, // there's no management here to be sure this has loaded + dataProvider: models, statuses: function () { return statuses; }, polyglot: function () { return polyglot; }, plugin: function () { return plugin; }, @@ -84,29 +87,11 @@ return instance; }; - Ghost.prototype.init = function() { - var initGlobals = jsonDataProvider.save(config.blogData).then(function () { - return jsonDataProvider.findAll().then(function (data) { - // We must have an instance to be able to call ghost.init(), right? - instance.globalsData = data; - }); - }); - - return when.all([initGlobals, instance.dataProvider().init()]); + Ghost.prototype.init = function () { + this.globalConfig = config.blogData; + return when.all([instance.dataProvider.init()]); }; - /** - * Holds the filters - * @type {Array} - */ - Ghost.prototype.filterCallbacks = []; - - /** - * Holds the filter hooks (that are built in to Ghost Core) - * @type {Array} - */ - Ghost.prototype.filters = []; - /** * @param {string} name * @param {Function} fn diff --git a/core/shared/api.js b/core/shared/api.js index 85760a5ef5..0d32514bc6 100644 --- a/core/shared/api.js +++ b/core/shared/api.js @@ -13,6 +13,7 @@ _ = require('underscore'), ghost = new Ghost(), + dataProvider = ghost.dataProvider, posts, users, settings, @@ -23,50 +24,50 @@ // takes filter / pagination parameters // returns a list of posts in a json response browse: function (options) { - return ghost.dataProvider().posts.findAll(options); + return dataProvider.Post.findAll(options); }, // takes an identifier (id or slug?) // returns a single post in a json response read: function (args) { - return ghost.dataProvider().posts.findOne(args); + return dataProvider.Post.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 ghost.dataProvider().posts.edit(postData); + return dataProvider.Post.edit(postData); }, // takes a json object representing a post, // returns the resulting post in a json response add: function (postData) { - return ghost.dataProvider().posts.add(postData); + return dataProvider.Post.add(postData); }, // takes an identifier (id or slug?) // returns a json response with the id of the deleted post destroy: function (args) { - return ghost.dataProvider().posts.destroy(args.id); + return dataProvider.Post.destroy(args.id); } }; // # Users users = { add: function (postData) { - return ghost.dataProvider().users.add(postData); + return dataProvider.User.add(postData); }, check: function (postData) { - return ghost.dataProvider().users.check(postData); + return dataProvider.User.check(postData); } }; // # Settings settings = { browse: function (options) { - return ghost.dataProvider().settings.browse(options); + return dataProvider.Setting.browse(options); }, read: function (options) { - return ghost.dataProvider().settings.read(options.key); + return dataProvider.Setting.read(options.key); }, edit: function (options) { - return ghost.dataProvider().settings.edit(options); + return dataProvider.Setting.edit(options); } }; diff --git a/core/shared/data/migration/001.js b/core/shared/data/migration/001.js index 86630a1575..5e01bb5fac 100644 --- a/core/shared/data/migration/001.js +++ b/core/shared/data/migration/001.js @@ -5,7 +5,7 @@ var when = require('when'), - knex = require('../../models/knex_init'), + knex = require('../../models/base').Knex, fixtures = require('../fixtures/001'), up, down; diff --git a/core/shared/models/base.js b/core/shared/models/base.js new file mode 100644 index 0000000000..0133a335b1 --- /dev/null +++ b/core/shared/models/base.js @@ -0,0 +1,96 @@ +(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 deleted file mode 100644 index 6f44c41771..0000000000 --- a/core/shared/models/dataProvider.bookshelf.base.js +++ /dev/null @@ -1,74 +0,0 @@ -/*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 deleted file mode 100644 index 8923e59dd0..0000000000 --- a/core/shared/models/dataProvider.bookshelf.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 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 deleted file mode 100644 index e7c02c7f7c..0000000000 --- a/core/shared/models/dataProvider.bookshelf.posts.js +++ /dev/null @@ -1,91 +0,0 @@ -(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 deleted file mode 100644 index e8a6538a84..0000000000 --- a/core/shared/models/dataProvider.bookshelf.settings.js +++ /dev/null @@ -1,37 +0,0 @@ -(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 deleted file mode 100644 index c4518fbed3..0000000000 --- a/core/shared/models/dataProvider.bookshelf.users.js +++ /dev/null @@ -1,70 +0,0 @@ -(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) { - 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 deleted file mode 100644 index 5b98c8c233..0000000000 --- a/core/shared/models/dataProvider.json.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * 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, - instance; - - DataProvider = function () { - if (!instance) { - instance = this; - _.extend(instance, { - data: [], - findAll: function() { - return when(instance.data); - }, - save: function (globals) { - _.each(globals, function (global, key) { - instance.data[key] = global; - }, instance); - - return when(globals); - } - }); - } - return instance; - }; - - module.exports = DataProvider; -}()); \ No newline at end of file diff --git a/core/shared/models/index.js b/core/shared/models/index.js new file mode 100644 index 0000000000..f52150cb8e --- /dev/null +++ b/core/shared/models/index.js @@ -0,0 +1,26 @@ +/*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 deleted file mode 100644 index 7a310fce4a..0000000000 --- a/core/shared/models/knex_init.js +++ /dev/null @@ -1,11 +0,0 @@ -/*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/post.js b/core/shared/models/post.js new file mode 100644 index 0000000000..115a875792 --- /dev/null +++ b/core/shared/models/post.js @@ -0,0 +1,134 @@ +(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 new file mode 100644 index 0000000000..e27f0612b4 --- /dev/null +++ b/core/shared/models/setting.js @@ -0,0 +1,45 @@ +(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 new file mode 100644 index 0000000000..ed477d241e --- /dev/null +++ b/core/shared/models/user.js @@ -0,0 +1,78 @@ +(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 this.forge({email_address: userData.email_address}).fetch().then(function (user) { + if (!!user.attributes.email_address) { + 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 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.forge({ + email_address: _userdata.email + }).fetch({require: true}).then(function (user) { + 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; + }); + }); + } + + }); + + 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 36f9084a30..95524025f1 100644 --- a/core/test/ghost/api_posts_spec.js +++ b/core/test/ghost/api_posts_spec.js @@ -6,21 +6,20 @@ var _ = require("underscore"), should = require('should'), helpers = require('./helpers'), - PostProvider = require('../../shared/models/dataProvider.bookshelf.posts'); + Models = require('../../shared/models'); - describe('Bookshelf PostsProvider', function () { + describe('Bookshelf Post Model', function () { - var posts; + var PostModel = Models.Post; beforeEach(function (done) { helpers.resetData().then(function () { - posts = new PostProvider(); done(); }, done); }); it('can browse', function (done) { - posts.browse().then(function (results) { + PostModel.browse().then(function (results) { should.exist(results); results.length.should.equal(2); @@ -32,14 +31,14 @@ it('can read', function (done) { var firstPost; - posts.browse().then(function (results) { + PostModel.browse().then(function (results) { should.exist(results); results.length.should.be.above(0); firstPost = results.models[0]; - return posts.read({slug: firstPost.attributes.slug}); + return PostModel.read({slug: firstPost.attributes.slug}); }).then(function (found) { should.exist(found); @@ -52,7 +51,7 @@ it('can edit', function (done) { var firstPost; - posts.browse().then(function (results) { + PostModel.browse().then(function (results) { should.exist(results); @@ -60,7 +59,7 @@ firstPost = results.models[0]; - return posts.edit({id: firstPost.id, title: "new title"}); + return PostModel.edit({id: firstPost.id, title: "new title"}); }).then(function (edited) { @@ -79,7 +78,7 @@ content: 'Test Content 1' }; - posts.add(newPost).then(function (createdPost) { + PostModel.add(newPost).then(function (createdPost) { should.exist(createdPost); createdPost.attributes.title.should.equal(newPost.title, "title is correct"); @@ -92,7 +91,7 @@ it('can delete', function (done) { var firstPostId; - posts.browse().then(function (results) { + PostModel.browse().then(function (results) { should.exist(results); @@ -100,11 +99,11 @@ firstPostId = results.models[0].id; - return posts.destroy(firstPostId); + return PostModel.destroy(firstPostId); }).then(function () { - return posts.browse(); + return PostModel.browse(); }).then(function (newResults) { var ids, hasDeletedId; @@ -126,7 +125,7 @@ helpers.insertMorePosts().then(function () { - return posts.findPage({page: 2}); + return PostModel.findPage({page: 2}); }).then(function (paginationResult) { @@ -138,7 +137,7 @@ paginationResult.pages.should.equal(4); - return posts.findPage({page: 5}); + return PostModel.findPage({page: 5}); }).then(function (paginationResult) { @@ -150,7 +149,7 @@ paginationResult.pages.should.equal(4); - return posts.findPage({limit: 30}); + return PostModel.findPage({limit: 30}); }).then(function (paginationResult) { @@ -162,7 +161,7 @@ paginationResult.pages.should.equal(2); - return posts.findPage({limit: 10, page: 2, where: {language: 'fr'}}); + return PostModel.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 5bb9fd5e49..8f6de03be1 100644 --- a/core/test/ghost/api_settings_spec.js +++ b/core/test/ghost/api_settings_spec.js @@ -6,21 +6,20 @@ var _ = require("underscore"), should = require('should'), helpers = require('./helpers'), - SettingProvider = require('../../shared/models/dataProvider.bookshelf.settings'); + Models = require('../../shared/models'); - describe('Bookshelf SettingsProvider', function () { + describe('Bookshelf Setting Model', function () { - var settings; + var SettingModel = Models.Setting; beforeEach(function (done) { helpers.resetData().then(function () { - settings = new SettingProvider(); done(); - }); + }, done); }); it('can browse', function (done) { - settings.browse().then(function (results) { + SettingModel.browse().then(function (results) { should.exist(results); @@ -33,7 +32,7 @@ it('can read', function (done) { var firstSetting; - settings.browse().then(function (results) { + SettingModel.browse().then(function (results) { should.exist(results); @@ -41,7 +40,7 @@ firstSetting = results.models[0]; - return settings.read(firstSetting.attributes.key); + return SettingModel.read(firstSetting.attributes.key); }).then(function (found) { @@ -58,7 +57,7 @@ var firstPost, toEdit = {}; - settings.browse().then(function (results) { + SettingModel.browse().then(function (results) { should.exist(results); @@ -70,7 +69,7 @@ // key/value pairs toEdit[firstPost.attributes.key] = "new value"; - return settings.edit(toEdit); + return SettingModel.edit(toEdit); }).then(function (edited) { @@ -94,7 +93,7 @@ editedPost, toEdit = {}; - settings.browse().then(function (results) { + SettingModel.browse().then(function (results) { should.exist(results); @@ -108,7 +107,7 @@ toEdit[firstPost.attributes.key] = "new value1"; toEdit[secondPost.attributes.key] = "new value2"; - return settings.edit(toEdit); + return SettingModel.edit(toEdit); }).then(function (edited) { @@ -137,7 +136,7 @@ value: 'Test Content 1' }; - settings.add(newSetting).then(function (createdSetting) { + SettingModel.add(newSetting).then(function (createdSetting) { should.exist(createdSetting); @@ -151,7 +150,7 @@ it('can delete', function (done) { var firstSettingId; - settings.browse().then(function (results) { + SettingModel.browse().then(function (results) { should.exist(results); @@ -159,11 +158,11 @@ firstSettingId = results.models[0].id; - return settings.destroy(firstSettingId); + return SettingModel.destroy(firstSettingId); }).then(function () { - return settings.browse(); + return SettingModel.browse(); }).then(function (newResults) { diff --git a/core/test/ghost/api_users_spec.js b/core/test/ghost/api_users_spec.js index 7311f90e03..32be67ed5e 100644 --- a/core/test/ghost/api_users_spec.js +++ b/core/test/ghost/api_users_spec.js @@ -6,22 +6,21 @@ var _ = require('underscore'), should = require('should'), helpers = require('./helpers'), - UserProvider = require('../../shared/models/dataProvider.bookshelf.users'); + Models = require('../../shared/models'); - describe('Bookshelf UsersProvider', function () { + describe('Bookshelf User Model', function () { - var users; + var UserModel = Models.User; beforeEach(function (done) { helpers.resetData().then(function () { - users = new UserProvider(); done(); - }); + }, done); }); it('can browse', function (done) { - users.browse().then(function (results) { + UserModel.browse().then(function (results) { should.exist(results); @@ -35,7 +34,7 @@ it('can read', function (done) { var firstUser; - users.browse().then(function (results) { + UserModel.browse().then(function (results) { should.exist(results); @@ -43,7 +42,7 @@ firstUser = results.models[0]; - return users.read({email_address: firstUser.attributes.email_address}); + return UserModel.read({email_address: firstUser.attributes.email_address}); }).then(function (found) { @@ -60,7 +59,7 @@ it('can edit', function (done) { var firstUser; - users.browse().then(function (results) { + UserModel.browse().then(function (results) { should.exist(results); @@ -68,7 +67,7 @@ firstUser = results.models[0]; - return users.edit({id: firstUser.id, url: "some.newurl.com"}); + return UserModel.edit({id: firstUser.id, url: "some.newurl.com"}); }).then(function (edited) { @@ -87,7 +86,7 @@ email_address: "test@test1.com" }; - users.add(userData).then(function (createdUser) { + UserModel.add(userData).then(function (createdUser) { should.exist(createdUser); @@ -101,7 +100,7 @@ it('can delete', function (done) { var firstUserId; - users.browse().then(function (results) { + UserModel.browse().then(function (results) { should.exist(results); @@ -109,11 +108,11 @@ firstUserId = results.models[0].id; - return users.destroy(firstUserId); + return UserModel.destroy(firstUserId); }).then(function () { - return users.browse(); + return UserModel.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 deleted file mode 100644 index c264427f5f..0000000000 --- a/core/test/ghost/dataProvider.json_spec.js +++ /dev/null @@ -1,20 +0,0 @@ -/*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/ghost_spec.js b/core/test/ghost/ghost_spec.js index 11212ac9da..88856529b1 100644 --- a/core/test/ghost/ghost_spec.js +++ b/core/test/ghost/ghost_spec.js @@ -20,26 +20,28 @@ it("uses init() to initialize", function (done) { var ghost = new Ghost(), fakeDataProvider = { - init: function() { + init: function () { return when.resolve(); } }, - dataProviderInitSpy = sinon.spy(fakeDataProvider, "init"); + dataProviderInitSpy = sinon.spy(fakeDataProvider, "init"), + oldDataProvder = ghost.dataProvider; - // Stub out the dataProvider - sinon.stub(ghost, "dataProvider", function () { - return fakeDataProvider; - }); + ghost.dataProvider = fakeDataProvider; should.not.exist(ghost.globals()); ghost.init().then(function () { + should.exist(ghost.globals()); dataProviderInitSpy.called.should.equal(true); + ghost.dataProvider = oldDataProvder; + done(); - }, done); + }).then(null, done); + }); }); diff --git a/core/test/ghost/helpers.js b/core/test/ghost/helpers.js index 11557800eb..7d1d422ad2 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('knex'), + var knex = require('../../shared/models/base').Knex, migrations = { one: require("../../shared/data/migration/001") },