Responses and data entry

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.

References