A response is a series of field values collected by users.
Responses are entries
In our web and mobile clients responses are named entries
File fields in entries
A new field with type attribute equal to File
was added to support the new file features. The field values can be:
""
(empty string): no files are attached to the field"[]"
(stringified empty array): no files are attached to the field"['db9d8c44c8c3da25', 'db9d8c44c8c3da24', ...]"
(stringified array): files with these hex identifiers have been uploaded to the field
Clients must GET /blob/:id/metadata
to get the metadata associated with a file. An example request is shown below.
=> GET /blob/ff39ac91a4fafec1/metadata
<=
{
"updatedAt": "2021-04-08T00:10:18.670Z",
"identifier": "ff39ac91a4fafec1",
"createdAt": "2021-04-08T00:10:12.923Z",
"storedAt": "2021-04-08T00:10:18.669Z",
"investigationIdentifier": "44baeaae64344342",
"contentType": "image/jpeg",
"creatorIdentifier": "a13ce6fc8531a0c7",
"name": "TopSecretBlueprint.jpeg",
"surveyIdentifier": "071ead1597b606a6",
"hasThumbnail": true
}
Clients can assess whether a file has thumbnails or not by looking at the hasThumbnail
attribute of the metadata response. If a file has a thumbnail, it'll be available at GET /blob/:id/thumbnail
.
File upload flow
First, the client must POST /survey/:surveyId/file
. The backend will return a pre-signed POST request to upload the file directly to S3 and a file identifier :fileIdentifier
. An example request is shown below.
=> POST /survey/:id/file
{
// Item hex identifier
"itemIdentifier": "bdc2de",
// File name of the file that needs to be uploaded
"filename": "TopSecretProject.docx",
// MIME-type of the file that needs to be uploaded
"filetype": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
// File size in bytes
"size": 5929168
}
<= Response
{
"presignedPost": {
"url": "https://s3.eu-central-1.amazonaws.com/ts-blobs",
"fields": {
"key": "db9d8c44c8c3da25-TopSecretProject.docx",
"bucket": "ts-blobs",
"X-Amz-Algorithm": "AWS4-HMAC-SHA256",
"X-Amz-Credential": "ASIA5FOJN3ZNAN63FAW5/20210408/eu-central-1/s3/aws4_request",
"X-Amz-Date": "20210408T153540Z",
"X-Amz-Security-Token": "IQoJb3JpZ2luX2VjEAMaDGV1LWNlbnRyYWwtMSJHMEUCIQDIgZ50zHukUqCPlXuY4T+YAoNIQ/iqzie7IyVt0r3lZQIgaNGj5SYW6DVFRFqzb9LXy7g5LdslxRMSkHiC6GZurUYqugMIXBAAGgw5MDUwNDk5MjMxNjIiDAUNMJ8CMhfhA9a5XCqXA+r1+C3rxdMSdgJ6DOxlZ+cXferMnNXEpij2tXhv7u4OUR+EnckjKwW4kb2GLjC+EK9muV95N1jorJ/53r3mwXWx6e0l++1D9kEqfvZs0wYaDcp+GjgxSAqbSb0moP+/JgocuIJE6miOl45gp3TV8yINzs4MFFAW5IH9x0+4eU+5OLky5c5sxGwqi4E/rEe+DjIr3Fu70oRBN4cNttS82RYzIm+YVBafaL+c6BJDgC3S8U6KXgeiqq+1bIgHPHlC6PbtBmD6uYqrotGihjN5u0kJ2EjHoyk6tpoKgXDHCUox2n6ScPyy+zYyJi6C6TlCxo8cVJHXLyEAJ6vMxDrENTQiTbnsB5brkOLWprBv+VLNsZYX4lie5CMojcrYdpSKdQKO1t8qvzjqFOFnSUDu8Z5cGDaOoLd4NzS4owE41IAgi8vW+TYSQDmCAk/DVuKkkC4KTg88jNEqyVk16OMXlBEmN8yM60QszXDGNQ0Lffb7Vz3PYtonCBOm5923cltmaVkmlD2TNPjL5tBX0oVdVjFrSSAc0JTqMMq5u4MGOusBnYyV6+T2V+PfiN5ooP3AdoooxCpq+jclK9f5BlEOAFal2QGz7J2Vd89OSENA7UYr/1mCOhJXNiDPutCiO0tlDn4NUkVvCbGl69XFqBeUzkSQLPjay2PGHxD/szSxK3OfhJj8k5eud+zS/25zwBQpAn2+5WfQqfHuHsFHQ4QKpWKGENdC1CbguPMIDGa5roKAQcZwsYtRJfv+X1EhZRU3mha0Xbfc4nC9TfOCo41Bg6OYLVWAs9rVqdjXCcGXSX6QzlLjRNmVUhhs8Y2VWj98AlvswezTuUJwUGnJnNJs83oQShg3DvLfkUoF6A==",
"Policy": "eyJleHBpcmF0aW9uIjoiMjAyMS0wNC0wOFQxNjozNTo0MFoiLCJjb25kaXRpb25zIjpbeyJrZXkiOiJkYjlkOGM0NGM4YzNkYTI1LVByb3llY3RvIGlDQlMuZG9jeCJ9LHsiYnVja2V0IjoidHMtYmxvYnMifSx7IlgtQW16LUFsZ29yaXRobSI6IkFXUzQtSE1BQy1TSEEyNTYifSx7IlgtQW16LUNyZWRlbnRpYWwiOiJBU0lBNUZPSk4zWk5BTjYzRkFXNS8yMDIxMDQwOC9ldS1jZW50cmFsLTEvczMvYXdzNF9yZXF1ZXN0In0seyJYLUFtei1EYXRlIjoiMjAyMTA0MDhUMTUzNTQwWiJ9LHsiWC1BbXotU2VjdXJpdHktVG9rZW4iOiJJUW9KYjNKcFoybHVYMlZqRUFNYURHVjFMV05sYm5SeVlXd3RNU0pITUVVQ0lRRElnWjUwekh1a1VxQ1BsWHVZNFQrWUFvTklRL2lxemllN0l5VnQwcjNsWlFJZ2FOR2o1U1lXNkRWRlJGcXpiOUxYeTdnNUxkc2x4Uk1Ta0hpQzZHWnVyVVlxdWdNSVhCQUFHZ3c1TURVd05EazVNak14TmpJaURBVU5NSjhDTWhmaEE5YTVYQ3FYQStyMStDM3J4ZE1TZGdKNkRPeGxaK2NYZmVyTW5OWEVwaWoydFhodjd1NE9VUitFbmNrakt3VzRrYjJHTGpDK0VLOW11Vjk1TjFqb3JKLzUzcjNtd1hXeDZlMGwrKzFEOWtFcWZ2WnMwd1lhRGNwK0dqZ3hTQXFiU2IwbW9QKy9KZ29jdUlKRTZtaU9sNDVncDNUVjh5SU56czRNRkZBVzVJSDl4MCs0ZVUrNU9Ma3k1YzVzeEd3cWk0RS9yRWUrRGpJcjNGdTcwb1JCTjRjTnR0UzgyUll6SW0rWVZCYWZhTCtjNkJKRGdDM1M4VTZLWGdlaXFxKzFiSWdIUEhsQzZQYnRCbUQ2dVlxcm90R2loak41dTBrSjJFakhveWs2dHBvS2dYREhDVW94Mm42U2NQeXkrell5Smk2QzZUbEN4bzhjVkpIWEx5RUFKNnZNeERyRU5UUWlUYm5zQjVicmtPTFdwckJ2K1ZMTnNaWVg0bGllNUNNb2pjcllkcFNLZFFLTzF0OHF2empxRk9GblNVRHU4WjVjR0RhT29MZDROelM0b3dFNDFJQWdpOHZXK1RZU1FEbUNBay9EVnVLa2tDNEtUZzg4ak5FcXlWazE2T01YbEJFbU44eU02MFFzelhER05RMExmZmI3VnozUFl0b25DQk9tNTkyM2NsdG1hVmttbEQyVE5Qakw1dEJYMG9WZFZqRnJTU0FjMEpUcU1NcTV1NE1HT3VzQm5ZeVY2K1QyVitQZmlONW9vUDNBZG9vb3hDcHEramNsSzlmNUJsRU9BRmFsMlFHejdKMlZkODlPU0VOQTdVWXIvMW1DT2hKWE5pRFB1dENpTzB0bERuNE5Va1Z2Q2JHbDY5WEZxQmVVemtTUUxQamF5MlBHSHhEL3N6U3hLM09maEpqOGs1ZXVkK3pTLzI1endCUXBBbjIrNVdmUXFmSHVIc0ZIUTRRS3BXS0dFTmRDMUNiZ3VQTUlER2E1cm9LQVFjWndzWXRSSmZ2K1gxRWhaUlUzbWhhMFhiZmM0bkM5VGZPQ280MUJnNk9ZTFZXQXM5clZxZGpYQ2NHWFNYNlF6bExqUk5tVlVoaHM4WTJWV2o5OEFsdnN3ZXpUdVVKd1VHbkpuTkpzODNvUVNoZzNEdkxma1VvRjZBPT0ifV19",
"X-Amz-Signature": "62e2976e820cfce52c735b879209a1cb49207d4143f18807068ce058caf7684a"
}
},
"blob": {
"identifier": "db9d8c44c8c3da25",
"investigationIdentifier": "dd729a1492cddcfd",
"surveyIdentifier": "48192834c7cc47a4",
"creatorIdentifier": "a13ce6fc8531a0c7",
"name": "TopSecretProject.docx",
"contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"createdAt": 1617896140448,
"updatedAt": 1617896140448
}
}
Then, the client must perform the pre-signed the POST request with the file contents. An example of how we construct the request in the web app is shown below.
const { url, fields } = presignedPost;
const form = new FormData();
Object.keys(fields).forEach((key) => {
form.append(key, fields[key]);
});
form.append("file", file); // This is the file binary
const res = await axios({
url,
method: "POST",
data: form,
});
return res.data;
On entry submission, the server will flag the files specified in the entry's file fields as uploaded. We'll also run a daily worker task to tidy up the ts-blobs
bucket and delete the orphan objects that might exist. An orphan object might exist if a user uploads a file but doesn't submit an entry.
File download flow
Client must GET /blob/:fileIdentifier
to download a file. The backend will return the appropriate blob.