0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Refactoring fixtures

refs #2600, refs #2379

Refactoring fixtures to make permission management a little easier
- Separate fixtures into JSON file and split permissions fixtures from other fixtures
- make fixture migrations more robust by fetching objects, not relying on
  ids and checking before adding
- changed owner fixture slightly to remove any confusion between the 'Owner' role and 'Ghost Owner' user.
- moved 003 fixture versions out of config into logic, possibly not a good
  idea
- refactored permissions fixtures and added permissions_roles fixtures to
  make it easier to read / add
This commit is contained in:
Hannah Wolfe 2014-07-11 08:12:07 +01:00
parent 516fd2680d
commit 0565027900
5 changed files with 422 additions and 381 deletions

View file

@ -0,0 +1,61 @@
{
"users": [
{
"name": "Ghost Owner",
"email": "ghost@ghost.org",
"status": "inactive"
}
],
"posts": [
{
"title": "Welcome to Ghost",
"slug": "welcome-to-ghost",
"markdown": "You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at `<your blog URL>/ghost/`. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!\n\n## Getting Started\n\nGhost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!\n\nWriting in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use *shortcuts* to **style** your content. For example, a list:\n\n* Item number one\n* Item number two\n * A nested item\n* A final item\n\nor with numbers!\n\n1. Remember to buy some milk\n2. Drink the milk\n3. Tweet that I remembered to buy the milk, and drank it\n\n### Links\n\nWant to link to a source? No problem. If you paste in url, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to [the Ghost website](http://ghost.org). Neat.\n\n### What about Images?\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:\n\n![The Ghost Logo](https://ghost.org/images/ghost.png)\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:\n\n![A bowl of bananas]\n\n\n### Quoting\n\nSometimes a link isn't enough, you want to quote someone on what they've said. It was probably very wisdomous. Is wisdomous a word? Find out in a future release when we introduce spellcheck! For now - it's definitely a word.\n\n> Wisdomous - it's definitely a word.\n\n### Working with Code\n\nGot a streak of geek? We've got you covered there, too. You can write inline `<code>` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n<input type=\"text\" placeholder=\"I'm an input field!\" />\n\nThat should be enough to get you started. Have fun - and let us know what you think :)",
"image": null,
"featured": false,
"page": false,
"status": "published",
"language": "en_US",
"meta_title": null,
"meta_description": null
}
],
"tags": [
{
"name": "Getting Started",
"slug": "getting-started",
"description": null,
"parent_id": null,
"meta_title": null,
"meta_description": null
}
],
"roles": [
{
"name": "Administrator",
"description": "Administrators"
},
{
"name": "Editor",
"description": "Editors"
},
{
"name": "Author",
"description": "Authors"
},
{
"name": "Owner",
"description": "Blog Owner"
}
],
"client": [
{
"name": "Ghost Admin",
"slug": "ghost-admin",
"secret": "not_available"
}
]
}

View file

