Skip to content

Commit 56de058

Browse files
committed
Updates for returning large attachments immediately and continuing the S3 copy in the background
1 parent 0994e2d commit 56de058

File tree

1 file changed

+78
-96
lines changed

1 file changed

+78
-96
lines changed

src/routes/attachments/create.js

Lines changed: 78 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module.exports = [
3939
* Add project attachment
4040
* In development mode we have to mock the ec2 file transfer and file service calls
4141
*/
42-
(req, res, next) => {
42+
async (req, res, next) => {
4343
const data = req.body;
4444
// default values
4545
const projectId = req.params.projectId;
@@ -53,64 +53,22 @@ module.exports = [
5353
// extract file name
5454
const fileName = Path.parse(data.path).base;
5555
// create file path
56-
const path = _.join([
56+
const attachmentPath = _.join([
5757
config.get('projectAttachmentPathPrefix'),
5858
data.projectId,
5959
config.get('projectAttachmentPathPrefix'),
6060
fileName,
6161
], '/');
62-
let newAttachment = null;
6362

6463
const sourceBucket = data.s3Bucket;
6564
const sourceKey = data.path;
6665
const destBucket = config.get('attachmentsS3Bucket');
67-
const destKey = path;
66+
const destKey = attachmentPath;
6867

69-
if (data.type === ATTACHMENT_TYPES.LINK) {
70-
// We create the record in the db and return (i.e. no need to handle transferring file between S3 buckets)
71-
Promise.resolve(models.ProjectAttachment.create({
72-
projectId,
73-
allowedUsers,
74-
createdBy: req.authUser.userId,
75-
updatedBy: req.authUser.userId,
76-
title: data.title,
77-
size: data.size,
78-
category: data.category || null,
79-
description: data.description,
80-
contentType: data.contentType,
81-
path: data.path,
82-
type: data.type,
83-
tags: data.tags,
84-
})).then((_link) => {
85-
const link = _link.get({ plain: true });
86-
req.log.debug('New Link Attachment record: ', link);
87-
88-
// emit the Kafka event
89-
util.sendResourceToKafkaBus(
90-
req,
91-
EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED,
92-
RESOURCES.ATTACHMENT,
93-
link);
94-
95-
res.status(201).json(link);
96-
return Promise.resolve();
97-
})
98-
.catch((error) => {
99-
req.log.error('Error adding link attachment', error);
100-
const rerr = error;
101-
rerr.status = rerr.status || 500;
102-
next(rerr);
103-
});
104-
} else {
105-
// don't actually transfer file in development mode if file uploading is disabled, so we can test this endpoint
106-
const fileTransferPromise = (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true')
107-
? util.s3FileTransfer(req, sourceBucket, sourceKey, destBucket, destKey)
108-
: Promise.resolve();
109-
110-
fileTransferPromise.then(() => {
111-
// file copied to final destination, create DB record
112-
req.log.debug('creating db file record');
113-
return models.ProjectAttachment.create({
68+
try {
69+
if (data.type === ATTACHMENT_TYPES.LINK) {
70+
// Create the record and return immediately (no file transfer needed)
71+
const linkInstance = await models.ProjectAttachment.create({
11472
projectId,
11573
allowedUsers,
11674
createdBy: req.authUser.userId,
@@ -120,60 +78,84 @@ module.exports = [
12078
category: data.category || null,
12179
description: data.description,
12280
contentType: data.contentType,
123-
path,
81+
path: data.path,
12482
type: data.type,
12583
tags: data.tags,
12684
});
127-
}).then((_newAttachment) => {
128-
newAttachment = _newAttachment.get({ plain: true });
129-
req.log.debug('New Attachment record: ', newAttachment);
130-
if (process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true') {
131-
// retrieve download url for the response
132-
req.log.debug('retrieving download url');
133-
return getDownloadUrl(destBucket, path);
134-
}
135-
return Promise.resolve();
136-
}).then((url) => {
137-
if (
138-
process.env.NODE_ENV !== 'development' ||
139-
config.get('enableFileUpload') === 'true'
140-
) {
141-
req.log.debug('Retreiving Presigned Url resp: ', url);
142-
let response = _.cloneDeep(newAttachment);
143-
response = _.omit(response, ['path', 'deletedAt']);
144-
145-
response.downloadUrl = url;
146-
147-
// emit the event
148-
util.sendResourceToKafkaBus(
149-
req,
150-
EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED,
151-
RESOURCES.ATTACHMENT,
152-
newAttachment,
153-
);
154-
res.status(201).json(response);
155-
return Promise.resolve();
156-
}
157-
let response = _.cloneDeep(newAttachment);
158-
response = _.omit(response, ['path', 'deletedAt']);
159-
// only in development mode
160-
response.downloadUrl = path;
161-
// emit the event
85+
const link = linkInstance.get({ plain: true });
86+
req.log.debug('New Link Attachment record: ', link);
87+
16288
util.sendResourceToKafkaBus(
16389
req,
16490
EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED,
16591
RESOURCES.ATTACHMENT,
166-
newAttachment);
167-
168-
res.status(201).json(response);
169-
return Promise.resolve();
170-
})
171-
.catch((error) => {
172-
req.log.error('Error adding file attachment', error);
173-
const rerr = error;
174-
rerr.status = rerr.status || 500;
175-
next(rerr);
176-
});
92+
link,
93+
);
94+
95+
res.status(201).json(link);
96+
return;
97+
}
98+
99+
const shouldTransfer = process.env.NODE_ENV !== 'development' || config.get('enableFileUpload') === 'true';
100+
const downloadUrlPromise = shouldTransfer
101+
? getDownloadUrl(destBucket, destKey)
102+
: Promise.resolve(destKey);
103+
104+
req.log.debug('creating db file record');
105+
const attachmentInstance = await models.ProjectAttachment.create({
106+
projectId,
107+
allowedUsers,
108+
createdBy: req.authUser.userId,
109+
updatedBy: req.authUser.userId,
110+
title: data.title,
111+
size: data.size,
112+
category: data.category || null,
113+
description: data.description,
114+
contentType: data.contentType,
115+
path: destKey,
116+
type: data.type,
117+
tags: data.tags,
118+
});
119+
120+
const newAttachment = attachmentInstance.get({ plain: true });
121+
req.log.debug('New Attachment record: ', newAttachment);
122+
123+
const downloadUrl = await downloadUrlPromise;
124+
req.log.debug('Retrieved presigned url for new attachment');
125+
126+
let response = _.cloneDeep(newAttachment);
127+
response = _.omit(response, ['path', 'deletedAt']);
128+
response.downloadUrl = downloadUrl;
129+
130+
util.sendResourceToKafkaBus(
131+
req,
132+
EVENT.ROUTING_KEY.PROJECT_ATTACHMENT_ADDED,
133+
RESOURCES.ATTACHMENT,
134+
newAttachment,
135+
);
136+
137+
res.status(201).json(response);
138+
139+
if (shouldTransfer) {
140+
util.s3FileTransfer(req, sourceBucket, sourceKey, destBucket, destKey)
141+
.then(() => {
142+
req.log.debug('File attachment copied asynchronously', { attachmentId: newAttachment.id });
143+
})
144+
.catch((error) => {
145+
req.log.error('Async S3 file transfer failed', {
146+
error: error.message,
147+
stack: error.stack,
148+
attachmentId: newAttachment.id,
149+
source: `${sourceBucket}/${sourceKey}`,
150+
destination: `${destBucket}/${destKey}`,
151+
});
152+
});
153+
}
154+
} catch (error) {
155+
req.log.error('Error adding attachment', error);
156+
const rerr = error;
157+
rerr.status = rerr.status || 500;
158+
next(rerr);
177159
}
178160
},
179161
];

0 commit comments

Comments
 (0)