diff --git a/apps/paf/behaviours/disable-file-upload.js b/apps/paf/behaviours/disable-file-upload.js
new file mode 100644
index 00000000..f9210b99
--- /dev/null
+++ b/apps/paf/behaviours/disable-file-upload.js
@@ -0,0 +1,13 @@
+module.exports = superclass => class extends superclass {
+ locals(req, res) {
+ const locals = super.locals(req, res);
+ const images = req.sessionModel.get('images');
+ if (images && images.length >= 3) {
+ // disable file upload if attachment limit reached.
+ req.form.options.fields['other-info-file-upload'].attributes = [{attribute: 'disabled'}];
+ return locals;
+ }
+ req.form.options.fields['other-info-file-upload'].attributes = [];
+ return locals;
+}
+}
diff --git a/apps/paf/behaviours/limit-documents.js b/apps/paf/behaviours/limit-documents.js
new file mode 100644
index 00000000..833fc911
--- /dev/null
+++ b/apps/paf/behaviours/limit-documents.js
@@ -0,0 +1,16 @@
+module.exports = superclass => class LimitDocs extends superclass {
+ validate(req, res, next) {
+ const images = req.sessionModel.get('images');
+ if (images && images.length >= 3 && req.form.values['other-info-file-uploads-add-another'] === 'yes') {
+ return next({
+ 'other-info-file-uploads-add-another': new this.ValidationError(
+ 'other-info-file-uploads-add-another',
+ {
+ type: 'tooMany'
+ }
+ )
+ });
+ } super.validate(req, res, next);
+ return next;
+ }
+};
diff --git a/apps/paf/behaviours/remove-file.js b/apps/paf/behaviours/remove-file.js
new file mode 100644
index 00000000..31401a45
--- /dev/null
+++ b/apps/paf/behaviours/remove-file.js
@@ -0,0 +1,15 @@
+'use strict';
+
+module.exports = superclass => class extends superclass {
+ configure(req, res, next) {
+ if (req.query.delete) {
+ const images = req.sessionModel.get('images') || [];
+ const remaining = images.filter(i => i.id !== req.query.delete);
+ req.log('info', `Reference: ${req.sessionModel.get('reference')}, Removing image: ${req.query.delete}`);
+ req.sessionModel.set('images', remaining);
+ const path = req.baseUrl + req.path;
+ return res.redirect(path);
+ }
+ return super.configure(req, res, next);
+ }
+};
diff --git a/apps/paf/behaviours/save-file.js b/apps/paf/behaviours/save-file.js
new file mode 100644
index 00000000..13bff411
--- /dev/null
+++ b/apps/paf/behaviours/save-file.js
@@ -0,0 +1,41 @@
+'use strict';
+
+const _ = require('lodash');
+const Model = require('../models/file-upload');
+
+module.exports = name => superclass => class extends superclass {
+ process(req) {
+ if (req.files && req.files[name]) {
+ // set image name on values for filename extension validation
+ // N:B validation controller gets values from
+ // req.form.values and not on req.files
+ req.form.values[name] = req.files[name].name;
+ req.log('info', `Reference: ${req.sessionModel.get('reference')},
+ Processing image: ${req.form.values[name]}`);
+ }
+ super.process.apply(this, arguments);
+ }
+
+ locals(req, res, next) {
+ if (!Object.keys(req.form.errors).length) {
+ req.form.values['other-info-file-upload'] = null;
+ }
+ return super.locals(req, res, next);
+ }
+
+ saveValues(req, res, next) {
+ const images = req.sessionModel.get('images') || [];
+ if (req.files && req.files[name]) {
+ req.log('info', `Reference: ${req.sessionModel.get('reference')}, Saving image: ${req.files[name].name}`);
+ const image = _.pick(req.files[name], ['name', 'data', 'mimetype']);
+ const model = new Model(image);
+ return model.save()
+ .then(() => {
+ req.sessionModel.set('images', [...images, model.toJSON()]);
+ return super.saveValues(req, res, next);
+ })
+ .catch(next);
+ }
+ return super.saveValues.apply(this, arguments);
+ }
+};
diff --git a/apps/paf/fields/index.js b/apps/paf/fields/index.js
index 8c23afad..e14508f2 100644
--- a/apps/paf/fields/index.js
+++ b/apps/paf/fields/index.js
@@ -221,7 +221,7 @@ module.exports = {
dependent: {
field: 'vehicle-type',
value: 'cars'
- },
+ }
},
'crime-hgv-group': {
mixin: 'radio-group',
@@ -238,7 +238,7 @@ module.exports = {
dependent: {
field: 'vehicle-type',
value: 'hgvs'
- },
+ }
},
'crime-lorry-group': {
mixin: 'radio-group',
@@ -252,7 +252,7 @@ module.exports = {
dependent: {
field: 'vehicle-type',
value: 'lorries'
- },
+ }
},
'crime-van-group': {
mixin: 'radio-group',
@@ -268,7 +268,7 @@ module.exports = {
dependent: {
field: 'vehicle-type',
value: 'vans'
- },
+ }
},
'boat-type': {
isPageHeading: true,
@@ -320,7 +320,7 @@ module.exports = {
dependent: {
field: 'boat-type',
value: 'carriers'
- },
+ }
},
'crime-general-cargo-group': {
mixin: 'radio-group',
@@ -334,7 +334,7 @@ module.exports = {
dependent: {
field: 'boat-type',
value: 'general-cargos'
- },
+ }
},
'crime-vessel-group': {
mixin: 'radio-group',
@@ -349,7 +349,7 @@ module.exports = {
dependent: {
field: 'boat-type',
value: 'vessels'
- },
+ }
},
'boat-name': {
mixin: 'input-text'
@@ -913,7 +913,7 @@ module.exports = {
dependent: {
field: 'report-person-transport-type',
value: 'cars'
- },
+ }
},
'report-person-transport-hgv-group': {
mixin: 'radio-group',
@@ -930,7 +930,7 @@ module.exports = {
dependent: {
field: 'report-person-transport-type',
value: 'hgv'
- },
+ }
},
'report-person-transport-lorry-group': {
mixin: 'radio-group',
@@ -944,7 +944,7 @@ module.exports = {
dependent: {
field: 'report-person-transport-type',
value: 'lorries'
- },
+ }
},
'report-person-transport-van-group': {
mixin: 'radio-group',
@@ -960,7 +960,7 @@ module.exports = {
dependent: {
field: 'report-person-transport-type',
value: 'vans'
- },
+ }
},
'report-person-transport-make': {
mixin: 'input-text'
@@ -1167,6 +1167,16 @@ module.exports = {
value: 'yes'
}
},
+ 'other-info-file-upload': {
+ mixin: 'input-file',
+ className: 'govuk-file-upload',
+ attributes: []
+ },
+ 'add-other-info-file-upload': {
+ isPageHeading: true,
+ mixin: 'radio-group',
+ options: ['yes', 'no']
+ },
'about-you-first-name': {
mixin: 'input-text'
},
diff --git a/apps/paf/index.js b/apps/paf/index.js
index 708a372c..eac8b3f2 100644
--- a/apps/paf/index.js
+++ b/apps/paf/index.js
@@ -1,4 +1,9 @@
'use strict';
+const saveImage = require('./behaviours/save-file');
+const removeImage = require('./behaviours/remove-file');
+const CombineAndLoopFields = require('hof').components.combineAndLoopFields;
+const limitDocs = require('./behaviours/limit-documents');
+const disableUpload = require('./behaviours/disable-file-upload');
const SummaryPageBehaviour = require('hof').components.summary;
const transportBehaviour = require('./behaviours/transport-behaviour');
const Aggregate = require('./behaviours/aggregator');
@@ -356,7 +361,7 @@ module.exports = {
field: 'report-person-occupation',
value: 'yes'
}
- },
+ }
]
},
'/report-person-occupation-type': {
@@ -662,6 +667,36 @@ module.exports = {
next: '/other-info-file-upload'
},
'/other-info-file-upload': {
+ behaviours: [saveImage('other-info-file-upload'), disableUpload],
+ fields: ['other-info-file-upload'],
+ continueOnEdit: true,
+ forks: [{
+ target: '/add-other-info-file-upload',
+ condition: req => {
+ if (req.form.values['other-info-file-upload']) {
+ return true
+ }
+ return false;
+ }
+ }],
+ next: '/about-you'
+ },
+ '/add-other-info-file-upload': {
+ template: 'list-add-looped-files',
+ behaviours: [CombineAndLoopFields({
+ groupName: 'other-info-file-uploads',
+ fieldsToGroup: [
+ 'other-info-file-upload'
+ ],
+ groupOptional: true,
+ removePrefix: 'other-',
+ combineValuesToSingleField: 'record',
+ returnTo: '/other-info-file-upload'
+ }), removeImage, limitDocs],
+ next: '/about-you',
+ locals: {
+ section: 'other-info-file-upload'
+ }
},
'/about-you': {
fields: ['how-did-you-find-out-about-the-crime'],
diff --git a/apps/paf/models/file-upload.js b/apps/paf/models/file-upload.js
new file mode 100644
index 00000000..db94ae8a
--- /dev/null
+++ b/apps/paf/models/file-upload.js
@@ -0,0 +1,75 @@
+'use strict';
+
+const url = require('url');
+const Model = require('hof').model;
+const uuid = require('uuid').v4;
+const config = require('../../../config');
+
+module.exports = class UploadModel extends Model {
+ constructor(...args) {
+ super(...args);
+ this.set('id', uuid());
+ }
+
+ async save() {
+ const result = await new Promise((resolve, reject) => {
+ const attributes = {
+ url: config.upload.hostname
+ };
+ const reqConf = url.parse(this.url(attributes));
+ reqConf.formData = {
+ document: {
+ value: this.get('data'),
+ options: {
+ filename: this.get('name'),
+ contentType: this.get('mimetype')
+ }
+ }
+ };
+ reqConf.method = 'POST';
+ this.request(reqConf, (err, data) => {
+ if (err) {
+ return reject(err);
+ }
+ resolve(data);
+ });
+ });
+ this.set({ url: result.url });
+ return this.unset('data');
+ }
+
+ auth() {
+ if (!config.keycloak.token) {
+ // eslint-disable-next-line no-console
+ console.error('keycloak token url is not defined');
+ return Promise.resolve({
+ bearer: 'abc123'
+ });
+ }
+ const tokenReq = {
+ url: config.keycloak.token,
+ form: {
+ username: config.keycloak.username,
+ password: config.keycloak.password,
+ grant_type: 'password',
+ client_id: config.keycloak.clientId,
+ client_secret: config.keycloak.secret
+ },
+ method: 'POST'
+ };
+
+ return new Promise((resolve, reject) => {
+ this._request(tokenReq, (err, response) => {
+ const body = JSON.parse(response.body);
+
+ if (err || body.error) {
+ return reject(err || new Error(`${body.error} - ${body.error_description}`));
+ }
+
+ resolve({
+ bearer: JSON.parse(response.body).access_token
+ });
+ });
+ });
+ }
+};
diff --git a/apps/paf/sections/summary-data-sections.js b/apps/paf/sections/summary-data-sections.js
index 8e78f576..b06003f9 100644
--- a/apps/paf/sections/summary-data-sections.js
+++ b/apps/paf/sections/summary-data-sections.js
@@ -221,7 +221,17 @@ module.exports = {
'other-info': [
'other-info-description',
'other-info-another-crime',
- 'other-info-another-crime-description'
+ 'other-info-another-crime-description',
+ {
+ step: '/add-other-info-file-upload',
+ field: 'images',
+ parse: (list, req) => {
+ if (!req.sessionModel.get('images') ) {
+ return null;
+ }
+ return list && list.map(a => a.name).join('\n————————————————\n') || 'None';
+ }
+ }
],
'about-you': [
'how-did-you-find-out-about-the-crime',
diff --git a/apps/paf/translations/src/en/fields.json b/apps/paf/translations/src/en/fields.json
index 3b47cd00..c9b5d9c6 100644
--- a/apps/paf/translations/src/en/fields.json
+++ b/apps/paf/translations/src/en/fields.json
@@ -1087,6 +1087,21 @@
},
"legendClassName": "govuk-fieldset__legend"
},
+ "other-info-file-upload": {
+ "hint": "You can add up to 3 documents",
+ "label": "Attachment"
+ },
+ "other-info-file-uploads-add-another": {
+ "options": {
+ "yes": {
+ "label": "Yes"
+ },
+ "no": {
+ "label": "No"
+ }
+ },
+ "legendClassName": "govuk-fieldset__legend"
+ },
"persons": {
"label": "Additional People",
"changeLinkDescription": "Additional People"
@@ -1356,5 +1371,8 @@
},
"when-to-contact": {
"label": "When would you like us to contact you?"
+ },
+ "images": {
+ "label": "Attachments"
}
}
diff --git a/apps/paf/translations/src/en/pages.json b/apps/paf/translations/src/en/pages.json
index 7b1e11f8..91c178ee 100644
--- a/apps/paf/translations/src/en/pages.json
+++ b/apps/paf/translations/src/en/pages.json
@@ -122,8 +122,15 @@
"other-info-description": {
"header": "Other information"
},
- "other-info-file-upload": {
- "header": "Please attach any documents which may help us investigate this crime - TO DO"
+ "other-info-file-upload":{
+ "header": "Please attach any documents which may help us investigate this crime"
+ },
+ "add-other-info-file-upload": {
+ "header": "Do you need to add any other documents?",
+ "hint": "You can submit up to 3 attachments"
+ },
+ "other-info-file-uploads": {
+ "header": "Attachments"
},
"about-you": {
"header": "About You"
diff --git a/apps/paf/translations/src/en/validation.json b/apps/paf/translations/src/en/validation.json
index da70b892..8e0d32a2 100644
--- a/apps/paf/translations/src/en/validation.json
+++ b/apps/paf/translations/src/en/validation.json
@@ -25,5 +25,9 @@
},
"company-website": {
"url": "Enter a website address in the correct format, like www.example.com"
+ },
+ "other-info-file-uploads-add-another": {
+ "required": "Please select an option",
+ "tooMany": "You can only submit up to 3 attachments."
}
}
diff --git a/apps/paf/views/list-add-looped-files.html b/apps/paf/views/list-add-looped-files.html
new file mode 100644
index 00000000..1568645a
--- /dev/null
+++ b/apps/paf/views/list-add-looped-files.html
@@ -0,0 +1,8 @@
+{{
{{name}} |
+ Delete | +