Merge branch 'new-data-model'

This commit is contained in:
Hannah Wolfe 2013-09-15 00:36:54 +01:00
commit 9fa659aeee
41 changed files with 974 additions and 976 deletions

View File

@ -67,10 +67,10 @@ fancifyPlugin = {
if (_.isArray(posts)) { if (_.isArray(posts)) {
_.each(posts, function (post) { _.each(posts, function (post) {
post.content = self.fancify(post.content); post.html = self.fancify(post.html);
}); });
} else if (posts.hasOwnProperty('content')) { } else if (posts.hasOwnProperty('html')) {
posts.content = this.fancify(posts.content); posts.html = this.fancify(posts.html);
} }
return posts; return posts;
@ -84,7 +84,7 @@ fancifyPlugin = {
}, },
// Registers the prePostsRender filter to alter the content. // Registers the prePostsRender filter to alter the html.
activate: function (ghost) { activate: function (ghost) {
ghost.registerFilter('prePostsRender', this.fancifyPosts); ghost.registerFilter('prePostsRender', this.fancifyPosts);
}, },

View File

@ -5,7 +5,7 @@
{{! TODO: JavaScript toggle featured/unfeatured}} {{! TODO: JavaScript toggle featured/unfeatured}}
<span class="status">{{#if published}}Published{{else}}Written{{/if}}</span> <span class="status">{{#if published}}Published{{else}}Written{{/if}}</span>
<span class="normal">by</span> <span class="normal">by</span>
<span class="author">{{#if author.name}}{{author.name}}{{else}}{{author.email_address}}{{/if}}</span> <span class="author">{{#if author.name}}{{author.name}}{{else}}{{author.email}}{{/if}}</span>
<section class="post-controls"> <section class="post-controls">
<a class="post-edit" href="#"><span class="hidden">Edit Post</span></a> <a class="post-edit" href="#"><span class="hidden">Edit Post</span></a>
<a class="post-settings" href="#" data-toggle=".menu-drop-right"><span class="hidden">Post Settings</span></a> <a class="post-settings" href="#" data-toggle=".menu-drop-right"><span class="hidden">Post Settings</span></a>
@ -31,5 +31,5 @@
</section> </section>
</header> </header>
<section class="content-preview-content"> <section class="content-preview-content">
<div class="wrapper"><h1>{{{title}}}</h1>{{{content}}}</div> <div class="wrapper"><h1>{{{title}}}</h1>{{{html}}}</div>
</section> </section>

View File

@ -7,25 +7,25 @@
<section class="content no-padding"> <section class="content no-padding">
<header class="user-profile-header"> <header class="user-profile-header">
<figure class="cover-image"> <figure class="cover-image">
<img id="user-cover-picture" src="{{#if cover_picture}}{{cover_picture}}{{else}}/shared/img/default-user-cover-picture.jpg{{/if}}" title="{{full_name}} Cover Image"/> <img id="user-cover" src="{{#if cover}}{{cover}}{{else}}/shared/img/default-user-cover-picture.jpg{{/if}}" title="{{name}} Cover Image"/>
<button class="button-change-cover js-modal-cover-picture">Change Cover</button> <button class="button-change-cover js-modal-cover">Change Cover</button>
</figure> </figure>
</header> </header>
<form class="user-details-container" novalidate="novalidate"> <form class="user-details-container" novalidate="novalidate">
<fieldset class="user-details-top"> <fieldset class="user-details-top">
<figure class="user-avatar-image"> <figure class="user-avatar-image">
<img id="user-profile-picture" src="{{#if profile_picture}}{{profile_picture}}{{else}}/shared/img/default-user-profile-picture.jpg{{/if}}" title="{{full_name}}"/> <img id="user-image" src="{{#if image}}{{image}}{{else}}/shared/img/default-user-profile-picture.jpg{{/if}}" title="{{name}}"/>
<button class="button-change-avatar js-modal-profile-picture">Edit Picture</button> <button class="button-change-avatar js-modal-image">Edit Picture</button>
</figure> </figure>
<label> <label>
<input type="url" value="{{full_name}}" id="user-name" placeholder="Joe Bloggs" autocapitalize="off" autocorrect="off"> <input type="url" value="{{name}}" id="user-name" placeholder="Joe Bloggs" autocapitalize="off" autocorrect="off">
<p>Use your real name so people can recognise you.</p> <p>Use your real name so people can recognise you.</p>
</label> </label>
</fieldset> </fieldset>
<fieldset class="user-details-bottom"> <fieldset class="user-details-bottom">
<div class="form-group"> <div class="form-group">
<label><strong>Email</strong></label> <label><strong>Email</strong></label>
<input type="email" value="{{email_address}}" id="user-email" placeholder="Email Address" autocapitalize="off" autocorrect="off"> <input type="email" value="{{email}}" id="user-email" placeholder="Email Address" autocapitalize="off" autocorrect="off">
<p>Email will not be publicly displayed. <a class="highlight" href="#" >Learn more</a>.</p> <p>Email will not be publicly displayed. <a class="highlight" href="#" >Learn more</a>.</p>
</div> </div>
@ -37,7 +37,7 @@
<div class="form-group"> <div class="form-group">
<label><strong>Website</strong></label> <label><strong>Website</strong></label>
<input type="text" value="{{url}}" id="user-website"> <input type="text" value="{{website}}" id="user-website">
<p>Have a website or blog other than this one? Link it.</p> <p>Have a website or blog other than this one? Link it.</p>
</div> </div>

View File

@ -199,14 +199,14 @@
}, },
savePost: function (data) { savePost: function (data) {
// TODO: The content_raw getter here isn't great, shouldn't rely on currentView. // TODO: The markdown getter here isn't great, shouldn't rely on currentView.
_.each(this.model.blacklist, function (item) { _.each(this.model.blacklist, function (item) {
this.model.unset(item); this.model.unset(item);
}, this); }, this);
var saved = this.model.save(_.extend({ var saved = this.model.save(_.extend({
title: $('#entry-title').val(), title: $('#entry-title').val(),
content_raw: Ghost.currentView.editor.getValue() markdown: Ghost.currentView.editor.getValue()
}, data)); }, data));
// TODO: Take this out if #2489 gets merged in Backbone. Or patch Backbone // TODO: Take this out if #2489 gets merged in Backbone. Or patch Backbone
@ -255,7 +255,7 @@
this.addSubview(new PublishBar({el: "#publish-bar", model: this.model})).render(); this.addSubview(new PublishBar({el: "#publish-bar", model: this.model})).render();
this.$('#entry-title').val(this.model.get('title')); this.$('#entry-title').val(this.model.get('title'));
this.$('#entry-markdown').html(this.model.get('content_raw')); this.$('#entry-markdown').html(this.model.get('markdown'));
this.initMarkdown(); this.initMarkdown();
this.renderPreview(); this.renderPreview();

View File

@ -248,17 +248,17 @@
events: { events: {
'click .button-save': 'saveUser', 'click .button-save': 'saveUser',
'click .button-change-password': 'changePassword', 'click .button-change-password': 'changePassword',
'click .js-modal-cover-picture': 'showCoverPicture', 'click .js-modal-cover': 'showCover',
'click .js-modal-profile-picture': 'showProfilePicture' 'click .js-modal-image': 'showImage'
}, },
showCoverPicture: function () { showCover: function () {
var user = this.model.toJSON(); var user = this.model.toJSON();
this.showUpload('#user-cover-picture', 'cover_picture', user.cover_picture); this.showUpload('#user-cover', 'cover', user.cover);
}, },
showProfilePicture: function (e) { showImage: function (e) {
e.preventDefault(); e.preventDefault();
var user = this.model.toJSON(); var user = this.model.toJSON();
this.showUpload('#user-profile-picture', 'profile_picture', user.profile_picture); this.showUpload('#user-image', 'image', user.image);
}, },
showUpload: function (id, key, src) { showUpload: function (id, key, src) {
var self = this, upload = new Ghost.Models.uploadModal({'id': id, 'key': key, 'src': src, 'accept': { var self = this, upload = new Ghost.Models.uploadModal({'id': id, 'key': key, 'src': src, 'accept': {
@ -314,13 +314,13 @@
} else { } else {
this.model.save({ this.model.save({
'full_name': userName, 'name': userName,
'email_address': userEmail, 'email': userEmail,
'location': userLocation, 'location': userLocation,
'url': userWebsite, 'website': userWebsite,
'bio': userBio, 'bio': userBio,
'profile_picture': this.$('#user-profile-picture').attr('src'), 'image': this.$('#user-image').attr('src'),
'cover_picture': this.$('#user-cover-picture').attr('src') 'cover': this.$('#user-cover').attr('src')
}, { }, {
success: this.saveSuccess, success: this.saveSuccess,
error: this.saveError error: this.saveError

View File

@ -118,8 +118,8 @@ function ghostLocals(req, res, next) {
availableThemes: ghost.paths().availableThemes, availableThemes: ghost.paths().availableThemes,
availablePlugins: ghost.paths().availablePlugins, availablePlugins: ghost.paths().availablePlugins,
currentUser: { currentUser: {
name: currentUser.attributes.full_name, name: currentUser.attributes.name,
profile: currentUser.attributes.profile_picture profile: currentUser.attributes.image
} }
}); });
next(); next();

View File

@ -167,8 +167,8 @@ adminControllers = {
password = req.body.password; password = req.body.password;
api.users.add({ api.users.add({
full_name: name, name: name,
email_address: email, email: email,
password: password password: password
}).then(function (user) { }).then(function (user) {
@ -274,7 +274,7 @@ adminControllers = {
}, },
'export': function (req, res) { 'export': function (req, res) {
// Get current version from settings // Get current version from settings
api.settings.read({ key: "currentVersion" }) api.settings.read({ key: "databaseVersion" })
.then(function (setting) { .then(function (setting) {
// Export the current versions data // Export the current versions data
return dataExport(setting.value); return dataExport(setting.value);
@ -324,13 +324,13 @@ adminControllers = {
} }
// Get the current version for importing // Get the current version for importing
api.settings.read({ key: "currentVersion" }) api.settings.read({ key: "databaseVersion" })
.then(function (setting) { .then(function (setting) {
return when(setting.value); return when(setting.value);
}, function () { }, function () {
return when("001"); return when("001");
}) })
.then(function (currentVersion) { .then(function (databaseVersion) {
// Read the file contents // Read the file contents
return nodefn.call(fs.readFile, req.files.importfile.path) return nodefn.call(fs.readFile, req.files.importfile.path)
.then(function (fileContents) { .then(function (fileContents) {
@ -348,7 +348,7 @@ adminControllers = {
} }
// Import for the current version // Import for the current version
return dataImport(currentVersion, importData); return dataImport(databaseVersion, importData);
}); });
}) })
.then(function importSuccess() { .then(function importSuccess() {

View File

@ -1,44 +1,44 @@
{ {
"core": { "core": {
"currentVersion": { "databaseVersion": {
"default": "000" "defaultValue": "000"
} }
}, },
"blog": { "blog": {
"title": { "title": {
"default": "Ghost" "defaultValue": "Ghost"
}, },
"description": { "description": {
"default": "Just a blogging platform." "defaultValue": "Just a blogging platform."
}, },
"email": { "email": {
"default": "ghost@example.com", "defaultValue": "ghost@example.com",
"validations": { "validations": {
"notNull": true, "notNull": true,
"isEmail": true "isEmail": true
} }
}, },
"logo": { "logo": {
"default": "" "defaultValue": ""
}, },
"cover": { "cover": {
"default": "" "defaultValue": ""
}, },
"defaultLang": { "defaultLang": {
"default": "en", "defaultValue": "en_US",
"validations": { "validations": {
"notNull": true "notNull": true
} }
}, },
"postsPerPage": { "postsPerPage": {
"default": "6", "defaultValue": "6",
"validations": { "validations": {
"notNull": true, "notNull": true,
"isNumeric": true "isNumeric": true
} }
}, },
"forceI18n": { "forceI18n": {
"default": "true", "defaultValue": "true",
"validations": { "validations": {
"notNull": true, "notNull": true,
"isIn": ["true", "false"] "isIn": ["true", "false"]
@ -47,15 +47,15 @@
}, },
"theme": { "theme": {
"activePlugins": { "activePlugins": {
"default": "" "defaultValue": ""
}, },
"activeTheme": { "activeTheme": {
"default": "casper" "defaultValue": "casper"
} }
}, },
"plugin": { "plugin": {
"installedPlugins": { "installedPlugins": {
"default": "[]" "defaultValue": "[]"
} }
} }
} }

View File

@ -4,7 +4,7 @@ var when = require('when'),
module.exports = function (version) { module.exports = function (version) {
var exporter; var exporter;
if (version > migration.currentVersion) { if (version > migration.databaseVersion) {
return when.reject("Your data version is ahead of the current Ghost version. Please upgrade in order to export."); return when.reject("Your data version is ahead of the current Ghost version. Please upgrade in order to export.");
} }

View File

@ -1,11 +0,0 @@
var uuid = require('node-uuid');
module.exports = {
posts: [],
roles: [],
permissions: [],
permissions_roles: []
};

View File

@ -1,84 +1,89 @@
var uuid = require('node-uuid'); var sequence = require('when/sequence'),
_ = require('underscore'),
Post = require('../../models/post').Post,
Role = require('../../models/role').Role,
Permission = require('../../models/permission').Permission,
uuid = require('node-uuid');
module.exports = { var fixtures = {
posts: [ posts: [
{ {
"uuid": uuid.v4(), "uuid": uuid.v4(),
"title": "Welcome to Ghost", "title": "Welcome to Ghost",
"slug": "welcome-to-ghost", "slug": "welcome-to-ghost",
"content_raw": "This short guide will teach you how to get Ghost up and running on your computer. It doesn't cover deploying it to a live server, just getting it running on your machine so that you can use it, and develop on top of it.\n\n## Setup Instructions\n\n### Compatibility Notes\n\nGhost uses SQLite which must be built natively on the operating system you intend to run Ghost on. We are working to improve this process, but in the meantime the following OS compatibility notes apply:\n\n* **Linux** - Ghost should install and run with no problems\n* **Mac** - you may require Xcode (free) and the CLI Tools which can be installed from Xcode to get Ghost installed\n* **Windows** - Ghost will and does install and run (really well actually) on Windows, but there are a set of pre-requisites which are tricky to install. Detailed instructions for this are coming very soon.\n\n### Pre-requisites\n\nGhost requires [node][1] 0.10.* or 0.11.* and npm. Download and install from [nodejs.org][1]\n\n### Installing\n\n1. Once you've downloaded one of the release packages, unzip it, and place the directory wherever you would like to run the code\n2. Fire up a terminal (or node command prompt in Windows) and change directory to the root of the Ghost application (where config.js and index.js are)\n3. run `npm install` to install the node dependencies (if you get errors to do with SQLite, please see the compatibility notes)\n4. To start ghost, run `npm start`\n5. Visit [http://localhost:2368/](http://localhost:2368/) in your web browser\n\n## Logging in For The First Time\n\nOnce you have the Ghost server up and running, you should be able to navigate to [http://localhost:2368/ghost](http://localhost:2368/ghost) from a web browser, where you will be prompted for a login.\n\n1. Click on the \"register new user\" link\n2. Enter your user details (careful here: There is no password reset yet!)\n3. Return to the login screen and use those details to log in.\n\n## Finding Your Way Around Ghost\n\nYou should now be logged in and up and running with the very first, very earliest, most historically significant, most prototypal version of the Ghost blogging platform. Click around the dashboard. You will find that most things work, but many things do not. We're still working on those. Keep downloading the new packages as we release them, and you should hopefully see big changes between each version as we go!\n\n [1]: http://nodejs.org/", "markdown": "This short guide will teach you how to get Ghost up and running on your computer. It doesn't cover deploying it to a live server, just getting it running on your machine so that you can use it, and develop on top of it.\n\n## Setup Instructions\n\n### Compatibility Notes\n\nGhost uses SQLite which must be built natively on the operating system you intend to run Ghost on. We are working to improve this process, but in the meantime the following OS compatibility notes apply:\n\n* **Linux** - Ghost should install and run with no problems\n* **Mac** - you may require Xcode (free) and the CLI Tools which can be installed from Xcode to get Ghost installed\n* **Windows** - Ghost will and does install and run (really well actually) on Windows, but there are a set of pre-requisites which are tricky to install. Detailed instructions for this are coming very soon.\n\n### Pre-requisites\n\nGhost requires [node][1] 0.10.* or 0.11.* and npm. Download and install from [nodejs.org][1]\n\n### Installing\n\n1. Once you've downloaded one of the release packages, unzip it, and place the directory wherever you would like to run the code\n2. Fire up a terminal (or node command prompt in Windows) and change directory to the root of the Ghost application (where config.js and index.js are)\n3. run `npm install` to install the node dependencies (if you get errors to do with SQLite, please see the compatibility notes)\n4. To start ghost, run `npm start`\n5. Visit [http://localhost:2368/](http://localhost:2368/) in your web browser\n\n## Logging in For The First Time\n\nOnce you have the Ghost server up and running, you should be able to navigate to [http://localhost:2368/ghost](http://localhost:2368/ghost) from a web browser, where you will be prompted for a login.\n\n1. Click on the \"register new user\" link\n2. Enter your user details (careful here: There is no password reset yet!)\n3. Return to the login screen and use those details to log in.\n\n## Finding Your Way Around Ghost\n\nYou should now be logged in and up and running with the very first, very earliest, most historically significant, most prototypal version of the Ghost blogging platform. Click around the dashboard. You will find that most things work, but many things do not. We're still working on those. Keep downloading the new packages as we release them, and you should hopefully see big changes between each version as we go!\n\n [1]: http://nodejs.org/",
"content": "<p>This short guide will teach you how to get Ghost up and running on your computer. It doesn't cover deploying it to a live server, just getting it running on your machine so that you can use it, and develop on top of it.</p>\n\n<h2 id=\"setupinstructions\">Setup Instructions</h2>\n\n<h3 id=\"compatibilitynotes\">Compatibility Notes</h3>\n\n<p>Ghost uses SQLite which must be built natively on the operating system you intend to run Ghost on. We are working to improve this process, but in the meantime the following OS compatibility notes apply:</p>\n\n<ul>\n<li><strong>Linux</strong> - Ghost should install and run with no problems</li>\n<li><strong>Mac</strong> - you may require Xcode (free) and the CLI Tools which can be installed from Xcode to get Ghost installed</li>\n<li><strong>Windows</strong> - Ghost will and does install and run (really well actually) on Windows, but there are a set of pre-requisites which are tricky to install. Detailed instructions for this are coming very soon.</li>\n</ul>\n\n<h3 id=\"prerequisites\">Pre-requisites</h3>\n\n<p>Ghost requires <a href=\"http://nodejs.org/\">node</a> 0.10.* or 0.11.* and npm. Download and install from <a href=\"http://nodejs.org/\">nodejs.org</a></p>\n\n<h3 id=\"installing\">Installing</h3>\n\n<ol>\n<li>Once you've downloaded one of the release packages, unzip it, and place the directory wherever you would like to run the code</li>\n<li>Fire up a terminal (or node command prompt in Windows) and change directory to the root of the Ghost application (where config.js and index.js are)</li>\n<li>run <code>npm install</code> to install the node dependencies (if you get errors to do with SQLite, please see the compatibility notes)</li>\n<li>To start ghost, run <code>npm start</code></li>\n<li>Visit <a href=\"http://localhost:2368/\">http://localhost:2368/</a> in your web browser</li>\n</ol>\n\n<h2 id=\"logginginforthefirsttime\">Logging in For The First Time</h2>\n\n<p>Once you have the Ghost server up and running, you should be able to navigate to <a href=\"http://localhost:2368/ghost\">http://localhost:2368/ghost</a> from a web browser, where you will be prompted for a login.</p>\n\n<ol>\n<li>Click on the \"register new user\" link</li>\n<li>Enter your user details (careful here: There is no password reset yet!)</li>\n<li>Return to the login screen and use those details to log in.</li>\n</ol>\n\n<h2 id=\"findingyourwayaroundghost\">Finding Your Way Around Ghost</h2>\n\n<p>You should now be logged in and up and running with the very first, very earliest, most historically significant, most prototypal version of the Ghost blogging platform. Click around the dashboard. You will find that most things work, but many things do not. We're still working on those. Keep downloading the new packages as we release them, and you should hopefully see big changes between each version as we go!</p>", "html": "<p>This short guide will teach you how to get Ghost up and running on your computer. It doesn't cover deploying it to a live server, just getting it running on your machine so that you can use it, and develop on top of it.</p>\n\n<h2 id=\"setupinstructions\">Setup Instructions</h2>\n\n<h3 id=\"compatibilitynotes\">Compatibility Notes</h3>\n\n<p>Ghost uses SQLite which must be built natively on the operating system you intend to run Ghost on. We are working to improve this process, but in the meantime the following OS compatibility notes apply:</p>\n\n<ul>\n<li><strong>Linux</strong> - Ghost should install and run with no problems</li>\n<li><strong>Mac</strong> - you may require Xcode (free) and the CLI Tools which can be installed from Xcode to get Ghost installed</li>\n<li><strong>Windows</strong> - Ghost will and does install and run (really well actually) on Windows, but there are a set of pre-requisites which are tricky to install. Detailed instructions for this are coming very soon.</li>\n</ul>\n\n<h3 id=\"prerequisites\">Pre-requisites</h3>\n\n<p>Ghost requires <a href=\"http://nodejs.org/\">node</a> 0.10.* or 0.11.* and npm. Download and install from <a href=\"http://nodejs.org/\">nodejs.org</a></p>\n\n<h3 id=\"installing\">Installing</h3>\n\n<ol>\n<li>Once you've downloaded one of the release packages, unzip it, and place the directory wherever you would like to run the code</li>\n<li>Fire up a terminal (or node command prompt in Windows) and change directory to the root of the Ghost application (where config.js and index.js are)</li>\n<li>run <code>npm install</code> to install the node dependencies (if you get errors to do with SQLite, please see the compatibility notes)</li>\n<li>To start ghost, run <code>npm start</code></li>\n<li>Visit <a href=\"http://localhost:2368/\">http://localhost:2368/</a> in your web browser</li>\n</ol>\n\n<h2 id=\"logginginforthefirsttime\">Logging in For The First Time</h2>\n\n<p>Once you have the Ghost server up and running, you should be able to navigate to <a href=\"http://localhost:2368/ghost\">http://localhost:2368/ghost</a> from a web browser, where you will be prompted for a login.</p>\n\n<ol>\n<li>Click on the \"register new user\" link</li>\n<li>Enter your user details (careful here: There is no password reset yet!)</li>\n<li>Return to the login screen and use those details to log in.</li>\n</ol>\n\n<h2 id=\"findingyourwayaroundghost\">Finding Your Way Around Ghost</h2>\n\n<p>You should now be logged in and up and running with the very first, very earliest, most historically significant, most prototypal version of the Ghost blogging platform. Click around the dashboard. You will find that most things work, but many things do not. We're still working on those. Keep downloading the new packages as we release them, and you should hopefully see big changes between each version as we go!</p>",
"meta_title": null,
"meta_description": null,
"meta_keywords": null,
"featured": null,
"image": null, "image": null,
"featured": false,
"page": false,
"status": "published", "status": "published",
"language": null, "language": "en_US",
"author_id": 1, "meta_title": null,
"created_at": 1373578890610, "meta_description": null
"created_by": 1,
"updated_at": 1373578997173,
"updated_by": 1,
"published_at": 1373578895817,
"published_by": 1
} }
], ],
roles: [ roles: [
{ {
"id": 1, "uuid": uuid.v4(),
"name": "Administrator", "name": "Administrator",
"description": "Administrators" "description": "Administrators"
}, },
{ {
"id": 2, "uuid": uuid.v4(),
"name": "Editor", "name": "Editor",
"description": "Editors" "description": "Editors"
}, },
{ {
"id": 3, "uuid": uuid.v4(),
"name": "Author", "name": "Author",
"description": "Authors" "description": "Authors"
} }
], ],
permissions: [ permissions: [
{ {
"id": 1, "uuid": uuid.v4(),
"name": "Edit posts", "name": "Edit posts",
"action_type": "edit", "action_type": "edit",
"object_type": "post" "object_type": "post"
}, },
{ {
"id": 2, "uuid": uuid.v4(),
"name": "Remove posts", "name": "Remove posts",
"action_type": "remove", "action_type": "remove",
"object_type": "post" "object_type": "post"
}, },
{ {
"id": 3, "uuid": uuid.v4(),
"name": "Create posts", "name": "Create posts",
"action_type": "create", "action_type": "create",
"object_type": "post" "object_type": "post"
}
],
permissions_roles: [
{
"id": 1,
"permission_id": 1,
"role_id": 1
},
{
"id": 2,
"permission_id": 2,
"role_id": 1
},
{
"id": 3,
"permission_id": 3,
"role_id": 1
} }
] ]
}; };
module.exports = {
populateFixtures: function () {
var ops = [];
_.each(fixtures.posts, function (post) {
ops.push(function () {return Post.add(post); });
});
_.each(fixtures.roles, function (role) {
ops.push(function () {return Role.add(role); });
});
_.each(fixtures.permissions, function (permission) {
ops.push(function () {return Permission.add(permission); });
});
// finally, grant admins all permissions
ops.push(function () {
Role.forge({id: 1}).fetch({withRelated: ['permissions']}).then(function (role) {
role.permissions().attach([1, 2, 3]);
});
});
return sequence(ops);
}
};

View File

@ -68,8 +68,8 @@ Importer002.prototype.basicImport = function (data) {
break; break;
case 'settings': case 'settings':
// for settings we need to update individual settings, and insert any missing ones // for settings we need to update individual settings, and insert any missing ones
// the one setting we MUST NOT update is the currentVersion settings // the one setting we MUST NOT update is the databaseVersion settings
var blackList = ['currentVersion']; var blackList = ['databaseVersion'];
if (tableData && tableData.length) { if (tableData && tableData.length) {
tableData = stripProperties(['id'], tableData); tableData = stripProperties(['id'], tableData);
_.each(tableData, function (data) { _.each(tableData, function (data) {

View File

@ -0,0 +1,152 @@
var when = require('when'),
knex = require('../../models/base').Knex,
up,
down;
up = function () {
return when.all([
knex.Schema.createTable('posts', function (t) {
t.increments().primary();
t.string('uuid', 36).notNull();
t.string('title', 150).notNull();
t.string('slug', 150).notNull().unique();
t.text('markdown', 'medium').nullable(); // max-length 16777215
t.text('html', 'medium').nullable(); // max-length 16777215
t.text('image').nullable(); // max-length 2000
t.bool('featured').notNull().defaultTo(false);
t.bool('page').notNull().defaultTo(false);
t.string('status', 150).notNull().defaultTo('draft');
t.string('language', 6).notNull().defaultTo('en_US');
t.string('meta_title', 150).nullable();
t.string('meta_description', 200).nullable();
t.integer('author_id').notNull();
t.dateTime('created_at').notNull();
t.integer('created_by').notNull();
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
t.dateTime('published_at').nullable();
t.integer('published_by').nullable();
}),
knex.Schema.createTable('users', function (t) {
t.increments().primary();
t.string('uuid', 36).notNull();
t.string('name', 150).notNull();
t.string('slug', 150).notNull().unique();
t.string('password', 60).notNull();
t.string('email', 254).notNull().unique();
t.text('image').nullable(); // max-length 2000
t.text('cover').nullable(); // max-length 2000
t.string('bio', 200).nullable();
t.text('website').nullable(); // max-length 2000
t.text('location').nullable(); // max-length 65535
t.text('accessibility').nullable(); // max-length 65535
t.string('status', 150).notNull().defaultTo('active');
t.string('language', 6).notNull().defaultTo('en_US');
t.string('meta_title', 150).nullable();
t.string('meta_description', 200).nullable();
t.dateTime('last_login').nullable();
t.dateTime('created_at').notNull();
t.integer('created_by').notNull();
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
}),
knex.Schema.createTable('roles', function (t) {
t.increments().primary();
t.string('uuid', 36).notNull();
t.string('name', 150).notNull();
t.string('description', 200).nullable();
t.dateTime('created_at').notNull();
t.integer('created_by').notNull();
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
}),
knex.Schema.createTable('roles_users', function (t) {
t.increments().primary();
t.integer('role_id').notNull();
t.integer('user_id').notNull();
}),
knex.Schema.createTable('permissions', function (t) {
t.increments().primary();
t.string('uuid', 36).notNull();
t.string('name', 150).notNull();
t.string('object_type', 150).notNull();
t.string('action_type', 150).notNull();
t.integer('object_id').nullable();
t.dateTime('created_at').notNull();
t.integer('created_by').notNull();
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
}),
knex.Schema.createTable('permissions_users', function (t) {
t.increments().primary();
t.integer('user_id').notNull();
t.integer('permission_id').notNull();
}),
knex.Schema.createTable('permissions_roles', function (t) {
t.increments().primary();
t.integer('role_id').notNull();
t.integer('permission_id').notNull();
}),
knex.Schema.createTable('settings', function (t) {
t.increments().primary();
t.string('uuid', 36).notNull();
t.string('key', 150).notNull().unique();
t.text('value').nullable(); // max-length 65535
t.string('type', 150).notNull().defaultTo('core');
t.dateTime('created_at').notNull();
t.integer('created_by').notNull();
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
}),
knex.Schema.createTable('tags', function (t) {
t.increments().primary();
t.string('uuid', 36).notNull();
t.string('name', 150).notNull();
t.string('slug', 150).notNull().unique();
t.string('description', 200).nullable();
t.integer('parent_id').nullable();
t.string('meta_title', 150).nullable();
t.string('meta_description', 200).nullable();
t.dateTime('created_at').notNull();
t.integer('created_by').notNull();
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
}),
knex.Schema.createTable('posts_tags', function (t) {
t.increments().primary();
t.integer('post_id').notNull().unsigned().references('id').inTable('posts');
t.integer('tag_id').notNull().unsigned().references('id').inTable('tags');
})
]);
};
down = function () {
return when.all([
knex.Schema.dropTableIfExists("posts"),
knex.Schema.dropTableIfExists("users"),
knex.Schema.dropTableIfExists("roles"),
knex.Schema.dropTableIfExists("settings"),
knex.Schema.dropTableIfExists("permissions"),
knex.Schema.dropTableIfExists("tags")
]).then(function () {
// Drop the relation tables after the model tables
return when.all([
knex.Schema.dropTableIfExists("roles_users"),
knex.Schema.dropTableIfExists("permissions_users"),
knex.Schema.dropTableIfExists("permissions_roles"),
knex.Schema.dropTableIfExists("posts_tags")
]);
});
};
exports.up = up;
exports.down = down;

View File

@ -1,128 +0,0 @@
var when = require('when'),
knex = require('../../models/base').Knex,
fixtures = require('../fixtures/001'),
up,
down;
up = function () {
return when.all([
knex.Schema.createTable('posts', function (t) {
t.increments().primary();
t.string('uuid');
t.string('title');
t.string('slug');
t.text('content_raw');
t.text('content');
t.string('meta_title');
t.string('meta_description');
t.string('meta_keywords');
t.bool('featured');
t.string('image');
t.string('status');
t.string('language');
t.integer('author_id');
t.dateTime('created_at');
t.integer('created_by');
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
t.dateTime('published_at').nullable();
t.integer('published_by').nullable();
}),
knex.Schema.createTable('users', function (t) {
t.increments().primary();
t.string('uuid');
t.string('full_name');
t.string('password');
t.string('email_address');
t.string('profile_picture');
t.string('cover_picture');
t.text('bio');
t.string('url');
t.dateTime('created_at');
t.integer('created_by');
t.dateTime('updated_at');
t.integer('updated_by');
}),
knex.Schema.createTable('roles', function (t) {
t.increments().primary();
t.string('name');
t.string('description');
}),
knex.Schema.createTable('roles_users', function (t) {
t.increments().primary();
t.integer('role_id');
t.integer('user_id');
}),
knex.Schema.createTable('permissions', function (t) {
t.increments().primary();
t.string('name');
t.string('object_type');
t.string('action_type');
t.integer('object_id');
}),
knex.Schema.createTable('permissions_users', function (t) {
t.increments().primary();
t.integer('user_id');
t.integer('permission_id');
}),
knex.Schema.createTable('permissions_roles', function (t) {
t.increments().primary();
t.integer('role_id');
t.integer('permission_id');
}),
knex.Schema.createTable('settings', function (t) {
t.increments().primary();
t.string('uuid');
t.string('key').unique();
t.text('value');
t.string('type');
t.dateTime('created_at');
t.integer('created_by');
t.dateTime('updated_at');
t.integer('updated_by');
})
// Once we create all of the initial tables, bootstrap any of the data
]).then(function () {
return when.all([
knex('posts').insert(fixtures.posts),
// knex('users').insert(fixtures.users),
knex('roles').insert(fixtures.roles),
// knex('roles_users').insert(fixtures.roles_users),
knex('permissions').insert(fixtures.permissions),
knex('permissions_roles').insert(fixtures.permissions_roles),
knex('settings').insert({ key: 'currentVersion', 'value': '001', type: 'core' })
]);
});
};
down = function () {
return when.all([
knex.Schema.dropTableIfExists("posts"),
knex.Schema.dropTableIfExists("users"),
knex.Schema.dropTableIfExists("roles"),
knex.Schema.dropTableIfExists("settings"),
knex.Schema.dropTableIfExists("permissions")
]).then(function () {
// Drop the relation tables after the model tables?
return when.all([
knex.Schema.dropTableIfExists("roles_users"),
knex.Schema.dropTableIfExists("permissions_users"),
knex.Schema.dropTableIfExists("permissions_roles")
]);
});
};
exports.up = up;
exports.down = down;

View File

@ -1,83 +0,0 @@
var when = require('when'),
knex = require('../../models/base').Knex,
migrationVersion = '002',
fixtures = require('../fixtures/' + migrationVersion),
errors = require('../../errorHandling'),
up,
down;
up = function () {
return when.all([
knex.Schema.createTable('tags', function (t) {
t.increments().primary();
t.string('uuid');
t.string('name');
t.string('slug');
t.text('descripton');
t.integer('parent_id').nullable();
t.string('meta_title');
t.text('meta_description');
t.string('meta_keywords');
t.dateTime('created_at');
t.integer('created_by');
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
}),
knex.Schema.createTable('posts_tags', function (t) {
t.increments().primary();
t.string('uuid');
t.integer('post_id');
t.integer('tag_id');
}),
knex.Schema.createTable('custom_data', function (t) {
t.increments().primary();
t.string('uuid');
t.string('name');
t.string('slug');
t.text('value');
t.string('type').defaultTo('html');
t.string('owner').defaultTo('Ghost');
t.string('meta_title');
t.text('meta_description');
t.string('meta_keywords');
t.dateTime('created_at');
t.integer('created_by');
t.dateTime('updated_at').nullable();
t.integer('updated_by').nullable();
}),
knex.Schema.createTable('posts_custom_data', function (t) {
t.increments().primary();
t.string('uuid');
t.integer('post_id');
t.integer('custom_data_id');
}),
knex.Schema.table('users', function (t) {
t.string('location').after('bio');
})
]).then(function () {
// Lastly, update the current version settings to reflect this version
return knex('settings')
.where('key', 'currentVersion')
.update({ 'value': migrationVersion });
});
};
down = function () {
return when.all([
knex.Schema.dropTableIfExists("tags"),
knex.Schema.dropTableIfExists("custom_data")
]).then(function () {
// Drop the relation tables after the model tables?
return when.all([
knex.Schema.dropTableIfExists("posts_tags"),
knex.Schema.dropTableIfExists("posts_custom_data")
]);
});
// Should we also drop the currentVersion?
};
exports.up = up;
exports.down = down;

View File

@ -4,49 +4,105 @@ var _ = require('underscore'),
series = require('when/sequence'), series = require('when/sequence'),
errors = require('../../errorHandling'), errors = require('../../errorHandling'),
knex = require('../../models/base').Knex, knex = require('../../models/base').Knex,
initialVersion = "001",
// This currentVersion string should always be the current version of Ghost,
// we could probably load it from the config file.
// - Will be possible after default-settings.json restructure
currentVersion = "002";
function getCurrentVersion() { defaultSettings = require('../default-settings'),
return knex.Schema.hasTable('settings').then(function () { Settings = require('../../models/settings').Settings,
fixtures = require('../fixtures'),
initialVersion = '000',
defaultDatabaseVersion;
// Default Database Version
// The migration version number according to the hardcoded default settings
// This is the version the database should be at or migrated to
function getDefaultDatabaseVersion() {
if (!defaultDatabaseVersion) {
// This be the current version according to the software
defaultDatabaseVersion = _.find(defaultSettings.core, function (setting) {
return setting.key === 'databaseVersion';
}).defaultValue;
}
return defaultDatabaseVersion;
}
// Database Current Version
// The migration version number according to the database
// This is what the database is currently at and may need to be updated
function getDatabaseVersion() {
return knex.Schema.hasTable('settings').then(function (exists) {
// Check for the current version from the settings table // Check for the current version from the settings table
return knex('settings') if (exists) {
.where('key', 'currentVersion') // Temporary code to deal with old databases with currentVersion settings
.select('value') // TODO: remove before release
.then(function (currentVersionSetting) { return knex('settings')
if (currentVersionSetting && currentVersionSetting.length > 0) { .where('key', 'databaseVersion')
currentVersionSetting = currentVersionSetting[0].value; .orWhere('key', 'currentVersion')
} else { .select('value')
// we didn't get a response we understood, assume initialVersion .then(function (versions) {
currentVersionSetting = initialVersion; var databaseVersion = _.reduce(versions, function (memo, version) {
} return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo;
return currentVersionSetting; }, initialVersion);
});
if (!databaseVersion || databaseVersion.length === 0) {
// we didn't get a response we understood, assume initialVersion
databaseVersion = initialVersion;
}
return when.resolve(databaseVersion);
});
}
return when.reject('Settings table does not exist');
}); });
} }
function setDatabaseVersion() {
return knex('settings')
.where('key', 'databaseVersion')
.update({ 'value': defaultDatabaseVersion });
}
module.exports = { module.exports = {
currentVersion: currentVersion,
// Check for whether data is needed to be bootstrapped or not // Check for whether data is needed to be bootstrapped or not
init: function () { init: function () {
var self = this; var self = this;
return getCurrentVersion().then(function (currentVersionSetting) { // There are 4 possibilities:
// We are assuming here that the currentVersionSetting will // 1. The database exists and is up-to-date
// always be less than the currentVersion value. // 2. The database exists but is out of date
if (currentVersionSetting === currentVersion) { // 3. The database exists but the currentVersion setting does not or cannot be understood
// 4. The database has not yet been created
return getDatabaseVersion().then(function (databaseVersion) {
var defaultVersion = getDefaultDatabaseVersion();
if (databaseVersion === defaultVersion) {
// 1. The database exists and is up-to-date
return when.resolve(); return when.resolve();
} }
// Bring the data up to the latest version if (databaseVersion < defaultVersion) {
return self.migrateUpFromVersion(currentVersion); // 2. The database exists but is out of date
}, function () { return self.migrateUpFromVersion(databaseVersion);
// If the settings table doesn't exist, bring everything up from initial version. }
return self.migrateUpFromVersion(initialVersion);
if (databaseVersion > defaultVersion) {
// 3. The database exists but the currentVersion setting does not or cannot be understood
// In this case we don't understand the version because it is too high
errors.logError('Database is not compatible with software version.');
process.exit(-3);
}
}, function (err) {
if (err === 'Settings table does not exist') {
// 4. The database has not yet been created
// Bring everything up from initial version.
return self.migrateUpFreshDb();
}
// 3. The database exists but the currentVersion setting does not or cannot be understood
// In this case the setting was missing or there was some other problem
errors.logError('Database is not recognisable.' + err);
process.exit(-2);
}); });
}, },
@ -55,19 +111,33 @@ module.exports = {
reset: function () { reset: function () {
var self = this; var self = this;
return getCurrentVersion().then(function (currentVersionSetting) { return getDatabaseVersion().then(function (databaseVersion) {
// bring everything down from the current version // bring everything down from the current version
return self.migrateDownFromVersion(currentVersionSetting); return self.migrateDownFromVersion(databaseVersion);
}, function () { }, function () {
// If the settings table doesn't exist, bring everything down from initial version. // If the settings table doesn't exist, bring everything down from initial version.
return self.migrateDownFromVersion(initialVersion); return self.migrateDownFromVersion(initialVersion);
}); });
}, },
// Only do this if we have no database at all
migrateUpFreshDb: function () {
var migration = require('./' + initialVersion);
return migration.up().then(function () {
// Load the fixtures
return fixtures.populateFixtures();
}).then(function () {
// Initialise the default settings
return Settings.populateDefaults();
});
},
// Migrate from a specific version to the latest // Migrate from a specific version to the latest
migrateUpFromVersion: function (version, max) { migrateUpFromVersion: function (version, max) {
var versions = [], var versions = [],
maxVersion = max || this.getVersionAfter(currentVersion), maxVersion = max || this.getVersionAfter(getDefaultDatabaseVersion()),
currVersion = version, currVersion = version,
tasks = []; tasks = [];
@ -91,11 +161,15 @@ module.exports = {
}); });
// Run each migration in series // Run each migration in series
return series(tasks); return series(tasks).then(function () {
// Finally update the databases current version
return setDatabaseVersion();
});
}, },
migrateDownFromVersion: function (version) { migrateDownFromVersion: function (version) {
var versions = [], var self = this,
versions = [],
minVersion = this.getVersionBefore(initialVersion), minVersion = this.getVersionBefore(initialVersion),
currVersion = version, currVersion = version,
tasks = []; tasks = [];
@ -114,7 +188,7 @@ module.exports = {
return migration.down(); return migration.down();
} catch (e) { } catch (e) {
errors.logError(e); errors.logError(e);
return when.reject(e); return self.migrateDownFromVersion(initialVersion);
} }
}; };
}); });

View File

@ -87,7 +87,7 @@ coreHelpers = function (ghost) {
// if the author could not be determined. // if the author could not be determined.
// //
ghost.registerThemeHelper('author', function (context, options) { ghost.registerThemeHelper('author', function (context, options) {
return this.author ? this.author.full_name : ""; return this.author ? this.author.name : "";
}); });
// ### Tags Helper // ### Tags Helper
@ -133,11 +133,11 @@ coreHelpers = function (ghost) {
if (truncateOptions.words || truncateOptions.characters) { if (truncateOptions.words || truncateOptions.characters) {
return new hbs.handlebars.SafeString( return new hbs.handlebars.SafeString(
downsize(this.content, truncateOptions) downsize(this.html, truncateOptions)
); );
} }
return new hbs.handlebars.SafeString(this.content); return new hbs.handlebars.SafeString(this.html);
}); });
@ -161,7 +161,7 @@ coreHelpers = function (ghost) {
truncateOptions = _.pick(truncateOptions, ["words", "characters"]); truncateOptions = _.pick(truncateOptions, ["words", "characters"]);
/*jslint regexp:true */ /*jslint regexp:true */
excerpt = String(this.content).replace(/<\/?[^>]+>/gi, ""); excerpt = String(this.html).replace(/<\/?[^>]+>/gi, "");
/*jslint regexp:false */ /*jslint regexp:false */
if (!truncateOptions.words && !truncateOptions.characters) { if (!truncateOptions.words && !truncateOptions.characters) {

View File

@ -1,6 +1,9 @@
var GhostBookshelf, var GhostBookshelf,
Bookshelf = require('bookshelf'), Bookshelf = require('bookshelf'),
when = require('when'),
moment = require('moment'),
_ = require('underscore'), _ = require('underscore'),
uuid = require('node-uuid'),
config = require('../../../config'), config = require('../../../config'),
Validator = require('validator').Validator; Validator = require('validator').Validator;
@ -14,12 +17,39 @@ GhostBookshelf.validator = new Validator();
// including some convenience functions as static properties on the model. // including some convenience functions as static properties on the model.
GhostBookshelf.Model = GhostBookshelf.Model.extend({ GhostBookshelf.Model = GhostBookshelf.Model.extend({
hasTimestamps: true,
defaults: function () {
return {
uuid: uuid.v4()
};
},
initialize: function () {
this.on('creating', this.creating, this);
this.on('saving', this.saving, this);
this.on('saving', this.validate, this);
},
creating: function () {
if (!this.get('created_by')) {
this.set('created_by', 1);
}
},
saving: function () {
// Remove any properties which don't belong on the post model
this.attributes = this.pick(this.permittedAttributes);
this.set('updated_by', 1);
},
// Base prototype properties will go here // Base prototype properties will go here
// Fix problems with dates // Fix problems with dates
fixDates: function (attrs) { fixDates: function (attrs) {
_.each(attrs, function (value, key) { _.each(attrs, function (value, key) {
if (key.substr(-3) === '_at' && value !== null) { if (key.substr(-3) === '_at' && value !== null) {
attrs[key] = new Date(attrs[key]); attrs[key] = moment(attrs[key]).toDate();
} }
}); });
@ -45,6 +75,60 @@ GhostBookshelf.Model = GhostBookshelf.Model.extend({
}); });
return attrs; return attrs;
},
// #### generateSlug
// Create a string act as the permalink for an object.
generateSlug: function (Model, base) {
var slug,
slugTryCount = 1,
// Look for a post with a matching slug, append an incrementing number if so
checkIfSlugExists = function (slugToFind) {
return Model.read({slug: slugToFind}).then(function (found) {
var trimSpace;
if (!found) {
return when.resolve(slugToFind);
}
slugTryCount += 1;
// If this is the first time through, add the hyphen
if (slugTryCount === 2) {
slugToFind += '-';
} else {
// Otherwise, trim the number off the end
trimSpace = -(String(slugTryCount - 1).length);
slugToFind = slugToFind.slice(0, trimSpace);
}
slugToFind += slugTryCount;
return checkIfSlugExists(slugToFind);
});
};
// Remove URL reserved chars: `:/?#[]@!$&'()*+,;=` as well as `\%<>|^~£"`
slug = base.trim().replace(/[:\/\?#\[\]@!$&'()*+,;=\\%<>\|\^~£"]/g, '')
// Replace dots and spaces with a dash
.replace(/(\s|\.)/g, '-')
// Convert 2 or more dashes into a single dash
.replace(/-+/g, '-')
// Make the whole thing lowercase
.toLowerCase();
// Remove trailing hypen
slug = slug.charAt(slug.length - 1) === '-' ? slug.substr(0, slug.length - 1) : slug;
// Check the filtered slug doesn't match any of the reserved keywords
slug = /^(ghost|ghost\-admin|admin|wp\-admin|dashboard|login|archive|archives|category|categories|tag|tags|page|pages|post|posts|user|users)$/g
.test(slug) ? slug + '-post' : slug;
//if slug is empty after trimming use "post"
if (!slug) {
slug = "post";
}
// Test for duplicate slugs.
return checkIfSlugExists(slug);
} }
}, { }, {

View File

@ -16,7 +16,7 @@ module.exports = {
}); });
}, },
isPost: function (jsonData) { isPost: function (jsonData) {
return jsonData.hasOwnProperty("content") && jsonData.hasOwnProperty("content_raw") return jsonData.hasOwnProperty("html") && jsonData.hasOwnProperty("markdown")
&& jsonData.hasOwnProperty("title") && jsonData.hasOwnProperty("slug"); && jsonData.hasOwnProperty("title") && jsonData.hasOwnProperty("slug");
} }
}; };

View File

@ -5,27 +5,18 @@ var GhostBookshelf = require('./base'),
Permissions; Permissions;
Permission = GhostBookshelf.Model.extend({ Permission = GhostBookshelf.Model.extend({
tableName: 'permissions', tableName: 'permissions',
permittedAttributes: ['id', 'name', 'object_type', 'action_type', 'object_id'], permittedAttributes: ['id', 'uuid', 'name', 'object_type', 'action_type', 'object_id', 'created_at', 'created_by',
'updated_at', 'updated_by'],
initialize: function () {
this.on('saving', this.saving, this);
this.on('saving', this.validate, this);
},
validate: function () { validate: function () {
// TODO: validate object_type, action_type and object_id // TODO: validate object_type, action_type and object_id
GhostBookshelf.validator.check(this.get('name'), "Permission name cannot be blank").notEmpty(); GhostBookshelf.validator.check(this.get('name'), "Permission name cannot be blank").notEmpty();
}, },
saving: function () {
// Deal with the related data here
// Remove any properties which don't belong on the post model
this.attributes = this.pick(this.permittedAttributes);
},
roles: function () { roles: function () {
return this.belongsToMany(Role); return this.belongsToMany(Role);
}, },

View File

@ -8,6 +8,7 @@ var Post,
github = require('../../shared/vendor/showdown/extensions/github'), github = require('../../shared/vendor/showdown/extensions/github'),
converter = new Showdown.converter({extensions: [github]}), converter = new Showdown.converter({extensions: [github]}),
User = require('./user').User, User = require('./user').User,
config = require('../../../config'),
Tag = require('./tag').Tag, Tag = require('./tag').Tag,
Tags = require('./tag').Tags, Tags = require('./tag').Tags,
GhostBookshelf = require('./base'); GhostBookshelf = require('./base');
@ -17,18 +18,15 @@ Post = GhostBookshelf.Model.extend({
tableName: 'posts', tableName: 'posts',
permittedAttributes: [ permittedAttributes: [
'id', 'uuid', 'title', 'slug', 'content_raw', 'content', 'meta_title', 'meta_description', 'meta_keywords', 'id', 'uuid', 'title', 'slug', 'markdown', 'html', 'meta_title', 'meta_description',
'featured', 'image', 'status', 'language', 'author_id', 'created_at', 'created_by', 'updated_at', 'updated_by', 'featured', 'image', 'status', 'language', 'author_id', 'created_at', 'created_by', 'updated_at', 'updated_by',
'published_at', 'published_by' 'published_at', 'published_by'
], ],
hasTimestamps: true,
defaults: function () { defaults: function () {
return { return {
uuid: uuid.v4(), uuid: uuid.v4(),
status: 'draft' status: 'draft'
// TODO: language: ghost.config().defaultLang);
}; };
}, },
@ -46,108 +44,49 @@ Post = GhostBookshelf.Model.extend({
}, },
saving: function () { saving: function () {
// Deal with the related data here
var self = this; var self = this;
// Remove any properties which don't belong on the post model // Remove any properties which don't belong on the post model
this.attributes = this.pick(this.permittedAttributes); this.attributes = this.pick(this.permittedAttributes);
this.set('content', converter.makeHtml(this.get('content_raw'))); this.set('html', converter.makeHtml(this.get('markdown')));
this.set('title', this.get('title').trim()); this.set('title', this.get('title').trim());
if (this.hasChanged('slug')) {
// Pass the new slug through the generator to strip illegal characters, detect duplicates
return this.generateSlug(this.get('slug'))
.then(function (slug) {
self.set({slug: slug});
});
}
if (this.hasChanged('status') && this.get('status') === 'published') { if (this.hasChanged('status') && this.get('status') === 'published') {
this.set('published_at', new Date()); this.set('published_at', new Date());
// This will need to go elsewhere in the API layer. // This will need to go elsewhere in the API layer.
this.set('published_by', 1); this.set('published_by', 1);
} }
this.set('updated_by', 1); GhostBookshelf.Model.prototype.saving.call(this);
// refactoring of ghost required in order to make these details available here if (this.hasChanged('slug')) {
// Pass the new slug through the generator to strip illegal characters, detect duplicates
}, return this.generateSlug(Post, this.get('slug'))
creating: function () {
// set any dynamic default properties
var self = this;
if (!this.get('created_by')) {
this.set('created_by', 1);
}
if (!this.get('author_id')) {
this.set('author_id', 1);
}
if (!this.get('slug')) {
// Generating a slug requires a db call to look for conflicting slugs
return this.generateSlug(this.get('title'))
.then(function (slug) { .then(function (slug) {
self.set({slug: slug}); self.set({slug: slug});
}); });
} }
}, },
// #### generateSlug creating: function () {
// Create a string act as the permalink for a post. // set any dynamic default properties
generateSlug: function (title) { var self = this;
var slug,
slugTryCount = 1,
// Look for a post with a matching slug, append an incrementing number if so
checkIfSlugExists = function (slugToFind) {
return Post.read({slug: slugToFind}).then(function (found) {
var trimSpace;
if (!found) { if (!this.get('author_id')) {
return when.resolve(slugToFind); this.set('author_id', 1);
} }
slugTryCount += 1; GhostBookshelf.Model.prototype.creating.call(this);
// If this is the first time through, add the hyphen if (!this.get('slug')) {
if (slugTryCount === 2) { // Generating a slug requires a db call to look for conflicting slugs
slugToFind += '-'; return this.generateSlug(Post, this.get('title'))
} else { .then(function (slug) {
// Otherwise, trim the number off the end self.set({slug: slug});
trimSpace = -(String(slugTryCount - 1).length); });
slugToFind = slugToFind.slice(0, trimSpace);
}
slugToFind += slugTryCount;
return checkIfSlugExists(slugToFind);
});
};
// Remove URL reserved chars: `:/?#[]@!$&'()*+,;=` as well as `\%<>|^~£"`
slug = title.trim().replace(/[:\/\?#\[\]@!$&'()*+,;=\\%<>\|\^~£"]/g, '')
// Replace dots and spaces with a dash
.replace(/(\s|\.)/g, '-')
// Convert 2 or more dashes into a single dash
.replace(/-+/g, '-')
// Make the whole thing lowercase
.toLowerCase();
// Remove trailing hypen
slug = slug.charAt(slug.length - 1) === '-' ? slug.substr(0, slug.length - 1) : slug;
// Check the filtered slug doesn't match any of the reserved keywords
slug = /^(ghost|ghost\-admin|admin|wp\-admin|dashboard|login|archive|archives|category|categories|tag|tags|page|pages|post|posts)$/g
.test(slug) ? slug + '-post' : slug;
//if slug is empty after trimming use "post"
if (!slug) {
slug = "post";
} }
// Test for duplicate slugs.
return checkIfSlugExists(slug);
}, },
updateTags: function (newTags) { updateTags: function (newTags) {

View File

@ -5,27 +5,16 @@ var User = require('./user').User,
Roles; Roles;
Role = GhostBookshelf.Model.extend({ Role = GhostBookshelf.Model.extend({
tableName: 'roles', tableName: 'roles',
permittedAttributes: ['id', 'name', 'description'], permittedAttributes: ['id', 'uuid', 'name', 'description', 'created_at', 'created_by', 'updated_at', 'updated_by'],
initialize: function () {
this.on('saving', this.saving, this);
this.on('saving', this.validate, this);
},
validate: function () { validate: function () {
GhostBookshelf.validator.check(this.get('name'), "Role name cannot be blank").notEmpty(); GhostBookshelf.validator.check(this.get('name'), "Role name cannot be blank").notEmpty();
GhostBookshelf.validator.check(this.get('description'), "Role description cannot be blank").notEmpty(); GhostBookshelf.validator.check(this.get('description'), "Role description cannot be blank").notEmpty();
}, },
saving: function () {
// Deal with the related data here
// Remove any properties which don't belong on the post model
this.attributes = this.pick(this.permittedAttributes);
},
users: function () { users: function () {
return this.belongsToMany(User); return this.belongsToMany(User);
}, },

View File

@ -33,8 +33,6 @@ Settings = GhostBookshelf.Model.extend({
tableName: 'settings', tableName: 'settings',
hasTimestamps: true,
permittedAttributes: ['id', 'uuid', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'update_by'], permittedAttributes: ['id', 'uuid', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'update_by'],
defaults: function () { defaults: function () {
@ -44,10 +42,6 @@ Settings = GhostBookshelf.Model.extend({
}; };
}, },
initialize: function () {
this.on('saving', this.saving, this);
this.on('saving', this.validate, this);
},
// Validate default settings using the validator module. // Validate default settings using the validator module.
// Each validation's key is a name and its value is an array of options // Each validation's key is a name and its value is an array of options
@ -79,13 +73,6 @@ Settings = GhostBookshelf.Model.extend({
validation[validationName].apply(validation, validationOptions); validation[validationName].apply(validation, validationOptions);
}, this); }, this);
} }
},
saving: function () {
// Deal with the related data here
// Remove any properties which don't belong on the model
this.attributes = this.pick(this.permittedAttributes);
} }
}, { }, {
read: function (_key) { read: function (_key) {
@ -122,7 +109,7 @@ Settings = GhostBookshelf.Model.extend({
_.each(defaultSettings, function (defaultSetting, defaultSettingKey) { _.each(defaultSettings, function (defaultSetting, defaultSettingKey) {
var isMissingFromDB = usedKeys.indexOf(defaultSettingKey) === -1; var isMissingFromDB = usedKeys.indexOf(defaultSettingKey) === -1;
if (isMissingFromDB) { if (isMissingFromDB) {
defaultSetting.value = defaultSetting.default; defaultSetting.value = defaultSetting.defaultValue;
insertOperations.push(Settings.forge(defaultSetting).save()); insertOperations.push(Settings.forge(defaultSetting).save());
} }
}); });

View File

@ -1,18 +1,34 @@
var Tag, var Tag,
Tags, Tags,
uuid = require('node-uuid'),
Posts = require('./post').Posts, Posts = require('./post').Posts,
GhostBookshelf = require('./base'); GhostBookshelf = require('./base');
Tag = GhostBookshelf.Model.extend({ Tag = GhostBookshelf.Model.extend({
tableName: 'tags', tableName: 'tags',
hasTimestamps: true, permittedAttributes: [
'id', 'uuid', 'name', 'slug', 'description', 'parent_id', 'meta_title', 'meta_description', 'created_at',
'created_by', 'updated_at', 'updated_by'
],
defaults: function () { validate: function () {
return {
uuid: uuid.v4() return true;
}; },
creating: function () {
var self = this;
GhostBookshelf.Model.prototype.creating.call(this);
if (!this.get('slug')) {
// Generating a slug requires a db call to look for conflicting slugs
return this.generateSlug(Tag, this.get('name'))
.then(function (slug) {
self.set({slug: slug});
});
}
}, },
posts: function () { posts: function () {

View File

@ -26,67 +26,33 @@ User = GhostBookshelf.Model.extend({
tableName: 'users', tableName: 'users',
hasTimestamps: true,
permittedAttributes: [ permittedAttributes: [
'id', 'uuid', 'full_name', 'password', 'email_address', 'profile_picture', 'cover_picture', 'bio', 'url', 'location', 'id', 'uuid', 'name', 'slug', 'password', 'email', 'image', 'cover', 'bio', 'website', 'location',
'created_at', 'created_by', 'updated_at', 'updated_by' 'accessibility', 'status', 'language', 'meta_title', 'meta_description', 'created_at', 'created_by',
'updated_at', 'updated_by'
], ],
defaults: function () {
return {
uuid: uuid.v4()
};
},
parse: function (attrs) {
// temporary alias of name for full_name (will get changed in the schema)
if (attrs.full_name && !attrs.name) {
attrs.name = attrs.full_name;
}
// temporary alias of website for url (will get changed in the schema)
if (attrs.url && !attrs.website) {
attrs.website = attrs.url;
}
// temporary alias of email for email_address (will get changed in the schema)
if (attrs.email_address && !attrs.email) {
attrs.email = attrs.email_address;
}
// temporary alias of image for profile_picture (will get changed in the schema)
if (attrs.profile_picture && !attrs.image) {
attrs.image = attrs.profile_picture;
}
// temporary alias of cover for cover_picture (will get changed in the schema)
if (attrs.cover_picture && !attrs.cover) {
attrs.cover = attrs.cover_picture;
}
return attrs;
},
initialize: function () {
this.on('saving', this.saving, this);
this.on('saving', this.validate, this);
},
validate: function () { validate: function () {
GhostBookshelf.validator.check(this.get('email_address'), "Please check your email address. It does not seem to be valid.").isEmail(); GhostBookshelf.validator.check(this.get('email'), "Please check your email address. It does not seem to be valid.").isEmail();
GhostBookshelf.validator.check(this.get('bio'), "Your bio is too long. Please keep it to 200 chars.").len(0, 200); GhostBookshelf.validator.check(this.get('bio'), "Your bio is too long. Please keep it to 200 chars.").len(0, 200);
if (this.get('url') && this.get('url').length > 0) { if (this.get('website') && this.get('website').length > 0) {
GhostBookshelf.validator.check(this.get('url'), "Your website is not a valid URL.").isUrl(); GhostBookshelf.validator.check(this.get('website'), "Your website is not a valid URL.").isUrl();
} }
return true; return true;
}, },
saving: function () { creating: function () {
// Deal with the related data here var self = this;
// Remove any properties which don't belong on the post model GhostBookshelf.Model.prototype.creating.call(this);
this.attributes = this.pick(this.permittedAttributes);
if (!this.get('slug')) {
// Generating a slug requires a db call to look for conflicting slugs
return this.generateSlug(User, this.get('name'))
.then(function (slug) {
self.set({slug: slug});
});
}
}, },
posts: function () { posts: function () {
@ -152,7 +118,7 @@ User = GhostBookshelf.Model.extend({
* @author javorszky * @author javorszky
*/ */
// return this.forge({email_address: userData.email_address}).fetch().then(function (user) { // return this.forge({email: userData.email}).fetch().then(function (user) {
// if (user !== null) { // if (user !== null) {
// return when.reject(new Error('A user with that email address already exists.')); // return when.reject(new Error('A user with that email address already exists.'));
// } // }
@ -168,7 +134,7 @@ User = GhostBookshelf.Model.extend({
// Finds the user by email, and checks the password // Finds the user by email, and checks the password
check: function (_userdata) { check: function (_userdata) {
return this.forge({ return this.forge({
email_address: _userdata.email email: _userdata.email
}).fetch({require: true}).then(function (user) { }).fetch({require: true}).then(function (user) {
return nodefn.call(bcrypt.compare, _userdata.pw, user.get('password')).then(function (matched) { return nodefn.call(bcrypt.compare, _userdata.pw, user.get('password')).then(function (matched) {
if (!matched) { if (!matched) {
@ -220,7 +186,7 @@ User = GhostBookshelf.Model.extend({
var newPassword = Math.random().toString(36).slice(2, 12), // This is magick var newPassword = Math.random().toString(36).slice(2, 12), // This is magick
user = null; user = null;
return this.forge({email_address: email}).fetch({require: true}).then(function (_user) { return this.forge({email: email}).fetch({require: true}).then(function (_user) {
user = _user; user = _user;
return nodefn.call(bcrypt.hash, newPassword, null, null); return nodefn.call(bcrypt.hash, newPassword, null, null);
}).then(function (hash) { }).then(function (hash) {

View File

@ -14,11 +14,11 @@ I18n = function (ghost) {
return function (req, res, next) { return function (req, res, next) {
if (lang === 'en') { if (lang === 'en_US') {
// TODO: do stuff here to optimise for en // TODO: do stuff here to optimise for en
// Make jslint empty block error go away // Make jslint empty block error go away
lang = 'en'; lang = 'en_US';
} }
/** TODO: potentially use req.acceptedLanguages rather than the default /** TODO: potentially use req.acceptedLanguages rather than the default
@ -26,8 +26,8 @@ I18n = function (ghost) {
* TODO: switch this mess to be promise driven */ * TODO: switch this mess to be promise driven */
fs.stat(langFilePath, function (error) { fs.stat(langFilePath, function (error) {
if (error) { if (error) {
console.log('No language file found for language ' + lang + '. Defaulting to en'); console.log('No language file found for language ' + lang + '. Defaulting to en_US');
lang = 'en'; lang = 'en_US';
} }
fs.readFile(langFilePath, function (error, data) { fs.readFile(langFilePath, function (error, data) {

View File

@ -29,7 +29,7 @@ casper.test.begin("Ghost editor is correct", 10, function suite(test) {
casper.then(function createTestPost() { casper.then(function createTestPost() {
casper.sendKeys('#entry-title', testPost.title); casper.sendKeys('#entry-title', testPost.title);
casper.writeContentToCodeMirror(testPost.content); casper.writeContentToCodeMirror(testPost.html);
}); });
// We must wait after sending keys to CodeMirror // We must wait after sending keys to CodeMirror
@ -50,7 +50,7 @@ casper.test.begin("Ghost editor is correct", 10, function suite(test) {
}, testPost.title, 'Title is correct'); }, testPost.title, 'Title is correct');
// TODO: make this work - spaces & newlines are problematic // TODO: make this work - spaces & newlines are problematic
// test.assertTextExists(testPost.content, 'Post content exists'); // test.assertTextExists(testPost.html, 'Post html exists');
}); });
casper.run(function () { casper.run(function () {

View File

@ -42,7 +42,7 @@ var host = casper.cli.options.host || 'localhost',
}, },
testPost = { testPost = {
title: "Bacon ipsum dolor sit amet", title: "Bacon ipsum dolor sit amet",
content: "I am a test post.\n#I have some small content" html: "I am a test post.\n#I have some small content"
}; };
casper.writeContentToCodeMirror = function (content) { casper.writeContentToCodeMirror = function (content) {

View File

@ -161,7 +161,9 @@ describe("Permission Model", function () {
it("can add permissions", function (done) { it("can add permissions", function (done) {
var newPerm = { var newPerm = {
name: "testperm1" name: "testperm1",
object_type: 'test',
action_type: 'test'
}; };
PermissionModel.add(newPerm).then(function (createdPerm) { PermissionModel.add(newPerm).then(function (createdPerm) {

View File

@ -14,8 +14,8 @@ describe('Post Model', function () {
UserModel = Models.User, UserModel = Models.User,
userData = { userData = {
password: 'testpass1', password: 'testpass1',
email_address: "test@test1.com", email: "test@test1.com",
full_name: "Mr Biscuits" name: "Mr Biscuits"
}; };
before(function (done) { before(function (done) {
@ -81,8 +81,8 @@ describe('Post Model', function () {
firstPost.author.should.be.a("object"); firstPost.author.should.be.a("object");
firstPost.user.should.be.a("object"); firstPost.user.should.be.a("object");
firstPost.author.full_name.should.equal("Mr Biscuits"); firstPost.author.name.should.equal("Mr Biscuits");
firstPost.user.full_name.should.equal("Mr Biscuits"); firstPost.user.name.should.equal("Mr Biscuits");
done(); done();
}, done); }, done);
@ -97,8 +97,8 @@ describe('Post Model', function () {
firstPost.author.should.be.a("object"); firstPost.author.should.be.a("object");
firstPost.user.should.be.a("object"); firstPost.user.should.be.a("object");
firstPost.author.full_name.should.equal("Mr Biscuits"); firstPost.author.name.should.equal("Mr Biscuits");
firstPost.user.full_name.should.equal("Mr Biscuits"); firstPost.user.name.should.equal("Mr Biscuits");
done(); done();
}, done); }, done);
@ -125,7 +125,7 @@ describe('Post Model', function () {
var createdPostUpdatedDate, var createdPostUpdatedDate,
newPost = { newPost = {
title: 'Test Title 1', title: 'Test Title 1',
content_raw: 'Test Content 1' markdown: 'Test Content 1'
}; };
PostModel.add(newPost).then(function (createdPost) { PostModel.add(newPost).then(function (createdPost) {
@ -135,9 +135,9 @@ describe('Post Model', function () {
createdPost.has('uuid').should.equal(true); createdPost.has('uuid').should.equal(true);
createdPost.get('status').should.equal('draft'); createdPost.get('status').should.equal('draft');
createdPost.get('title').should.equal(newPost.title, "title is correct"); createdPost.get('title').should.equal(newPost.title, "title is correct");
createdPost.get('content_raw').should.equal(newPost.content_raw, "content_raw is correct"); createdPost.get('markdown').should.equal(newPost.markdown, "markdown is correct");
createdPost.has('content').should.equal(true); createdPost.has('html').should.equal(true);
createdPost.get('content').should.equal('<p>' + newPost.content_raw + '</p>'); createdPost.get('html').should.equal('<p>' + newPost.markdown + '</p>');
createdPost.get('slug').should.equal('test-title-1'); createdPost.get('slug').should.equal('test-title-1');
createdPost.get('created_at').should.be.below(new Date().getTime()).and.be.above(new Date(0).getTime()); createdPost.get('created_at').should.be.below(new Date().getTime()).and.be.above(new Date(0).getTime());
createdPost.get('created_by').should.equal(1); createdPost.get('created_by').should.equal(1);
@ -169,7 +169,7 @@ describe('Post Model', function () {
untrimmedUpdateTitle = ' test trimmed update title ', untrimmedUpdateTitle = ' test trimmed update title ',
newPost = { newPost = {
title: untrimmedCreateTitle, title: untrimmedCreateTitle,
content_raw: 'Test Content' markdown: 'Test Content'
}; };
PostModel.add(newPost).then(function (createdPost) { PostModel.add(newPost).then(function (createdPost) {
@ -189,7 +189,7 @@ describe('Post Model', function () {
it('can generate a non conflicting slug', function (done) { it('can generate a non conflicting slug', function (done) {
var newPost = { var newPost = {
title: 'Test Title', title: 'Test Title',
content_raw: 'Test Content 1' markdown: 'Test Content 1'
}; };
this.timeout(5000); // this is a patch to ensure it doesn't timeout. this.timeout(5000); // this is a patch to ensure it doesn't timeout.
@ -199,7 +199,7 @@ describe('Post Model', function () {
return function () { return function () {
return PostModel.add({ return PostModel.add({
title: "Test Title", title: "Test Title",
content_raw: "Test Content " + (i+1) markdown: "Test Content " + (i+1)
}); });
}; };
})).then(function (createdPosts) { })).then(function (createdPosts) {
@ -217,7 +217,7 @@ describe('Post Model', function () {
} }
post.get('slug').should.equal('test-title-' + num); post.get('slug').should.equal('test-title-' + num);
post.get('content_raw').should.equal('Test Content ' + num); post.get('markdown').should.equal('Test Content ' + num);
}); });
done(); done();
@ -228,7 +228,7 @@ describe('Post Model', function () {
it('can generate slugs without duplicate hyphens', function (done) { it('can generate slugs without duplicate hyphens', function (done) {
var newPost = { var newPost = {
title: 'apprehensive titles have too many spaces ', title: 'apprehensive titles have too many spaces ',
content_raw: 'Test Content 1' markdown: 'Test Content 1'
}; };
PostModel.add(newPost).then(function (createdPost) { PostModel.add(newPost).then(function (createdPost) {

View File

@ -76,7 +76,7 @@ describe('Settings Model', function () {
results.length.should.be.above(0); results.length.should.be.above(0);
firstSetting = results.models[0]; firstSetting = results.models[1];
// The edit method has been modified to take an object of // The edit method has been modified to take an object of
// key/value pairs // key/value pairs
@ -111,8 +111,8 @@ describe('Settings Model', function () {
results.length.should.be.above(0); results.length.should.be.above(0);
model1 = results.models[0]; model1 = results.models[1];
model2 = results.models[1]; model2 = results.models[2];
// The edit method has been modified to take an object of // The edit method has been modified to take an object of
// key/value pairs // key/value pairs

View File

@ -21,7 +21,6 @@ describe('Tag Model', function () {
beforeEach(function (done) { beforeEach(function (done) {
this.timeout(5000); this.timeout(5000);
testUtils.initData() testUtils.initData()
.then(function () {})
.then(function () { .then(function () {
done(); done();
}, done); }, done);
@ -37,7 +36,7 @@ describe('Tag Model', function () {
var PostModel = Models.Post; var PostModel = Models.Post;
it('can add a tag', function (done) { it('can add a tag', function (done) {
var newPost = {title: 'Test Title 1', content_raw: 'Test Content 1'}, var newPost = {title: 'Test Title 1', markdown: 'Test Content 1'},
newTag = {name: 'tag1'}, newTag = {name: 'tag1'},
createdPostID; createdPostID;
@ -63,7 +62,7 @@ describe('Tag Model', function () {
// The majority of this test is ripped from above, which is obviously a Bad Thing. // The majority of this test is ripped from above, which is obviously a Bad Thing.
// Would be nice to find a way to seed data with relations for cases like this, // Would be nice to find a way to seed data with relations for cases like this,
// because there are more DB hits than needed // because there are more DB hits than needed
var newPost = {title: 'Test Title 1', content_raw: 'Test Content 1'}, var newPost = {title: 'Test Title 1', markdown: 'Test Content 1'},
newTag = {name: 'tag1'}, newTag = {name: 'tag1'},
createdTagID, createdTagID,
createdPostID; createdPostID;
@ -97,7 +96,7 @@ describe('Tag Model', function () {
function seedTags(tagNames) { function seedTags(tagNames) {
var createOperations = [ var createOperations = [
PostModel.add({title: 'title', content_raw: 'content'}) PostModel.add({title: 'title', markdown: 'content'})
]; ];
var tagModels = tagNames.map(function (tagName) { return TagModel.add({name: tagName}); }); var tagModels = tagNames.map(function (tagName) { return TagModel.add({name: tagName}); });
@ -210,7 +209,7 @@ describe('Tag Model', function () {
it('can add a tag to a post on creation', function (done) { it('can add a tag to a post on creation', function (done) {
var newPost = {title: 'Test Title 1', content_raw: 'Test Content 1', tags: ['test_tag_1']}; var newPost = {title: 'Test Title 1', markdown: 'Test Content 1', tags: [{name: 'test_tag_1'}]};
PostModel.add(newPost).then(function (createdPost) { PostModel.add(newPost).then(function (createdPost) {
return PostModel.read({id: createdPost.id}, { withRelated: ['tags']}); return PostModel.read({id: createdPost.id}, { withRelated: ['tags']});

View File

@ -34,15 +34,16 @@ describe('User Model', function run() {
it('can add first', function (done) { it('can add first', function (done) {
var userData = { var userData = {
name: 'test',
password: 'testpass1', password: 'testpass1',
email_address: "test@test1.com" email: "test@test1.com"
}; };
UserModel.add(userData).then(function (createdUser) { UserModel.add(userData).then(function (createdUser) {
should.exist(createdUser); should.exist(createdUser);
createdUser.has('uuid').should.equal(true); createdUser.has('uuid').should.equal(true);
createdUser.attributes.password.should.not.equal(userData.password, "password was hashed"); createdUser.attributes.password.should.not.equal(userData.password, "password was hashed");
createdUser.attributes.email_address.should.eql(userData.email_address, "email address correct"); createdUser.attributes.email.should.eql(userData.email, "email address correct");
done(); done();
}).then(null, done); }).then(null, done);
@ -64,16 +65,15 @@ describe('User Model', function run() {
it('can\'t add second', function (done) { it('can\'t add second', function (done) {
var userData = { var userData = {
name: 'test',
password: 'testpass3', password: 'testpass3',
email_address: "test3@test1.com" email: "test3@test1.com"
}; };
return testUtils.insertDefaultUser().then(function () { return UserModel.add(userData).then(done, function (failure) {
UserModel.add(userData).then(done, function (failure) { failure.message.should.eql('A user is already registered. Only one user for now!');
failure.message.should.eql('A user is already registered. Only one user for now!'); done();
done(); }).then(null, done);
}).then(null, done);
});
}); });
it('can browse', function (done) { it('can browse', function (done) {
@ -99,13 +99,13 @@ describe('User Model', function run() {
firstUser = results.models[0]; firstUser = results.models[0];
return UserModel.read({email_address: firstUser.attributes.email_address}); return UserModel.read({email: firstUser.attributes.email});
}).then(function (found) { }).then(function (found) {
should.exist(found); should.exist(found);
found.attributes.full_name.should.equal(firstUser.attributes.full_name); found.attributes.name.should.equal(firstUser.attributes.name);
done(); done();
@ -124,13 +124,13 @@ describe('User Model', function run() {
firstUser = results.models[0]; firstUser = results.models[0];
return UserModel.edit({id: firstUser.id, url: "some.newurl.com"}); return UserModel.edit({id: firstUser.id, website: "some.newurl.com"});
}).then(function (edited) { }).then(function (edited) {
should.exist(edited); should.exist(edited);
edited.attributes.url.should.equal('some.newurl.com'); edited.attributes.website.should.equal('some.newurl.com');
done(); done();

View File

@ -1,122 +1,122 @@
/*globals describe, beforeEach, it*/ ///*globals describe, beforeEach, it*/
var testUtils = require('./testUtils'), //var testUtils = require('./testUtils'),
should = require('should'), // should = require('should'),
sinon = require('sinon'), // sinon = require('sinon'),
when = require('when'), // when = require('when'),
_ = require("underscore"), // _ = require("underscore"),
errors = require('../../server/errorHandling'), // errors = require('../../server/errorHandling'),
//
// Stuff we are testing // // Stuff we are testing
migration = require('../../server/data/migration'), // migration = require('../../server/data/migration'),
exporter = require('../../server/data/export'), // exporter = require('../../server/data/export'),
Exporter001 = require('../../server/data/export/001'), // Exporter001 = require('../../server/data/export/001'),
Exporter002 = require('../../server/data/export/002'), // Exporter002 = require('../../server/data/export/002'),
Settings = require('../../server/models/settings').Settings; // Settings = require('../../server/models/settings').Settings;
//
describe("Export", function () { //describe("Export", function () {
//
should.exist(exporter); // should.exist(exporter);
//
beforeEach(function (done) { // beforeEach(function (done) {
// clear database... we need to initialise it manually for each test // // clear database... we need to initialise it manually for each test
testUtils.clearData().then(function () { // testUtils.clearData().then(function () {
done(); // done();
}, done); // }, done);
}); // });
//
it("resolves 001", function (done) { // it("resolves 001", function (done) {
var exportStub = sinon.stub(Exporter001, "exportData", function () { // var exportStub = sinon.stub(Exporter001, "exportData", function () {
return when.resolve(); // return when.resolve();
}); // });
//
exporter("001").then(function () { // exporter("001").then(function () {
exportStub.called.should.equal(true); // exportStub.called.should.equal(true);
//
exportStub.restore(); // exportStub.restore();
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
//
describe("001", function () { // describe("001", function () {
//
should.exist(Exporter001); // should.exist(Exporter001);
//
it("exports data", function (done) { // it("exports data", function (done) {
// initialise database to version 001 - confusingly we have to set the max version to be one higher // // initialise database to version 001 - confusingly we have to set the max version to be one higher
// than the migration version we want // // than the migration version we want
migration.migrateUpFromVersion('001', '002').then(function () { // migration.migrateUpFromVersion('001', '002').then(function () {
return Settings.populateDefaults(); // return Settings.populateDefaults();
}).then(function () { // }).then(function () {
return exporter("001"); // return exporter("001");
}).then(function (exportData) { // }).then(function (exportData) {
var tables = ['posts', 'users', 'roles', 'roles_users', 'permissions', 'permissions_roles', 'settings']; // var tables = ['posts', 'users', 'roles', 'roles_users', 'permissions', 'permissions_roles', 'settings'];
//
should.exist(exportData); // should.exist(exportData);
//
should.exist(exportData.meta); // should.exist(exportData.meta);
should.exist(exportData.data); // should.exist(exportData.data);
//
exportData.meta.version.should.equal("001"); // exportData.meta.version.should.equal("001");
_.findWhere(exportData.data.settings, {key: "currentVersion"}).value.should.equal("001"); // _.findWhere(exportData.data.settings, {key: "databaseVersion"}).value.should.equal("001");
//
_.each(tables, function (name) { // _.each(tables, function (name) {
should.exist(exportData.data[name]); // should.exist(exportData.data[name]);
}); // });
// 002 data should not be present // // 002 data should not be present
should.not.exist(exportData.data.tags); // should.not.exist(exportData.data.tags);
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
}); // });
//
it("resolves 002", function (done) { // it("resolves 002", function (done) {
var exportStub = sinon.stub(Exporter002, "exportData", function () { // var exportStub = sinon.stub(Exporter002, "exportData", function () {
return when.resolve(); // return when.resolve();
}); // });
//
exporter("002").then(function () { // exporter("002").then(function () {
exportStub.called.should.equal(true); // exportStub.called.should.equal(true);
//
exportStub.restore(); // exportStub.restore();
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
//
describe("002", function () { // describe("002", function () {
this.timeout(5000); // this.timeout(5000);
//
should.exist(Exporter001); // should.exist(Exporter001);
//
it("exports data", function (done) { // it("exports data", function (done) {
// initialise database to version 001 - confusingly we have to set the max version to be one higher // // initialise database to version 001 - confusingly we have to set the max version to be one higher
// than the migration version we want // // than the migration version we want
migration.migrateUpFromVersion('001', '003').then(function () { // migration.migrateUpFromVersion('001', '003').then(function () {
return Settings.populateDefaults(); // return Settings.populateDefaults();
}).then(function () { // }).then(function () {
return exporter("002"); // return exporter("002");
}).then(function (exportData) { // }).then(function (exportData) {
var tables = [ // var tables = [
'posts', 'users', 'roles', 'roles_users', 'permissions', 'permissions_roles', // 'posts', 'users', 'roles', 'roles_users', 'permissions', 'permissions_roles',
'settings', 'tags', 'posts_tags', 'custom_data', 'posts_custom_data' // 'settings', 'tags', 'posts_tags', 'custom_data', 'posts_custom_data'
]; // ];
//
should.exist(exportData); // should.exist(exportData);
//
should.exist(exportData.meta); // should.exist(exportData.meta);
should.exist(exportData.data); // should.exist(exportData.data);
//
exportData.meta.version.should.equal("002"); // exportData.meta.version.should.equal("002");
_.findWhere(exportData.data.settings, {key: "currentVersion"}).value.should.equal("002"); // _.findWhere(exportData.data.settings, {key: "databaseVersion"}).value.should.equal("002");
//
_.each(tables, function (name) { // _.each(tables, function (name) {
should.exist(exportData.data[name]); // should.exist(exportData.data[name]);
}); // });
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
}); // });
}); //});

View File

@ -1,4 +1,4 @@
/*globals describe, beforeEach, it*/ /*globals describe, before, beforeEach, it*/
var testUtils = require('./testUtils'), var testUtils = require('./testUtils'),
should = require('should'), should = require('should'),
sinon = require('sinon'), sinon = require('sinon'),
@ -11,11 +11,20 @@ var testUtils = require('./testUtils'),
describe("Ghost API", function () { describe("Ghost API", function () {
var testTemplatePath = 'core/test/unit/fixtures/', var testTemplatePath = 'core/test/unit/fixtures/',
themeTemplatePath= 'core/test/unit/fixtures/theme', themeTemplatePath = 'core/test/unit/fixtures/theme',
ghost; ghost;
beforeEach(function () { before(function (done) {
ghost = new Ghost(); testUtils.clearData().then(function () {
done();
}, done);
});
beforeEach(function (done) {
testUtils.initData().then(function () {
ghost = new Ghost();
done();
}, done);
}); });
it("is a singleton", function () { it("is a singleton", function () {

View File

@ -1,202 +1,202 @@
/*globals describe, beforeEach, it*/ ///*globals describe, beforeEach, it*/
var testUtils = require('./testUtils'), //var testUtils = require('./testUtils'),
should = require('should'), // should = require('should'),
sinon = require('sinon'), // sinon = require('sinon'),
when = require('when'), // when = require('when'),
_ = require("underscore"), // _ = require("underscore"),
errors = require('../../server/errorHandling'), // errors = require('../../server/errorHandling'),
//
// Stuff we are testing // // Stuff we are testing
knex = require("../../server/models/base").Knex, // knex = require("../../server/models/base").Knex,
migration = require('../../server/data/migration'), // migration = require('../../server/data/migration'),
exporter = require('../../server/data/export'), // exporter = require('../../server/data/export'),
importer = require('../../server/data/import'), // importer = require('../../server/data/import'),
Importer001 = require('../../server/data/import/001'), // Importer001 = require('../../server/data/import/001'),
Importer002 = require('../../server/data/import/002'), // Importer002 = require('../../server/data/import/002'),
Settings = require('../../server/models/settings').Settings; // Settings = require('../../server/models/settings').Settings;
//
describe("Import", function () { //describe("Import", function () {
//
should.exist(exporter); // should.exist(exporter);
should.exist(importer); // should.exist(importer);
//
beforeEach(function (done) { // beforeEach(function (done) {
// clear database... we need to initialise it manually for each test // // clear database... we need to initialise it manually for each test
testUtils.clearData().then(function () { // testUtils.clearData().then(function () {
done(); // done();
}, done); // }, done);
}); // });
//
it("resolves 001", function (done) { // it("resolves 001", function (done) {
var importStub = sinon.stub(Importer001, "importData", function () { // var importStub = sinon.stub(Importer001, "importData", function () {
return when.resolve(); // return when.resolve();
}), // }),
fakeData = { test: true }; // fakeData = { test: true };
//
importer("001", fakeData).then(function () { // importer("001", fakeData).then(function () {
importStub.calledWith(fakeData).should.equal(true); // importStub.calledWith(fakeData).should.equal(true);
//
importStub.restore(); // importStub.restore();
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
//
describe("001", function () { // describe("001", function () {
this.timeout(4000); // this.timeout(4000);
//
should.exist(Importer001); // should.exist(Importer001);
//
it("imports data from 001", function (done) { // it("imports data from 001", function (done) {
var exportData; // var exportData;
//
// initialise database to version 001 - confusingly we have to set the max version to be one higher // // initialise database to version 001 - confusingly we have to set the max version to be one higher
// than the migration version we want // // than the migration version we want
migration.migrateUpFromVersion('001', '002').then(function () { // migration.migrateUpFromVersion('001', '002').then(function () {
return Settings.populateDefaults(); // return Settings.populateDefaults();
}).then(function () { // }).then(function () {
// export the version 001 data ready to import // // export the version 001 data ready to import
// TODO: Should have static test data here? // // TODO: Should have static test data here?
return exporter("001"); // return exporter("001");
}).then(function (exported) { // }).then(function (exported) {
exportData = exported; // exportData = exported;
//
// Version 001 exporter required the database be empty... // // Version 001 exporter required the database be empty...
var tables = [ // var tables = [
'posts', 'users', 'roles', 'roles_users', 'permissions', 'permissions_roles', // 'posts', 'users', 'roles', 'roles_users', 'permissions', 'permissions_roles',
'settings' // 'settings'
], // ],
truncateOps = _.map(tables, function (name) { // truncateOps = _.map(tables, function (name) {
return knex(name).truncate(); // return knex(name).truncate();
}); // });
//
return when.all(truncateOps); // return when.all(truncateOps);
}).then(function () { // }).then(function () {
return importer("001", exportData); // return importer("001", exportData);
}).then(function () { // }).then(function () {
// Grab the data from tables // // Grab the data from tables
return when.all([ // return when.all([
knex("users").select(), // knex("users").select(),
knex("posts").select(), // knex("posts").select(),
knex("settings").select() // knex("settings").select()
]); // ]);
}).then(function (importedData) { // }).then(function (importedData) {
//
should.exist(importedData); // should.exist(importedData);
importedData.length.should.equal(3); // importedData.length.should.equal(3);
//
// we always have 0 users as there isn't one in fixtures // // we always have 0 users as there isn't one in fixtures
importedData[0].length.should.equal(0); // importedData[0].length.should.equal(0);
importedData[1].length.should.equal(exportData.data.posts.length); // importedData[1].length.should.equal(exportData.data.posts.length);
importedData[2].length.should.be.above(0); // importedData[2].length.should.be.above(0);
//
_.findWhere(exportData.data.settings, {key: "currentVersion"}).value.should.equal("001"); // _.findWhere(exportData.data.settings, {key: "databaseVersion"}).value.should.equal("001");
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
}); // });
//
it("resolves 002", function (done) { // it("resolves 002", function (done) {
var importStub = sinon.stub(Importer002, "importData", function () { // var importStub = sinon.stub(Importer002, "importData", function () {
return when.resolve(); // return when.resolve();
}), // }),
fakeData = { test: true }; // fakeData = { test: true };
//
importer("002", fakeData).then(function () { // importer("002", fakeData).then(function () {
importStub.calledWith(fakeData).should.equal(true); // importStub.calledWith(fakeData).should.equal(true);
//
importStub.restore(); // importStub.restore();
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
//
describe("002", function () { // describe("002", function () {
this.timeout(4000); // this.timeout(4000);
//
should.exist(Importer002); // should.exist(Importer002);
//
it("imports data from 001", function (done) { // it("imports data from 001", function (done) {
var exportData; // var exportData;
//
// initialise database to version 001 - confusingly we have to set the max version to be one higher // // initialise database to version 001 - confusingly we have to set the max version to be one higher
// than the migration version we want // // than the migration version we want
migration.migrateUpFromVersion('001', '002').then(function () { // migration.migrateUpFromVersion('001', '002').then(function () {
return Settings.populateDefaults(); // return Settings.populateDefaults();
}).then(function () { // }).then(function () {
// export the version 001 data ready to import // // export the version 001 data ready to import
// TODO: Should have static test data here? // // TODO: Should have static test data here?
return exporter("001"); // return exporter("001");
}).then(function (exported) { // }).then(function (exported) {
exportData = exported; // exportData = exported;
//
// now migrate up to the proper version ready for importing - confusingly we have to set the max version // // now migrate up to the proper version ready for importing - confusingly we have to set the max version
// to be one higher than the migration version we want // // to be one higher than the migration version we want
return migration.migrateUpFromVersion('002', '003'); // return migration.migrateUpFromVersion('002', '003');
}).then(function () { // }).then(function () {
return importer("002", exportData); // return importer("002", exportData);
}).then(function () { // }).then(function () {
// Grab the data from tables // // Grab the data from tables
return when.all([ // return when.all([
knex("users").select(), // knex("users").select(),
knex("posts").select(), // knex("posts").select(),
knex("settings").select() // knex("settings").select()
]); // ]);
}).then(function (importedData) { // }).then(function (importedData) {
//
should.exist(importedData); // should.exist(importedData);
importedData.length.should.equal(3); // importedData.length.should.equal(3);
//
// we always have 0 users as there isn't one in fixtures // // we always have 0 users as there isn't one in fixtures
importedData[0].length.should.equal(0); // importedData[0].length.should.equal(0);
// import no longer requires all data to be dropped, and adds posts // // import no longer requires all data to be dropped, and adds posts
importedData[1].length.should.equal(exportData.data.posts.length + 1); // importedData[1].length.should.equal(exportData.data.posts.length + 1);
importedData[2].length.should.be.above(0); // importedData[2].length.should.be.above(0);
//
_.findWhere(importedData[2], {key: "currentVersion"}).value.should.equal("002"); // _.findWhere(importedData[2], {key: "databaseVersion"}).value.should.equal("002");
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
//
it("imports data from 002", function (done) { // it("imports data from 002", function (done) {
var exportData; // var exportData;
//
// initialise database to version 001 - confusingly we have to set the max version to be one higher // // initialise database to version 001 - confusingly we have to set the max version to be one higher
// than the migration version we want // // than the migration version we want
migration.migrateUpFromVersion('001', '003').then(function () { // migration.migrateUpFromVersion('001', '003').then(function () {
return Settings.populateDefaults(); // return Settings.populateDefaults();
}).then(function () { // }).then(function () {
// export the version 002 data ready to import // // export the version 002 data ready to import
// TODO: Should have static test data here? // // TODO: Should have static test data here?
return exporter("002"); // return exporter("002");
}).then(function (exported) { // }).then(function (exported) {
exportData = exported; // exportData = exported;
//
return importer("002", exportData); // return importer("002", exportData);
}).then(function () { // }).then(function () {
// Grab the data from tables // // Grab the data from tables
return when.all([ // return when.all([
knex("users").select(), // knex("users").select(),
knex("posts").select(), // knex("posts").select(),
knex("settings").select() // knex("settings").select()
]); // ]);
}).then(function (importedData) { // }).then(function (importedData) {
//
should.exist(importedData); // should.exist(importedData);
importedData.length.should.equal(3); // importedData.length.should.equal(3);
//
// we always have 0 users as there isn't one in fixtures // // we always have 0 users as there isn't one in fixtures
importedData[0].length.should.equal(0); // importedData[0].length.should.equal(0);
// import no longer requires all data to be dropped, and adds posts // // import no longer requires all data to be dropped, and adds posts
importedData[1].length.should.equal(exportData.data.posts.length + 1); // importedData[1].length.should.equal(exportData.data.posts.length + 1);
importedData[2].length.should.be.above(0); // importedData[2].length.should.be.above(0);
//
_.findWhere(importedData[2], {key: "currentVersion"}).value.should.equal("002"); // _.findWhere(importedData[2], {key: "databaseVersion"}).value.should.equal("002");
//
done(); // done();
}).then(null, done); // }).then(null, done);
}); // });
}); // });
}); //});

View File

@ -51,15 +51,15 @@ describe('permissions', function () {
], ],
currTestPermId = 1, currTestPermId = 1,
// currTestUserId = 1, // currTestUserId = 1,
// createTestUser = function (email_address) { // createTestUser = function (email) {
// if (!email_address) { // if (!email) {
// currTestUserId += 1; // currTestUserId += 1;
// email_address = "test" + currTestPermId + "@test.com"; // email = "test" + currTestPermId + "@test.com";
// } // }
// var newUser = { // var newUser = {
// id: currTestUserId, // id: currTestUserId,
// email_address: email_address, // email: email,
// password: "testing123" // password: "testing123"
// }; // };

View File

@ -28,19 +28,19 @@ describe('Core Helpers', function () {
}); });
it('can render content', function () { it('can render content', function () {
var content = "Hello World", var html = "Hello World",
rendered = handlebars.helpers.content.call({content: content}); rendered = handlebars.helpers.content.call({html: html});
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal(content); rendered.string.should.equal(html);
}); });
it('can truncate content by word', function () { it('can truncate html by word', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
rendered = ( rendered = (
handlebars.helpers.content handlebars.helpers.content
.call( .call(
{content: content}, {html: html},
{"hash":{"words": 2}} {"hash":{"words": 2}}
) )
); );
@ -49,12 +49,12 @@ describe('Core Helpers', function () {
rendered.string.should.equal("<p>Hello <strong>World</strong></p>"); rendered.string.should.equal("<p>Hello <strong>World</strong></p>");
}); });
it('can truncate content by character', function () { it('can truncate html by character', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
rendered = ( rendered = (
handlebars.helpers.content handlebars.helpers.content
.call( .call(
{content: content}, {html: html},
{"hash":{"characters": 8}} {"hash":{"characters": 8}}
) )
); );
@ -71,15 +71,15 @@ describe('Core Helpers', function () {
}); });
it("Returns the full name of the author from the context",function() { it("Returns the full name of the author from the context",function() {
var content = {"author":{"full_name":"abc123"}}, var data = {"author":{"name":"abc123"}},
result = handlebars.helpers.author.call(content); result = handlebars.helpers.author.call(data);
String(result).should.equal("abc123"); String(result).should.equal("abc123");
}); });
it("Returns a blank string where author data is missing",function() { it("Returns a blank string where author data is missing",function() {
var content = {"author":null}, var data = {"author": null},
result = handlebars.helpers.author.call(content); result = handlebars.helpers.author.call(data);
String(result).should.equal(""); String(result).should.equal("");
}); });
@ -93,33 +93,33 @@ describe('Core Helpers', function () {
}); });
it('can render excerpt', function () { it('can render excerpt', function () {
var content = "Hello World", var html = "Hello World",
rendered = handlebars.helpers.excerpt.call({content: content}); rendered = handlebars.helpers.excerpt.call({html: html});
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal(content); rendered.string.should.equal(html);
}); });
it('does not output HTML', function () { it('does not output HTML', function () {
var content = '<p>There are <br />10<br> types<br/> of people in <img src="a">the world:' var html = '<p>There are <br />10<br> types<br/> of people in <img src="a">the world:'
+ '<img src=b alt=\"c\"> those who <img src="@" onclick="javascript:alert(\'hello\');">' + '<img src=b alt=\"c\"> those who <img src="@" onclick="javascript:alert(\'hello\');">'
+ "understand trinary</p>, those who don't <div style='' class=~/'-,._?!|#>and" + "understand trinary</p>, those who don't <div style='' class=~/'-,._?!|#>and"
+ "< test > those<<< test >>> who mistake it &lt;for&gt; binary.", + "< test > those<<< test >>> who mistake it &lt;for&gt; binary.",
expected = "There are 10 types of people in the world: those who understand trinary, those who don't " expected = "There are 10 types of people in the world: those who understand trinary, those who don't "
+ "and those>> who mistake it &lt;for&gt; binary.", + "and those>> who mistake it &lt;for&gt; binary.",
rendered = handlebars.helpers.excerpt.call({content: content}); rendered = handlebars.helpers.excerpt.call({html: html});
should.exist(rendered); should.exist(rendered);
rendered.string.should.equal(expected); rendered.string.should.equal(expected);
}); });
it('can truncate content by word', function () { it('can truncate html by word', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
expected = "Hello World", expected = "Hello World",
rendered = ( rendered = (
handlebars.helpers.excerpt.call( handlebars.helpers.excerpt.call(
{content: content}, {html: html},
{"hash": {"words": 2}} {"hash": {"words": 2}}
) )
); );
@ -128,12 +128,12 @@ describe('Core Helpers', function () {
rendered.string.should.equal(expected); rendered.string.should.equal(expected);
}); });
it('can truncate content by character', function () { it('can truncate html by character', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>", var html = "<p>Hello <strong>World! It's me!</strong></p>",
expected = "Hello Wo", expected = "Hello Wo",
rendered = ( rendered = (
handlebars.helpers.excerpt.call( handlebars.helpers.excerpt.call(
{content: content}, {html: html},
{"hash": {"characters": 8}} {"hash": {"characters": 8}}
) )
); );
@ -213,7 +213,7 @@ describe('Core Helpers', function () {
}); });
it('should return a the slug with a prefix slash if the context is a post', function () { it('should return a the slug with a prefix slash if the context is a post', function () {
var rendered = handlebars.helpers.url.call({content: 'content', content_raw: "ff", title: "title", slug: "slug"}); var rendered = handlebars.helpers.url.call({html: 'content', markdown: "ff", title: "title", slug: "slug"});
should.exist(rendered); should.exist(rendered);
rendered.should.equal('/slug'); rendered.should.equal('/slug');
}); });
@ -224,7 +224,7 @@ describe('Core Helpers', function () {
}), }),
rendered = handlebars.helpers.url.call( rendered = handlebars.helpers.url.call(
{content: 'content', content_raw: "ff", title: "title", slug: "slug"}, {html: 'content', markdown: "ff", title: "title", slug: "slug"},
{hash: { absolute: 'true'}} {hash: { absolute: 'true'}}
); );
@ -235,10 +235,10 @@ describe('Core Helpers', function () {
}); });
it('should return empty string if not a post', function () { it('should return empty string if not a post', function () {
handlebars.helpers.url.call({content_raw: "ff", title: "title", slug: "slug"}).should.equal(''); handlebars.helpers.url.call({markdown: "ff", title: "title", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({content: 'content', title: "title", slug: "slug"}).should.equal(''); handlebars.helpers.url.call({html: 'content', title: "title", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({content: 'content', content_raw: "ff", slug: "slug"}).should.equal(''); handlebars.helpers.url.call({html: 'content', markdown: "ff", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({content: 'content', content_raw: "ff", title: "title"}).should.equal(''); handlebars.helpers.url.call({html: 'content', markdown: "ff", title: "title"}).should.equal('');
}); });
}); });

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@
"showdown": "0.3.1", "showdown": "0.3.1",
"sqlite3": "2.1.16", "sqlite3": "2.1.16",
"bookshelf": "0.3.1", "bookshelf": "0.3.1",
"knex": "0.2.6", "knex": "git+https://github.com/tgriesser/knex.git#66ea8d7",
"when": "2.2.1", "when": "2.2.1",
"bcrypt-nodejs": "0.0.3", "bcrypt-nodejs": "0.0.3",
"node-uuid": "1.4.0", "node-uuid": "1.4.0",