Skip to content

Commit

Permalink
Add support for importing images
Browse files Browse the repository at this point in the history
closes TryGhost#4608, TryGhost#4609

- image handler loads in any image files & figures out where they'll get stored
- image importer has a preprocessor which replaces image paths in
  pertinent spots of post, tag and user models
- image importer stores images, keeping the path where it makes sense
- basic test for the preprocessor
  • Loading branch information
ErisDS committed Dec 21, 2014
1 parent 0af2bc6 commit ba3d4b3
Show file tree
Hide file tree
Showing 26 changed files with 461 additions and 35 deletions.
47 changes: 47 additions & 0 deletions core/server/data/importer/handlers/image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
var _ = require('lodash'),
Promise = require('bluebird'),
path = require('path'),
config = require('../../../config'),
storage = require('../../../storage'),

ImageHandler;

ImageHandler = {
type: 'images',
extensions: config.uploads.extensions,
types: config.uploads.contentTypes,

loadFile: function (files, startDir) {
var store = storage.getStorage(),
startDirRegex = startDir ? new RegExp('^' + startDir + '/') : new RegExp(''),
imageFolderRegexes = _.map(config.paths.imagesRelPath.split('/'), function (dir) {
return new RegExp('^' + dir + '/');
});

// normalize the directory structure
files = _.map(files, function (file) {
var noStartDir = file.name.replace(startDirRegex, ''),
noGhostDirs = noStartDir;

_.each(imageFolderRegexes, function (regex) {
noGhostDirs = noGhostDirs.replace(regex, '');
});

file.originalPath = noStartDir;
file.name = noGhostDirs;
file.targetDir = path.join(config.paths.imagesPath, path.dirname(noGhostDirs));
return file;
});

return Promise.map(files, function (image) {
return store.getUniqueFileName(store, image, image.targetDir).then(function (targetFilename) {
image.newPath = (config.paths.subdir + '/' +
config.paths.imagesRelPath + '/' + path.relative(config.paths.imagesPath, targetFilename))
.replace(new RegExp('\\' + path.sep, 'g'), '/');
return image;
});
});
}
};

module.exports = ImageHandler;
73 changes: 73 additions & 0 deletions core/server/data/importer/importers/image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
var _ = require('lodash'),
Promise = require('bluebird'),
storage = require('../../../storage'),
replaceImage,
ImageImporter,
preProcessPosts,
preProcessTags,
preProcessUsers;

replaceImage = function (markdown, image) {
// Normalizes to include a trailing slash if there was one
var regex = new RegExp('(/)?' + image.originalPath, 'gm');

return markdown.replace(regex, image.newPath);
};

preProcessPosts = function (data, image) {
_.each(data.posts, function (post) {
post.markdown = replaceImage(post.markdown, image);
if (post.html) {
post.html = replaceImage(post.html, image);
}
if (post.image) {
post.image = replaceImage(post.image, image);
}
});
};

preProcessTags = function (data, image) {
_.each(data.tags, function (tag) {
if (tag.image) {
tag.image = replaceImage(tag.image, image);
}
});
};

preProcessUsers = function (data, image) {
_.each(data.users, function (user) {
if (user.cover) {
user.cover = replaceImage(user.cover, image);
}
if (user.image) {
user.image = replaceImage(user.image, image);
}
});
};

ImageImporter = {
type: 'images',
preProcess: function (importData) {
if (importData.images && importData.data) {
_.each(importData.images, function (image) {
preProcessPosts(importData.data.data, image);
preProcessTags(importData.data.data, image);
preProcessUsers(importData.data.data, image);
});
}

importData.preProcessedByImage = true;
return importData;
},
doImport: function (imageData) {
var store = storage.getStorage();

return Promise.map(imageData, function (image) {
return store.save(image, image.targetDir).then(function (result) {
return {originalPath: image.originalPath, newPath: image.newPath, stored: result};
});
});
}
};

module.exports = ImageImporter;
10 changes: 6 additions & 4 deletions core/server/data/importer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ var _ = require('lodash'),
uuid = require('node-uuid'),
extract = require('extract-zip'),
errors = require('../../errors'),
JSONHandler = require('./handlers/json'),
DataImporter = require('./importers/data'),
ImageHandler = require('./handlers/image'),
JSONHandler = require('./handlers/json'),
ImageImporter = require('./importers/image'),
DataImporter = require('./importers/data'),

defaults;

Expand All @@ -20,8 +22,8 @@ defaults = {
};

function ImportManager() {
this.importers = [DataImporter];
this.handlers = [JSONHandler];
this.importers = [ImageImporter, DataImporter];
this.handlers = [ImageHandler, JSONHandler];
}

