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

@ -19,11 +19,11 @@ fancifyPlugin = {
getIndexOfNextCharacter = function (beginFrom) {
var currIndex = beginFrom,
nextChar;
nextChar = originalContent.substr(currIndex, 1);
while (_.contains(whiteSpace, nextChar) && currIndex !== originalContent.length) {
currIndex += 1;
nextChar = originalContent.substr(currIndex, 1);
nextChar = originalContent.substr(currIndex, 1);
}
return currIndex;
@ -31,7 +31,7 @@ fancifyPlugin = {
getAfterNextClosingTag = function (beginFrom) {
return originalContent.indexOf('>', beginFrom) + 1;
};
// Skip any leading white space until we get a character
firstCharIndex = getIndexOfNextCharacter(firstCharIndex);
@ -67,10 +67,10 @@ fancifyPlugin = {
if (_.isArray(posts)) {
_.each(posts, function (post) {
post.content = self.fancify(post.content);
post.html = self.fancify(post.html);
});
} else if (posts.hasOwnProperty('content')) {
posts.content = this.fancify(posts.content);
} else if (posts.hasOwnProperty('html')) {
posts.html = this.fancify(posts.html);
}
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) {
ghost.registerFilter('prePostsRender', this.fancifyPosts);
},

View File

@ -5,7 +5,7 @@
{{! TODO: JavaScript toggle featured/unfeatured}}
<span class="status">{{#if published}}Published{{else}}Written{{/if}}</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">
<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>
@ -31,5 +31,5 @@
</section>
</header>
<section class="content-preview-content">
<div class="wrapper"><h1>{{{title}}}</h1>{{{content}}}</div>
<div class="wrapper"><h1>{{{title}}}</h1>{{{html}}}</div>
</section>

View File

@ -7,25 +7,25 @@
<section class="content no-padding">
<header class="user-profile-header">
<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"/>
<button class="button-change-cover js-modal-cover-picture">Change Cover</button>
<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">Change Cover</button>
</figure>
</header>
<form class="user-details-container" novalidate="novalidate">
<fieldset class="user-details-top">
<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}}"/>
<button class="button-change-avatar js-modal-profile-picture">Edit Picture</button>
<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-image">Edit Picture</button>
</figure>
<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>
</label>
</fieldset>
<fieldset class="user-details-bottom">
<div class="form-group">
<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>
</div>
@ -37,7 +37,7 @@
<div class="form-group">
<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>
</div>

View File

@ -199,14 +199,14 @@
},
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) {
this.model.unset(item);
}, this);
var saved = this.model.save(_.extend({
title: $('#entry-title').val(),
content_raw: Ghost.currentView.editor.getValue()
markdown: Ghost.currentView.editor.getValue()
}, data));
// 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.$('#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.renderPreview();

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ var when = require('when'),
module.exports = function (version) {
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.");
}

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: [
{
"uuid": uuid.v4(),
"title": "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/",
"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>",
"meta_title": null,
"meta_description": null,
"meta_keywords": null,
"featured": null,
"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/",
"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>",
"image": null,
"featured": false,
"page": false,
"status": "published",
"language": null,
"author_id": 1,
"created_at": 1373578890610,
"created_by": 1,
"updated_at": 1373578997173,
"updated_by": 1,
"published_at": 1373578895817,
"published_by": 1
"language": "en_US",
"meta_title": null,
"meta_description": null
}
],
roles: [
{
"id": 1,
"name": "Administrator",
"description": "Administrators"
"uuid": uuid.v4(),
"name": "Administrator",
"description": "Administrators"
},
{
"id": 2,
"name": "Editor",
"description": "Editors"
"uuid": uuid.v4(),
"name": "Editor",
"description": "Editors"
},
{
"id": 3,
"name": "Author",
"description": "Authors"
"uuid": uuid.v4(),
"name": "Author",
"description": "Authors"
}
],
permissions: [
{
"id": 1,
"name": "Edit posts",
"action_type": "edit",
"object_type": "post"
"uuid": uuid.v4(),
"name": "Edit posts",
"action_type": "edit",
"object_type": "post"
},
{
"id": 2,
"name": "Remove posts",
"action_type": "remove",
"object_type": "post"
"uuid": uuid.v4(),
"name": "Remove posts",
"action_type": "remove",
"object_type": "post"
},
{
"id": 3,
"name": "Create posts",
"action_type": "create",
"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
"uuid": uuid.v4(),
"name": "Create posts",
"action_type": "create",
"object_type": "post"
}
]
};
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;
case 'settings':
// for settings we need to update individual settings, and insert any missing ones
// the one setting we MUST NOT update is the currentVersion settings
var blackList = ['currentVersion'];
// the one setting we MUST NOT update is the databaseVersion settings
var blackList = ['databaseVersion'];
if (tableData && tableData.length) {
tableData = stripProperties(['id'], tableData);
_.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'),
errors = require('../../errorHandling'),
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() {
return knex.Schema.hasTable('settings').then(function () {
defaultSettings = require('../default-settings'),
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
return knex('settings')
.where('key', 'currentVersion')
.select('value')
.then(function (currentVersionSetting) {
if (currentVersionSetting && currentVersionSetting.length > 0) {
currentVersionSetting = currentVersionSetting[0].value;
} else {
// we didn't get a response we understood, assume initialVersion
currentVersionSetting = initialVersion;
}
return currentVersionSetting;
});
if (exists) {
// Temporary code to deal with old databases with currentVersion settings
// TODO: remove before release
return knex('settings')
.where('key', 'databaseVersion')
.orWhere('key', 'currentVersion')
.select('value')
.then(function (versions) {
var databaseVersion = _.reduce(versions, function (memo, version) {
return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo;
}, 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 = {
currentVersion: currentVersion,
// Check for whether data is needed to be bootstrapped or not
init: function () {
var self = this;
return getCurrentVersion().then(function (currentVersionSetting) {
// We are assuming here that the currentVersionSetting will
// always be less than the currentVersion value.
if (currentVersionSetting === currentVersion) {
// There are 4 possibilities:
// 1. The database exists and is up-to-date
// 2. The database exists but is out of date
// 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();
}
// Bring the data up to the latest version
return self.migrateUpFromVersion(currentVersion);
}, function () {
// If the settings table doesn't exist, bring everything up from initial version.
return self.migrateUpFromVersion(initialVersion);
if (databaseVersion < defaultVersion) {
// 2. The database exists but is out of date
return self.migrateUpFromVersion(databaseVersion);
}
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 () {
var self = this;
return getCurrentVersion().then(function (currentVersionSetting) {
return getDatabaseVersion().then(function (databaseVersion) {
// bring everything down from the current version
return self.migrateDownFromVersion(currentVersionSetting);
return self.migrateDownFromVersion(databaseVersion);
}, function () {
// If the settings table doesn't exist, bring everything down from initial version.
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
migrateUpFromVersion: function (version, max) {
var versions = [],
maxVersion = max || this.getVersionAfter(currentVersion),
maxVersion = max || this.getVersionAfter(getDefaultDatabaseVersion()),
currVersion = version,
tasks = [];
@ -91,11 +161,15 @@ module.exports = {
});
// Run each migration in series
return series(tasks);
return series(tasks).then(function () {
// Finally update the databases current version
return setDatabaseVersion();
});
},
migrateDownFromVersion: function (version) {
var versions = [],
var self = this,
versions = [],
minVersion = this.getVersionBefore(initialVersion),
currVersion = version,
tasks = [];
@ -114,7 +188,7 @@ module.exports = {
return migration.down();
} catch (e) {
errors.logError(e);
return when.reject(e);
return self.migrateDownFromVersion(initialVersion);
}
};
});

View File

@ -44,10 +44,10 @@ coreHelpers = function (ghost) {
});
// ### Page URL Helper
//
//
// *Usage example:*
// `{{pageUrl 2}}`
//
//
// Returns the URL for the page specified in the current object
// context.
//
@ -87,7 +87,7 @@ coreHelpers = function (ghost) {
// if the author could not be determined.
//
ghost.registerThemeHelper('author', function (context, options) {
return this.author ? this.author.full_name : "";
return this.author ? this.author.name : "";
});
// ### Tags Helper
@ -133,11 +133,11 @@ coreHelpers = function (ghost) {
if (truncateOptions.words || truncateOptions.characters) {
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"]);
/*jslint regexp:true */
excerpt = String(this.content).replace(/<\/?[^>]+>/gi, "");
excerpt = String(this.html).replace(/<\/?[^>]+>/gi, "");
/*jslint regexp:false */
if (!truncateOptions.words && !truncateOptions.characters) {

View File

@ -1,6 +1,9 @@
var GhostBookshelf,
Bookshelf = require('bookshelf'),
when = require('when'),
moment = require('moment'),
_ = require('underscore'),
uuid = require('node-uuid'),
config = require('../../../config'),
Validator = require('validator').Validator;
@ -14,12 +17,39 @@ GhostBookshelf.validator = new Validator();
// including some convenience functions as static properties on the model.
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
// Fix problems with dates
fixDates: function (attrs) {
_.each(attrs, function (value, key) {
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;
},
// #### 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) {
return jsonData.hasOwnProperty("content") && jsonData.hasOwnProperty("content_raw")
return jsonData.hasOwnProperty("html") && jsonData.hasOwnProperty("markdown")
&& jsonData.hasOwnProperty("title") && jsonData.hasOwnProperty("slug");
}
};

View File

@ -5,27 +5,18 @@ var GhostBookshelf = require('./base'),
Permissions;
Permission = GhostBookshelf.Model.extend({
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 () {
// TODO: validate object_type, action_type and object_id
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 () {
return this.belongsToMany(Role);
},

View File

@ -8,6 +8,7 @@ var Post,
github = require('../../shared/vendor/showdown/extensions/github'),
converter = new Showdown.converter({extensions: [github]}),
User = require('./user').User,
config = require('../../../config'),
Tag = require('./tag').Tag,
Tags = require('./tag').Tags,
GhostBookshelf = require('./base');
@ -17,18 +18,15 @@ Post = GhostBookshelf.Model.extend({
tableName: 'posts',
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',
'published_at', 'published_by'
],
hasTimestamps: true,
defaults: function () {
return {
uuid: uuid.v4(),
status: 'draft'
// TODO: language: ghost.config().defaultLang);
};
},
@ -46,108 +44,49 @@ Post = GhostBookshelf.Model.extend({
},
saving: function () {
// Deal with the related data here
var self = this;
// Remove any properties which don't belong on the post model
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());
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') {
this.set('published_at', new Date());
// This will need to go elsewhere in the API layer.
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
},
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'))
if (this.hasChanged('slug')) {
// Pass the new slug through the generator to strip illegal characters, detect duplicates
return this.generateSlug(Post, this.get('slug'))
.then(function (slug) {
self.set({slug: slug});
});
}
},
// #### generateSlug
// Create a string act as the permalink for a post.
generateSlug: function (title) {
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;
creating: function () {
// set any dynamic default properties
var self = this;
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 = 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";
if (!this.get('author_id')) {
this.set('author_id', 1);
}
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(Post, this.get('title'))
.then(function (slug) {
self.set({slug: slug});
});
}
// Test for duplicate slugs.
return checkIfSlugExists(slug);
},
updateTags: function (newTags) {

View File

@ -5,27 +5,16 @@ var User = require('./user').User,
Roles;
Role = GhostBookshelf.Model.extend({
tableName: 'roles',
permittedAttributes: ['id', 'name', 'description'],
initialize: function () {
this.on('saving', this.saving, this);
this.on('saving', this.validate, this);
},
permittedAttributes: ['id', 'uuid', 'name', 'description', 'created_at', 'created_by', 'updated_at', 'updated_by'],
validate: function () {
GhostBookshelf.validator.check(this.get('name'), "Role name 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 () {
return this.belongsToMany(User);
},

View File

@ -33,8 +33,6 @@ Settings = GhostBookshelf.Model.extend({
tableName: 'settings',
hasTimestamps: true,
permittedAttributes: ['id', 'uuid', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'update_by'],
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.
// 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);
}, 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) {
@ -122,7 +109,7 @@ Settings = GhostBookshelf.Model.extend({
_.each(defaultSettings, function (defaultSetting, defaultSettingKey) {
var isMissingFromDB = usedKeys.indexOf(defaultSettingKey) === -1;
if (isMissingFromDB) {
defaultSetting.value = defaultSetting.default;
defaultSetting.value = defaultSetting.defaultValue;
insertOperations.push(Settings.forge(defaultSetting).save());
}
});

View File

@ -1,18 +1,34 @@
var Tag,
Tags,
uuid = require('node-uuid'),
Posts = require('./post').Posts,
GhostBookshelf = require('./base');
Tag = GhostBookshelf.Model.extend({
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 () {
return {
uuid: uuid.v4()
};
validate: function () {
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 () {

View File

@ -26,67 +26,33 @@ User = GhostBookshelf.Model.extend({
tableName: 'users',
hasTimestamps: true,
permittedAttributes: [
'id', 'uuid', 'full_name', 'password', 'email_address', 'profile_picture', 'cover_picture', 'bio', 'url', 'location',
'created_at', 'created_by', 'updated_at', 'updated_by'
'id', 'uuid', 'name', 'slug', 'password', 'email', 'image', 'cover', 'bio', 'website', 'location',
'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 () {
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);
if (this.get('url') && this.get('url').length > 0) {
GhostBookshelf.validator.check(this.get('url'), "Your website is not a valid URL.").isUrl();
if (this.get('website') && this.get('website').length > 0) {
GhostBookshelf.validator.check(this.get('website'), "Your website is not a valid URL.").isUrl();
}
return true;
},
saving: function () {
// Deal with the related data here
creating: function () {
var self = this;
// Remove any properties which don't belong on the post model
this.attributes = this.pick(this.permittedAttributes);
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(User, this.get('name'))
.then(function (slug) {
self.set({slug: slug});
});
}
},
posts: function () {
@ -152,7 +118,7 @@ User = GhostBookshelf.Model.extend({
* @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) {
// 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
check: function (_userdata) {
return this.forge({
email_address: _userdata.email
email: _userdata.email
}).fetch({require: true}).then(function (user) {
return nodefn.call(bcrypt.compare, _userdata.pw, user.get('password')).then(function (matched) {
if (!matched) {
@ -220,7 +186,7 @@ User = GhostBookshelf.Model.extend({
var newPassword = Math.random().toString(36).slice(2, 12), // This is magick
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;
return nodefn.call(bcrypt.hash, newPassword, null, null);
}).then(function (hash) {

View File

@ -14,11 +14,11 @@ I18n = function (ghost) {
return function (req, res, next) {
if (lang === 'en') {
if (lang === 'en_US') {
// TODO: do stuff here to optimise for en
// Make jslint empty block error go away
lang = 'en';
lang = 'en_US';
}
/** TODO: potentially use req.acceptedLanguages rather than the default
@ -26,8 +26,8 @@ I18n = function (ghost) {
* TODO: switch this mess to be promise driven */
fs.stat(langFilePath, function (error) {
if (error) {
console.log('No language file found for language ' + lang + '. Defaulting to en');
lang = 'en';
console.log('No language file found for language ' + lang + '. Defaulting to en_US');
lang = 'en_US';
}
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.sendKeys('#entry-title', testPost.title);
casper.writeContentToCodeMirror(testPost.content);
casper.writeContentToCodeMirror(testPost.html);
});
// 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');
// 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 () {
@ -149,7 +149,7 @@ casper.test.begin('Title Trimming', function suite(test) {
test.assertEvalEquals(function () {
return $('#entry-title').val();
}, trimmedTitle, 'Entry title should match expected value.');
});

View File

@ -42,7 +42,7 @@ var host = casper.cli.options.host || 'localhost',
},
testPost = {
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) {

View File

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

View File

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

View File

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

View File

@ -21,7 +21,6 @@ describe('Tag Model', function () {
beforeEach(function (done) {
this.timeout(5000);
testUtils.initData()
.then(function () {})
.then(function () {
done();
}, done);
@ -37,7 +36,7 @@ describe('Tag Model', function () {
var PostModel = Models.Post;
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'},
createdPostID;
@ -63,7 +62,7 @@ describe('Tag Model', function () {
// 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,
// 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'},
createdTagID,
createdPostID;
@ -97,7 +96,7 @@ describe('Tag Model', function () {
function seedTags(tagNames) {
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}); });
@ -210,7 +209,7 @@ describe('Tag Model', function () {
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) {
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) {
var userData = {
name: 'test',
password: 'testpass1',
email_address: "test@test1.com"
email: "test@test1.com"
};
UserModel.add(userData).then(function (createdUser) {
should.exist(createdUser);
createdUser.has('uuid').should.equal(true);
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();
}).then(null, done);
@ -64,16 +65,15 @@ describe('User Model', function run() {
it('can\'t add second', function (done) {
var userData = {
name: 'test',
password: 'testpass3',
email_address: "test3@test1.com"
email: "test3@test1.com"
};
return testUtils.insertDefaultUser().then(function () {
UserModel.add(userData).then(done, function (failure) {
failure.message.should.eql('A user is already registered. Only one user for now!');
done();
}).then(null, done);
});
return UserModel.add(userData).then(done, function (failure) {
failure.message.should.eql('A user is already registered. Only one user for now!');
done();
}).then(null, done);
});
it('can browse', function (done) {
@ -99,13 +99,13 @@ describe('User Model', function run() {
firstUser = results.models[0];
return UserModel.read({email_address: firstUser.attributes.email_address});
return UserModel.read({email: firstUser.attributes.email});
}).then(function (found) {
should.exist(found);
found.attributes.full_name.should.equal(firstUser.attributes.full_name);
found.attributes.name.should.equal(firstUser.attributes.name);
done();
@ -124,13 +124,13 @@ describe('User Model', function run() {
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) {
should.exist(edited);
edited.attributes.url.should.equal('some.newurl.com');
edited.attributes.website.should.equal('some.newurl.com');
done();

View File

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

View File

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

View File

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

View File

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

View File

@ -28,19 +28,19 @@ describe('Core Helpers', function () {
});
it('can render content', function () {
var content = "Hello World",
rendered = handlebars.helpers.content.call({content: content});
var html = "Hello World",
rendered = handlebars.helpers.content.call({html: html});
should.exist(rendered);
rendered.string.should.equal(content);
rendered.string.should.equal(html);
});
it('can truncate content by word', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>",
it('can truncate html by word', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>",
rendered = (
handlebars.helpers.content
.call(
{content: content},
{html: html},
{"hash":{"words": 2}}
)
);
@ -49,12 +49,12 @@ describe('Core Helpers', function () {
rendered.string.should.equal("<p>Hello <strong>World</strong></p>");
});
it('can truncate content by character', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>",
it('can truncate html by character', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>",
rendered = (
handlebars.helpers.content
.call(
{content: content},
{html: html},
{"hash":{"characters": 8}}
)
);
@ -71,15 +71,15 @@ describe('Core Helpers', function () {
});
it("Returns the full name of the author from the context",function() {
var content = {"author":{"full_name":"abc123"}},
result = handlebars.helpers.author.call(content);
var data = {"author":{"name":"abc123"}},
result = handlebars.helpers.author.call(data);
String(result).should.equal("abc123");
});
it("Returns a blank string where author data is missing",function() {
var content = {"author":null},
result = handlebars.helpers.author.call(content);
var data = {"author": null},
result = handlebars.helpers.author.call(data);
String(result).should.equal("");
});
@ -93,33 +93,33 @@ describe('Core Helpers', function () {
});
it('can render excerpt', function () {
var content = "Hello World",
rendered = handlebars.helpers.excerpt.call({content: content});
var html = "Hello World",
rendered = handlebars.helpers.excerpt.call({html: html});
should.exist(rendered);
rendered.string.should.equal(content);
rendered.string.should.equal(html);
});
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\');">'
+ "understand trinary</p>, those who don't <div style='' class=~/'-,._?!|#>and"
+ "< 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 "
+ "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);
rendered.string.should.equal(expected);
});
it('can truncate content by word', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>",
it('can truncate html by word', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>",
expected = "Hello World",
rendered = (
handlebars.helpers.excerpt.call(
{content: content},
{html: html},
{"hash": {"words": 2}}
)
);
@ -128,12 +128,12 @@ describe('Core Helpers', function () {
rendered.string.should.equal(expected);
});
it('can truncate content by character', function () {
var content = "<p>Hello <strong>World! It's me!</strong></p>",
it('can truncate html by character', function () {
var html = "<p>Hello <strong>World! It's me!</strong></p>",
expected = "Hello Wo",
rendered = (
handlebars.helpers.excerpt.call(
{content: content},
{html: html},
{"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 () {
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);
rendered.should.equal('/slug');
});
@ -224,7 +224,7 @@ describe('Core Helpers', function () {
}),
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'}}
);
@ -235,10 +235,10 @@ describe('Core Helpers', 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({content: 'content', title: "title", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({content: 'content', content_raw: "ff", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({content: 'content', content_raw: "ff", title: "title"}).should.equal('');
handlebars.helpers.url.call({markdown: "ff", title: "title", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({html: 'content', title: "title", slug: "slug"}).should.equal('');
handlebars.helpers.url.call({html: 'content', markdown: "ff", slug: "slug"}).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",
"sqlite3": "2.1.16",
"bookshelf": "0.3.1",
"knex": "0.2.6",
"knex": "git+https://github.com/tgriesser/knex.git#66ea8d7",
"when": "2.2.1",
"bcrypt-nodejs": "0.0.3",
"node-uuid": "1.4.0",