UNPKG

@gov-cy/govcy-express-services

Version:

An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.

295 lines (253 loc) 15.3 kB
import * as govcyResources from "../resources/govcyResources.mjs"; import { validateFormElements } from "../utils/govcyValidator.mjs"; // Import your validator import * as dataLayer from "../utils/govcyDataLayer.mjs"; import { logger } from "../utils/govcyLogger.mjs"; import { prepareSubmissionData, prepareSubmissionDataAPI, generateSubmitEmail } from "../utils/govcySubmitData.mjs"; import { govcyApiRequest } from "../utils/govcyApiRequest.mjs"; import { getEnvVariable, getEnvVariableBool } from "../utils/govcyEnvVariables.mjs"; import { handleMiddlewareError } from "../utils/govcyUtils.mjs"; import { sendEmail } from "../utils/govcyNotification.mjs" import { evaluatePageConditions } from "../utils/govcyExpressions.mjs"; import { createUmdManualPageTemplate } from "./govcyUpdateMyDetails.mjs" import { validateMultipleThings } from "../utils/govcyMultipleThingsValidation.mjs"; import { resetCustomPages } from "../utils/govcyCustomPages.mjs"; /** * Middleware to handle review page form submission * This middleware processes the review page, validates form data, and shows validation errors. */ export function govcyReviewPostHandler() { return async (req, res, next) => { try { const { siteId } = req.params; // ✅ Load service and check if it exists const service = req.serviceData; let validationErrors = {}; // to be used for sending email let updateMyDetailsData = null; // Loop through all pages in the service for (const page of service.pages) { //get page url const pageUrl = page.pageData.url; // to be used for sending email // get updateMyDetails data if not found before if (!updateMyDetailsData) { updateMyDetailsData = dataLayer.getPageUpdateMyDetails(req.session, siteId, pageUrl); } // ----- taskList handling // Task list pages are navigation wrappers; skip validation here if (page.taskList) { continue; } // ----- Conditional logic comes here // ✅ Skip validation if page is conditionally excluded const conditionResult = evaluatePageConditions(page, req.session, siteId, req); if (conditionResult.result === false) { logger.debug(`⏩ Skipping validation for conditionally excluded page '${pageUrl}' → Redirects to '${conditionResult.redirect}'`, req); continue; } // Find the form definition inside `pageTemplate.sections` let formElement = null; // ----- `updateMyDetails` handling if (page.updateMyDetails) { logger.debug("Validating UpdateMyDetails page during review POST", { siteId, pageUrl }); // Build the manual UMD page template const umdTemplate = createUmdManualPageTemplate(siteId, service.site.lang, page, req, true); // Extract the form element formElement = umdTemplate.sections .flatMap(section => section.elements) .find(el => el.element === "form"); if (!formElement) { logger.error("🚨 UMD form element not found during review validation", { siteId, pageUrl }); return handleMiddlewareError("🚨 UMD form element not found during review validation", 500, next); } // ----- `updateMyDetails` handling } else { // Normal flow for (const section of page.pageTemplate.sections) { formElement = section.elements.find(el => el.element === "form"); if (formElement) break; } } if (!formElement) continue; // Skip pages without forms // Get stored form data for this page (or default to empty) const formData = dataLayer.getPageData(req.session, siteId, pageUrl) || {}; let errors = {}; // ----- MultipleThings hub handling ----- if (page.multipleThings) { // Use multiple things validator const items = Array.isArray(formData) ? formData : []; const mtErrors = validateMultipleThings(page, items, service.site.lang); if (Object.keys(mtErrors).length > 0) { errors[pageUrl] = { type: "multipleThings", // ✅ mark it hub: { errors: mtErrors } // keep hub-style structure }; } } else { // ----- Normal form handling ----- // Normal page validation const v = validateFormElements(formElement.params.elements, formData, pageUrl); if (Object.keys(v).length > 0) { errors[pageUrl] = { type: "normal", // ✅ mark it ...v }; } // // Run validations // errors = validateFormElements(formElement.params.elements, formData, pageUrl); } // Add errors to the validationErrors object validationErrors = { ...validationErrors, ...errors }; } // ========================================================== // Custom pages // ========================================================== // Handle custom summary validation blocks from session const customPages = dataLayer.getSiteCustomPages(req.session, siteId); // Convert existing validationErrors object to ordered array of [pageUrl, errorObj] // so we can splice into it in the correct order. let orderedErrors = Object.entries(validationErrors); for (const [key, block] of Object.entries(customPages)) { if (Array.isArray(block.errors) && block.errors.length > 0) { const customErrObj = {}; for (const err of block.errors) { customErrObj[err.id] = { id: err.id, message: err.text, // ⚙️ uses your structure pageUrl: err.pageUrl || key }; } const newErrorEntry = [ key, { type: "custom", // mark it as custom for debug clarity ...customErrObj } ]; // If block.insertAfter exists, insert after that page’s index if (block.insertAfterPageUrl) { const idx = orderedErrors.findIndex(([pageUrl]) => pageUrl === block.insertAfterPageUrl); if (idx >= 0) { orderedErrors.splice(idx + 1, 0, newErrorEntry); // insert after continue; // move to next custom block } } // Fallback: append to top if insertAfter not found or not defined orderedErrors.unshift(newErrorEntry); } } // Convert back into a normal object validationErrors = Object.fromEntries(orderedErrors); // ========================================================== // ❌ Return validation errors if any exist if (Object.keys(validationErrors).length > 0) { logger.debug("🚨 Validation errors:", validationErrors, req); logger.info("🚨 Validation errors:", req.originalUrl); dataLayer.storeSiteValidationErrors(req.session, siteId, validationErrors); //redirect to the same page with error summary return res.redirect(govcyResources.constructErrorSummaryUrl(req.originalUrl)); } else { // ------------ DO SUBMISSION --------------------- // get the submission API endpoint URL, clientKey, serviceId from the environment variable (handle edge cases) const submissionUrl = getEnvVariable(service?.site?.submissionAPIEndpoint?.url || "", false); const clientKey = getEnvVariable(service?.site?.submissionAPIEndpoint?.clientKey || "", false); const serviceId = getEnvVariable(service?.site?.submissionAPIEndpoint?.serviceId || "", false); const dsfGtwApiKey = getEnvVariable(service?.site?.submissionAPIEndpoint?.dsfgtwApiKey || "", ""); const allowSelfSignedCerts = getEnvVariableBool("ALLOW_SELF_SIGNED_CERTIFICATES", false); // Default to false if not set if (!submissionUrl) { return handleMiddlewareError("🚨 Submission API endpoint URL is missing", 500, next); } if (!clientKey) { return handleMiddlewareError("🚨 Submission API clientKey is missing", 500, next); } if (!serviceId) { return handleMiddlewareError("🚨 Submission API serviceId is missing", 500, next); } // Prepare submission data const submissionData = prepareSubmissionData(req, siteId, service); // Prepare submission data for API const submissionDataAPI = prepareSubmissionDataAPI(submissionData, service, req); logger.debug("Prepared submission data for API:", submissionDataAPI); // Call the API to submit the data const response = await govcyApiRequest( "post", // Use POST method submissionUrl, // Use the submission URL from the environment variable submissionDataAPI, // Pass the prepared submission data true, // Use access token authentication dataLayer.getUser(req.session), // Get the user from the session { accept: "text/plain", // Set Accept header to text/plain "client-key": clientKey, // Set the client key header "service-id": serviceId, // Set the service ID header ...(dsfGtwApiKey !== '' && { "dsfgtw-api-key": dsfGtwApiKey }) // Use the DSF API GTW secret from environment variables }, 3, allowSelfSignedCerts ); // Check if the response is successful if (response.Succeeded) { let referenceNo = response?.Data?.referenceValue || ""; // Add the reference number to the submission data submissionData.referenceNumber = referenceNo; logger.info("✅ Data submitted", siteId, referenceNo); // Get the user email address let emailAddress = ""; // if Update my details not provided the use user email if (!updateMyDetailsData || !updateMyDetailsData.email) { emailAddress = dataLayer.getUser(req.session).email; } else { emailAddress = updateMyDetailsData.email; } // add contact email to submission data submissionData.contactEmailAddress = emailAddress; // handle data layer submission dataLayer.storeSiteSubmissionData( req.session, siteId, submissionData); //-- Send email to user // Generate the email body let emailBody = generateSubmitEmail(service, submissionData.printFriendlyData, referenceNo, req); // ========================================================== // Custom pages // ========================================================= // 🆕 Reset per-session custom pages from the global app definition const app = req.app; // ✅ Express automatically provides this resetCustomPages(app, req.session, siteId); // ========================================================== // Send the email sendEmail( service.site.title[service.site.lang], emailBody, [emailAddress], "eMail").catch(err => { logger.error("Email sending failed (async):", err); }); // --- End of email sending logger.debug("🔄 Redirecting to success page:", req); // redirect to success return res.redirect(govcyResources.constructPageUrl(siteId, `success`)); // logger.debug("The submission data prepared:", printFriendlyData); // let reviewSummary = generateReviewSummary(printFriendlyData,req, siteId, false); // res.send(emailBody); // // Clear any existing submission errors from the session // dataLayer.clearSiteSubmissionErrors(req.session, siteId); } else { // Handle submission failure const errorCode = response.ErrorCode; const errorPage = service.site?.submissionAPIEndpoint?.response?.errorResponse?.[errorCode]?.page; if (errorPage) { logger.info("🚨 Submission returned failed:", response.ErrorCode); return res.redirect(errorPage); } else { return handleMiddlewareError("🚨 Unknown error code received from API.", 500, next); } } } // Proceed to final submission if no errors // return next(); // print the submission data to the page } catch (error) { return next(error); } }; }