2020-01-22 23:47:34 +02:00
|
|
|
import {
|
|
|
|
Entry,
|
|
|
|
AssetProxy,
|
|
|
|
PersistOptions,
|
|
|
|
User,
|
|
|
|
Config,
|
|
|
|
Implementation,
|
|
|
|
ImplementationFile,
|
|
|
|
EditorialWorkflowError,
|
|
|
|
APIError,
|
|
|
|
} from 'netlify-cms-lib-util';
|
|
|
|
import AuthenticationPage from './AuthenticationPage';
|
|
|
|
|
|
|
|
const serializeAsset = async (assetProxy: AssetProxy) => {
|
|
|
|
const base64content = await assetProxy.toBase64!();
|
|
|
|
|
|
|
|
return { path: assetProxy.path, content: base64content, encoding: 'base64' };
|
|
|
|
};
|
|
|
|
|
|
|
|
type MediaFile = {
|
|
|
|
id: string;
|
|
|
|
content: string;
|
|
|
|
encoding: string;
|
|
|
|
name: string;
|
|
|
|
path: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
const deserializeMediaFile = ({ id, content, encoding, path, name }: MediaFile) => {
|
|
|
|
let byteArray = new Uint8Array(0);
|
|
|
|
if (encoding !== 'base64') {
|
|
|
|
console.error(`Unsupported encoding '${encoding}' for file '${path}'`);
|
|
|
|
} else {
|
|
|
|
const decodedContent = atob(content);
|
|
|
|
byteArray = new Uint8Array(decodedContent.length);
|
|
|
|
for (let i = 0; i < decodedContent.length; i++) {
|
|
|
|
byteArray[i] = decodedContent.charCodeAt(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const file = new File([byteArray], name);
|
|
|
|
const url = URL.createObjectURL(file);
|
|
|
|
return { id, name, path, file, size: file.size, url, displayURL: url };
|
|
|
|
};
|
|
|
|
|
|
|
|
export default class ProxyBackend implements Implementation {
|
|
|
|
proxyUrl: string;
|
|
|
|
mediaFolder: string;
|
|
|
|
options: { initialWorkflowStatus?: string };
|
|
|
|
branch: string;
|
|
|
|
|
|
|
|
constructor(config: Config, options = {}) {
|
|
|
|
if (!config.backend.proxy_url) {
|
|
|
|
throw new Error('The Proxy backend needs a "proxy_url" in the backend configuration.');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.branch = config.backend.branch || 'master';
|
|
|
|
this.proxyUrl = config.backend.proxy_url;
|
|
|
|
this.mediaFolder = config.media_folder;
|
|
|
|
this.options = options;
|
|
|
|
}
|
|
|
|
|
2020-04-01 06:13:27 +03:00
|
|
|
isGitBackend() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-22 23:47:34 +02:00
|
|
|
authComponent() {
|
|
|
|
return AuthenticationPage;
|
|
|
|
}
|
|
|
|
|
|
|
|
restoreUser() {
|
|
|
|
return this.authenticate();
|
|
|
|
}
|
|
|
|
|
|
|
|
authenticate() {
|
|
|
|
return (Promise.resolve() as unknown) as Promise<User>;
|
|
|
|
}
|
|
|
|
|
|
|
|
logout() {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
getToken() {
|
|
|
|
return Promise.resolve('');
|
|
|
|
}
|
|
|
|
|
|
|
|
async request(payload: { action: string; params: Record<string, unknown> }) {
|
|
|
|
const response = await fetch(this.proxyUrl, {
|
|
|
|
method: 'POST',
|
|
|
|
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
|
|
body: JSON.stringify({ branch: this.branch, ...payload }),
|
|
|
|
});
|
|
|
|
|
|
|
|
const json = await response.json();
|
|
|
|
if (response.ok) {
|
|
|
|
return json;
|
|
|
|
} else {
|
2020-04-02 12:46:34 +03:00
|
|
|
throw new APIError(json.error, response.status, 'Proxy');
|
2020-01-22 23:47:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
entriesByFolder(folder: string, extension: string, depth: number) {
|
|
|
|
return this.request({
|
|
|
|
action: 'entriesByFolder',
|
|
|
|
params: { branch: this.branch, folder, extension, depth },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
entriesByFiles(files: ImplementationFile[]) {
|
|
|
|
return this.request({
|
|
|
|
action: 'entriesByFiles',
|
|
|
|
params: { branch: this.branch, files },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getEntry(path: string) {
|
|
|
|
return this.request({
|
|
|
|
action: 'getEntry',
|
|
|
|
params: { branch: this.branch, path },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
unpublishedEntries() {
|
|
|
|
return this.request({
|
|
|
|
action: 'unpublishedEntries',
|
|
|
|
params: { branch: this.branch },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async unpublishedEntry(collection: string, slug: string) {
|
|
|
|
try {
|
|
|
|
const entry = await this.request({
|
|
|
|
action: 'unpublishedEntry',
|
|
|
|
params: { branch: this.branch, collection, slug },
|
|
|
|
});
|
|
|
|
|
|
|
|
const mediaFiles = entry.mediaFiles.map(deserializeMediaFile);
|
|
|
|
return { ...entry, mediaFiles };
|
|
|
|
} catch (e) {
|
|
|
|
if (e.status === 404) {
|
|
|
|
throw new EditorialWorkflowError('content is not under editorial workflow', true);
|
|
|
|
}
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteUnpublishedEntry(collection: string, slug: string) {
|
|
|
|
return this.request({
|
|
|
|
action: 'deleteUnpublishedEntry',
|
|
|
|
params: { branch: this.branch, collection, slug },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async persistEntry(entry: Entry, assetProxies: AssetProxy[], options: PersistOptions) {
|
|
|
|
const assets = await Promise.all(assetProxies.map(serializeAsset));
|
|
|
|
return this.request({
|
|
|
|
action: 'persistEntry',
|
|
|
|
params: {
|
|
|
|
branch: this.branch,
|
|
|
|
entry,
|
|
|
|
assets,
|
|
|
|
options: { ...options, status: options.status || this.options.initialWorkflowStatus },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
updateUnpublishedEntryStatus(collection: string, slug: string, newStatus: string) {
|
|
|
|
return this.request({
|
|
|
|
action: 'updateUnpublishedEntryStatus',
|
|
|
|
params: { branch: this.branch, collection, slug, newStatus },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
publishUnpublishedEntry(collection: string, slug: string) {
|
|
|
|
return this.request({
|
|
|
|
action: 'publishUnpublishedEntry',
|
|
|
|
params: { branch: this.branch, collection, slug },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async getMedia(mediaFolder = this.mediaFolder) {
|
|
|
|
const files: MediaFile[] = await this.request({
|
|
|
|
action: 'getMedia',
|
|
|
|
params: { branch: this.branch, mediaFolder },
|
|
|
|
});
|
|
|
|
|
|
|
|
return files.map(deserializeMediaFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getMediaFile(path: string) {
|
|
|
|
const file = await this.request({
|
|
|
|
action: 'getMediaFile',
|
|
|
|
params: { branch: this.branch, path },
|
|
|
|
});
|
|
|
|
return deserializeMediaFile(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
async persistMedia(assetProxy: AssetProxy, options: PersistOptions) {
|
|
|
|
const asset = await serializeAsset(assetProxy);
|
|
|
|
const file: MediaFile = await this.request({
|
|
|
|
action: 'persistMedia',
|
|
|
|
params: { branch: this.branch, asset, options: { commitMessage: options.commitMessage } },
|
|
|
|
});
|
|
|
|
|
|
|
|
return deserializeMediaFile(file);
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteFile(path: string, commitMessage: string) {
|
|
|
|
return this.request({
|
|
|
|
action: 'deleteFile',
|
|
|
|
params: { branch: this.branch, path, options: { commitMessage } },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getDeployPreview(collection: string, slug: string) {
|
|
|
|
return this.request({
|
|
|
|
action: 'getDeployPreview',
|
|
|
|
params: { branch: this.branch, collection, slug },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|