mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Implemented '/media/upload' API endpoint
refs https://linear.app/tryghost/issue/CORE-121/create-a-video-storage-adapter - This is an experimental implementation of video file upload support - Also the output serializer skipped use of url utils in favor of inline implementatoin - this should almost certainly be it's own package
This commit is contained in:
parent
4907b7bf1e
commit
4a551661d9
7 changed files with 129 additions and 0 deletions
|
@ -105,6 +105,10 @@ module.exports = {
|
||||||
return shared.pipeline(require('./images'), localUtils);
|
return shared.pipeline(require('./images'), localUtils);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get media() {
|
||||||
|
return shared.pipeline(require('./media'), localUtils);
|
||||||
|
},
|
||||||
|
|
||||||
get tags() {
|
get tags() {
|
||||||
return shared.pipeline(require('./tags'), localUtils);
|
return shared.pipeline(require('./tags'), localUtils);
|
||||||
},
|
},
|
||||||
|
|
12
core/server/api/canary/media.js
Normal file
12
core/server/api/canary/media.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const storage = require('../../adapters/storage');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
docName: 'media',
|
||||||
|
upload: {
|
||||||
|
statusCode: 201,
|
||||||
|
permissions: false,
|
||||||
|
query(frame) {
|
||||||
|
return storage.getStorage('media').save(frame.file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -85,6 +85,10 @@ module.exports = {
|
||||||
return require('./images');
|
return require('./images');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get media() {
|
||||||
|
return require('./media');
|
||||||
|
},
|
||||||
|
|
||||||
get tags() {
|
get tags() {
|
||||||
return require('./tags');
|
return require('./tags');
|
||||||
},
|
},
|
||||||
|
|
27
core/server/api/canary/utils/serializers/output/media.js
Normal file
27
core/server/api/canary/utils/serializers/output/media.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const config = require('../../../../../../shared/config');
|
||||||
|
|
||||||
|
function getURL(urlPath) {
|
||||||
|
const STATIC_VIDEO_URL_PREFIX = 'content/media';
|
||||||
|
const imagePathRe = new RegExp('^' + config.getSubdir() + '/' + STATIC_VIDEO_URL_PREFIX);
|
||||||
|
const absolute = imagePathRe.test(urlPath) ? true : false;
|
||||||
|
|
||||||
|
if (absolute) {
|
||||||
|
// Remove the sub-directory from the URL because ghostConfig will add it back.
|
||||||
|
urlPath = urlPath.replace(new RegExp('^' + config.getSubdir()), '');
|
||||||
|
const baseUrl = config.getSiteUrl().replace(/\/$/, '');
|
||||||
|
urlPath = baseUrl + urlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
upload(path, apiConfig, frame) {
|
||||||
|
return frame.response = {
|
||||||
|
media: [{
|
||||||
|
url: getURL(path),
|
||||||
|
ref: frame.data.ref || null
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
|
@ -236,6 +236,14 @@ module.exports = function apiRoutes() {
|
||||||
http(api.images.upload)
|
http(api.images.upload)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ## media
|
||||||
|
router.post('/media/upload',
|
||||||
|
mw.authAdminApi,
|
||||||
|
apiMw.upload.single('file'),
|
||||||
|
apiMw.upload.validation({type: 'media'}),
|
||||||
|
http(api.media.upload)
|
||||||
|
);
|
||||||
|
|
||||||
// ## Invites
|
// ## Invites
|
||||||
router.get('/invites', mw.authAdminApi, http(api.invites.browse));
|
router.get('/invites', mw.authAdminApi, http(api.invites.browse));
|
||||||
router.get('/invites/:id', mw.authAdminApi, http(api.invites.read));
|
router.get('/invites/:id', mw.authAdminApi, http(api.invites.read));
|
||||||
|
|
|
@ -30,6 +30,10 @@
|
||||||
"extensions": [".jpg", ".jpeg", ".gif", ".png", ".svg", ".svgz", ".ico", ".webp"],
|
"extensions": [".jpg", ".jpeg", ".gif", ".png", ".svg", ".svgz", ".ico", ".webp"],
|
||||||
"contentTypes": ["image/jpeg", "image/png", "image/gif", "image/svg+xml", "image/x-icon", "image/vnd.microsoft.icon", "image/webp"]
|
"contentTypes": ["image/jpeg", "image/png", "image/gif", "image/svg+xml", "image/x-icon", "image/vnd.microsoft.icon", "image/webp"]
|
||||||
},
|
},
|
||||||
|
"media": {
|
||||||
|
"extensions": [".mp4",".webm", ".ogv"],
|
||||||
|
"contentTypes": ["video/mp4", "video/webm", "video/ogg"]
|
||||||
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"extensions": [".png", ".ico"],
|
"extensions": [".png", ".ico"],
|
||||||
"contentTypes": ["image/png", "image/x-icon", "image/vnd.microsoft.icon"]
|
"contentTypes": ["image/png", "image/x-icon", "image/vnd.microsoft.icon"]
|
||||||
|
|
70
test/e2e-api/admin/media.test.js
Normal file
70
test/e2e-api/admin/media.test.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const should = require('should');
|
||||||
|
const supertest = require('supertest');
|
||||||
|
const localUtils = require('./utils');
|
||||||
|
const testUtils = require('../../utils');
|
||||||
|
const config = require('../../../core/shared/config');
|
||||||
|
|
||||||
|
describe('Media API', function () {
|
||||||
|
// NOTE: holds paths to media that need to be cleaned up after the tests are run
|
||||||
|
const media = [];
|
||||||
|
let request;
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
await testUtils.startGhost();
|
||||||
|
request = supertest.agent(config.get('url'));
|
||||||
|
await localUtils.doAuth(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function () {
|
||||||
|
media.forEach(function (image) {
|
||||||
|
fs.removeSync(config.get('paths').appRoot + image);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can upload a MP4', async function () {
|
||||||
|
const res = await request.post(localUtils.API.getApiQuery('media/upload'))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.field('purpose', 'video')
|
||||||
|
.field('ref', 'https://ghost.org/sample_640x360.mp4')
|
||||||
|
.attach('file', path.join(__dirname, '/../../utils/fixtures/media/sample_640x360.mp4'))
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
res.body.media[0].url.should.match(new RegExp(`${config.get('url')}/content/media/\\d+/\\d+/sample_640x360.mp4`));
|
||||||
|
res.body.media[0].ref.should.equal('https://ghost.org/sample_640x360.mp4');
|
||||||
|
|
||||||
|
media.push(res.body.media[0].url.replace(config.get('url'), ''));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can upload a WebM', async function () {
|
||||||
|
const res = await request.post(localUtils.API.getApiQuery('media/upload'))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.field('purpose', 'video')
|
||||||
|
.field('ref', 'https://ghost.org/sample_640x360.webm')
|
||||||
|
.attach('file', path.join(__dirname, '/../../utils/fixtures/media/sample_640x360.webm'))
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
res.body.media[0].url.should.match(new RegExp(`${config.get('url')}/content/media/\\d+/\\d+/sample_640x360.webm`));
|
||||||
|
res.body.media[0].ref.should.equal('https://ghost.org/sample_640x360.webm');
|
||||||
|
|
||||||
|
media.push(res.body.media[0].url.replace(config.get('url'), ''));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can upload an Ogg', async function () {
|
||||||
|
const res = await request.post(localUtils.API.getApiQuery('media/upload'))
|
||||||
|
.set('Origin', config.get('url'))
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.field('purpose', 'video')
|
||||||
|
.field('ref', 'https://ghost.org/sample_640x360.ogv')
|
||||||
|
.attach('file', path.join(__dirname, '/../../utils/fixtures/media/sample_640x360.ogv'))
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
res.body.media[0].url.should.match(new RegExp(`${config.get('url')}/content/media/\\d+/\\d+/sample_640x360.ogv`));
|
||||||
|
res.body.media[0].ref.should.equal('https://ghost.org/sample_640x360.ogv');
|
||||||
|
|
||||||
|
media.push(res.body.media[0].url.replace(config.get('url'), ''));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Reference in a new issue