@ -1,388 +1,52 @@
// # Fixtures
// This module handles populating or updating fixtures.
//
// Currently fixtures only change between data version 002 and 003, therefore the update logic is hard coded
// rather than abstracted into a migration system. The upgrade function checks that its changes are safe before
// making them.
var when = require('when'),
sequence = require('when/sequence'),
_ = require('lodash'),
utils = require('../../utils'),
models = require('../../models'),
fixtures = require('./fixtures'),
permissions = require('./permissions'),
populateFixtures,
updateFixtures;
populate,
update,
to003,
fetchAdmin,
convertAdminToOwner,
createOwner;
var fixtures = {
posts: [
{
"title": "Welcome to Ghost",
"slug": "welcome-to-ghost",
"markdown": "You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at `<your blog URL>/ghost/`. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!\n\n## Getting Started\n\nGhost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!\n\nWriting in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use *shortcuts* to **style** your content. For example, a list:\n\n* Item number one\n* Item number two\n * A nested item\n* A final item\n\nor with numbers!\n\n1. Remember to buy some milk\n2. Drink the milk\n3. Tweet that I remembered to buy the milk, and drank it\n\n### Links\n\nWant to link to a source? No problem. If you paste in url, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to [the Ghost website](http://ghost.org). Neat.\n\n### What about Images?\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:\n\n![The Ghost Logo](https://ghost.org/images/ghost.png)\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:\n\n![A bowl of bananas]\n\n\n### Quoting\n\nSometimes a link isn't enough, you want to quote someone on what they've said. It was probably very wisdomous. Is wisdomous a word? Find out in a future release when we introduce spellcheck! For now - it's definitely a word.\n\n> Wisdomous - it's definitely a word.\n\n### Working with Code\n\nGot a streak of geek? We've got you covered there, too. You can write inline `<code>` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n<input type=\"text\" placeholder=\"I'm an input field!\" />\n\nThat should be enough to get you started. Have fun - and let us know what you think :)",
"image": null,
"featured": false,
"page": false,
"status": "published",
"language": "en_US",
"meta_title": null,
"meta_description": null
}
],
tags: [
{
"name": "Getting Started",
"slug": "getting-started",
"description": null,
"parent_id": null,
"meta_title": null,
"meta_description": null
}
],
roles: [
{
"name": "Administrator",
"description": "Administrators"
},
{
"name": "Editor",
"description": "Editors"
},
{
"name": "Author",
"description": "Authors"
}
],
permissions: [
{
"name": "Edit posts",
"action_type": "edit",
"object_type": "post"
},
{
"name": "Remove posts",
"action_type": "remove",
"object_type": "post"
},
{
"name": "Create posts",
"action_type": "create",
"object_type": "post"
}
],
permissions003: [
{
"name": "Generate post slug",
"action_type": "generate",
"object_type": "slug"
},
{
"name": "Generate tag slug",
"action_type": "generate",
"object_type": "slug"
},
{
"name": "Export database",
"action_type": "exportContent",
"object_type": "db"
},
{
"name": "Import database",
"action_type": "importContent",
"object_type": "db"
},
{
"name": "Delete all content",
"action_type": "deleteAllContent",
"object_type": "db"
},
{
"name": "Browse users",
"action_type": "browse",
"object_type": "user"
},
{
"name": "Read users",
"action_type": "read",
"object_type": "user"
},
{
"name": "Edit users",
"action_type": "edit",
"object_type": "user"
},
{
"name": "Add users",
"action_type": "add",
"object_type": "user"
},
{
"name": "Remove users",
"action_type": "remove",
"object_type": "user"
},
{
"name": "Browse settings",
"action_type": "browse",
"object_type": "setting"
},
{
"name": "Read settings",
"action_type": "read",
"object_type": "setting"
},
{
"name": "Edit settings",
"action_type": "edit",
"object_type": "setting"
},
{
"name": "Browse themes",
"action_type": "browse",
"object_type": "theme"
},
{
"name": "Edit themes",
"action_type": "edit",
"object_type": "theme"
}
],
client003: [
{
"name": "Ghost Admin",
"slug": "ghost-admin",
"secret": "not_available"
},
],
roles003: [
{
"name": "Owner",
"description": "Owners"
}
],
users003: [
{
"name": "Owner",
"email": "ghost@ghost.org",
"status": "inactive",
"password": utils.uid(50),
}
]
};
populateFixtures = function () {
var ops = [],
relations = [],
Post = models.Post,
Tag = models.Tag,
Role = models.Role,
Permission = models.Permission,
Permissions = models.Permissions,
Client = models.Client,
User = models.User;
_.each(fixtures.posts, function (post) {
ops.push(function () {return Post.add(post, {user: 1}); });
});
_.each(fixtures.tags, function (tag) {
ops.push(function () {return Tag.add(tag, {user: 1}); });
});
_.each(fixtures.roles, function (role) {
ops.push(function () {return Role.add(role, {user: 1}); });
});
_.each(fixtures.roles003, function (role) {
ops.push(function () {return Role.add(role, {user: 1}); });
});
_.each(fixtures.permissions, function (permission) {
ops.push(function () {return Permission.add(permission, {user: 1}); });
});
_.each(fixtures.permissions003, function (permission) {
ops.push(function () {return Permission.add(permission, {user: 1}); });
});
_.each(fixtures.client003, function (client) {
ops.push(function () {return Client.add(client, {user: 1}); });
});
// add the tag to the post
relations.push(function () {
return Post.forge({id: 1}).fetch({withRelated: ['tags']}).then(function (post) {
return post.tags().attach([1]);
});
});
//grant permissions to roles
relations.push(function () {
var relationOps = [],
relationOp;
// admins gets all permissions
relationOp = Role.forge({name: 'Administrator'}).fetch({withRelated: ['permissions']}).then(function (role) {
return Permissions.forge().fetch().then(function (perms) {
var admin_perm = _.map(perms.toJSON(), function (perm) {
return perm.id;
});
return role.permissions().attach(_.compact(admin_perm));
});
});
relationOps.push(relationOp);
// editor gets access to posts, users and settings.browse, settings.read
relationOp = Role.forge({name: 'Editor'}).fetch({withRelated: ['permissions']}).then(function (role) {
return Permissions.forge().fetch().then(function (perms) {
var editor_perm = _.map(perms.toJSON(), function (perm) {
if (perm.object_type === 'post' || perm.object_type === 'user' || perm.object_type === 'slug') {
return perm.id;
}
if (perm.object_type === 'setting' &&
(perm.action_type === 'browse' || perm.action_type === 'read')) {
return perm.id;
}
return null;
});
return role.permissions().attach(_.compact(editor_perm));
});
});
relationOps.push(relationOp);
// author gets access to post.add, slug.generate, settings.browse, settings.read, users.browse and users.read
relationOp = Role.forge({name: 'Author'}).fetch({withRelated: ['permissions']}).then(function (role) {
return Permissions.forge().fetch().then(function (perms) {
var author_perm = _.map(perms.toJSON(), function (perm) {
if (perm.object_type === 'post' && perm.action_type === 'add') {
return perm.id;
}
if (perm.object_type === 'slug' && perm.action_type === 'generate') {
return perm.id;
}
if (perm.object_type === 'setting' &&
(perm.action_type === 'browse' || perm.action_type === 'read')) {
return perm.id;
}
if (perm.object_type === 'user' &&
(perm.action_type === 'browse' || perm.action_type === 'read')) {
return perm.id;
}
return null;
});
return role.permissions().attach(_.compact(author_perm));
});
});
relationOps.push(relationOp);
return when.all(relationOps);
});
return sequence(ops).then(function () {
return sequence(relations);
}).then(function () {
return Role.findOne({name: 'Owner'});
}).then(function (ownerRole) {
var user = fixtures.users003[0];
user.role = ownerRole.id;
return User.add(fixtures.users003[0], {user: 1});
/**
* Fetch Admin
* Find the user with the admin role, who can be upgraded to an owner.
* @returns {Promise|*}
*/
fetchAdmin = function () {
return models.User.forge().fetch({
withRelated: [{
'roles': function (qb) {
qb.where('name', 'Administrator');
}
}]
});
};
updateFixtures = function () {
var ops = [],
relations = [],
adminUser,
Role = models.Role,
Permission = models.Permission,
Permissions = models.Permissions,
Client = models.Client,
User = models.User;
/**
* Convert admin to Owner
* Changes an admin user to have the owner role
* @returns {Promise|*}
*/
convertAdminToOwner = function () {
var adminUser;
_.each(fixtures.permissions003, function (permission) {
ops.push(function () {return Permission.add(permission, {user: 1}); });
});
_.each(fixtures.client003, function (client) {
ops.push(function () {return Client.add(client, {user: 1}); });
});
_.each(fixtures.roles003, function (role) {
ops.push(function () {return Role.add(role, {user: 1}); });
});
relations.push(function () {
var relationOps = [],
relationOp;
// admin gets all new permissions
relationOp = Role.forge({name: 'Administrator'}).fetch({withRelated: ['permissions']}).then(function (role) {
return Permissions.forge().fetch().then(function (perms) {
var admin_perm = _.map(perms.toJSON(), function (perm) {
var result = fixtures.permissions003.filter(function (object) {
return object.object_type === perm.object_type && object.action_type === perm.action_type;
});
if (!_.isEmpty(result)) {
return perm.id;
}
return null;
});
return role.permissions().attach(_.compact(admin_perm));
});
});
relationOps.push(relationOp);
// editor gets access to posts, users and settings.browse, settings.read
relationOp = Role.forge({name: 'Editor'}).fetch({withRelated: ['permissions']}).then(function (role) {
return Permissions.forge().fetch().then(function (perms) {
var editor_perm = _.map(perms.toJSON(), function (perm) {
if (perm.object_type === 'post' || perm.object_type === 'user') {
return perm.id;
}
if (perm.object_type === 'setting' &&
(perm.action_type === 'browse' || perm.action_type === 'read')) {
return perm.id;
}
return null;
});
return role.permissions().attach(_.compact(editor_perm));
});
});
relationOps.push(relationOp);
// author gets access to post.add, post.slug, settings.browse, settings.read, users.browse and users.read
relationOp = Role.forge({name: 'Author'}).fetch({withRelated: ['permissions']}).then(function (role) {
return Permissions.forge().fetch().then(function (perms) {
var author_perm = _.map(perms.toJSON(), function (perm) {
if (perm.object_type === 'post' && perm.action_type === 'add') {
return perm.id;
}
if (perm.object_type === 'slug' && perm.action_type === 'generate') {
return perm.id;
}
if (perm.object_type === 'setting' &&
(perm.action_type === 'browse' || perm.action_type === 'read')) {
return perm.id;
}
if (perm.object_type === 'user' &&
(perm.action_type === 'browse' || perm.action_type === 'read')) {
return perm.id;
}
return null;
});
return role.permissions().attach(_.compact(author_perm));
});
});
relationOps.push(relationOp);
return when.all(relationOps);
});
return sequence(ops).then(function () {
return sequence(relations);
}).then(function () {
return User.forge({id: 1}).fetch();
}).then(function (user) {
return fetchAdmin().then(function (user) {
adminUser = user;
return Role.findOne({name: 'Owner'});
return models.Role.findOne({name: 'Owner'});
}).then(function (ownerRole) {
if (adminUser) {
return adminUser.roles().updatePivot({role_id: ownerRole.id});
@ -390,7 +54,112 @@ updateFixtures = function () {
});
};
module.exports = {
populateFixtures: populateFixtures,
updateFixtures: updateFixtures
/**
* Create Owner
* Creates the user fixture and gives it the owner role
* @returns {Promise|*}
*/
createOwner = function () {
var user = fixtures.users[0];
return models.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
user.role = ownerRole.id;
user.password = utils.uid(50);
return models.User.add(user);
});
};
populate = function () {
var ops = [],
relations = [],
Post = models.Post,
Tag = models.Tag,
Role = models.Role,
Client = models.Client;
_.each(fixtures.posts, function (post) {
ops.push(function () { return Post.add(post); });
});
_.each(fixtures.tags, function (tag) {
ops.push(function () { return Tag.add(tag); });
});
_.each(fixtures.roles, function (role) {
ops.push(function () { return Role.add(role); });
});
_.each(fixtures.client, function (client) {
ops.push(function () { return Client.add(client); });
});
// add the tag to the post
relations.push(function () {
return Post.forge({slug: fixtures.posts[0].slug}).fetch({withRelated: ['tags']}).then(function (post) {
return Tag.forge({slug: fixtures.tags[0].slug}).fetch().then(function (tag) {
return post.tags().attach(tag);
});
});
});
return sequence(ops).then(function () {
return sequence(relations);
}).then(function () {
return permissions.populate();
}).then(function () {
return createOwner();
});
};
/**
* ### Update fixtures to 003
* Need to add client & owner role, then update permissions to 003 as well
* By doing this in a way that checks before adding, we can ensure that it's possible to force a migration safely.
*
* Note: At the moment this is pretty adhoc & untestable, in future it would be better to have a config based system.
* @returns {Promise|*}
*/
to003 = function () {
var ops = [],
upgradeOp,
Role = models.Role,
Client = models.Client;
// Add the client fixture if missing
upgradeOp = Client.findOne({secret: fixtures.client[0].secret}).then(function (client) {
if (!client) {
_.each(fixtures.client, function (client) {
return Client.add(client);
});
}
});
ops.push(upgradeOp);
// Add the owner role if missing
upgradeOp = Role.findOne({name: fixtures.roles[3].name}).then(function (owner) {
if (!owner) {
_.each(fixtures.roles.slice(3), function (role) {
return Role.add(role);
});
}
});
ops.push(upgradeOp);
return when.all(ops).then(function () {
return permissions.to003();
}).then(function () {
return convertAdminToOwner();
});
};
update = function (fromVersion, toVersion) {
if (fromVersion < '003' && toVersion >= '003') {
return to003();
}
};
module.exports = {
populate: populate,
update: update
};

View file

@ -0,0 +1,101 @@
// # Permissions Fixtures
// Sets up the permissions, and the default permissions_roles relationships
var when = require('when'),
sequence = require('when/sequence'),
_ = require('lodash'),
models = require('../../../models'),
fixtures = require('./permissions'),
populate,
to003,
addAllPermissions,
addAllRolesPermissions,
addRolesPermissionsForRole;
addRolesPermissionsForRole = function (roleName) {
var fixturesForRole = fixtures.permissions_roles[roleName],
permissionsToAdd;
return models.Role.forge({name: roleName}).fetch({withRelated: ['permissions']}).then(function (role) {
return models.Permissions.forge().fetch().then(function (permissions) {
if (_.isObject(fixturesForRole)) {
permissionsToAdd = _.map(permissions.toJSON(), function (permission) {
var objectPermissions = fixturesForRole[permission.object_type];
if (objectPermissions === 'all') {
return permission.id;
} else if (_.isArray(objectPermissions) && _.contains(objectPermissions, permission.action_type)) {
return permission.id;
}
return null;
});
}
return role.permissions().attach(_.compact(permissionsToAdd));
});
});
};
addAllRolesPermissions = function () {
var roleNames = _.keys(fixtures.permissions_roles),
ops = [];
_.each(roleNames, function (roleName) {
ops.push(addRolesPermissionsForRole(roleName));
});
return when.all(ops);
};
addAllPermissions = function () {
var ops = [];
_.each(fixtures.permissions, function (permissions, object_type) {
_.each(permissions, function (permission) {
ops.push(function () {
permission.object_type = object_type;
return models.Permission.add(permission);
});
});
});
return sequence(ops);
};
// ## Populate
populate = function () {
// ### Ensure all permissions are added
return addAllPermissions().then(function () {
// ### Ensure all roles_permissions are added
return addAllRolesPermissions();
});
};
// ## Update
// Update permissions to 003
// Need to rename old permissions, and then add all of the missing ones
to003 = function () {
var ops = [];
// To safely upgrade, we need to clear up the existing permissions and permissions_roles before recreating the new
// full set of permissions defined as of version 003
models.Permissions.forge().fetch().then(function (permissions) {
permissions.each(function (permission) {
ops.push(permission.related('roles').detach().then(function () {
return permission.destroy();
}));
});
});
// Now we can perfom the normal populate
return when.all(ops).then(function () {
return populate();
});
};
module.exports = {
populate: populate,
to003: to003
};

View file

@ -0,0 +1,110 @@
{
"permissions": {
"post": [
{
"name": "Edit posts",
"action_type": "edit"
},
{
"name": "Remove posts",
"action_type": "remove"
},
{
"name": "Create posts",
"action_type": "create"
}
],
"slug": [
{
"name": "Generate post slug",
"action_type": "generate"
},
{
"name": "Generate tag slug",
"action_type": "generate"
}
],
"db": [
{
"name": "Export database",
"action_type": "exportContent"
},
{
"name": "Import database",
"action_type": "importContent"
},
{
"name": "Delete all content",
"action_type": "deleteAllContent"
}
],
"user": [
{
"name": "Browse users",
"action_type": "browse"
},
{
"name": "Read users",
"action_type": "read"
},
{
"name": "Edit users",
"action_type": "edit"
},
{
"name": "Add users",
"action_type": "add"
},
{
"name": "Remove users",
"action_type": "remove"
}
],
"setting": [
{
"name": "Browse settings",
"action_type": "browse"
},
{
"name": "Read settings",
"action_type": "read"
},
{
"name": "Edit settings",
"action_type": "edit"
}
],
"theme": [
{
"name": "Browse themes",
"action_type": "browse"
},
{
"name": "Edit themes",
"action_type": "edit"
}
]
},
"permissions_roles": {
"Administrator": {
"post": "all",
"slug": "all",
"db": "all",
"user": "all",
"setting": "all",
"theme": "all"
},
"Editor": {
"post": "all",
"slug": "all",
"user": "all",
"setting": ["browse", "read"]
},
"Author": {
"post": ["add"],
"slug": "all",
"user": ["browse", "read"],
"setting": ["browse", "read"]
}
}
}

View file

@ -46,7 +46,7 @@ function getAddCommands(oldTables, newTables) {
function addColumnCommands(table, columns) {
var columnKeys = _.keys(schema[table]),
addColumns = _.difference(columnKeys, columns);
return _.map(addColumns, function (column) {
return function () {
utils.addColumn(table, column);
@ -90,7 +90,7 @@ init = function () {
if (databaseVersion < defaultVersion) {
// 2. The database exists but is out of date
// Migrate to latest version
return self.migrateUp().then(function () {
return self.migrateUp(databaseVersion, defaultVersion).then(function () {
// Finally update the databases current version
return versioning.setDatabaseVersion();
});
@ -139,7 +139,7 @@ migrateUpFreshDb = function () {
return sequence(tables).then(function () {
// Load the fixtures
return fixtures.populateFixtures().then(function () {
return fixtures.populate().then(function () {
// Initialise the default settings
return models.Settings.populateDefaults();
});
@ -174,7 +174,7 @@ function backupDatabase() {
}
// Migrate from a specific version to the latest
migrateUp = function () {
migrateUp = function (fromVersion, toVersion) {
var deleteCommands,
addCommands,
oldTables,
@ -236,7 +236,7 @@ migrateUp = function () {
}
return;
}).then(function () {
return fixtures.updateFixtures();
return fixtures.update(fromVersion, toVersion);
});
};
@ -245,4 +245,4 @@ module.exports = {
reset: reset,
migrateUp: migrateUp,
migrateUpFreshDb: migrateUpFreshDb
};
};