/**
Expand Down
6 changes: 3 additions & 3 deletions core/server/storage/local-file-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ util.inherits(LocalFileStore, baseStore);
// Saves the image to storage (the file system)
// - image is the express image object
// - returns a promise which ultimately returns the full url to the uploaded image
LocalFileStore.prototype.save = function (image) {
var targetDir = this.getTargetDir(config.paths.imagesPath),
targetFilename;
LocalFileStore.prototype.save = function (image, targetDir) {
targetDir = targetDir || this.getTargetDir(config.paths.imagesPath);
var targetFilename;

return this.getUniqueFileName(this, image, targetDir).then(function (filename) {
targetFilename = filename;
Expand Down
20 changes: 15 additions & 5 deletions core/test/unit/config_spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*globals describe, it, before, beforeEach, afterEach */
/*globals describe, it, before, beforeEach, afterEach, after */
/*jshint expr:true*/
var should = require('should'),
sinon = require('sinon'),
Expand All @@ -13,13 +13,22 @@ var should = require('should'),
// Thing we are testing
defaultConfig = require('../../../config.example')[process.env.NODE_ENV],
config = require('../../server/config'),
origConfig = _.cloneDeep(config),
// storing current environment
currentEnv = process.env.NODE_ENV;

// To stop jshint complaining
should.equal(true, true);

function resetConfig() {
config.set(_.merge({}, origConfig, defaultConfig));
}

describe('Config', function () {
after(function () {
resetConfig();
});

describe('Theme', function () {
beforeEach(function () {
config.set({
Expand All @@ -34,7 +43,7 @@ describe('Config', function () {
});

afterEach(function () {
config.set(_.merge({}, defaultConfig));
resetConfig();
});

it('should have exactly the right keys', function () {
Expand All @@ -61,7 +70,7 @@ describe('Config', function () {
// Make a copy of the default config file
// so we can restore it after every test.
// Using _.merge to recursively apply every property.
config.set(_.merge({}, config));
resetConfig();
});

it('should have exactly the right keys', function () {
Expand Down Expand Up @@ -140,11 +149,11 @@ describe('Config', function () {

describe('urlFor', function () {
before(function () {
config.set(_.merge({}, defaultConfig));
resetConfig();
});

afterEach(function () {
config.set({url: defaultConfig.url});
resetConfig();
});

it('should return the home url with no options', function () {
Expand Down Expand Up @@ -274,6 +283,7 @@ describe('Config', function () {

afterEach(function () {
config = rewire('../../server/config');
resetConfig();
sandbox.restore();
});

Expand Down
34 changes: 22 additions & 12 deletions core/test/unit/frontend_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,23 @@ var assert = require('assert'),

// Stuff we are testing
api = require('../../server/api'),
config = rewire('../../server/config'),
frontend = rewire('../../server/controllers/frontend');

frontend = rewire('../../server/controllers/frontend'),
config = require('../../server/config'),
origConfig = _.cloneDeep(config),
defaultConfig = require('../../../config.example')[process.env.NODE_ENV];

// To stop jshint complaining
should.equal(true, true);

describe('Frontend Controller', function () {
var sandbox,
apiSettingsStub,
adminEditPagePath = '/ghost/editor/';
adminEditPagePath = '/ghost/editor/',

resetConfig = function () {
config.set(_.merge({}, origConfig, defaultConfig));
};

beforeEach(function () {
sandbox = sinon.sandbox.create();
Expand All @@ -29,6 +36,7 @@ describe('Frontend Controller', function () {
});

afterEach(function () {
resetConfig();
sandbox.restore();
});

Expand Down Expand Up @@ -63,6 +71,12 @@ describe('Frontend Controller', function () {
}));
});

afterEach(function () {
config.set({paths: origConfig.paths});
frontend.__set__('config', {paths: origConfig.paths});
sandbox.restore();
});

it('Redirects to home if page number is -1', function () {
var req = {params: {page: -1}, route: {path: '/page/:page/'}};

Expand Down Expand Up @@ -1294,11 +1308,7 @@ describe('Frontend Controller', function () {

describe('rss redirects', function () {
var res,
apiUsersStub,
overwriteConfig = function (newConfig) {
var existingConfig = frontend.__get__('config');
config.set(_.extend(existingConfig, newConfig));
};
apiUsersStub;

beforeEach(function () {
res = {
Expand Down Expand Up @@ -1365,7 +1375,7 @@ describe('Frontend Controller', function () {
});

it('Redirects to home if page number is 0 with subdirectory', function () {
overwriteConfig({paths: {subdir: '/blog'}});
config.set({url: 'http://testurl.com/blog'});

var req = {params: {page: 0}, route: {path: '/rss/:page/'}};

Expand All @@ -1377,7 +1387,7 @@ describe('Frontend Controller', function () {
});

it('Redirects to home if page number is 1 with subdirectory', function () {
overwriteConfig({paths: {subdir: '/blog'}});
config.set({url: 'http://testurl.com/blog'});

var req = {params: {page: 1}, route: {path: '/rss/:page/'}};

Expand All @@ -1389,7 +1399,7 @@ describe('Frontend Controller', function () {
});

it('Redirects to last page if page number too big', function (done) {
overwriteConfig({paths: {subdir: ''}});
config.set({url: 'http://testurl.com/'});

var req = {params: {page: 4}, route: {path: '/rss/:page/'}};

Expand All @@ -1402,7 +1412,7 @@ describe('Frontend Controller', function () {
});

it('Redirects to last page if page number too big with subdirectory', function (done) {
overwriteConfig({paths: {subdir: '/blog'}});
config.set({url: 'http://testurl.com/blog'});

var req = {params: {page: 4}, route: {path: '/rss/:page/'}};

Expand Down
Loading

0 comments on commit ba3d4b3

Please sign in to comment.