mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Merge branch '0.4-maintenance'
Conflicts: core/server/config/paths.js core/test/unit/config_spec.js
This commit is contained in:
commit
199a92d9cb
30 changed files with 970 additions and 161 deletions
|
@ -49,9 +49,6 @@
|
|||
};
|
||||
|
||||
Ghost.init = function () {
|
||||
// remove the temporary message which appears
|
||||
$('.js-msg').remove();
|
||||
|
||||
Ghost.router = new Ghost.Router();
|
||||
|
||||
// This is needed so Backbone recognizes elements already rendered server side
|
||||
|
|
|
@ -4,37 +4,29 @@
|
|||
"use strict";
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
Ghost.ProgressModel = Backbone.Model.extend({
|
||||
|
||||
// Adds in a call to start a loading bar
|
||||
// This is sets up a success function which completes the loading bar
|
||||
fetch : function (options) {
|
||||
options = options || {};
|
||||
|
||||
// Adds in a call to start a loading bar
|
||||
// This is sets up a success function which completes the loading bar
|
||||
function wrapSync(method, model, options) {
|
||||
if (options !== undefined && _.isObject(options)) {
|
||||
NProgress.start();
|
||||
|
||||
var self = this,
|
||||
oldSuccess = options.success;
|
||||
|
||||
options.success = function () {
|
||||
NProgress.done();
|
||||
return oldSuccess.apply(self, arguments);
|
||||
};
|
||||
|
||||
return Backbone.Model.prototype.fetch.call(this, options);
|
||||
}
|
||||
|
||||
return Backbone.sync.call(this, method, model, options);
|
||||
}
|
||||
|
||||
Ghost.ProgressModel = Backbone.Model.extend({
|
||||
sync: wrapSync
|
||||
});
|
||||
|
||||
Ghost.ProgressCollection = Backbone.Collection.extend({
|
||||
|
||||
// Adds in a call to start a loading bar
|
||||
// This is sets up a success function which completes the loading bar
|
||||
fetch : function (options) {
|
||||
options = options || {};
|
||||
|
||||
NProgress.start();
|
||||
|
||||
options.success = function () {
|
||||
NProgress.done();
|
||||
};
|
||||
|
||||
return Backbone.Collection.prototype.fetch.call(this, options);
|
||||
}
|
||||
sync: wrapSync
|
||||
});
|
||||
}());
|
|
@ -134,6 +134,12 @@
|
|||
var $target = $(e.currentTarget),
|
||||
searchTerm = $.trim($target.val());
|
||||
|
||||
if (searchTerm) {
|
||||
this.showSuggestions($target, searchTerm);
|
||||
} else {
|
||||
this.$suggestions.hide();
|
||||
}
|
||||
|
||||
if (e.keyCode === this.keys.UP) {
|
||||
e.preventDefault();
|
||||
if (this.$suggestions.is(":visible")) {
|
||||
|
@ -159,12 +165,6 @@
|
|||
if (e.keyCode === this.keys.UP || e.keyCode === this.keys.DOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (searchTerm) {
|
||||
this.showSuggestions($target, searchTerm);
|
||||
} else {
|
||||
this.$suggestions.hide();
|
||||
}
|
||||
},
|
||||
|
||||
handleKeydown: function (e) {
|
||||
|
|
|
@ -212,6 +212,7 @@
|
|||
ne2Password = this.$('input[name="ne2password"]').val();
|
||||
|
||||
if (newPassword !== ne2Password) {
|
||||
Ghost.notifications.clearEverything();
|
||||
Ghost.notifications.addItem({
|
||||
type: 'error',
|
||||
message: "Your passwords do not match.",
|
||||
|
|
|
@ -38,11 +38,11 @@ BSStore.prototype.set = function (sid, sessData, callback) {
|
|||
sessionModel.forge({id: sid}).fetch()
|
||||
.then(function (model) {
|
||||
if (model) {
|
||||
sessionModel.forge({id: sid, expires: expires, sess: sessData }).save();
|
||||
} else {
|
||||
sessionModel.forge({id: sid, expires: expires, sess: sessData })
|
||||
.save(null, {method: 'insert'});
|
||||
return sessionModel.forge({id: sid, expires: expires, sess: sessData }).save();
|
||||
}
|
||||
return sessionModel.forge({id: sid, expires: expires, sess: sessData })
|
||||
.save(null, {method: 'insert'});
|
||||
}).then(function () {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -41,7 +41,8 @@ function paths() {
|
|||
'lang': path.join(corePath, '/shared/lang/'),
|
||||
'debugPath': subdir + '/ghost/debug/',
|
||||
'availableThemes': availableThemes,
|
||||
'availableApps': availableApps
|
||||
'availableApps': availableApps,
|
||||
'builtScriptPath': path.join(corePath, 'built/scripts/')
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"core": {
|
||||
"databaseVersion": {
|
||||
"defaultValue": "001"
|
||||
"defaultValue": "002"
|
||||
},
|
||||
"dbHash": {
|
||||
"defaultValue": null
|
||||
|
@ -10,7 +10,7 @@
|
|||
"defaultValue": null
|
||||
},
|
||||
"displayUpdateNotification": {
|
||||
"defaultValue": false
|
||||
"defaultValue": null
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
|
|
|
@ -12,7 +12,8 @@ Importer000 = function () {
|
|||
|
||||
this.importFrom = {
|
||||
'000': this.basicImport,
|
||||
'001': this.basicImport
|
||||
'001': this.basicImport,
|
||||
'002': this.basicImport
|
||||
};
|
||||
};
|
||||
|
||||
|
|
8
core/server/data/import/002.js
Normal file
8
core/server/data/import/002.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
var Importer000 = require('./000');
|
||||
|
||||
module.exports = {
|
||||
Importer002: Importer000,
|
||||
importData: function (data) {
|
||||
return new Importer000.importData(data);
|
||||
}
|
||||
};
|
|
@ -77,11 +77,15 @@ function createTable(table) {
|
|||
var column,
|
||||
columnKeys = _.keys(schema[table]);
|
||||
_.each(columnKeys, function (key) {
|
||||
if (schema[table][key].hasOwnProperty('maxlength')) {
|
||||
// creation distinguishes between text with fieldtype, string with maxlength and all others
|
||||
if (schema[table][key].type === 'text' && schema[table][key].hasOwnProperty('fieldtype')) {
|
||||
column = t[schema[table][key].type](key, schema[table][key].fieldtype);
|
||||
} else if (schema[table][key].type === 'string' && schema[table][key].hasOwnProperty('maxlength')) {
|
||||
column = t[schema[table][key].type](key, schema[table][key].maxlength);
|
||||
} else {
|
||||
column = t[schema[table][key].type](key);
|
||||
}
|
||||
|
||||
if (schema[table][key].hasOwnProperty('nullable') && schema[table][key].nullable === true) {
|
||||
column.nullable();
|
||||
} else {
|
||||
|
@ -229,12 +233,37 @@ migrateUpFreshDb = function () {
|
|||
});
|
||||
};
|
||||
|
||||
// This function changes the type of posts.html and posts.markdown columns to mediumtext. Due to
|
||||
// a wrong datatype in schema.js some installations using mysql could have been created using the
|
||||
// data type text instead of mediumtext.
|
||||
// For details see: https://github.com/TryGhost/Ghost/issues/1947
|
||||
function checkMySQLPostTable() {
|
||||
return knex.raw("SHOW FIELDS FROM posts where Field ='html' OR Field = 'markdown'").then(function (response) {
|
||||
return _.flatten(_.map(response[0], function (entry) {
|
||||
if (entry.Type.toLowerCase() !== 'mediumtext') {
|
||||
return knex.raw("ALTER TABLE posts MODIFY " + entry.Field + " MEDIUMTEXT").then(function () {
|
||||
return when.resolve();
|
||||
});
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
// Migrate from a specific version to the latest
|
||||
migrateUp = function () {
|
||||
return getTables().then(function (oldTables) {
|
||||
// if tables exist and lient is mysqls check if posts table is okay
|
||||
if (!_.isEmpty(oldTables) && client === 'mysql') {
|
||||
return checkMySQLPostTable().then(function () {
|
||||
return oldTables;
|
||||
});
|
||||
}
|
||||
return oldTables;
|
||||
}).then(function (oldTables) {
|
||||
var deleteCommands = getDeleteCommands(oldTables, schemaTables),
|
||||
addCommands = getAddCommands(oldTables, schemaTables),
|
||||
commands = [];
|
||||
|
||||
if (!_.isEmpty(deleteCommands)) {
|
||||
commands = commands.concat(deleteCommands);
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ var db = {
|
|||
uuid: {type: 'string', maxlength: 36, nullable: false},
|
||||
title: {type: 'string', maxlength: 150, nullable: false},
|
||||
slug: {type: 'string', maxlength: 150, nullable: false, unique: true},
|
||||
markdown: {type: 'text', maxlength: 16777215, nullable: true},
|
||||
html: {type: 'text', maxlength: 16777215, nullable: true},
|
||||
markdown: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
|
||||
html: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
|
||||
image: {type: 'text', maxlength: 2000, nullable: true},
|
||||
featured: {type: 'bool', nullable: false, defaultTo: false},
|
||||
page: {type: 'bool', nullable: false, defaultTo: false},
|
||||
|
|
|
@ -5,6 +5,7 @@ var _ = require('underscore'),
|
|||
configPaths = require('./config/paths'),
|
||||
path = require('path'),
|
||||
when = require('when'),
|
||||
hbs = require('express-hbs'),
|
||||
errors,
|
||||
|
||||
// Paths for views
|
||||
|
@ -121,6 +122,8 @@ errors = {
|
|||
renderErrorPage: function (code, err, req, res, next) {
|
||||
/*jslint unparam:true*/
|
||||
|
||||
var self = this;
|
||||
|
||||
function parseStack(stack) {
|
||||
if (!_.isString(stack)) {
|
||||
return stack;
|
||||
|
@ -158,16 +161,30 @@ errors = {
|
|||
stack = parseStack(err.stack);
|
||||
}
|
||||
|
||||
// TODO: Attach node-polyglot
|
||||
res.status(code).render((errorView || 'error'), {
|
||||
message: err.message || err,
|
||||
code: code,
|
||||
stack: stack
|
||||
}, function (templateErr, html) {
|
||||
if (!templateErr) {
|
||||
return res.send(code, html);
|
||||
}
|
||||
// There was an error trying to render the error page, output the error
|
||||
self.logError(templateErr, 'Error whilst rendering error page', 'Error template has an error');
|
||||
|
||||
// And then try to explain things to the user...
|
||||
// Cheat and output the error using handlebars escapeExpression
|
||||
return res.send(500, "<h1>Oops, seems there is an an error in the error template.</h1>"
|
||||
+ "<p>Encountered the error: </p>"
|
||||
+ "<pre>" + hbs.handlebars.Utils.escapeExpression(templateErr.message || templateErr) + "</pre>"
|
||||
+ "<br ><p>whilst trying to render an error page for the error: </p>"
|
||||
+ code + " " + "<pre>" + hbs.handlebars.Utils.escapeExpression(err.message || err) + "</pre>"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (code >= 500) {
|
||||
this.logError(err, "ErrorPage", "Ghost caught a processing error in the middleware layer.");
|
||||
this.logError(err, "Rendering Error Page", "Ghost caught a processing error in the middleware layer.");
|
||||
}
|
||||
|
||||
// Are we admin? If so, don't worry about the user template
|
||||
|
|
|
@ -12,15 +12,27 @@ var downsize = require('downsize'),
|
|||
filters = require('../filters'),
|
||||
template = require('./template'),
|
||||
schema = require('../data/schema').checks,
|
||||
updateCheck = require('../update-check'),
|
||||
|
||||
assetTemplate = _.template('<%= source %>?v=<%= version %>'),
|
||||
scriptTemplate = _.template('<script src="<%= source %>?v=<%= version %>"></script>'),
|
||||
isProduction = process.env.NODE_ENV === 'production',
|
||||
|
||||
coreHelpers = {},
|
||||
registerHelpers;
|
||||
|
||||
registerHelpers,
|
||||
|
||||
scriptFiles = {
|
||||
production: [
|
||||
'ghost.min.js'
|
||||
],
|
||||
development: [
|
||||
'vendor.js',
|
||||
'helpers.js',
|
||||
'templates.js',
|
||||
'models.js',
|
||||
'views.js'
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* [ description]
|
||||
|
@ -262,28 +274,16 @@ coreHelpers.fileStorage = function (context, options) {
|
|||
};
|
||||
|
||||
coreHelpers.ghostScriptTags = function () {
|
||||
var scriptFiles = [];
|
||||
var scriptList = isProduction ? scriptFiles.production : scriptFiles.development;
|
||||
|
||||
if (isProduction) {
|
||||
scriptFiles.push("ghost.min.js");
|
||||
} else {
|
||||
scriptFiles = [
|
||||
'vendor.js',
|
||||
'helpers.js',
|
||||
'templates.js',
|
||||
'models.js',
|
||||
'views.js'
|
||||
];
|
||||
}
|
||||
|
||||
scriptFiles = _.map(scriptFiles, function (fileName) {
|
||||
scriptList = _.map(scriptList, function (fileName) {
|
||||
return scriptTemplate({
|
||||
source: config.paths().subdir + '/ghost/scripts/' + fileName,
|
||||
version: coreHelpers.assetHash
|
||||
});
|
||||
});
|
||||
|
||||
return scriptFiles.join('');
|
||||
return scriptList.join('');
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -385,14 +385,14 @@ coreHelpers.ghost_foot = function (options) {
|
|||
|
||||
coreHelpers.meta_title = function (options) {
|
||||
/*jslint unparam:true*/
|
||||
var title,
|
||||
var title = "",
|
||||
blog;
|
||||
|
||||
if (_.isString(this.relativeUrl)) {
|
||||
if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '' || this.relativeUrl.match(/\/page/)) {
|
||||
blog = config.theme();
|
||||
title = blog.title;
|
||||
} else {
|
||||
} else if (this.post) {
|
||||
title = this.post.title;
|
||||
}
|
||||
}
|
||||
|
@ -569,8 +569,8 @@ coreHelpers.updateNotification = function () {
|
|||
return when(output);
|
||||
}
|
||||
|
||||
return api.settings.read('displayUpdateNotification').then(function (display) {
|
||||
if (display && display.value && display.value === 'true') {
|
||||
return updateCheck.showUpdateNotification().then(function (result) {
|
||||
if (result) {
|
||||
output = '<div class="notification-success">' +
|
||||
'A new version of Ghost is available! Hot damn. ' +
|
||||
'<a href="http://ghost.org/download">Upgrade now</a></div>';
|
||||
|
@ -677,3 +677,4 @@ module.exports = coreHelpers;
|
|||
module.exports.loadCoreHelpers = registerHelpers;
|
||||
module.exports.registerThemeHelper = registerThemeHelper;
|
||||
module.exports.registerAsyncThemeHelper = registerAsyncThemeHelper;
|
||||
module.exports.scriptFiles = scriptFiles;
|
||||
|
|
|
@ -17,8 +17,7 @@ templates.execute = function (name, context) {
|
|||
|
||||
// If the partial view is not compiled, it compiles and saves in handlebars
|
||||
if (typeof partial === 'string') {
|
||||
partial = hbs.handlebars.compile(partial);
|
||||
hbs.handlebars.partials[name] = partial;
|
||||
hbs.registerPartial(partial);
|
||||
}
|
||||
|
||||
return new hbs.handlebars.SafeString(partial(context));
|
||||
|
|
|
@ -70,6 +70,43 @@ function initDbHashAndFirstRun() {
|
|||
});
|
||||
}
|
||||
|
||||
// Checks for the existence of the "built" javascript files from grunt concat.
|
||||
// Returns a promise that will be resolved if all files exist or rejected if
|
||||
// any are missing.
|
||||
function builtFilesExist() {
|
||||
var deferreds = [],
|
||||
location = config.paths().builtScriptPath,
|
||||
|
||||
fileNames = process.env.NODE_ENV === 'production' ?
|
||||
helpers.scriptFiles.production : helpers.scriptFiles.development;
|
||||
|
||||
function checkExist(fileName) {
|
||||
var deferred = when.defer(),
|
||||
errorMessage = "Javascript files have not been built.",
|
||||
errorHelp = "\nPlease read the getting started instructions at:" +
|
||||
"\nhttps://github.com/TryGhost/Ghost#getting-started-guide-for-developers";
|
||||
|
||||
fs.exists(fileName, function (exists) {
|
||||
if (exists) {
|
||||
deferred.resolve(true);
|
||||
} else {
|
||||
var err = new Error(errorMessage);
|
||||
|
||||
err.help = errorHelp;
|
||||
deferred.reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
fileNames.forEach(function (fileName) {
|
||||
deferreds.push(checkExist(location + fileName));
|
||||
});
|
||||
|
||||
return when.all(deferreds);
|
||||
}
|
||||
|
||||
// Sets up the express server instance.
|
||||
// Instantiates the ghost singleton, helpers, routes, middleware, and apps.
|
||||
// Finally it starts the http server.
|
||||
|
@ -101,6 +138,9 @@ function setup(server) {
|
|||
// Initialize the permissions actions and objects
|
||||
permissions.init()
|
||||
);
|
||||
}).then(function () {
|
||||
// Make sure javascript files have been built via grunt concat
|
||||
return builtFilesExist();
|
||||
}).then(function () {
|
||||
// Initialize mail
|
||||
return mailer.init();
|
||||
|
@ -225,7 +265,7 @@ function setup(server) {
|
|||
|
||||
});
|
||||
}, function (err) {
|
||||
errors.logErrorAndExit(err);
|
||||
errors.logErrorAndExit(err, err.context, err.help);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ var middleware = require('./middleware'),
|
|||
slashes = require('connect-slashes'),
|
||||
errors = require('../errorHandling'),
|
||||
api = require('../api'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
hbs = require('express-hbs'),
|
||||
config = require('../config'),
|
||||
|
@ -91,6 +92,7 @@ function initViews(req, res, next) {
|
|||
// Helper for manageAdminAndTheme
|
||||
function activateTheme(activeTheme) {
|
||||
var hbsOptions,
|
||||
themePartials = path.join(config.paths().themePath, activeTheme, 'partials'),
|
||||
stackLocation = _.indexOf(expressServer.stack, _.find(expressServer.stack, function (stackItem) {
|
||||
return stackItem.route === config.paths().subdir && stackItem.handle.name === 'settingEnabled';
|
||||
}));
|
||||
|
@ -106,10 +108,14 @@ function activateTheme(activeTheme) {
|
|||
|
||||
// set view engine
|
||||
hbsOptions = { partialsDir: [ config.paths().helperTemplates ] };
|
||||
if (config.paths().availableThemes[activeTheme].hasOwnProperty('partials')) {
|
||||
|
||||
fs.stat(themePartials, function (err, stats) {
|
||||
// Check that the theme has a partials directory before trying to use it
|
||||
hbsOptions.partialsDir.push(path.join(config.paths().themePath, activeTheme, 'partials'));
|
||||
}
|
||||
if (!err && stats && stats.isDirectory()) {
|
||||
hbsOptions.partialsDir.push(themePartials);
|
||||
}
|
||||
});
|
||||
|
||||
expressServer.set('theme view engine', hbs.express3(hbsOptions));
|
||||
|
||||
// Update user error template
|
||||
|
@ -136,13 +142,18 @@ function manageAdminAndTheme(req, res, next) {
|
|||
if (!config.paths().availableThemes.hasOwnProperty(activeTheme.value)) {
|
||||
if (!res.isAdmin) {
|
||||
// Throw an error if the theme is not available, but not on the admin UI
|
||||
errors.logAndThrowError('The currently active theme ' + activeTheme.value + ' is missing.');
|
||||
return errors.throwError('The currently active theme ' + activeTheme.value + ' is missing.');
|
||||
}
|
||||
} else {
|
||||
activateTheme(activeTheme.value);
|
||||
}
|
||||
}
|
||||
next();
|
||||
}).otherwise(function (err) {
|
||||
// Trying to start up without the active theme present, setup a simple hbs instance
|
||||
// and render an error page straight away.
|
||||
expressServer.engine('hbs', hbs.express3());
|
||||
next(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,9 @@ Post = ghostBookshelf.Model.extend({
|
|||
}
|
||||
// This will need to go elsewhere in the API layer.
|
||||
this.set('published_by', 1);
|
||||
} else if (this.get('status') === 'published' && !this.get('published_at')) {
|
||||
// If somehow this is a published post with no date, fix it... see #2015
|
||||
this.set('published_at', new Date());
|
||||
}
|
||||
|
||||
ghostBookshelf.Model.prototype.saving.call(this);
|
||||
|
@ -115,7 +118,8 @@ Post = ghostBookshelf.Model.extend({
|
|||
var existingTags = thisPostWithTags.related('tags').toJSON(),
|
||||
tagOperations = [],
|
||||
tagsToDetach = [],
|
||||
tagsToAttach = [];
|
||||
tagsToAttach = [],
|
||||
createdTagsToAttach = [];
|
||||
|
||||
// First find any tags which have been removed
|
||||
_.each(existingTags, function (existingTag) {
|
||||
|
@ -143,23 +147,45 @@ Post = ghostBookshelf.Model.extend({
|
|||
tagsToAttach = _.reject(tagsToAttach, function (tagToAttach) {
|
||||
return tagToAttach.name === matchingTag.name;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Return if no tags to add
|
||||
if (tagsToAttach.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set method to insert, so each tag gets inserted with the appropriate options
|
||||
var opt = options.method;
|
||||
options.method = 'insert';
|
||||
|
||||
// Create each tag that doesn't yet exist
|
||||
_.each(tagsToAttach, function (tagToCreateAndAttach) {
|
||||
var createAndAttachOperation,
|
||||
opt = options.method;
|
||||
//TODO: remove when refactor; ugly fix to overcome bookshelf
|
||||
options.method = 'insert';
|
||||
createAndAttachOperation = Tag.add({name: tagToCreateAndAttach.name}, options).then(function (createdTag) {
|
||||
options.method = opt;
|
||||
return self.tags().attach(createdTag.id, createdTag.name, options);
|
||||
var createAndAttachOperation = Tag.add({name: tagToCreateAndAttach.name}, options).then(function (createdTag) {
|
||||
createdTagsToAttach.push(createdTag);
|
||||
|
||||
// If the tags are all inserted, process them
|
||||
if (tagsToAttach.length === createdTagsToAttach.length) {
|
||||
|
||||
// Set method back to whatever it was, for tag attachment
|
||||
options.method = opt;
|
||||
|
||||
// Attach each newly created tag
|
||||
_.each(createdTagsToAttach, function (tagToAttach) {
|
||||
self.tags().attach(tagToAttach.id, tagToAttach.name, options);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
tagOperations.push(createAndAttachOperation);
|
||||
|
||||
});
|
||||
|
||||
// Return when all tags attached
|
||||
return when.all(tagOperations);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -381,10 +407,7 @@ Post = ghostBookshelf.Model.extend({
|
|||
var self = this;
|
||||
|
||||
return ghostBookshelf.Model.edit.call(this, editedPost, options).then(function (editedObj) {
|
||||
return when(editedObj.updateTags(editedPost.tags, null, options)).then(function () {
|
||||
return self.findOne({status: 'all', id: editedObj.id}, options);
|
||||
});
|
||||
//return self.findOne({status: 'all', id: editedObj.id}, options);
|
||||
return self.findOne({status: 'all', id: editedObj.id}, options);
|
||||
});
|
||||
},
|
||||
destroy: function (_identifier, options) {
|
||||
|
@ -411,4 +434,4 @@ Posts = ghostBookshelf.Collection.extend({
|
|||
module.exports = {
|
||||
Post: Post,
|
||||
Posts: Posts
|
||||
};
|
||||
};
|
|
@ -69,11 +69,10 @@ User = ghostBookshelf.Model.extend({
|
|||
|
||||
// disabling sanitization until we can implement a better version
|
||||
// this.set('name', this.sanitize('name'));
|
||||
// this.set('email', this.sanitize('email').toLocaleLowerCase());
|
||||
// this.set('email', this.sanitize('email'));
|
||||
// this.set('location', this.sanitize('location'));
|
||||
// this.set('website', this.sanitize('website'));
|
||||
// this.set('bio', this.sanitize('bio'));
|
||||
this.set('email', this.get('email').toLocaleLowerCase());
|
||||
|
||||
return ghostBookshelf.Model.prototype.saving.apply(this, arguments);
|
||||
},
|
||||
|
@ -182,9 +181,8 @@ User = ghostBookshelf.Model.extend({
|
|||
check: function (_userdata) {
|
||||
var self = this,
|
||||
s;
|
||||
return this.forge({
|
||||
email: _userdata.email.toLocaleLowerCase()
|
||||
}).fetch({require: true}).then(function (user) {
|
||||
|
||||
return this.getByEmail(_userdata.email).then(function (user) {
|
||||
if (user.get('status') !== 'locked') {
|
||||
return nodefn.call(bcrypt.compare, _userdata.pw, user.get('password')).then(function (matched) {
|
||||
if (!matched) {
|
||||
|
@ -205,8 +203,11 @@ User = ghostBookshelf.Model.extend({
|
|||
'the "Forgotten password?" link!'));
|
||||
|
||||
}, function (error) {
|
||||
/*jslint unparam:true*/
|
||||
return when.reject(new Error('There is no user with that email address.'));
|
||||
if (error.message === 'NotFound' || error.message === 'EmptyResponse') {
|
||||
return when.reject(new Error('There is no user with that email address.'));
|
||||
}
|
||||
|
||||
return when.reject(error);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -248,7 +249,7 @@ User = ghostBookshelf.Model.extend({
|
|||
},
|
||||
|
||||
generateResetToken: function (email, expires, dbHash) {
|
||||
return this.forge({email: email.toLocaleLowerCase()}).fetch({require: true}).then(function (foundUser) {
|
||||
return this.getByEmail(email).then(function (foundUser) {
|
||||
var hash = crypto.createHash('sha256'),
|
||||
text = "";
|
||||
|
||||
|
@ -375,8 +376,28 @@ User = ghostBookshelf.Model.extend({
|
|||
});
|
||||
|
||||
return checkPromise.promise;
|
||||
}
|
||||
},
|
||||
|
||||
// Get the user by email address, enforces case insensitivity rejects if the user is not found
|
||||
// When multi-user support is added, email addresses must be deduplicated with case insensitivity, so that
|
||||
// joe@bloggs.com and JOE@BLOGGS.COM cannot be created as two separate users.
|
||||
getByEmail: function (email) {
|
||||
// We fetch all users and process them in JS as there is no easy way to make this query across all DBs
|
||||
// Although they all support `lower()`, sqlite can't case transform unicode characters
|
||||
// This is somewhat mute, as validator.isEmail() also doesn't support unicode, but this is much easier / more
|
||||
// likely to be fixed in the near future.
|
||||
return Users.forge().fetch({require: true}).then(function (users) {
|
||||
var userWithEmail = users.find(function (user) {
|
||||
return user.get('email').toLowerCase() === email.toLowerCase();
|
||||
});
|
||||
|
||||
if (userWithEmail) {
|
||||
return when.resolve(userWithEmail);
|
||||
}
|
||||
|
||||
return when.reject(new Error('NotFound'));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Users = ghostBookshelf.Collection.extend({
|
||||
|
|
|
@ -20,7 +20,7 @@ localFileStore = _.extend(baseStore, {
|
|||
// - returns a promise which ultimately returns the full url to the uploaded image
|
||||
'save': function (image) {
|
||||
var saved = when.defer(),
|
||||
targetDir = this.getTargetDir(configPaths().imagesRelPath),
|
||||
targetDir = this.getTargetDir(configPaths().imagesPath),
|
||||
targetFilename;
|
||||
|
||||
this.getUniqueFileName(this, image, targetDir).then(function (filename) {
|
||||
|
@ -33,7 +33,7 @@ localFileStore = _.extend(baseStore, {
|
|||
}).then(function () {
|
||||
// The src for the image must be in URI format, not a file system path, which in Windows uses \
|
||||
// For local file system storage can use relative path so add a slash
|
||||
var fullUrl = (configPaths().subdir + '/' + targetFilename).replace(new RegExp('\\' + path.sep, 'g'), '/');
|
||||
var fullUrl = (configPaths().subdir + '/' + path.relative(configPaths().appRoot, targetFilename)).replace(new RegExp('\\' + path.sep, 'g'), '/');
|
||||
return saved.resolve(fullUrl);
|
||||
}).otherwise(function (e) {
|
||||
errors.logError(e);
|
||||
|
|
|
@ -31,10 +31,11 @@ var crypto = require('crypto'),
|
|||
api = require('./api'),
|
||||
config = require('./config'),
|
||||
errors = require('./errorHandling'),
|
||||
packageInfo = require('../../package.json'),
|
||||
|
||||
allowedCheckEnvironments = ['development', 'production'],
|
||||
checkEndpoint = 'updates.ghost.org',
|
||||
currentVersion;
|
||||
currentVersion = packageInfo.version;
|
||||
|
||||
function updateCheckError(error) {
|
||||
errors.logError(
|
||||
|
@ -140,13 +141,12 @@ function updateCheckRequest() {
|
|||
// 1. Updates the time we can next make a check
|
||||
// 2. Checks if the version in the response is new, and updates the notification setting
|
||||
function updateCheckResponse(response) {
|
||||
var ops = [],
|
||||
displayUpdateNotification = currentVersion && semver.gt(response.version, currentVersion);
|
||||
var ops = [];
|
||||
|
||||
ops.push(api.settings.edit('nextUpdateCheck', response.next_check)
|
||||
.otherwise(errors.rejectError));
|
||||
|
||||
ops.push(api.settings.edit('displayUpdateNotification', displayUpdateNotification)
|
||||
ops.push(api.settings.edit('displayUpdateNotification', response.version)
|
||||
.otherwise(errors.rejectError));
|
||||
|
||||
return when.settle(ops).then(function (descriptors) {
|
||||
|
@ -159,7 +159,7 @@ function updateCheckResponse(response) {
|
|||
});
|
||||
}
|
||||
|
||||
function updateCheck(res) {
|
||||
function updateCheck() {
|
||||
var deferred = when.defer();
|
||||
|
||||
// The check will not happen if:
|
||||
|
@ -175,8 +175,7 @@ function updateCheck(res) {
|
|||
// It's not time to check yet
|
||||
deferred.resolve();
|
||||
} else {
|
||||
// We need to do a check, store the current version
|
||||
currentVersion = res.locals.version;
|
||||
// We need to do a check
|
||||
return updateCheckRequest()
|
||||
.then(updateCheckResponse)
|
||||
.otherwise(updateCheckError);
|
||||
|
@ -188,4 +187,21 @@ function updateCheck(res) {
|
|||
return deferred.promise;
|
||||
}
|
||||
|
||||
function showUpdateNotification() {
|
||||
return api.settings.read('displayUpdateNotification').then(function (display) {
|
||||
// Version 0.4 used boolean to indicate the need for an update. This special case is
|
||||
// translated to the version string.
|
||||
// TODO: remove in future version.
|
||||
if (display.value === 'false' || display.value === 'true') {
|
||||
display.value = '0.4.0';
|
||||
}
|
||||
|
||||
if (display && display.value && currentVersion && semver.gt(display.value, currentVersion)) {
|
||||
return when(true);
|
||||
}
|
||||
return when(false);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = updateCheck;
|
||||
module.exports.showUpdateNotification = showUpdateNotification;
|
||||
|
|
|
@ -44,11 +44,6 @@
|
|||
</aside>
|
||||
|
||||
{{{body}}}
|
||||
|
||||
<div class="js-msg">
|
||||
<p>Hello There! Looks like something went wrong with your JavaScript.</p>
|
||||
<p>Either you need to enable JavaScript, or you haven't built the JavaScript files yet. See the README and CONTRIBUTING files for more info.</p>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div id="modal-container">
|
||||
|
|
|
@ -185,7 +185,7 @@ describe('Tag Model', function () {
|
|||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('creates and attaches tags that are new to the Tags table', function (done) {
|
||||
it('creates and attaches a tag that is new to the Tags table', function (done) {
|
||||
var seededTagNames = ['tag1', 'tag2'];
|
||||
|
||||
seedTags(seededTagNames).then(function (postModel) {
|
||||
|
@ -205,6 +205,87 @@ describe('Tag Model', function () {
|
|||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('creates and attaches multiple tags that are new to the Tags table', function (done) {
|
||||
var seededTagNames = ['tag1'];
|
||||
|
||||
seedTags(seededTagNames).then(function (postModel) {
|
||||
// the tag API expects tags to be provided like {id: 1, name: 'draft'}
|
||||
var tagData = seededTagNames.map(function (tagName, i) { return {id: i + 1, name: tagName}; });
|
||||
|
||||
// add the additional tags, and save
|
||||
tagData.push({id: null, name: 'tag2'});
|
||||
tagData.push({id: null, name: 'tag3'});
|
||||
return postModel.set('tags', tagData).save();
|
||||
}).then(function (postModel) {
|
||||
return PostModel.read({id: postModel.id, status: 'all'}, { withRelated: ['tags']});
|
||||
}).then(function (reloadedPost) {
|
||||
var tagNames = reloadedPost.related('tags').models.map(function (t) { return t.attributes.name; });
|
||||
tagNames.sort().should.eql(['tag1', 'tag2', 'tag3']);
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('attaches one tag that exists in the Tags database and one tag that is new to the Tags database', function (done) {
|
||||
var seededTagNames = ['tag1'],
|
||||
postModel;
|
||||
|
||||
seedTags(seededTagNames).then(function (_postModel) {
|
||||
postModel = _postModel;
|
||||
return TagModel.add({name: 'tag2'});
|
||||
}).then(function () {
|
||||
// the tag API expects tags to be provided like {id: 1, name: 'draft'}
|
||||
var tagData = seededTagNames.map(function (tagName, i) { return {id: i + 1, name: tagName}; });
|
||||
|
||||
// Add the tag that exists in the database
|
||||
tagData.push({id: 2, name: 'tag2'});
|
||||
|
||||
// Add the tag that doesn't exist in the database
|
||||
tagData.push({id: 3, name: 'tag3'});
|
||||
|
||||
return postModel.set('tags', tagData).save();
|
||||
}).then(function () {
|
||||
return PostModel.read({id: postModel.id, status: 'all'}, { withRelated: ['tags']});
|
||||
}).then(function (reloadedPost) {
|
||||
var tagModels = reloadedPost.related('tags').models,
|
||||
tagNames = tagModels.map(function (t) { return t.attributes.name; });
|
||||
tagNames.sort().should.eql(['tag1', 'tag2', 'tag3']);
|
||||
tagModels[2].id.should.eql(4); // make sure it hasn't just added a new tag with the same name
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('attaches one tag that exists in the Tags database and two tags that are new to the Tags database', function (done) {
|
||||
var seededTagNames = ['tag1'],
|
||||
postModel;
|
||||
|
||||
seedTags(seededTagNames).then(function (_postModel) {
|
||||
postModel = _postModel;
|
||||
return TagModel.add({name: 'tag2'});
|
||||
}).then(function () {
|
||||
// the tag API expects tags to be provided like {id: 1, name: 'draft'}
|
||||
var tagData = seededTagNames.map(function (tagName, i) { return {id: i + 1, name: tagName}; });
|
||||
|
||||
// Add the tag that exists in the database
|
||||
tagData.push({id: 2, name: 'tag2'});
|
||||
|
||||
// Add the tags that doesn't exist in the database
|
||||
tagData.push({id: 3, name: 'tag3'});
|
||||
tagData.push({id: 4, name: 'tag4'});
|
||||
|
||||
return postModel.set('tags', tagData).save();
|
||||
}).then(function () {
|
||||
return PostModel.read({id: postModel.id, status: 'all'}, { withRelated: ['tags']});
|
||||
}).then(function (reloadedPost) {
|
||||
var tagModels = reloadedPost.related('tags').models,
|
||||
tagNames = tagModels.map(function (t) { return t.attributes.name; });
|
||||
tagNames.sort().should.eql(['tag1', 'tag2', 'tag3', 'tag4']);
|
||||
tagModels[2].id.should.eql(4); // make sure it hasn't just added a new tag with the same name
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('can add a tag to a post on creation', function (done) {
|
||||
var newPost = _.extend(testUtils.DataGenerator.forModel.posts[0], {tags: [{name: 'test_tag_1'}]})
|
||||
|
@ -221,4 +302,4 @@ describe('Tag Model', function () {
|
|||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -49,7 +49,7 @@ describe('User Model', function run() {
|
|||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('can lowercase email', function (done) {
|
||||
it('does NOT lowercase email', function (done) {
|
||||
var userData = testUtils.DataGenerator.forModel.users[2],
|
||||
gravatarStub = sinon.stub(UserModel, 'gravatarLookup', function (userData) {
|
||||
return when.resolve(userData);
|
||||
|
@ -58,7 +58,7 @@ describe('User Model', function run() {
|
|||
UserModel.add(userData).then(function (createdUser) {
|
||||
should.exist(createdUser);
|
||||
createdUser.has('uuid').should.equal(true);
|
||||
createdUser.attributes.email.should.eql(userData.email.toLocaleLowerCase(), "email address correct");
|
||||
createdUser.attributes.email.should.eql(userData.email, "email address correct");
|
||||
gravatarStub.restore();
|
||||
done();
|
||||
}).then(null, done);
|
||||
|
@ -80,7 +80,7 @@ describe('User Model', function run() {
|
|||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('can handle no gravatar', function(done) {
|
||||
it('can handle no gravatar', function (done) {
|
||||
var userData = testUtils.DataGenerator.forModel.users[0],
|
||||
gravatarStub = sinon.stub(UserModel, 'gravatarLookup', function (userData) {
|
||||
return when.resolve(userData);
|
||||
|
@ -94,6 +94,39 @@ describe('User Model', function run() {
|
|||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('can find by email and is case insensitive', function (done) {
|
||||
var userData = testUtils.DataGenerator.forModel.users[2],
|
||||
email = testUtils.DataGenerator.forModel.users[2].email;
|
||||
|
||||
UserModel.add(userData).then(function () {
|
||||
// Test same case
|
||||
return UserModel.getByEmail(email).then(function (user) {
|
||||
should.exist(user);
|
||||
user.attributes.email.should.eql(email);
|
||||
});
|
||||
}).then(function () {
|
||||
// Test entered in lowercase
|
||||
return UserModel.getByEmail(email.toLowerCase()).then(function (user) {
|
||||
should.exist(user);
|
||||
user.attributes.email.should.eql(email);
|
||||
});
|
||||
}).then(function () {
|
||||
// Test entered in uppercase
|
||||
return UserModel.getByEmail(email.toUpperCase()).then(function (user) {
|
||||
should.exist(user);
|
||||
user.attributes.email.should.eql(email);
|
||||
});
|
||||
}).then(function () {
|
||||
// Test incorrect email address - swapped capital O for number 0
|
||||
return UserModel.getByEmail('jb0gendAth@example.com').then(null, function (error) {
|
||||
should.exist(error);
|
||||
error.message.should.eql('NotFound');
|
||||
});
|
||||
}).then(function () {
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Basic Operations', function () {
|
||||
|
@ -252,7 +285,7 @@ describe('User Model', function run() {
|
|||
return UserModel.generateResetToken(results.models[0].attributes.email, expires, dbHash);
|
||||
|
||||
}).then(function (token) {
|
||||
|
||||
|
||||
return UserModel.validateToken(token, dbHash);
|
||||
|
||||
}).then(function () {
|
||||
|
@ -278,7 +311,7 @@ describe('User Model', function run() {
|
|||
return UserModel.generateResetToken(firstUser.attributes.email, expires, dbHash);
|
||||
|
||||
}).then(function (token) {
|
||||
|
||||
|
||||
return UserModel.resetPassword(token, 'newpassword', 'newpassword', dbHash);
|
||||
|
||||
}).then(function (resetUser) {
|
||||
|
@ -329,7 +362,7 @@ describe('User Model', function run() {
|
|||
return UserModel.generateResetToken(results.models[0].attributes.email, expires, dbHash);
|
||||
|
||||
}).then(function (token) {
|
||||
|
||||
|
||||
var tokenText = new Buffer(token, 'base64').toString('ascii'),
|
||||
parts = tokenText.split('|'),
|
||||
fakeExpires,
|
||||
|
@ -341,7 +374,7 @@ describe('User Model', function run() {
|
|||
fakeToken = new Buffer(fakeToken).toString('base64');
|
||||
|
||||
return UserModel.validateToken(fakeToken, dbHash);
|
||||
|
||||
|
||||
}).then(function () {
|
||||
throw new Error("allowed invalid token");
|
||||
}, function (err) {
|
||||
|
|
|
@ -303,7 +303,8 @@ describe('Config', function () {
|
|||
'lang',
|
||||
'debugPath',
|
||||
'availableThemes',
|
||||
'availableApps'
|
||||
'availableApps',
|
||||
'builtScriptPath'
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ describe("Exporter", function () {
|
|||
it("exports data", function (done) {
|
||||
// Stub migrations to return 000 as the current database version
|
||||
var migrationStub = sinon.stub(migration, "getDatabaseVersion", function () {
|
||||
return when.resolve("001");
|
||||
return when.resolve("002");
|
||||
});
|
||||
|
||||
exporter().then(function (exportData) {
|
||||
|
@ -48,8 +48,8 @@ describe("Exporter", function () {
|
|||
should.exist(exportData.meta);
|
||||
should.exist(exportData.data);
|
||||
|
||||
exportData.meta.version.should.equal("001");
|
||||
_.findWhere(exportData.data.settings, {key: "databaseVersion"}).value.should.equal("001");
|
||||
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]);
|
||||
|
|
|
@ -14,6 +14,7 @@ var testUtils = require('../utils'),
|
|||
importer = require('../../server/data/import'),
|
||||
Importer000 = require('../../server/data/import/000'),
|
||||
Importer001 = require('../../server/data/import/001'),
|
||||
Importer002 = require('../../server/data/import/002'),
|
||||
fixtures = require('../../server/data/fixtures'),
|
||||
Settings = require('../../server/models/settings').Settings;
|
||||
|
||||
|
@ -59,6 +60,21 @@ describe("Import", function () {
|
|||
}).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("000", function () {
|
||||
should.exist(Importer000);
|
||||
|
||||
|
@ -113,7 +129,7 @@ describe("Import", function () {
|
|||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("001", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
|
@ -191,7 +207,7 @@ describe("Import", function () {
|
|||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("001", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
|
||||
// activeTheme should NOT have been overridden
|
||||
_.findWhere(settings, {key: "activeTheme"}).value.should.equal("casper", 'Wrong theme');
|
||||
|
@ -254,7 +270,7 @@ describe("Import", function () {
|
|||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("001", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
|
@ -300,7 +316,7 @@ describe("Import", function () {
|
|||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("001", 'Wrong database version');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
|
@ -312,4 +328,190 @@ describe("Import", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("002", function () {
|
||||
should.exist(Importer001);
|
||||
|
||||
beforeEach(function (done) {
|
||||
// migrate to current version
|
||||
migration.migrateUp().then(function () {
|
||||
// Load the fixtures
|
||||
return fixtures.populateFixtures();
|
||||
}).then(function () {
|
||||
// Initialise the default settings
|
||||
return Settings.populateDefaults();
|
||||
}).then(function () {
|
||||
return testUtils.insertDefaultUser();
|
||||
}).then(function () {
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it("safely imports data from 002", function (done) {
|
||||
var exportData,
|
||||
timestamp = 1349928000000;
|
||||
|
||||
testUtils.loadExportFixture('export-002').then(function (exported) {
|
||||
exportData = exported;
|
||||
|
||||
// Modify timestamp data for testing
|
||||
exportData.data.posts[0].created_at = timestamp;
|
||||
exportData.data.posts[0].updated_at = timestamp;
|
||||
exportData.data.posts[0].published_at = timestamp;
|
||||
|
||||
return importer("002", exportData);
|
||||
}).then(function () {
|
||||
// Grab the data from tables
|
||||
return when.all([
|
||||
knex("users").select(),
|
||||
knex("posts").select(),
|
||||
knex("settings").select(),
|
||||
knex("tags").select()
|
||||
]);
|
||||
}).then(function (importedData) {
|
||||
should.exist(importedData);
|
||||
|
||||
importedData.length.should.equal(4, 'Did not get data successfully');
|
||||
|
||||
var users = importedData[0],
|
||||
posts = importedData[1],
|
||||
settings = importedData[2],
|
||||
tags = importedData[3],
|
||||
exportEmail;
|
||||
|
||||
// we always have 1 user, the default user we added
|
||||
users.length.should.equal(1, 'There should only be one user');
|
||||
|
||||
// user should still have the credentials from the original insert, not the import
|
||||
users[0].email.should.equal(testUtils.DataGenerator.Content.users[0].email);
|
||||
users[0].password.should.equal(testUtils.DataGenerator.Content.users[0].password);
|
||||
// but the name, slug, and bio should have been overridden
|
||||
users[0].name.should.equal(exportData.data.users[0].name);
|
||||
users[0].slug.should.equal(exportData.data.users[0].slug);
|
||||
users[0].bio.should.equal(exportData.data.users[0].bio);
|
||||
|
||||
// import no longer requires all data to be dropped, and adds posts
|
||||
posts.length.should.equal(exportData.data.posts.length + 1, 'Wrong number of posts');
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
|
||||
// activeTheme should NOT have been overridden
|
||||
_.findWhere(settings, {key: "activeTheme"}).value.should.equal("casper", 'Wrong theme');
|
||||
|
||||
// email address should have been overridden
|
||||
exportEmail = _.findWhere(exportData.data.settings, {key: "email"}).value;
|
||||
_.findWhere(settings, {key: "email"}).value.should.equal(exportEmail, 'Wrong email in settings');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
|
||||
// Ensure imported post retains set timestamp
|
||||
// When in sqlite we are returned a unix timestamp number,
|
||||
// in MySQL we're returned a date object.
|
||||
// We pass the returned post always through the date object
|
||||
// to ensure the return is consistant for all DBs.
|
||||
assert.equal(new Date(posts[1].created_at).getTime(), timestamp);
|
||||
assert.equal(new Date(posts[1].updated_at).getTime(), timestamp);
|
||||
assert.equal(new Date(posts[1].published_at).getTime(), timestamp);
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it("doesn't import invalid post data from 002", function (done) {
|
||||
var exportData;
|
||||
|
||||
|
||||
testUtils.loadExportFixture('export-002').then(function (exported) {
|
||||
exportData = exported;
|
||||
|
||||
//change title to 151 characters
|
||||
exportData.data.posts[0].title = new Array(152).join('a');
|
||||
exportData.data.posts[0].tags = 'Tag';
|
||||
return importer("002", exportData);
|
||||
}).then(function () {
|
||||
(1).should.eql(0, 'Data import should not resolve promise.');
|
||||
}, function (error) {
|
||||
error.should.eql('Error importing data: Post title maximum length is 150 characters.');
|
||||
|
||||
when.all([
|
||||
knex("users").select(),
|
||||
knex("posts").select(),
|
||||
knex("settings").select(),
|
||||
knex("tags").select()
|
||||
]).then(function (importedData) {
|
||||
should.exist(importedData);
|
||||
|
||||
importedData.length.should.equal(4, 'Did not get data successfully');
|
||||
|
||||
var users = importedData[0],
|
||||
posts = importedData[1],
|
||||
settings = importedData[2],
|
||||
tags = importedData[3];
|
||||
|
||||
// we always have 1 user, the default user we added
|
||||
users.length.should.equal(1, 'There should only be one user');
|
||||
// import no longer requires all data to be dropped, and adds posts
|
||||
posts.length.should.equal(exportData.data.posts.length, 'Wrong number of posts');
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
}).then(null, done);
|
||||
});
|
||||
|
||||
it("doesn't import invalid settings data from 002", function (done) {
|
||||
var exportData;
|
||||
|
||||
testUtils.loadExportFixture('export-002').then(function (exported) {
|
||||
exportData = exported;
|
||||
//change to blank settings key
|
||||
exportData.data.settings[3].key = null;
|
||||
return importer("002", exportData);
|
||||
}).then(function () {
|
||||
(1).should.eql(0, 'Data import should not resolve promise.');
|
||||
}, function (error) {
|
||||
error.should.eql('Error importing data: Setting key cannot be blank');
|
||||
|
||||
when.all([
|
||||
knex("users").select(),
|
||||
knex("posts").select(),
|
||||
knex("settings").select(),
|
||||
knex("tags").select()
|
||||
]).then(function (importedData) {
|
||||
should.exist(importedData);
|
||||
|
||||
importedData.length.should.equal(4, 'Did not get data successfully');
|
||||
|
||||
var users = importedData[0],
|
||||
posts = importedData[1],
|
||||
settings = importedData[2],
|
||||
tags = importedData[3];
|
||||
|
||||
// we always have 1 user, the default user we added
|
||||
users.length.should.equal(1, 'There should only be one user');
|
||||
// import no longer requires all data to be dropped, and adds posts
|
||||
posts.length.should.equal(exportData.data.posts.length, 'Wrong number of posts');
|
||||
|
||||
// test settings
|
||||
settings.length.should.be.above(0, 'Wrong number of settings');
|
||||
_.findWhere(settings, {key: "databaseVersion"}).value.should.equal("002", 'Wrong database version');
|
||||
|
||||
// test tags
|
||||
tags.length.should.equal(exportData.data.tags.length, 'no new tags');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
}).then(null, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/*globals describe, beforeEach, afterEach, it*/
|
||||
var testUtils = require('../utils'),
|
||||
should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
when = require('when'),
|
||||
_ = require('underscore'),
|
||||
path = require('path'),
|
||||
rewire = require('rewire'),
|
||||
api = require('../../server/api'),
|
||||
hbs = require('express-hbs'),
|
||||
|
||||
var testUtils = require('../utils'),
|
||||
should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
when = require('when'),
|
||||
_ = require('underscore'),
|
||||
path = require('path'),
|
||||
rewire = require('rewire'),
|
||||
api = require('../../server/api'),
|
||||
hbs = require('express-hbs'),
|
||||
packageInfo = require('../../../package'),
|
||||
|
||||
// Stuff we are testing
|
||||
handlebars = hbs.handlebars,
|
||||
helpers = rewire('../../server/helpers'),
|
||||
config = require('../../server/config');
|
||||
handlebars = hbs.handlebars,
|
||||
helpers = rewire('../../server/helpers'),
|
||||
config = require('../../server/config');
|
||||
|
||||
describe('Core Helpers', function () {
|
||||
|
||||
|
@ -939,14 +939,16 @@ describe('Core Helpers', function () {
|
|||
});
|
||||
});
|
||||
describe('updateNotification', function () {
|
||||
it('outputs a correctly formatted notification when display is set to true', function (done) {
|
||||
it('outputs a correctly formatted notification when db version is higher than package version', function (done) {
|
||||
var output = '<div class="notification-success">' +
|
||||
'A new version of Ghost is available! Hot damn. ' +
|
||||
'<a href="http://ghost.org/download">Upgrade now</a></div>';
|
||||
|
||||
apiStub.restore();
|
||||
apiStub = sandbox.stub(api.settings, 'read', function () {
|
||||
return when({value: 'true'});
|
||||
var futureversion = packageInfo.version.split('.');
|
||||
futureversion[futureversion.length-1] = parseInt(futureversion[futureversion.length-1], 10) + 1;
|
||||
return when({value: futureversion.join('.')});
|
||||
});
|
||||
|
||||
helpers.updateNotification.call({currentUser: {name: 'bob'}}).then(function (rendered) {
|
||||
|
@ -957,7 +959,12 @@ describe('Core Helpers', function () {
|
|||
}).then(null, done);
|
||||
});
|
||||
|
||||
it('does NOT output a correctly formatted notification when display is not set to true', function (done) {
|
||||
it('does NOT output a correctly formatted notification when db version equals package version', function (done) {
|
||||
apiStub.restore();
|
||||
apiStub = sandbox.stub(api.settings, 'read', function () {
|
||||
return when({value: packageInfo.version});
|
||||
});
|
||||
|
||||
helpers.updateNotification.call({currentUser: {name: 'bob'}}).then(function (rendered) {
|
||||
should.exist(rendered);
|
||||
rendered.should.equal('');
|
||||
|
@ -983,7 +990,9 @@ describe('Core Helpers', function () {
|
|||
it('does NOT output a notification if the user is not logged in', function (done) {
|
||||
apiStub.restore();
|
||||
apiStub = sandbox.stub(api.settings, 'read', function () {
|
||||
return when({value: 'true'});
|
||||
var futureversion = packageInfo.version.split('.');
|
||||
futureversion[futureversion.length-1] = parseInt(futureversion[futureversion.length-1], 10) + 1;
|
||||
return when({value: futureversion.join('.')});
|
||||
});
|
||||
|
||||
helpers.updateNotification.call().then(function (rendered) {
|
||||
|
|
|
@ -61,7 +61,7 @@ describe('Local File System Storage', function () {
|
|||
it('should create month and year directory', function (done) {
|
||||
localfilesystem.save(image).then(function (url) {
|
||||
fs.mkdirs.calledOnce.should.be.true;
|
||||
fs.mkdirs.args[0][0].should.equal(path.join('content/images/2013/Sep'));
|
||||
fs.mkdirs.args[0][0].should.equal(path.resolve('./content/images/2013/Sep'));
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
@ -70,7 +70,7 @@ describe('Local File System Storage', function () {
|
|||
localfilesystem.save(image).then(function (url) {
|
||||
fs.copy.calledOnce.should.be.true;
|
||||
fs.copy.args[0][0].should.equal('tmp/123456.jpg');
|
||||
fs.copy.args[0][1].should.equal(path.join('content/images/2013/Sep/IMAGE.jpg'));
|
||||
fs.copy.args[0][1].should.equal(path.resolve('./content/images/2013/Sep/IMAGE.jpg'));
|
||||
done();
|
||||
}).then(null, done);
|
||||
});
|
||||
|
@ -86,13 +86,13 @@ describe('Local File System Storage', function () {
|
|||
it('can upload two different images with the same name without overwriting the first', function (done) {
|
||||
// Sun Sep 08 2013 10:57
|
||||
this.clock = sinon.useFakeTimers(new Date(2013, 8, 8, 10, 57).getTime());
|
||||
fs.exists.withArgs('content/images/2013/Sep/IMAGE.jpg').yields(true);
|
||||
fs.exists.withArgs('content/images/2013/Sep/IMAGE-1.jpg').yields(false);
|
||||
fs.exists.withArgs(path.resolve('./content/images/2013/Sep/IMAGE.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('./content/images/2013/Sep/IMAGE-1.jpg')).yields(false);
|
||||
|
||||
// if on windows need to setup with back slashes
|
||||
// doesn't hurt for the test to cope with both
|
||||
fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE.jpg').yields(true);
|
||||
fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-1.jpg').yields(false);
|
||||
fs.exists.withArgs(path.resolve('.\\content\\images\\2013\\Sep\\IMAGE.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('.\\content\\images\\2013\\Sep\\IMAGE-1.jpg')).yields(false);
|
||||
|
||||
localfilesystem.save(image).then(function (url) {
|
||||
url.should.equal('/content/images/2013/Sep/IMAGE-1.jpg');
|
||||
|
@ -103,18 +103,18 @@ describe('Local File System Storage', function () {
|
|||
it('can upload five different images with the same name without overwriting the first', function (done) {
|
||||
// Sun Sep 08 2013 10:57
|
||||
this.clock = sinon.useFakeTimers(new Date(2013, 8, 8, 10, 57).getTime());
|
||||
fs.exists.withArgs('content/images/2013/Sep/IMAGE.jpg').yields(true);
|
||||
fs.exists.withArgs('content/images/2013/Sep/IMAGE-1.jpg').yields(true);
|
||||
fs.exists.withArgs('content/images/2013/Sep/IMAGE-2.jpg').yields(true);
|
||||
fs.exists.withArgs('content/images/2013/Sep/IMAGE-3.jpg').yields(true);
|
||||
fs.exists.withArgs('content/images/2013/Sep/IMAGE-4.jpg').yields(false);
|
||||
fs.exists.withArgs(path.resolve('./content/images/2013/Sep/IMAGE.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('./content/images/2013/Sep/IMAGE-1.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('./content/images/2013/Sep/IMAGE-2.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('./content/images/2013/Sep/IMAGE-3.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('./content/images/2013/Sep/IMAGE-4.jpg')).yields(false);
|
||||
|
||||
// windows setup
|
||||
fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE.jpg').yields(true);
|
||||
fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-1.jpg').yields(true);
|
||||
fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-2.jpg').yields(true);
|
||||
fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-3.jpg').yields(true);
|
||||
fs.exists.withArgs('content\\images\\2013\\Sep\\IMAGE-4.jpg').yields(false);
|
||||
fs.exists.withArgs(path.resolve('.\\content\\images\\2013\\Sep\\IMAGE.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('.\\content\\images\\2013\\Sep\\IMAGE-1.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('.\\content\\images\\2013\\Sep\\IMAGE-2.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('.\\content\\images\\2013\\Sep\\IMAGE-3.jpg')).yields(true);
|
||||
fs.exists.withArgs(path.resolve('.\\content\\images\\2013\\Sep\\IMAGE-4.jpg')).yields(false);
|
||||
|
||||
localfilesystem.save(image).then(function (url) {
|
||||
url.should.equal('/content/images/2013/Sep/IMAGE-4.jpg');
|
||||
|
|
331
core/test/utils/fixtures/export-002.json
Normal file
331
core/test/utils/fixtures/export-002.json
Normal file
|
@ -0,0 +1,331 @@
|
|||
{
|
||||
"meta": {
|
||||
"exported_on": 1388318311015,
|
||||
"version": "002"
|
||||
},
|
||||
"data": {
|
||||
"posts": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "8492fbba-1102-4b53-8e3e-abe207952f0c",
|
||||
"title": "Welcome to Ghost",
|
||||
"slug": "welcome-to-ghost",
|
||||
"markdown": "You're live! Nice.",
|
||||
"html": "<p>You're live! Nice.</p>",
|
||||
"image": null,
|
||||
"featured": 0,
|
||||
"page": 0,
|
||||
"status": "published",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"author_id": 1,
|
||||
"created_at": 1388318310782,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310782,
|
||||
"updated_by": 1,
|
||||
"published_at": 1388318310783,
|
||||
"published_by": 1
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "e5188224-4742-4c32-a2d6-e9c5c5d4c123",
|
||||
"name": "Josephine Bloggs",
|
||||
"slug": "josephine-blogs",
|
||||
"password": "$2a$10$.pZeeBE0gHXd0PTnbT/ph.GEKgd0Wd3q2pWna3ynTGBkPKnGIKABC",
|
||||
"email": "josephinebloggs@example.com",
|
||||
"image": null,
|
||||
"cover": null,
|
||||
"bio": "A blogger",
|
||||
"website": null,
|
||||
"location": null,
|
||||
"accessibility": null,
|
||||
"status": "active",
|
||||
"language": "en_US",
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"last_login": null,
|
||||
"created_at": 1388319501897,
|
||||
"created_by": 1,
|
||||
"updated_at": null,
|
||||
"updated_by": null
|
||||
}
|
||||
],
|
||||
"roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "d2ea9c7f-7e6b-4cae-b009-35c298206852",
|
||||
"name": "Administrator",
|
||||
"description": "Administrators",
|
||||
"created_at": 1388318310794,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310794,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"uuid": "b0d7d6b0-5b88-45b5-b0e5-a487741b843d",
|
||||
"name": "Editor",
|
||||
"description": "Editors",
|
||||
"created_at": 1388318310796,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310796,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"uuid": "9f72e817-5490-4ccf-bc78-c557dc9613ca",
|
||||
"name": "Author",
|
||||
"description": "Authors",
|
||||
"created_at": 1388318310799,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310799,
|
||||
"updated_by": 1
|
||||
}
|
||||
],
|
||||
"roles_users": [
|
||||
{
|
||||
"id": 1,
|
||||
"role_id": 1,
|
||||
"user_id": 1
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "bdfbd261-e0fb-4c8e-abab-aece7a9e8e34",
|
||||
"name": "Edit posts",
|
||||
"object_type": "post",
|
||||
"action_type": "edit",
|
||||
"object_id": null,
|
||||
"created_at": 1388318310803,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310803,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"uuid": "580d31c4-e3db-40f3-969d-9a1caea9d1bb",
|
||||
"name": "Remove posts",
|
||||
"object_type": "post",
|
||||
"action_type": "remove",
|
||||
"object_id": null,
|
||||
"created_at": 1388318310814,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310814,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"uuid": "c1f8b024-e383-494a-835d-6fb673f143db",
|
||||
"name": "Create posts",
|
||||
"object_type": "post",
|
||||
"action_type": "create",
|
||||
"object_id": null,
|
||||
"created_at": 1388318310818,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310818,
|
||||
"updated_by": 1
|
||||
}
|
||||
],
|
||||
"permissions_users": [],
|
||||
"permissions_roles": [
|
||||
{
|
||||
"id": 1,
|
||||
"role_id": 1,
|
||||
"permission_id": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"role_id": 1,
|
||||
"permission_id": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"role_id": 1,
|
||||
"permission_id": 3
|
||||
}
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "f90aa810-4fa2-49fe-a39b-7c0d2ebb473e",
|
||||
"key": "databaseVersion",
|
||||
"value": "001",
|
||||
"type": "core",
|
||||
"created_at": 1388318310829,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310829,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"uuid": "95ce1c53-69b0-4f5f-be91-d3aeb39046b5",
|
||||
"key": "dbHash",
|
||||
"value": null,
|
||||
"type": "core",
|
||||
"created_at": 1388318310829,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310829,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"uuid": "c356fbde-0bc5-4fe1-9309-2510291aa34d",
|
||||
"key": "title",
|
||||
"value": "Ghost",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310830,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310830,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"uuid": "858dc11f-8f9e-4011-99ee-d94c48d5a2ce",
|
||||
"key": "description",
|
||||
"value": "Just a blogging platform.",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310830,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310830,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"uuid": "37ca5ae7-bca6-4dd5-8021-4ef6c6dcb097",
|
||||
"key": "email",
|
||||
"value": "josephinebloggs@example.com",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310830,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310830,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"uuid": "1672d62c-fab7-4f22-b333-8cf760189f67",
|
||||
"key": "logo",
|
||||
"value": "",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310830,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310830,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"uuid": "cd8b0456-578b-467a-857e-551bad17a14d",
|
||||
"key": "cover",
|
||||
"value": "",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310830,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310830,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"uuid": "c4a074a4-05c7-49f7-83eb-068302c15d82",
|
||||
"key": "defaultLang",
|
||||
"value": "en_US",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310830,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310830,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"uuid": "21f2f5da-9bee-4dae-b3b7-b8d7baf8be33",
|
||||
"key": "postsPerPage",
|
||||
"value": "6",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310830,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310830,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"uuid": "2d21b736-f85a-4119-a0e3-5fc898b1bf47",
|
||||
"key": "forceI18n",
|
||||
"value": "true",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310831,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310831,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"uuid": "5c5b91b8-6062-4104-b855-9e121f72b0f0",
|
||||
"key": "permalinks",
|
||||
"value": "/:slug/",
|
||||
"type": "blog",
|
||||
"created_at": 1388318310831,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310831,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"uuid": "795cb328-3e38-4906-81a8-fcdff19d914f",
|
||||
"key": "activeTheme",
|
||||
"value": "notcasper",
|
||||
"type": "theme",
|
||||
"created_at": 1388318310831,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310831,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"uuid": "f3afce35-5166-453e-86c3-50dfff74dca7",
|
||||
"key": "activePlugins",
|
||||
"value": "[]",
|
||||
"type": "plugin",
|
||||
"created_at": 1388318310831,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310831,
|
||||
"updated_by": 1
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"uuid": "2ea560a3-2304-449d-a62b-f7b622987510",
|
||||
"key": "installedPlugins",
|
||||
"value": "[]",
|
||||
"type": "plugin",
|
||||
"created_at": 1388318310831,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310831,
|
||||
"updated_by": 1
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "a950117a-9735-4584-931d-25a28015a80d",
|
||||
"name": "Getting Started",
|
||||
"slug": "getting-started",
|
||||
"description": null,
|
||||
"parent_id": null,
|
||||
"meta_title": null,
|
||||
"meta_description": null,
|
||||
"created_at": 1388318310790,
|
||||
"created_by": 1,
|
||||
"updated_at": 1388318310790,
|
||||
"updated_by": 1
|
||||
}
|
||||
],
|
||||
"posts_tags": [
|
||||
{
|
||||
"id": 1,
|
||||
"post_id": 1,
|
||||
"tag_id": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@
|
|||
"connect-slashes": "1.2.0",
|
||||
"downsize": "0.0.4",
|
||||
"express": "3.4.6",
|
||||
"express-hbs": "0.5.2",
|
||||
"express-hbs": "0.7.6",
|
||||
"fs-extra": "0.8.1",
|
||||
"knex": "0.5.0",
|
||||
"moment": "2.4.0",
|
||||
|
|
Loading…
Add table
Reference in a new issue