fix(gitlab): fetch media library images through API (#1433)

This commit is contained in:
Benaiah Mischenko
2018-08-22 12:28:52 -07:00
committed by Shawn Erquhart
parent a4ba66e1a6
commit 83d2adc0be
11 changed files with 225 additions and 35 deletions

View File

@ -1,6 +1,6 @@
import { localForage, unsentRequest, then, APIError, Cursor } from 'netlify-cms-lib-util';
import { Base64 } from 'js-base64';
import { List, Map } from 'immutable';
import { fromJS, List, Map } from 'immutable';
import { flow, partial, result } from 'lodash';
export default class API {
@ -29,22 +29,48 @@ export default class API {
p => p.catch(err => Promise.reject(new APIError(err.message, null, 'GitLab'))),
])(req);
parseResponse = async (res, { expectingOk = true, expectingFormat = false }) => {
const contentType = res.headers.get('Content-Type');
const isJSON = contentType === 'application/json';
catchFormatErrors = (format, formatter) => res => {
try {
return formatter(res);
} catch (err) {
throw new Error(
`Response cannot be parsed into the expected format (${format}): ${err.message}`,
);
}
};
responseFormats = fromJS({
json: async res => {
const contentType = res.headers.get('Content-Type');
if (contentType !== 'application/json' && contentType !== 'text/json') {
throw new Error(`${contentType} is not a valid JSON Content-Type`);
}
return res.json();
},
text: async res => res.text(),
blob: async res => res.blob(),
}).mapEntries(([format, formatter]) => [format, this.catchFormatErrors(format, formatter)]);
parseResponse = async (res, { expectingOk = true, expectingFormat = 'text' }) => {
let body;
try {
body = await (expectingFormat === 'json' || isJSON ? res.json() : res.text());
const formatter = this.responseFormats.get(expectingFormat, false);
if (!formatter) {
throw new Error(`${expectingFormat} is not a supported response format.`);
}
body = await formatter(res);
} catch (err) {
throw new APIError(err.message, res.status, 'GitLab');
}
if (expectingOk && !res.ok) {
const isJSON = expectingFormat === 'json';
throw new APIError(isJSON && body.message ? body.message : body, res.status, 'GitLab');
}
return body;
};
responseToJSON = res => this.parseResponse(res, { expectingFormat: 'json' });
responseToBlob = res => this.parseResponse(res, { expectingFormat: 'blob' });
responseToText = res => this.parseResponse(res, { expectingFormat: 'text' });
requestJSON = req => this.request(req).then(this.responseToJSON);
requestText = req => this.request(req).then(this.responseToText);
@ -64,30 +90,23 @@ export default class API {
return false;
});
readFile = async (path, sha, ref = this.branch) => {
const cachedFile = sha ? await localForage.getItem(`gl.${sha}`) : null;
readFile = async (path, sha, { ref = this.branch, parseText = true } = {}) => {
const cacheKey = parseText ? `gl.${sha}` : `gl.${sha}.blob`;
const cachedFile = sha ? await localForage.getItem(cacheKey) : null;
if (cachedFile) {
return cachedFile;
}
const result = await this.requestText({
const result = await this.request({
url: `${this.repoURL}/repository/files/${encodeURIComponent(path)}/raw`,
params: { ref },
cache: 'no-store',
});
}).then(parseText ? this.responseToText : this.responseToBlob);
if (sha) {
localForage.setItem(`gl.${sha}`, result);
localForage.setItem(cacheKey, result);
}
return result;
};
fileDownloadURL = (path, ref = this.branch) =>
unsentRequest.toURL(
this.buildRequest({
url: `${this.repoURL}/repository/files/${encodeURIComponent(path)}/raw`,
params: { ref },
}),
);
getCursorFromHeaders = headers => {
// indices and page counts are assumed to be zero-based, but the
// indices and page counts returned from GitLab are one-based

View File

@ -132,13 +132,21 @@ export default class GitLab {
}
getMedia() {
const sem = semaphore(MAX_CONCURRENT_DOWNLOADS);
return this.api.listAllFiles(this.config.get('media_folder')).then(files =>
files.map(({ id, name, path }) => {
const url = new URL(this.api.fileDownloadURL(path));
if (url.pathname.match(/.svg$/)) {
url.search += (url.search.slice(1) === '' ? '?' : '&') + 'sanitize=true';
}
return { id, name, url: url.href, path };
const getBlobPromise = () =>
new Promise((resolve, reject) =>
sem.take(() =>
this.api
.readFile(path, id, { parseText: false })
.then(resolve, reject)
.finally(() => sem.leave()),
),
);
return { id, name, getBlobPromise, path };
}),
);
}
@ -150,8 +158,8 @@ export default class GitLab {
async persistMedia(mediaFile, options = {}) {
await this.api.persistFiles([mediaFile], options);
const { value, path, fileObj } = mediaFile;
const url = this.api.fileDownloadURL(path);
return { name: value, size: fileObj.size, url, path: trimStart(path, '/') };
const getBlobPromise = () => Promise.resolve(fileObj);
return { name: value, size: fileObj.size, getBlobPromise, path: trimStart(path, '/') };
}
deleteFile(path, commitMessage, options) {