@platform/cell.client
Version:
A strongly typed HTTP client for operating with a CellOS service end-point.
115 lines (114 loc) • 4.7 kB
JavaScript
import { defaultValue, util, ERROR, FormData, Schema } from '../common';
export async function upload(args) {
const { http, urls, cellUri } = args;
const input = Array.isArray(args.input) ? args.input : [args.input];
const sendChanges = defaultValue(args.changes, false);
let changes;
const addChanges = (input) => {
if (sendChanges && input && input.length > 0) {
changes = changes || [];
changes = [...changes, ...input];
}
};
const cellUrls = urls.cell(cellUri);
const url = {
start: cellUrls.files.upload.query({ changes: sendChanges }).toString(),
complete: cellUrls.files.uploaded.query({ changes: sendChanges }).toString(),
};
const uploadStartBody = {
expires: undefined,
files: input.map(({ filename, data, mimetype }) => {
const filehash = Schema.hash.sha256(data);
return { filename, filehash, mimetype };
}),
};
const res1 = await http.post(url.start, uploadStartBody);
if (!res1.ok) {
const type = ERROR.HTTP.SERVER;
const message = `Failed during initial file-upload step to '${cellUri}'.`;
return util.toError(res1.status, type, message);
}
const uploadStart = res1.json;
addChanges(uploadStart.data.changes);
const uploadUrls = uploadStart.urls.uploads;
const fileUploadWait = uploadUrls
.map(upload => {
const file = input.find(item => item.filename === upload.filename);
const data = file ? file.data : undefined;
return { data, upload };
})
.filter(({ data }) => Boolean(data))
.map(async ({ upload, data }) => {
const { url } = upload;
const uploadToS3 = async () => {
const props = upload.props;
const contentType = props['content-type'];
const form = new FormData();
Object.keys(props)
.map(key => ({ key, value: props[key] }))
.forEach(({ key, value }) => form.append(key, value));
form.append('file', data, { contentType });
const headers = form.getHeaders();
return http.post(url, form, { headers });
};
const uploadToLocal = async () => {
const path = upload.props.key;
const headers = { 'content-type': 'multipart/form-data', path };
return http.post(url, data, { headers });
};
const isLocal = url.startsWith('http://localhost');
const res = await (isLocal ? uploadToLocal() : uploadToS3());
const { ok, status } = res;
const { uri, filename, expiresAt } = upload;
return { ok, status, uri, filename, expiresAt };
});
const res2 = await Promise.all(fileUploadWait);
const fileUploadSuccesses = res2.filter(item => item.ok);
const fileUploadFails = res2.filter(item => !item.ok);
const fileUploadErrors = fileUploadFails.map(item => {
const { filename } = item;
const message = `Failed while uploading file '${filename}'`;
const error = { type: 'FILE/upload', filename, message };
return error;
});
const res3 = await Promise.all(fileUploadSuccesses.map(async (item) => {
const url = urls
.file(item.uri)
.uploaded.query({ changes: sendChanges })
.toString();
const { filename } = item;
const body = { filename };
const res = await http.post(url, body);
const { status, ok } = res;
const json = res.json;
const file = json.data;
return { status, ok, json, file, filename };
}));
res3.forEach(res => addChanges(res.json.changes));
const fileCompleteFails = res3.filter(res => !res.ok);
const fileCompleteFailErrors = fileCompleteFails.map(res => {
const filename = res.filename || 'UNKNOWN';
const message = `Failed while completing upload of file '${filename}'`;
const error = { type: 'FILE/upload', filename, message };
return error;
});
const cellUploadCompleteBody = {};
const res4 = await http.post(url.complete, cellUploadCompleteBody);
const cellUploadComplete = res4.json;
const files = cellUploadComplete.data.files;
addChanges(cellUploadComplete.data.changes);
const errors = [
...uploadStart.data.errors,
...fileUploadErrors,
...fileCompleteFailErrors,
];
const status = errors.length === 0 ? 200 : 500;
const responseBody = {
uri: cellUri,
cell: cellUploadComplete.data.cell,
files,
errors,
changes,
};
return util.toClientResponse(status, responseBody);
}