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:
parent
f174d4d56b
commit
41ae8c03b9
4 changed files with 163 additions and 28 deletions
|
@ -96,7 +96,6 @@ ConfigManager.prototype.set = function (config) {
|
||||||
activeStorageAdapter,
|
activeStorageAdapter,
|
||||||
activeSchedulingAdapter,
|
activeSchedulingAdapter,
|
||||||
contentPath,
|
contentPath,
|
||||||
storagePath,
|
|
||||||
schedulingPath,
|
schedulingPath,
|
||||||
subdir,
|
subdir,
|
||||||
assetHash;
|
assetHash;
|
||||||
|
@ -162,11 +161,25 @@ ConfigManager.prototype.set = function (config) {
|
||||||
this._config.scheduling = this._config.scheduling || {};
|
this._config.scheduling = this._config.scheduling || {};
|
||||||
activeSchedulingAdapter = this._config.scheduling.active || defaultSchedulingAdapter;
|
activeSchedulingAdapter = this._config.scheduling.active || defaultSchedulingAdapter;
|
||||||
|
|
||||||
// we support custom adapters located in content folder
|
// storage.active can be an object like {images: 'my-custom-image-storage-adapter', themes: 'local-file-storage'}
|
||||||
if (activeStorageAdapter === defaultStorageAdapter) {
|
// we ensure that passing a string to storage.active still works, but internal it's always an object
|
||||||
storagePath = path.join(corePath, '/server/storage/');
|
if (_.isString(activeStorageAdapter)) {
|
||||||
|
this._config.storage = _.merge(this._config.storage, {
|
||||||
|
active: {
|
||||||
|
images: activeStorageAdapter,
|
||||||
|
themes: defaultStorageAdapter
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} 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) {
|
if (activeSchedulingAdapter === defaultSchedulingAdapter) {
|
||||||
|
@ -184,7 +197,10 @@ ConfigManager.prototype.set = function (config) {
|
||||||
configExample: path.join(appRoot, 'config.example.js'),
|
configExample: path.join(appRoot, 'config.example.js'),
|
||||||
corePath: corePath,
|
corePath: corePath,
|
||||||
|
|
||||||
storage: path.join(storagePath, activeStorageAdapter),
|
storagePath: {
|
||||||
|
default: path.join(corePath, '/server/storage/'),
|
||||||
|
custom: path.join(contentPath, 'storage/')
|
||||||
|
},
|
||||||
|
|
||||||
contentPath: contentPath,
|
contentPath: contentPath,
|
||||||
themePath: path.resolve(contentPath, 'themes'),
|
themePath: path.resolve(contentPath, 'themes'),
|
||||||
|
@ -205,9 +221,6 @@ ConfigManager.prototype.set = function (config) {
|
||||||
active: activeSchedulingAdapter,
|
active: activeSchedulingAdapter,
|
||||||
path: schedulingPath
|
path: schedulingPath
|
||||||
},
|
},
|
||||||
storage: {
|
|
||||||
active: activeStorageAdapter
|
|
||||||
},
|
|
||||||
theme: {
|
theme: {
|
||||||
// normalise the URL by removing any trailing slash
|
// normalise the URL by removing any trailing slash
|
||||||
url: this._config.url ? this._config.url.replace(/\/$/, '') : ''
|
url: this._config.url ? this._config.url.replace(/\/$/, '') : ''
|
||||||
|
|
|
@ -1,26 +1,39 @@
|
||||||
var errors = require('../errors'),
|
var errors = require('../errors'),
|
||||||
config = require('../config'),
|
config = require('../config'),
|
||||||
storage = {};
|
storage = {};
|
||||||
|
|
||||||
function getStorage(storageChoice) {
|
/**
|
||||||
var storagePath,
|
* type: images|themes
|
||||||
storageConfig;
|
*/
|
||||||
|
function getStorage(type) {
|
||||||
|
type = type || 'images';
|
||||||
|
|
||||||
storageChoice = config.storage.active;
|
var storageChoice = config.storage.active[type],
|
||||||
storagePath = config.paths.storage;
|
storageConfig = config.storage[storageChoice];
|
||||||
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]) {
|
if (storage[storageChoice]) {
|
||||||
return storage[storageChoice];
|
return storage[storageChoice];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CASE: load adapter from custom path (.../content/storage)
|
||||||
|
// CASE: load adapter from default path (.../server/storage)
|
||||||
try {
|
try {
|
||||||
// TODO: determine if storage has all the necessary methods.
|
storage[storageChoice] = require(config.paths.storagePath.custom + storageChoice);
|
||||||
storage[storageChoice] = require(storagePath);
|
} catch (err1) {
|
||||||
} catch (e) {
|
try {
|
||||||
errors.logError(e);
|
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.
|
// Instantiate and cache the storage module instance.
|
||||||
storage[storageChoice] = new storage[storageChoice](storageConfig);
|
storage[storageChoice] = new storage[storageChoice](storageConfig);
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,7 @@ describe('Config', function () {
|
||||||
'subdir',
|
'subdir',
|
||||||
'config',
|
'config',
|
||||||
'configExample',
|
'configExample',
|
||||||
'storage',
|
'storagePath',
|
||||||
'contentPath',
|
'contentPath',
|
||||||
'corePath',
|
'corePath',
|
||||||
'themePath',
|
'themePath',
|
||||||
|
@ -175,13 +175,18 @@ describe('Config', function () {
|
||||||
|
|
||||||
describe('Storage', function () {
|
describe('Storage', function () {
|
||||||
it('should default to local-file-store', 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', {
|
||||||
config.storage.should.have.property('active', 'local-file-store');
|
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');
|
var storagePath = path.join(config.paths.contentPath, 'storage', 's3');
|
||||||
|
|
||||||
configUtils.set({
|
configUtils.set({
|
||||||
|
@ -191,10 +196,48 @@ describe('Config', function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
config.paths.should.have.property('storage', storagePath);
|
config.storage.should.have.property('active', {
|
||||||
config.storage.should.have.property('active', 's3');
|
images: 's3',
|
||||||
|
themes: 'local-file-store'
|
||||||
|
});
|
||||||
|
|
||||||
config.storage.should.have.property('s3', {});
|
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 () {
|
describe('Url', function () {
|
||||||
|
|
66
core/test/unit/storage/index_spec.js
Normal file
66
core/test/unit/storage/index_spec.js
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue