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

Migrated redirects controller to API v2 (#10053)

refs #9866

- Migrated redirect routes to use new redirect controller
This commit is contained in:
Naz Gargol 2019-01-07 11:32:53 +00:00 committed by GitHub
parent d3194c95f4
commit 4177548a84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 363 additions and 25 deletions

View file

@ -23,6 +23,10 @@ module.exports = {
return shared.pipeline(require('./pages'), localUtils);
},
get redirects() {
return shared.pipeline(require('./redirects'), localUtils);
},
get roles() {
return shared.pipeline(require('./roles'), localUtils);
},

View file

@ -0,0 +1,98 @@
const fs = require('fs-extra');
const path = require('path');
const Promise = require('bluebird');
const moment = require('moment-timezone');
const config = require('../../config');
const common = require('../../lib/common');
const validation = require('../../data/validation');
const web = require('../../web');
const _private = {};
_private.readRedirectsFile = (customRedirectsPath) => {
const redirectsPath = customRedirectsPath || path.join(config.getContentPath('data'), 'redirects.json');
return fs.readFile(redirectsPath, 'utf-8')
.then((content) => {
try {
content = JSON.parse(content);
} catch (err) {
throw new common.errors.BadRequestError({
message: common.i18n.t('errors.general.jsonParse', {context: err.message})
});
}
return content;
})
.catch((err) => {
if (err.code === 'ENOENT') {
return Promise.resolve([]);
}
if (common.errors.utils.isIgnitionError(err)) {
throw err;
}
throw new common.errors.NotFoundError({
err: err
});
});
};
module.exports = {
docName: 'redirects',
download: {
headers: {
disposition: {
type: 'file',
value: 'redirects.json'
}
},
permissions: true,
query() {
return _private.readRedirectsFile();
}
},
upload: {
permissions: true,
headers: {
cacheInvalidate: true
},
query(frame) {
const redirectsPath = path.join(config.getContentPath('data'), 'redirects.json');
const backupRedirectsPath = path.join(config.getContentPath('data'), `redirects-${moment().format('YYYY-MM-DD-HH-mm-ss')}.json`);
return fs.pathExists(redirectsPath)
.then((exists) => {
if (!exists) {
return null;
}
return fs.pathExists(backupRedirectsPath)
.then((exists) => {
if (!exists) {
return null;
}
return fs.unlink(backupRedirectsPath);
})
.then(() => {
return fs.move(redirectsPath, backupRedirectsPath);
});
})
.then(() => {
return _private.readRedirectsFile(frame.file.path)
.then((content) => {
validation.validateRedirects(content);
return fs.writeFile(redirectsPath, JSON.stringify(content), 'utf-8');
})
.then(() => {
// CASE: trigger that redirects are getting re-registered
web.shared.middlewares.customRedirects.reload();
});
});
}
}
};

View file

@ -15,6 +15,10 @@ module.exports = {
return require('./pages');
},
get redirects() {
return require('./redirects');
},
get roles() {
return require('./roles');
},

View file

@ -0,0 +1,5 @@
module.exports = {
download(response, apiConfig, frame) {
frame.response = response;
}
};

View file

@ -214,12 +214,12 @@ module.exports = function apiRoutes() {
router.del('/invites/:id', mw.authAdminApi, apiv2.http(apiv2.invites.destroy));
// ## Redirects (JSON based)
router.get('/redirects/json', mw.authAdminApi, api.http(api.redirects.download));
router.get('/redirects/json', mw.authAdminApi, apiv2.http(apiv2.redirects.download));
router.post('/redirects/json',
mw.authAdminApi,
upload.single('redirects'),
shared.middlewares.validation.upload({type: 'redirects'}),
api.http(api.redirects.upload)
apiv2.http(apiv2.redirects.upload)
);
// ## Webhooks (RESTHooks)

View file

@ -10,30 +10,25 @@ var should = require('should'),
ghost = testUtils.startGhost,
request, accesstoken;
should.equal(true, true);
describe('Redirects API', function () {
var ghostServer;
let originalContentPath;
before(function () {
return ghost({redirectsFile: true})
.then(function (_ghostServer) {
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request, 'client:trusted-domain');
})
.then(function (token) {
accesstoken = token;
originalContentPath = configUtils.config.get('paths:contentPath');
});
});
describe('Download', function () {
let originalContentPath;
before(function () {
return ghost({redirectsFile: true})
.then(function (_ghostServer) {
ghostServer = _ghostServer;
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request, 'client:trusted-domain');
})
.then(function (token) {
accesstoken = token;
originalContentPath = configUtils.config.get('paths:contentPath');
});
});
afterEach(function () {
configUtils.config.set('paths:contentPath', originalContentPath);
});
@ -80,6 +75,8 @@ describe('Redirects API', function () {
res.headers['content-type'].should.eql('application/json; charset=utf-8');
res.headers['content-length'].should.eql('648');
res.body.should.not.be.empty();
res.body.length.should.equal(11);
done();
});
});
@ -151,8 +148,7 @@ describe('Redirects API', function () {
describe('Ensure re-registering redirects works', function () {
var startGhost = function (options) {
return ghost(options)
.then(function (_ghostServer) {
ghostServer = _ghostServer;
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {

View file

@ -0,0 +1,231 @@
const should = require('should');
const supertest = require('supertest');
const fs = require('fs-extra');
const Promise = require('bluebird');
const path = require('path');
const testUtils = require('../../../../utils');
const localUtils = require('./utils');
const configUtils = require('../../../../utils/configUtils');
const config = require('../../../../../../core/server/config');
const ghost = testUtils.startGhost;
let request;
describe('Redirects API', () => {
let originalContentPath;
before(() => {
return ghost({redirectsFile: true})
.then(() => {
request = supertest.agent(config.get('url'));
})
.then(() => {
return localUtils.doAuth(request, 'client:trusted-domain');
})
.then(() => {
originalContentPath = configUtils.config.get('paths:contentPath');
});
});
describe('Download', () => {
afterEach(() => {
configUtils.config.set('paths:contentPath', originalContentPath);
});
it('file does not exist', () => {
// Just set any content folder, which does not contain a redirects file.
configUtils.set('paths:contentPath', path.join(__dirname, '../../../utils/fixtures/data'));
return request
.get(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.expect(200)
.then((res) => {
res.headers['content-disposition'].should.eql('Attachment; filename="redirects.json"');
res.headers['content-type'].should.eql('application/json; charset=utf-8');
should.not.exist(res.headers['x-cache-invalidate']);
// API returns an empty file with the correct file structure (empty [])
res.headers['content-length'].should.eql('2');
});
});
it('file exists', () => {
return request
.get(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.expect('Content-Type', /application\/json/)
.expect('Content-Disposition', 'Attachment; filename="redirects.json"')
.expect(200)
.then((res) => {
res.headers['content-disposition'].should.eql('Attachment; filename="redirects.json"');
res.headers['content-type'].should.eql('application/json; charset=utf-8');
res.headers['content-length'].should.eql('648');
res.body.should.not.be.empty();
res.body.length.should.equal(11);
});
});
});
describe('Upload', () => {
describe('Error cases', () => {
it('syntax error', () => {
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), 'something');
return request
.post(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(400);
});
it('wrong format: no array', () => {
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), JSON.stringify({
from: 'c',
to: 'd'
}));
return request
.post(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(422);
});
it('wrong format: no from/to', () => {
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), JSON.stringify([{to: 'd'}]));
return request
.post(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(422);
});
});
describe('Ensure re-registering redirects works', () => {
const startGhost = (options) => {
return ghost(options)
.then(() => {
request = supertest.agent(config.get('url'));
})
.then(() => {
return localUtils.doAuth(request, 'client:trusted-domain');
});
};
it('no redirects file exists', () => {
return startGhost({redirectsFile: false, forceStart: true})
.then(() => {
return request
.get('/my-old-blog-post/')
.expect(404);
})
.then(() => {
// Provide a redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects-init.json'), JSON.stringify([{
from: 'k',
to: 'l'
}]));
})
.then(() => {
return request
.post(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects-init.json'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then((res) => {
res.headers['x-cache-invalidate'].should.eql('/*');
return request
.get('/k/')
.expect(302);
})
.then((response) => {
response.headers.location.should.eql('/l');
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(1);
});
});
it('override', () => {
return startGhost({forceStart: true})
.then(() => {
return request
.get('/my-old-blog-post/')
.expect(301);
})
.then((response) => {
response.headers.location.should.eql('/revamped-url/');
})
.then(() => {
// Provide a second redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects.json'), JSON.stringify([{
from: 'c',
to: 'd'
}]));
})
.then(() => {
// Override redirects file
return request
.post(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects.json'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then((res) => {
res.headers['x-cache-invalidate'].should.eql('/*');
return request
.get('/my-old-blog-post/')
.expect(404);
})
.then(() => {
return request
.get('/c/')
.expect(302);
})
.then((response) => {
response.headers.location.should.eql('/d');
// check backup of redirects files
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(2);
// Provide another redirects file in the root directory of the content test folder
fs.writeFileSync(path.join(config.get('paths:contentPath'), 'redirects-something.json'), JSON.stringify([{
from: 'e',
to: 'b'
}]));
})
.then(() => {
// the backup is in the format HH:mm:ss, we have to wait minimum a second
return new Promise((resolve) => {
setTimeout(resolve, 1100);
});
})
.then(() => {
// Override redirects file again and ensure the backup file works twice
return request
.post(localUtils.API.getApiQuery('redirects/json/?client_id=ghost-admin&client_secret=not_available'))
.set('Origin', config.get('url'))
.attach('redirects', path.join(config.get('paths:contentPath'), 'redirects-something.json'))
.expect('Content-Type', /application\/json/)
.expect(200);
})
.then(() => {
const dataFiles = fs.readdirSync(config.getContentPath('data'));
dataFiles.join(',').match(/(redirects)/g).length.should.eql(3);
});
});
});
});
});