@gov-cy/govcy-express-services 1.3.0-alpha.2 → 1.3.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.mjs +11 -1
- package/src/middleware/govcyMultipleThingsHubHandler.mjs +0 -1
- package/src/middleware/govcyPageHandler.mjs +11 -4
- package/src/middleware/govcyReviewPostHandler.mjs +24 -3
- package/src/middleware/govcyUpdateMyDetails.mjs +744 -0
- package/src/public/css/govcyExpress.css +4 -0
- package/src/resources/govcyResources.mjs +467 -101
- package/src/utils/govcyConstants.mjs +13 -1
- package/src/utils/govcySubmitData.mjs +45 -6
- package/src/utils/govcyValidator.mjs +74 -1
|
@@ -5,4 +5,16 @@ export const ALLOWED_FORM_ELEMENTS = ["textInput", "textArea", "select", "radios
|
|
|
5
5
|
export const ALLOWED_FILE_MIME_TYPES = ['application/pdf', 'image/jpeg', 'image/png'];
|
|
6
6
|
export const ALLOWED_FILE_EXTENSIONS = ['pdf', 'jpg', 'jpeg', 'png'];
|
|
7
7
|
export const ALLOWED_FILE_SIZE_MB = 4; // Maximum file size in MB
|
|
8
|
-
export const ALLOWED_MULTER_FILE_SIZE_MB = 10; // Maximum file size in MB
|
|
8
|
+
export const ALLOWED_MULTER_FILE_SIZE_MB = 10; // Maximum file size in MB
|
|
9
|
+
// UPDATE MY DETAILS
|
|
10
|
+
// Only allow certain hosts
|
|
11
|
+
export const UPDATE_MY_DETAILS_ALLOWED_HOSTS = [
|
|
12
|
+
"update-my-details.staging.service.gov.cy",
|
|
13
|
+
"update-my-details.service.gov.cy",
|
|
14
|
+
"localhost"
|
|
15
|
+
];
|
|
16
|
+
// Possible incoming routes
|
|
17
|
+
export const UPDATE_MY_DETAILS_REDIRECT_HOSTS = [
|
|
18
|
+
"update-my-details.staging.service.gov.cy",
|
|
19
|
+
"update-my-details.service.gov.cy"
|
|
20
|
+
];
|
|
@@ -6,6 +6,7 @@ import { ALLOWED_FORM_ELEMENTS } from "./govcyConstants.mjs";
|
|
|
6
6
|
import { evaluatePageConditions } from "./govcyExpressions.mjs";
|
|
7
7
|
import { getPageConfigData } from "./govcyLoadConfigData.mjs";
|
|
8
8
|
import { logger } from "./govcyLogger.mjs";
|
|
9
|
+
import { createUmdManualPageTemplate } from "../middleware/govcyUpdateMyDetails.mjs"
|
|
9
10
|
import nunjucks from "nunjucks";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -43,9 +44,30 @@ export function prepareSubmissionData(req, siteId, service) {
|
|
|
43
44
|
|
|
44
45
|
// Find the <form> element in the page
|
|
45
46
|
let formElement = null;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
|
|
48
|
+
// ----- `updateMyDetails` handling
|
|
49
|
+
// 🔹 Case C: updateMyDetails
|
|
50
|
+
if (page.updateMyDetails) {
|
|
51
|
+
logger.debug("Preparing submission data for UpdateMyDetails page", { siteId, pageUrl });
|
|
52
|
+
// Build the manual UMD page template
|
|
53
|
+
const umdTemplate = createUmdManualPageTemplate(siteId, service.site.lang, page, req);
|
|
54
|
+
|
|
55
|
+
// Extract the form element
|
|
56
|
+
formElement = umdTemplate .sections
|
|
57
|
+
.flatMap(section => section.elements)
|
|
58
|
+
.find(el => el.element === "form");
|
|
59
|
+
|
|
60
|
+
if (!formElement) {
|
|
61
|
+
logger.error("🚨 UMD form element not found during prepareSubmissionData", { siteId, pageUrl });
|
|
62
|
+
return handleMiddlewareError("🚨 UMD form element not found during prepareSubmissionData", 500, next);
|
|
63
|
+
}
|
|
64
|
+
// ----- `updateMyDetails` handling
|
|
65
|
+
} else {
|
|
66
|
+
// Normal flow
|
|
67
|
+
for (const section of page.pageTemplate.sections || []) {
|
|
68
|
+
formElement = section.elements.find(el => el.element === "form");
|
|
69
|
+
if (formElement) break;
|
|
70
|
+
}
|
|
49
71
|
}
|
|
50
72
|
|
|
51
73
|
if (!formElement) continue; // ⛔ Skip pages without a <form> element
|
|
@@ -235,8 +257,20 @@ export function preparePrintFriendlyData(req, siteId, service) {
|
|
|
235
257
|
continue; // ⛔ Skip this page from print-friendly data
|
|
236
258
|
}
|
|
237
259
|
|
|
260
|
+
let pageTemplate = page.pageTemplate;
|
|
261
|
+
let pageTitle = page.pageData.title || {};
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
// ----- MultipleThings hub handling
|
|
265
|
+
if (page.updateMyDetails) {
|
|
266
|
+
// create the page template
|
|
267
|
+
pageTemplate = createUmdManualPageTemplate(siteId, service.site.lang, page, req );
|
|
268
|
+
// set the page title
|
|
269
|
+
pageTitle = govcyResources.staticResources.text.updateMyDetailsTitle;
|
|
270
|
+
}
|
|
271
|
+
|
|
238
272
|
// find the form element in the page template
|
|
239
|
-
for (const section of
|
|
273
|
+
for (const section of pageTemplate.sections || []) {
|
|
240
274
|
for (const element of section.elements || []) {
|
|
241
275
|
if (element.element !== "form") continue;
|
|
242
276
|
|
|
@@ -282,6 +316,7 @@ export function preparePrintFriendlyData(req, siteId, service) {
|
|
|
282
316
|
|
|
283
317
|
// Special case: multipleThings page → extract item titles // ✅ new
|
|
284
318
|
if (page.multipleThings) {
|
|
319
|
+
pageTitle = page.multipleThings?.listPage?.title || pageTitle;
|
|
285
320
|
let mtItems = dataLayer.getPageData(req.session, siteId, page.pageData.url);
|
|
286
321
|
if (Array.isArray(mtItems)) {
|
|
287
322
|
const env = new nunjucks.Environment(null, { autoescape: false });
|
|
@@ -295,7 +330,7 @@ export function preparePrintFriendlyData(req, siteId, service) {
|
|
|
295
330
|
if (fields.length > 0) {
|
|
296
331
|
submissionData.push({
|
|
297
332
|
pageUrl: page.pageData.url,
|
|
298
|
-
pageTitle:
|
|
333
|
+
pageTitle: pageTitle,
|
|
299
334
|
fields,
|
|
300
335
|
items: (page.multipleThings ? items : null) // ✅ new
|
|
301
336
|
});
|
|
@@ -674,8 +709,12 @@ export function generateSubmitEmail(service, submissionData, submissionId, req)
|
|
|
674
709
|
// For each page in the submission data
|
|
675
710
|
for (const page of submissionData) {
|
|
676
711
|
// Get the page URL, title, and fields
|
|
677
|
-
|
|
712
|
+
let { pageUrl, pageTitle, fields, items } = page;
|
|
678
713
|
|
|
714
|
+
// 🔹 MultipleThings page
|
|
715
|
+
if (page.multipleThings) {
|
|
716
|
+
pageTitle = page.multipleThings?.listPage?.title || pageTitle;
|
|
717
|
+
}
|
|
679
718
|
// Add data title to the body
|
|
680
719
|
body.push(
|
|
681
720
|
{
|
|
@@ -394,4 +394,77 @@ export function validateFormElements(elements, formData, pageUrl) {
|
|
|
394
394
|
}
|
|
395
395
|
});
|
|
396
396
|
return validationErrors;
|
|
397
|
-
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Checks if a user is an Individual with a valid Cypriot citizen identifier.
|
|
402
|
+
* Rules:
|
|
403
|
+
* - profile_type must be "Individual"
|
|
404
|
+
* - unique_identifier must be a string
|
|
405
|
+
* - must start with "00"
|
|
406
|
+
* - must be 10 characters long
|
|
407
|
+
*
|
|
408
|
+
* @param {object} user - The user object (e.g. req.session.user)
|
|
409
|
+
* @returns {boolean} true if valid, false otherwise
|
|
410
|
+
*/
|
|
411
|
+
export function isValidCypriotCitizen(user = {}) {
|
|
412
|
+
const { profile_type, unique_identifier } = user;
|
|
413
|
+
|
|
414
|
+
if (
|
|
415
|
+
typeof profile_type === "string" &&
|
|
416
|
+
profile_type === "Individual" &&
|
|
417
|
+
typeof unique_identifier === "string" &&
|
|
418
|
+
unique_identifier.startsWith("00") &&
|
|
419
|
+
unique_identifier.length === 10
|
|
420
|
+
) {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Checks if the given user represents a valid foreign resident (ARC holder).
|
|
429
|
+
* Conditions:
|
|
430
|
+
* - profile_type must equal "Individual"
|
|
431
|
+
* - unique_identifier must be a string
|
|
432
|
+
* - unique_identifier must start with "05"
|
|
433
|
+
* - unique_identifier must be exactly 10 characters long
|
|
434
|
+
*
|
|
435
|
+
* @param {object} user - e.g. req.session.user
|
|
436
|
+
* @returns {boolean} True if valid foreign resident, otherwise false
|
|
437
|
+
*/
|
|
438
|
+
export function isValidForeignResident(user = {}) {
|
|
439
|
+
const { profile_type, unique_identifier } = user;
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
typeof profile_type === "string" &&
|
|
443
|
+
profile_type === "Individual" &&
|
|
444
|
+
typeof unique_identifier === "string" &&
|
|
445
|
+
unique_identifier.startsWith("05") &&
|
|
446
|
+
unique_identifier.length === 10
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Checks if the user is under 18 years old based on their date of birth.
|
|
452
|
+
* @param {string} dobString - The date of birth in the format "YYYY-MM-DD".
|
|
453
|
+
* @returns {boolean} True if the user is under 18 years old, otherwise false.
|
|
454
|
+
* @throws {Error} If the date of birth is missing or invalid.
|
|
455
|
+
* */
|
|
456
|
+
export function isUnder18(dobString) {
|
|
457
|
+
if (!dobString) throw new Error("DOB is missing");
|
|
458
|
+
const dob = new Date(dobString);
|
|
459
|
+
if (isNaN(dob)) throw new Error("Invalid DOB format");
|
|
460
|
+
|
|
461
|
+
const today = new Date();
|
|
462
|
+
const ageDiff = today.getFullYear() - dob.getFullYear();
|
|
463
|
+
const hasHadBirthday =
|
|
464
|
+
today.getMonth() > dob.getMonth() ||
|
|
465
|
+
(today.getMonth() === dob.getMonth() && today.getDate() >= dob.getDate());
|
|
466
|
+
|
|
467
|
+
return (hasHadBirthday ? ageDiff : ageDiff - 1) < 18;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|