0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

feature: storage adapter for images and themes (#7241)

refs #2852
- we offer the option to define a storage for themes and a storage for images
This commit is contained in:
Katharina Irrgang 2016-08-22 19:55:28 +02:00 committed by Hannah Wolfe
parent f174d4d56b
commit 41ae8c03b9
4 changed files with 163 additions and 28 deletions

View file

@ -96,7 +96,6 @@ ConfigManager.prototype.set = function (config) {
activeStorageAdapter,
activeSchedulingAdapter,
contentPath,
storagePath,
schedulingPath,
subdir,
assetHash;
@ -162,11 +161,25 @@ ConfigManager.prototype.set = function (config) {
this._config.scheduling = this._config.scheduling || {};
activeSchedulingAdapter = this._config.scheduling.active || defaultSchedulingAdapter;
// we support custom adapters located in content folder
if (activeStorageAdapter === defaultStorageAdapter) {
storagePath = path.join(corePath, '/server/storage/');
// storage.active can be an object like {images: 'my-custom-image-storage-adapter', themes: 'local-file-storage'}
// we ensure that passing a string to storage.active still works, but internal it's always an object
if (_.isString(activeStorageAdapter)) {
this._config.storage = _.merge(this._config.storage, {
active: {
images: activeStorageAdapter,
themes: defaultStorageAdapter
}
});
} else {
storagePath = path.join(contentPath, 'storage');
// ensure there is a default image storage adapter
if (!this._config.storage.active.images) {
this._config.storage.active.images = defaultSchedulingAdapter;
}
// ensure there is a default theme storage adapter
if (!this._config.storage.active.themes) {
this._config.storage.active.themes = defaultSchedulingAdapter;
}
}
if (activeSchedulingAdapter === defaultSchedulingAdapter) {
@ -184,7 +197,10 @@ ConfigManager.prototype.set = function (config) {
configExample: path.join(appRoot, 'config.example.js'),
corePath: corePath,
storage: path.join(storagePath, activeStorageAdapter),
storagePath: {
default: path.join(corePath, '/server/storage/'),
custom: path.join(contentPath, 'storage/')
},
contentPath: contentPath,
themePath: path.resolve(contentPath, 'themes'),
@ -205,9 +221,6 @@ ConfigManager.prototype.set = function (config) {
active: activeSchedulingAdapter,
path: schedulingPath
},
storage: {
active: activeStorageAdapter
},
theme: {
// normalise the URL by removing any trailing slash
url: this._config.url ? this._config.url.replace(/\/$/, '') : ''

View file

@ -1,26 +1,39 @@
var errors = require('../errors'),
config = require('../config'),
var errors = require('../errors'),
config = require('../config'),
storage = {};
function getStorage(storageChoice) {
var storagePath,
storageConfig;
/**
* type: images|themes
*/
function getStorage(type) {
type = type || 'images';
storageChoice = config.storage.active;
storagePath = config.paths.storage;
storageConfig = config.storage[storageChoice];
var storageChoice = config.storage.active[type],
storageConfig = config.storage[storageChoice];
// CASE: type does not exist
if (!storageChoice) {
throw new errors.IncorrectUsage('No adapter found for type: ' + type);
}
// cache?
if (storage[storageChoice]) {
return storage[storageChoice];
}
// CASE: load adapter from custom path (.../content/storage)
// CASE: load adapter from default path (.../server/storage)
try {
// TODO: determine if storage has all the necessary methods.
storage[storageChoice] = require(storagePath);
} catch (e) {
errors.logError(e);
storage[storageChoice] = require(config.paths.storagePath.custom + storageChoice);
} catch (err1) {
try {
storage[storageChoice] = require(config.paths.storagePath.default + storageChoice);
} catch (err2) {
throw err2;
}
}
// TODO: determine if storage has all the necessary methods.
// Instantiate and cache the storage module instance.
storage[storageChoice] = new storage[storageChoice](storageConfig);

View file

@ -100,7 +100,7 @@ describe('Config', function () {
'subdir',
'config',
'configExample',
'storage',
'storagePath',
'contentPath',
'corePath',
'themePath',
@ -175,13 +175,18 @@ describe('Config', function () {
describe('Storage', function () {
it('should default to local-file-store', function () {
var storagePath = path.join(config.paths.corePath, '/server/storage/', 'local-file-store');
config.paths.should.have.property('storagePath', {
default: path.join(config.paths.corePath, '/server/storage/'),
custom: path.join(config.paths.contentPath, 'storage/')
});
config.paths.should.have.property('storage', storagePath);
config.storage.should.have.property('active', 'local-file-store');
config.storage.should.have.property('active', {
images: 'local-file-store',
themes: 'local-file-store'
});
});
it('should allow setting a custom active storage', function () {
it('should allow setting a custom active storage as string', function () {
var storagePath = path.join(config.paths.contentPath, 'storage', 's3');
configUtils.set({
@ -191,10 +196,48 @@ describe('Config', function () {
}
});
config.paths.should.have.property('storage', storagePath);
config.storage.should.have.property('active', 's3');
config.storage.should.have.property('active', {
images: 's3',
themes: 'local-file-store'
});
config.storage.should.have.property('s3', {});
});
it('should allow setting a custom active storage as object', function () {
var storagePath = path.join(config.paths.contentPath, 'storage', 's3');
configUtils.set({
storage: {
active: {
themes: 's3'
}
}
});
config.storage.should.have.property('active', {
images: 'local-file-store',
themes: 's3'
});
});
it('should allow setting a custom active storage as object', function () {
var storagePath = path.join(config.paths.contentPath, 'storage', 's3');
configUtils.set({
storage: {
active: {
images: 's2',
themes: 's3'
}
}
});
config.storage.should.have.property('active', {
images: 's2',
themes: 's3'
});
});
});
describe('Url', function () {

View file

@ -0,0 +1,66 @@
var fs = require('fs-extra'),
should = require('should'),
configUtils = require('../../utils/configUtils'),
storage = require('../../../server/storage'),
errors = require('../../../server/errors'),
localFileStorage = require('../../../server/storage/local-file-store');
// to stop jshint complaining
should.equal(true, true);
describe('storage: index_spec', function () {
describe('default ghost storage config', function () {
it('load without a type', function () {
var chosenStorage = storage.getStorage();
(chosenStorage instanceof localFileStorage).should.eql(true);
});
it('load with themes type', function () {
var chosenStorage = storage.getStorage('themes');
(chosenStorage instanceof localFileStorage).should.eql(true);
});
it('load with unknown type', function () {
try {
storage.getStorage('theme');
} catch (err) {
(err instanceof errors.IncorrectUsage).should.eql(true);
}
});
});
describe('custom ghost storage config', function () {
it('images storage adapter is custom, themes is default', function () {
configUtils.set({
storage: {
active: {
images: 'custom-adapter'
}
}
});
var jsFile = '' +
'var util = require(\'util\');' +
'var StorageBase = require(__dirname + \'/../../core/server/storage/base\');' +
'var AnotherAdapter = function (){ StorageBase.call(this); };' +
'util.inherits(AnotherAdapter, StorageBase);' +
'AnotherAdapter.prototype.exists = function (){};' +
'AnotherAdapter.prototype.save = function (){};' +
'module.exports = AnotherAdapter', chosenStorage;
if (!fs.existsSync(configUtils.config.paths.storagePath.custom)) {
fs.mkdirSync(configUtils.config.paths.storagePath.custom);
}
fs.writeFileSync(configUtils.config.paths.storagePath.custom + 'custom-adapter.js', jsFile);
chosenStorage = storage.getStorage('themes');
(chosenStorage instanceof localFileStorage).should.eql(true);
chosenStorage = storage.getStorage('images');
(chosenStorage instanceof localFileStorage).should.eql(false);
fs.unlinkSync(configUtils.config.paths.storagePath.custom + 'custom-adapter.js');
});
});
});