diff --git a/server/index.js b/server/index.js index 28467bb8..f809c2b3 100644 --- a/server/index.js +++ b/server/index.js @@ -10,6 +10,8 @@ import logging from './plugins/logging.js' import session from './plugins/session.js' import onPostHandler from './plugins/on-post-handler.js' +const expire = 72 * 60 * 60 * 1000 + const createServer = async options => { // Create the hapi server options = { @@ -34,6 +36,11 @@ const createServer = async options => { const init = async server => { await _registerPlugins(server) + server.app.mediaUploadCache = server.cache({ + cache: 'redis_cache', + segment: 'media-upload', + expiresIn: expire + }) await server.start() } diff --git a/server/routes/__tests__/report-sent.spec.js b/server/routes/__tests__/report-sent.spec.js index 6707daa9..56602121 100644 --- a/server/routes/__tests__/report-sent.spec.js +++ b/server/routes/__tests__/report-sent.spec.js @@ -1,13 +1,169 @@ import { submitGetRequest } from '../../__test-helpers__/server.js' import constants from '../../utils/constants.js' +import { questionSets } from '../../utils/question-sets.js' +import config from '../../utils/config.js' +import reportSentRoutes from '../report-sent.js' const url = constants.routes.REPORT_SENT const header = 'Report sent' +const submissionTimestamp = '2026-04-09T09:00:00.000Z' +const sessionId = 'test-session-id' +const expectedMediaUploadLink = `${config.mediaUploadUrl}/upload-photo?sirid=${sessionId}` + +const journeySessionData = { + 100: { + contactDetails: { + reporterEmailAddress: 'water@test.com' + }, + imagesOrVideo: [{ + answerId: questionSets.WATER_POLLUTION.questions.WATER_POLLUTION_IMAGES_OR_VIDEO.answers.yes.answerId + }] + }, + 200: { + contactDetails: { + reporterEmailAddress: 'smell@test.com' + }, + imagesOrVideo: [{ + answerId: questionSets.SMELL.questions.SMELL_IMAGES_OR_VIDEO.answers.yes.answerId + }] + }, + 300: { + contactDetails: { + reporterEmailAddress: 'blockage@test.com' + }, + imagesOrVideo: [{ + answerId: questionSets.BLOCKAGE.questions.BLOCKAGE_IMAGES_OR_VIDEO.answers.yes.answerId + }] + }, + 1800: { + contactDetails: { + reporterEmailAddress: 'fishing@test.com' + }, + imagesOrVideo: [{ + answerId: questionSets.ILLEGAL_FISHING.questions.ILLEGAL_FISHING_IMAGES_OR_VIDEO.answers.yes.answerId + }] + } +} + +const handler = async (questionSetID, overrides = {}) => { + const set = jest.fn() + const view = jest.fn() + + const defaultJourneyData = journeySessionData[questionSetID] || { + contactDetails: { reporterEmailAddress: '' }, + imagesOrVideo: [] + } + + const contactDetails = overrides.contactDetails !== undefined + ? overrides.contactDetails + : defaultJourneyData.contactDetails + const imagesOrVideo = overrides.imagesOrVideo !== undefined + ? overrides.imagesOrVideo + : defaultJourneyData.imagesOrVideo + + const keyMap = { + 100: { + contactDetailsKey: constants.redisKeys.WATER_POLLUTION_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.WATER_POLLUTION_IMAGES_OR_VIDEO + }, + 200: { + contactDetailsKey: constants.redisKeys.SMELL_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.SMELL_IMAGES_OR_VIDEO + }, + 300: { + contactDetailsKey: constants.redisKeys.BLOCKAGE_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.BLOCKAGE_IMAGES_OR_VIDEO + }, + 1800: { + contactDetailsKey: constants.redisKeys.ILLEGAL_FISHING_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.ILLEGAL_FISHING_IMAGES_OR_VIDEO + } + } + + const journeyKeys = keyMap[questionSetID] || {} + + await reportSentRoutes[0].handler({ + yar: { + get: jest.fn(key => ({ + [constants.redisKeys.QUESTION_SET_ID]: questionSetID, + [constants.redisKeys.SUBMISSION_TIMESTAMP]: submissionTimestamp, + [journeyKeys.contactDetailsKey]: contactDetails, + [journeyKeys.imagesOrVideoKey]: imagesOrVideo + }[key])), + id: sessionId, + reset: jest.fn() + }, + server: { + app: { + mediaUploadCache: { set } + } + } + }, { view }) + + return { set, view } +} describe(url, () => { describe('GET', () => { it(`Should return success response and correct view for ${url}`, async () => { await submitGetRequest({ url }, header) }) + + it.each([ + { questionSetID: 100, expectedJourney: 'water pollution' }, + { questionSetID: 200, expectedJourney: 'smell' }, + { questionSetID: 300, expectedJourney: 'blockage' }, + { questionSetID: 1800, expectedJourney: 'illegal fishing' } + ])('should cache journey "$expectedJourney" for questionSetID $questionSetID', async ({ questionSetID, expectedJourney }) => { + const { set } = await handler(questionSetID) + + expect(set).toHaveBeenCalledWith(sessionId, { + journey: expectedJourney, + dateTime: submissionTimestamp + }) + }) + + it('should pass mediaUploadLink in photoUploadDetails', async () => { + const { view } = await handler(100) + + expect(view).toHaveBeenCalledWith(constants.views.REPORT_SENT, expect.objectContaining({ + photoUploadDetails: expect.objectContaining({ + mediaUploadLink: expectedMediaUploadLink + // mediaUploadLink: config.mediaUploadUrl + }) + })) + }) + + it.each([ + { questionSetID: 100, email: 'water@test.com' }, + { questionSetID: 200, email: 'smell@test.com' }, + { questionSetID: 300, email: 'blockage@test.com' }, + { questionSetID: 1800, email: 'fishing@test.com' } + ])('should pass photo upload details for questionSetID $questionSetID', async ({ questionSetID, email }) => { + const { view } = await handler(questionSetID) + + expect(view).toHaveBeenCalledWith(constants.views.REPORT_SENT, expect.objectContaining({ + photoUploadDetails: { + mediaUploadLink: expectedMediaUploadLink, + reportersEmail: email, + userAgreedForImages: true + } + })) + }) + + it('should default photo upload details when contact details and image answer are missing', async () => { + const { view } = await handler(100, { + contactDetails: null, + imagesOrVideo: null + }) + + expect(view).toHaveBeenCalledWith(constants.views.REPORT_SENT, expect.objectContaining({ + photoUploadDetails: { + mediaUploadLink: expectedMediaUploadLink, + reportersEmail: '', + userAgreedForImages: false + } + })) + }) }) }) diff --git a/server/routes/report-sent.js b/server/routes/report-sent.js index 0dabb104..930d8658 100644 --- a/server/routes/report-sent.js +++ b/server/routes/report-sent.js @@ -1,20 +1,82 @@ import constants from '../utils/constants.js' +import { questionSets } from '../utils/question-sets.js' +import config from '../utils/config.js' + +const mediaUploadBaseUrl = `${config.mediaUploadUrl}/upload-photo` + +const journeyMap = { + 100: 'water pollution', + 200: 'smell', + 300: 'blockage', + 1800: 'illegal fishing' +} + +const journeyConfigMap = { + 100: { + contactDetailsKey: constants.redisKeys.WATER_POLLUTION_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.WATER_POLLUTION_IMAGES_OR_VIDEO, + imagesQuestion: questionSets.WATER_POLLUTION.questions.WATER_POLLUTION_IMAGES_OR_VIDEO + }, + 200: { + contactDetailsKey: constants.redisKeys.SMELL_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.SMELL_IMAGES_OR_VIDEO, + imagesQuestion: questionSets.SMELL.questions.SMELL_IMAGES_OR_VIDEO + }, + 300: { + contactDetailsKey: constants.redisKeys.BLOCKAGE_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.BLOCKAGE_IMAGES_OR_VIDEO, + imagesQuestion: questionSets.BLOCKAGE.questions.BLOCKAGE_IMAGES_OR_VIDEO + }, + 1800: { + contactDetailsKey: constants.redisKeys.ILLEGAL_FISHING_CONTACT_DETAILS, + imagesOrVideoKey: constants.redisKeys.ILLEGAL_FISHING_IMAGES_OR_VIDEO, + imagesQuestion: questionSets.ILLEGAL_FISHING.questions.ILLEGAL_FISHING_IMAGES_OR_VIDEO + } +} const handlers = { get: async (request, h) => { const questionSetID = request.yar.get(constants.redisKeys.QUESTION_SET_ID) + const submissionTimestamp = request.yar.get(constants.redisKeys.SUBMISSION_TIMESTAMP) + const journey = journeyMap[questionSetID] + const sessionId = request.yar.id + const journeyConfig = journeyConfigMap[questionSetID] + + const contactDetails = journeyConfig + ? request.yar.get(journeyConfig.contactDetailsKey) + : null + const imagesOrVideoAnswer = journeyConfig + ? request.yar.get(journeyConfig.imagesOrVideoKey) + : null + + const reportersEmail = contactDetails?.reporterEmailAddress || '' + const userAgreedForImages = journeyConfig + ? imagesOrVideoAnswer?.[0]?.answerId === journeyConfig.imagesQuestion.answers.yes.answerId + : false + + await request.server.app.mediaUploadCache.set(sessionId, { + journey, + dateTime: submissionTimestamp + }) + + const mediaUploadLink = `${mediaUploadBaseUrl}?sirid=${sessionId}` + request.yar.reset() - request.yar.set(constants.redisKeys.QUESTION_SET_ID, questionSetID) - const context = _getContext() - return h.view(constants.views.REPORT_SENT, { - ...context + const context = _getContext({ + reportersEmail, + userAgreedForImages, + mediaUploadLink + // mediaUploadLink: config.mediaUploadUrl }) + return h.view(constants.views.REPORT_SENT, context) } } -const _getContext = () => { +const _getContext = (photoUploadDetails) => { return { - hideBackLink: true + hideBackLink: true, + photoUploadDetails + // mediaUploadLink: config.mediaUploadUrl } } diff --git a/server/utils/config.js b/server/utils/config.js index d8223f12..86fa3d08 100644 --- a/server/utils/config.js +++ b/server/utils/config.js @@ -29,7 +29,8 @@ const schema = Joi.object().keys({ captchaEnabled: Joi.bool().default(false), captchaApiKey: Joi.string().allow(''), captchaSiteKey: Joi.string().allow(''), - captchaBypassKey: Joi.string() + captchaBypassKey: Joi.string(), + mediaUploadUrl: Joi.string().uri({ scheme: ['http', 'https'] }).allow('').default('') }) const captchaEnabled = getBoolean(process.env.CAPTCHA_ENABLED) @@ -54,7 +55,8 @@ const config = { captchaEnabled, captchaApiKey: captchaEnabled ? process.env.CAPTCHA_API_KEY : '', captchaSiteKey: captchaEnabled ? process.env.CAPTCHA_SITE_KEY : '', - captchaBypassKey: process.env.CAPTCHA_BYPASS_KEY + captchaBypassKey: process.env.CAPTCHA_BYPASS_KEY, + mediaUploadUrl: process.env.MEDIA_UPLOAD_URL } // Validate config diff --git a/server/views/report-sent.html b/server/views/report-sent.html index 15a04e56..b04fb57c 100644 --- a/server/views/report-sent.html +++ b/server/views/report-sent.html @@ -11,6 +11,20 @@ titleText: "Report sent", html: "We have received your report" }) }} + {% if(photoUploadDetails.userAgreedForImages and photoUploadDetails.reportersEmail !== '') %} +
We have sent a confirmation of your report and a reference number to {{ photoUploadDetails.reportersEmail }}
+ {% endif %} + + {% if(photoUploadDetails.userAgreedForImages) %} +You can now upload any photos you have that might help us investigate the problem.
+ {% endif %} + {% if(photoUploadDetails.userAgreedForImages and photoUploadDetails.reportersEmail !== '') %} +We've also included this link in your confirmation email, if you want to do this later.
+ {% endif %} +