[IMPROVE] Send files over rest api (#16617)

pull/16681/head
Guilherme Gazzo 5 years ago committed by GitHub
commit d1d124f2b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 66
      app/api/server/v1/rooms.js
  2. 4
      app/apps/client/admin/appInstall.js
  3. 2
      app/file-upload/server/lib/FileUpload.js
  4. 87
      app/ui/client/lib/fileUpload.js
  5. 28
      app/utils/client/lib/RestApiClient.js

@ -59,38 +59,45 @@ API.v1.addRoute('rooms.get', { authRequired: true }, {
},
});
API.v1.addRoute('rooms.upload/:rid', { authRequired: true }, {
post() {
const room = Meteor.call('canAccessRoom', this.urlParams.rid, this.userId);
const getFiles = Meteor.wrapAsync(({ request }, callback) => {
const busboy = new Busboy({ headers: request.headers });
const files = [];
if (!room) {
return API.v1.unauthorized();
const fields = {};
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
if (fieldname !== 'file') {
return callback(new Meteor.Error('invalid-field'));
}
const busboy = new Busboy({ headers: this.request.headers });
const files = [];
const fields = {};
const fileDate = [];
file.on('data', (data) => fileDate.push(data));
Meteor.wrapAsync((callback) => {
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
if (fieldname !== 'file') {
return callback(new Meteor.Error('invalid-field'));
}
file.on('end', () => {
files.push({ fieldname, file, filename, encoding, mimetype, fileBuffer: Buffer.concat(fileDate) });
});
});
const fileDate = [];
file.on('data', (data) => fileDate.push(data));
busboy.on('field', (fieldname, value) => { fields[fieldname] = value; });
file.on('end', () => {
files.push({ fieldname, file, filename, encoding, mimetype, fileBuffer: Buffer.concat(fileDate) });
});
});
busboy.on('finish', Meteor.bindEnvironment(() => callback(null, { files, fields })));
busboy.on('field', (fieldname, value) => { fields[fieldname] = value; });
request.pipe(busboy);
});
busboy.on('finish', Meteor.bindEnvironment(() => callback()));
API.v1.addRoute('rooms.upload/:rid', { authRequired: true }, {
post() {
const room = Meteor.call('canAccessRoom', this.urlParams.rid, this.userId);
if (!room) {
return API.v1.unauthorized();
}
this.request.pipe(busboy);
})();
const { files, fields } = getFiles({
request: this.request,
});
if (files.length === 0) {
return API.v1.failure('File required');
@ -102,8 +109,6 @@ API.v1.addRoute('rooms.upload/:rid', { authRequired: true }, {
const file = files[0];
const fileStore = FileUpload.getStore('Uploads');
const details = {
name: file.filename,
size: file.fileBuffer.length,
@ -112,18 +117,17 @@ API.v1.addRoute('rooms.upload/:rid', { authRequired: true }, {
userId: this.userId,
};
let fileData = {};
Meteor.runAsUser(this.userId, () => {
const uploadedFile = Meteor.wrapAsync(fileStore.insert.bind(fileStore))(details, file.fileBuffer);
const fileData = Meteor.runAsUser(this.userId, () => {
const fileStore = FileUpload.getStore('Uploads');
const uploadedFile = fileStore.insertSync(details, file.fileBuffer);
uploadedFile.description = fields.description;
delete fields.description;
API.v1.success(Meteor.call('sendFileMessage', this.urlParams.rid, null, uploadedFile, fields));
Meteor.call('sendFileMessage', this.urlParams.rid, null, uploadedFile, fields);
fileData = uploadedFile;
return uploadedFile;
});
return API.v1.success({ message: Messages.getMessageByFileIdAndUsername(fileData._id, this.userId) });

@ -148,9 +148,9 @@ Template.appInstall.events({
let result;
if (isUpdating) {
result = await APIClient.upload(`apps/${ t.isUpdatingId.get() }`, data);
result = await APIClient.upload(`apps/${ t.isUpdatingId.get() }`, data).promise;
} else {
result = await APIClient.upload('apps', data);
result = await APIClient.upload('apps', data).promise;
}
FlowRouter.go(`/admin/apps/${ result.app.id }?version=${ result.app.version }`);

@ -435,6 +435,8 @@ export class FileUploadClass {
}
FileUpload.handlers[name] = this;
this.insertSync = Meteor.wrapAsync(this.insert, this);
}
getStore() {

@ -1,12 +1,11 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import s from 'underscore.string';
import { Handlebars } from 'meteor/ui';
import { Random } from 'meteor/random';
import { fileUploadHandler } from '../../../file-upload';
import { settings } from '../../../settings/client';
import { t, fileUploadIsValidContentType } from '../../../utils';
import { t, fileUploadIsValidContentType, APIClient } from '../../../utils';
import { modal, prependReplies } from '../../../ui-utils';
@ -193,7 +192,7 @@ export const fileUpload = async (files, input, { rid, tmid }) => {
cancelButtonText: t('Cancel'),
html: true,
onRendered: () => $('#file-name').focus(),
}, (isConfirm) => {
}, async (isConfirm) => {
if (!isConfirm) {
return;
}
@ -206,52 +205,48 @@ export const fileUpload = async (files, input, { rid, tmid }) => {
description: document.getElementById('file-description').value,
};
const upload = fileUploadHandler('Uploads', record, file.file);
const fileName = document.getElementById('file-name').value || file.name || file.file.name;
const data = new FormData();
record.description && data.append('description', record.description);
msg && data.append('msg', msg);
tmid && data.append('tmid', tmid);
data.append('file', file.file, fileName);
uploadNextFile();
const uploads = Session.get('uploading') || [];
uploads.push({
id: upload.id,
name: upload.getFileName(),
const upload = {
id: Random.id(),
name: fileName,
percentage: 0,
});
};
uploads.push(upload);
Session.set('uploading', uploads);
upload.onProgress = (progress) => {
const uploads = Session.get('uploading') || [];
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.percentage = Math.round(progress * 100) || 0;
});
Session.set('uploading', uploads);
};
uploadNextFile();
upload.start((error, file, storage) => {
if (error) {
const { xhr, promise } = APIClient.upload(`v1/rooms.upload/${ rid }`, {}, data, {
progress(progress) {
const uploads = Session.get('uploading') || [];
if (progress === 100) {
return;
}
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.percentage = Math.round(progress) || 0;
});
Session.set('uploading', uploads);
},
error(error) {
const uploads = Session.get('uploading') || [];
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.error = error.message;
u.percentage = 0;
});
Session.set('uploading', uploads);
return;
}
if (!file) {
return;
}
Meteor.call('sendFileMessage', rid, storage, file, { msg, tmid }, () => {
$(input)
.removeData('reply')
.trigger('dataChange');
setTimeout(() => {
const uploads = Session.get('uploading') || [];
Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
}, 2000);
});
},
});
Tracker.autorun((computation) => {
@ -259,13 +254,27 @@ export const fileUpload = async (files, input, { rid, tmid }) => {
if (!isCanceling) {
return;
}
computation.stop();
upload.stop();
Session.delete(`uploading-cancel-${ upload.id }`);
xhr.abort();
const uploads = Session.get('uploading') || {};
Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
});
try {
await promise;
const uploads = Session.get('uploading') || [];
return Session.set('uploading', uploads.filter((u) => u.id !== upload.id));
} catch (error) {
const uploads = Session.get('uploading') || [];
uploads.filter((u) => u.id === upload.id).forEach((u) => {
u.error = error.message;
u.percentage = 0;
});
Session.set('uploading', uploads);
}
}));
};

@ -21,13 +21,13 @@ export const APIClient = {
return APIClient._jqueryCall('POST', endpoint, params, body);
},
upload(endpoint, params, formData) {
upload(endpoint, params, formData, xhrOptions) {
if (!formData) {
formData = params;
params = {};
}
return APIClient._jqueryFormDataCall(endpoint, params, formData);
return APIClient._jqueryFormDataCall(endpoint, params, formData, xhrOptions);
},
_generateQueryFromParams(params) {
@ -68,15 +68,31 @@ export const APIClient = {
});
},
_jqueryFormDataCall(endpoint, params, formData) {
_jqueryFormDataCall(endpoint, params, formData, { progress = () => {}, error = () => {} } = {}) {
const ret = { };
const query = APIClient._generateQueryFromParams(params);
if (!(formData instanceof FormData)) {
throw new Error('The formData parameter MUST be an instance of the FormData class.');
}
return new Promise(function _jqueryFormDataPromise(resolve, reject) {
jQuery.ajax({
ret.promise = new Promise(function _jqueryFormDataPromise(resolve, reject) {
ret.xhr = jQuery.ajax({
xhr() {
const xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', function(evt) {
if (evt.lengthComputable) {
const percentComplete = evt.loaded / evt.total;
progress(percentComplete * 100);
}
}, false);
xhr.upload.addEventListener('error', error, false);
return xhr;
},
url: `${ baseURI }api/${ endpoint }${ query }`,
headers: {
'X-User-Id': Meteor._localStorage.getItem(Accounts.USER_ID_KEY),
@ -96,6 +112,8 @@ export const APIClient = {
},
});
});
return ret;
},
v1: {

Loading…
Cancel
Save