From 1f64263bb842b9225c70382561df99971c03edc5 Mon Sep 17 00:00:00 2001 From: IsaiahSama Date: Thu, 15 Jan 2026 16:47:01 -0400 Subject: [PATCH 1/5] Updated missing validation --- schemas/sell-goods-services-beach-park.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/schemas/sell-goods-services-beach-park.json b/schemas/sell-goods-services-beach-park.json index 4081786..c988130 100644 --- a/schemas/sell-goods-services-beach-park.json +++ b/schemas/sell-goods-services-beach-park.json @@ -60,13 +60,18 @@ "message": "Date of birth is required and must be in YYYY-MM-DD format" } }, + { + "name": "nationality", + "type": "string", + "label": "Nationality", + "required": true + }, { "name": "idNumber", "type": "string", "label": "National Identification (ID) Number", "required": false, "validations": { - "min": 2, "message": "ID Number must be at least 2 characters" } }, From ab42bd96d1f51052cfce290c0b4e1e0da329b554 Mon Sep 17 00:00:00 2001 From: Shannon Clarke Date: Fri, 16 Jan 2026 05:21:39 -0400 Subject: [PATCH 2/5] Fix (forms): update schema for "redirect business mail" api (#55) * fix: update schema to match figma * fix: update form schema and validations for empty strings --- schemas/post-office-redirection-business.json | 166 +++++++++--------- src/validation/schema-builder.service.ts | 35 ++-- 2 files changed, 98 insertions(+), 103 deletions(-) diff --git a/schemas/post-office-redirection-business.json b/schemas/post-office-redirection-business.json index 7f0b3e4..bc81c82 100644 --- a/schemas/post-office-redirection-business.json +++ b/schemas/post-office-redirection-business.json @@ -4,141 +4,141 @@ "description": "Change where your mail is sent (business)", "fields": [ { - "name": "applicant", + "name": "businessName", + "type": "string", + "label": "Business name", + "required": true, + "validations": { + "min": 2, + "message": "Business name is required" + } + }, + { + "name": "currentAddress", "type": "object", "required": true, "fields": [ { - "name": "title", - "type": "string", - "label": "Title", - "required": true, - "validations": { - "regex": "^(mr|ms|mrs)$", - "message": "Must select a valid title" - } - }, - { - "name": "firstName", + "name": "addressLine1", "type": "string", - "label": "First name", + "label": "Address line 1", "required": true, "validations": { - "min": 2, - "max": 100, - "message": "First name is required" + "min": 5, + "max": 200, + "message": "Address must be at least 5 characters" } }, { - "name": "lastName", + "name": "addressLine2", "type": "string", - "label": "Last name", - "required": true, - "validations": { - "min": 2, - "max": 100, - "message": "Last name is required" - } - }, - { - "name": "dateOfBirth", - "type": "date", - "label": "Date of birth", + "label": "Address line 2", "required": false, "validations": { - "regex": "^\\d{4}-\\d{2}-\\d{2}$", - "message": "Date of birth is required and must be in YYYY-MM-DD format" + "max": 200 } }, { - "name": "idNumber", + "name": "parish", "type": "string", - "label": "National Identification (ID) Number", - "required": false, + "label": "Parish", + "required": true, "validations": { - "min": 2, - "message": "ID Number must be at least 2 characters" + "regex": "^(christ-church|st-andrew|st-george|st-james|st-john|st-joseph|st-lucy|st-michael|st-peter|st-philip|st-thomas)$", + "message": "Must select a valid parish" } }, { - "name": "passportNumber", + "name": "postcode", "type": "string", - "label": "Passport Number", + "label": "Postcode", "required": false, "validations": { - "message": "Passport number must be at least 6 characters" + "regex": "^BB\\d{5}$", + "message": "Enter a valid postal code (e.g., BB17004)" } - }, - { - "name": "email", - "type": "email", - "label": "Email Address", - "required": true - }, - { - "name": "telephoneNumber", - "type": "string", - "label": "Telephone Number", - "required": true } ] }, { - "name": "oldBusinessAddress", + "name": "applicant", "type": "object", "required": true, "fields": [ { - "name": "addressLine1", + "name": "title", "type": "string", - "label": "Address Line 1", + "label": "Title", "required": true, "validations": { - "min": 5, - "max": 200, - "message": "Address must be at least 5 characters" + "regex": "^(mr|ms|mrs)$", + "message": "Must select a valid title" } }, { - "name": "addressLine2", + "name": "firstName", "type": "string", - "label": "Address Line 2", - "required": false, + "label": "First name", + "required": true, "validations": { - "max": 200 + "min": 2, + "max": 100, + "message": "First name is required" } }, { - "name": "parish", + "name": "middleName", "type": "string", - "label": "Parish", - "required": true, + "label": "Middle name", + "required": false, "validations": { - "regex": "^(christ-church|st-andrew|st-george|st-james|st-john|st-joseph|st-lucy|st-michael|st-peter|st-philip|st-thomas)$", - "message": "Must select a valid parish" + "max": 100, + "message": "Middle name is required" } }, { - "name": "postalCode", + "name": "lastName", "type": "string", - "label": "Postal Code", - "required": false, + "label": "Last name", + "required": true, "validations": { - "regex": "^BB\\d{5}$", - "message": "Enter a valid postal code (e.g., BB17004)" + "min": 2, + "max": 100, + "message": "Last name is required" } + }, + { + "name": "email", + "type": "email", + "label": "Email Address", + "required": true + }, + { + "name": "telephoneNumber", + "type": "string", + "label": "Telephone Number", + "required": true } ] }, { - "name": "newBusinessAddress", + "name": "permissionDetails", + "type": "string", + "label": "Tell us what permission you have to act for this business", + "required": true, + "validations": { + "message": "Permission details required" + } + }, + { + "name": "newAddress", "type": "object", "required": true, "fields": [ { "name": "addressLine1", "type": "string", - "label": "Address Line 1", + "label": "Address line 1", "required": true, "validations": { "min": 5, @@ -149,7 +149,7 @@ { "name": "addressLine2", "type": "string", - "label": "Address Line 2", + "label": "Address line 2", "required": false, "validations": { "max": 200 @@ -166,9 +166,9 @@ } }, { - "name": "postalCode", + "name": "postcode", "type": "string", - "label": "Postal Code", + "label": "Postcode", "required": false, "validations": { "regex": "^BB\\d{5}$", @@ -189,21 +189,13 @@ "name": "redirectionStartDate", "type": "date", "label": "Redirection Start Date", - "required": false, - "validations": { - "regex": "^\\d{4}-\\d{2}-\\d{2}$", - "message": "Redirection Start Date is required and must be in YYYY-MM-DD format" - } + "required": false }, { "name": "redirectionEndDate", "type": "date", "label": "Redirection End Date", - "required": false, - "validations": { - "regex": "^\\d{4}-\\d{2}-\\d{2}$", - "message": "Redirection End Date is required and must be in YYYY-MM-DD format" - } + "required": false } ] }, @@ -222,7 +214,7 @@ "type": "email", "config": { "to": "{{db:post-office-redirection-business:admin_email}}", - "subject": "New Request to Redirect Mail for a Business - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", + "subject": "New Request to Redirect my business mail - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "post-office-redirection-notice" } } diff --git a/src/validation/schema-builder.service.ts b/src/validation/schema-builder.service.ts index f420541..6659dce 100644 --- a/src/validation/schema-builder.service.ts +++ b/src/validation/schema-builder.service.ts @@ -70,7 +70,13 @@ export class SchemaBuilderService { // Build base schema based on field type switch (field.type) { case 'string': - schema = z.coerce.string(); + // For required strings, use z.string().min(1) to reject empty strings + // For optional strings, use z.coerce.string() but allow empty + if (field.required) { + schema = z.string().min(1, 'This field is required'); + } else { + schema = z.coerce.string(); + } break; case 'email': schema = z.string().email('Invalid email format'); @@ -138,21 +144,15 @@ export class SchemaBuilderService { ) { if (validations.min !== undefined) { const minLength = validations.min; - if (!required) { - // For optional fields, allow empty strings or strings that meet min length - schema = schema.refine( - (val: string) => !val || val.length >= minLength, - { - message: - validations.message || `Minimum length is ${minLength}`, - }, - ); - } else { - schema = schema.min( - minLength, - validations.message || `Minimum length is ${minLength}`, - ); - } + // Always enforce minimum length (use max(1, minLength) for required fields) + const effectiveMin = required ? Math.max(1, minLength) : minLength; + schema = schema.min( + effectiveMin, + validations.message || `Minimum length is ${effectiveMin}`, + ); + } else if (required) { + // If no min specified but field is required, enforce min length of 1 + schema = schema.min(1, validations.message || 'This field is required'); } if (validations.max !== undefined) { schema = schema.max( @@ -160,6 +160,9 @@ export class SchemaBuilderService { validations.message || `Maximum length is ${validations.max}`, ); } + } else if (fieldType === 'string' && required) { + // If no validations but field is required, enforce non-empty string + schema = schema.min(1, 'This field is required'); } // Min/Max for numbers From ff49d14f3683e73a2fc99d27e3c4548ea8d4d24d Mon Sep 17 00:00:00 2001 From: Shannon Clarke Date: Fri, 16 Jan 2026 05:42:40 -0400 Subject: [PATCH 3/5] fix: telephone validation in sell goods or services form (#53) --- schemas/sell-goods-services-beach-park.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/schemas/sell-goods-services-beach-park.json b/schemas/sell-goods-services-beach-park.json index 4081786..de73259 100644 --- a/schemas/sell-goods-services-beach-park.json +++ b/schemas/sell-goods-services-beach-park.json @@ -89,11 +89,7 @@ "name": "telephoneNumber", "type": "string", "label": "Telephone Number", - "required": true, - "validations": { - "regex": "^\\+?[0-9]{10,15}$", - "message": "Telephone number must be 10-15 digits" - } + "required": true }, { "name": "addressLine1", From c6ce26533f066194838d89e751d882c89a2029a6 Mon Sep 17 00:00:00 2001 From: Shannon Clarke Date: Fri, 16 Jan 2026 05:43:01 -0400 Subject: [PATCH 4/5] fix (forms): change api for "Tell Post Office someone died" form (#54) * fix: update schema for deceased form to match figma * fix: gracefully fail if unable to connect to smtp server --- schemas/post-office-redirection-deceased.json | 152 ++++++++----- src/email/email.service.ts | 6 +- .../post-office-redirection-notice.hbs | 210 +++++++++++------- .../implementations/email.processor.ts | 41 ++-- 4 files changed, 248 insertions(+), 161 deletions(-) diff --git a/schemas/post-office-redirection-deceased.json b/schemas/post-office-redirection-deceased.json index c8e9c8d..bf28cc4 100644 --- a/schemas/post-office-redirection-deceased.json +++ b/schemas/post-office-redirection-deceased.json @@ -4,7 +4,7 @@ "description": "Change where your mail is sent (deceased)", "fields": [ { - "name": "applicant", + "name": "deceased", "type": "object", "required": true, "fields": [ @@ -29,6 +29,12 @@ "message": "First name is required" } }, + { + "name": "middleName", + "type": "string", + "label": "Middle name", + "required": false + }, { "name": "lastName", "type": "string", @@ -41,45 +47,14 @@ } }, { - "name": "dateOfBirth", + "name": "dateOfDeath", "type": "date", - "label": "Date of birth", + "label": "Date of death", "required": false, "validations": { "regex": "^\\d{4}-\\d{2}-\\d{2}$", - "message": "Date of birth is required and must be in YYYY-MM-DD format" - } - }, - { - "name": "idNumber", - "type": "string", - "label": "National Identification (ID) Number", - "required": false, - "validations": { - "min": 2, - "message": "ID Number must be at least 2 characters" - } - }, - { - "name": "passportNumber", - "type": "string", - "label": "Passport Number", - "required": false, - "validations": { - "message": "Passport number must be at least 6 characters" + "message": "Date of death is required and must be in YYYY-MM-DD format" } - }, - { - "name": "email", - "type": "email", - "label": "Email Address", - "required": true - }, - { - "name": "telephoneNumber", - "type": "string", - "label": "Telephone Number", - "required": true } ] }, @@ -91,7 +66,7 @@ { "name": "addressLine1", "type": "string", - "label": "Address Line 1", + "label": "Address line 1", "required": true, "validations": { "min": 5, @@ -102,7 +77,7 @@ { "name": "addressLine2", "type": "string", - "label": "Address Line 2", + "label": "Address line 2", "required": false, "validations": { "max": 200 @@ -119,9 +94,9 @@ } }, { - "name": "postalCode", + "name": "postcode", "type": "string", - "label": "Postal Code", + "label": "Postcode", "required": false, "validations": { "regex": "^BB\\d{5}$", @@ -130,6 +105,77 @@ } ] }, + { + "name": "applicant", + "type": "object", + "required": true, + "fields": [ + { + "name": "title", + "type": "string", + "label": "Title", + "required": true, + "validations": { + "regex": "^(mr|ms|mrs)$", + "message": "Must select a valid title" + } + }, + { + "name": "firstName", + "type": "string", + "label": "First name", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "First name is required" + } + }, + { + "name": "relationshipToDeceased", + "type": "string", + "label": "What is your relationship to the person?", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "Relationship to deceased is required" + } + }, + { + "name": "lastName", + "type": "string", + "label": "Last name", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "Last name is required" + } + }, + { + "name": "email", + "type": "email", + "label": "Email Address", + "required": true + }, + { + "name": "telephoneNumber", + "type": "string", + "label": "Telephone Number", + "required": true + } + ] + }, + { + "name": "permissionDetails", + "type": "string", + "label": "Tell us what permission you have to act on behalf of the estate", + "required": true, + "validations": { + "message": "Permission details required" + } + }, { "name": "newAddress", "type": "object", @@ -138,7 +184,7 @@ { "name": "addressLine1", "type": "string", - "label": "Address Line 1", + "label": "Address line 1", "required": true, "validations": { "min": 5, @@ -149,7 +195,7 @@ { "name": "addressLine2", "type": "string", - "label": "Address Line 2", + "label": "Address line 2", "required": false, "validations": { "max": 200 @@ -166,9 +212,9 @@ } }, { - "name": "postalCode", + "name": "postcode", "type": "string", - "label": "Postal Code", + "label": "Postcode", "required": false, "validations": { "regex": "^BB\\d{5}$", @@ -176,9 +222,9 @@ } }, { - "name": "isMovingPermanent", + "name": "isRedirectPermanent", "type": "string", - "label": "Are you moving permanently?", + "label": "Are you redirecting their mail permanently?", "required": true, "validations": { "regex": "^(yes|no)$", @@ -187,23 +233,15 @@ }, { "name": "redirectionStartDate", - "type": "date", + "type": "string", "label": "Redirection Start Date", - "required": false, - "validations": { - "regex": "^\\d{4}-\\d{2}-\\d{2}$", - "message": "Redirection Start Date is required and must be in YYYY-MM-DD format" - } + "required": false }, { "name": "redirectionEndDate", - "type": "date", + "type": "string", "label": "Redirection End Date", - "required": false, - "validations": { - "regex": "^\\d{4}-\\d{2}-\\d{2}$", - "message": "Redirection End Date is required and must be in YYYY-MM-DD format" - } + "required": false } ] }, diff --git a/src/email/email.service.ts b/src/email/email.service.ts index 4571c9d..1bdcbbc 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -198,8 +198,10 @@ export class EmailService { `Email sent successfully to ${validToAddresses.join(', ')}`, ); } catch (error) { - this.logger.error(`Failed to send email: ${error.message}`, error.stack); - throw error; + this.logger.warn( + `Failed to send email (continuing silently): ${error.message}`, + ); + // Silently fail - do not throw the error } } diff --git a/src/email/templates/post-office-redirection-notice.hbs b/src/email/templates/post-office-redirection-notice.hbs index 5346102..5ba9bd9 100644 --- a/src/email/templates/post-office-redirection-notice.hbs +++ b/src/email/templates/post-office-redirection-notice.hbs @@ -18,136 +18,176 @@ border-radius: 5px; } .old-address { background-color: #fed7d7; } .new-address { background-color: #c6f6d5; } .footer { margin-top: 30px; padding: 20px; background-color: #edf2f7; border-radius: 5px; text-align: - center; font-size: 14px; color: #718096; } + center; font-size: 14px; color: #718096; } .document-list { list-style: + none; padding-left: 0; } .document-list li { padding: 8px; + background-color: #edf2f7; margin-bottom: 8px; border-radius: 3px; + word-break: break-all; }
-

📮 Post Office Redirection Notice Request

+

📮 Post Office Redirection for Deceased - Request Submission

-

A new post office redirection notice request has been submitted.

+

A new post office redirection request for a deceased person has been + submitted.

+
-
Personal Information
+
Deceased Person Information
- Name: - {{applicant.title}} - {{applicant.firstName}} - {{applicant.lastName}} + Title: + {{deceased.title}}
- Date of Birth: - {{applicant.dateOfBirth}} + First Name: + {{deceased.firstName}}
-
- ID Number: - {{applicant.idNumber}} -
- {{#if applicant.passportNumber}} + {{#if deceased.middleName}}
- Passport Number: - {{applicant.passportNumber}} + Middle Name: + {{deceased.middleName}}
{{/if}} - {{#if applicant.email}} +
+ Last Name: + {{deceased.lastName}} +
+ {{#if deceased.dateOfDeath}}
- Email: - {{applicant.email}} + Date of Death: + {{deceased.dateOfDeath}}
{{/if}} - {{#if applicant.email}} +
+ + +
+
Old/Previous Address
+
+ Address Line 1: + {{oldAddress.addressLine1}} +
+ {{#if oldAddress.addressLine2}}
- Email: - {{applicant.email}} + Address Line 2: + {{oldAddress.addressLine2}}
{{/if}} - {{#if applicant.telephoneNumber}} +
+ Parish: + {{oldAddress.parish}} +
+ {{#if oldAddress.postcode}}
- Phone Number: - {{applicant.telephoneNumber}} + Postcode: + {{oldAddress.postcode}}
{{/if}}
+
-
Address Redirection Details
-
-
-

Old Address

-
- Address Line 1: - {{oldaddress.addressLine1}} -
- {{#if oldaddress.addressLine2}} -
- Address Line 2: - {{oldaddress.addressLine2}} -
- {{/if}} -
- Parish: - {{oldaddress.parish}} -
- {{#if oldaddress.postalCode}} -
- Postal Code: - {{oldaddress.postalCode}} -
- {{/if}} -
+
Applicant Information
+
+ Title: + {{applicant.title}} +
+
+ First Name: + {{applicant.firstName}} +
+
+ Last Name: + {{applicant.lastName}} +
+
+ Relationship to Deceased: + {{applicant.relationshipToDeceased}} +
+
+ Email Address: + {{applicant.email}} +
+
+ Telephone Number: + {{applicant.telephoneNumber}} +
+
-
-

New Address

-
- Address Line 1: - {{newAddress.addressLine1}} -
- {{#if newAddress.addressLine2}} -
- Address Line 2: - {{newAddress.addressLine2}} -
- {{/if}} -
- Parish: - {{newAddress.parish}} -
- {{#if newAddress.postalCode}} -
- Postal Code: - {{newAddress.postalCode}} -
- {{/if}} -
+ +
+
Permission Details
+
+ Permission to Act on Estate: + {{permissionDetails}}
+
-
House Member Information
+
New Address for Mail Redirection
+
+ Address Line 1: + {{newAddress.addressLine1}} +
+ {{#if newAddress.addressLine2}} +
+ Address Line 2: + {{newAddress.addressLine2}} +
+ {{/if}}
- Member Name: - {{houseMembers.firstName}} - {{houseMembers.lastName}} + Parish: + {{newAddress.parish}}
+ {{#if newAddress.postcode}} +
+ Postcode: + {{newAddress.postcode}} +
+ {{/if}}
- Member ID Number: - {{houseMembers.idNumber}} + Permanent Redirection: + {{newAddress.isRedirectPermanent}}
- {{#if houseMembers.addAnother}} + {{#if newAddress.redirectionStartDate}}
- Add Another Member: - {{houseMembers.addAnother}} + Redirection Start Date: + {{newAddress.redirectionStartDate}} +
+ {{/if}} + {{#if newAddress.redirectionEndDate}} +
+ Redirection End Date: + {{newAddress.redirectionEndDate}}
{{/if}}
+ + {{#if uploadDocumentUrls}} +
+
Uploaded Documents
+ {{#if uploadDocumentUrls.length}} +
    + {{#each uploadDocumentUrls}} +
  • {{this}}
  • + {{/each}} +
+ {{else}} +

No documents uploaded.

+ {{/if}} +
+ {{/if}} +
diff --git a/src/processors/implementations/email.processor.ts b/src/processors/implementations/email.processor.ts index 3f7dcce..a2c2d5f 100644 --- a/src/processors/implementations/email.processor.ts +++ b/src/processors/implementations/email.processor.ts @@ -38,22 +38,29 @@ export class EmailProcessor implements IProcessor { return; } - await this.emailService.sendEmail({ - to, - from, - subject, - template, - html, - text, - data: { - formId: context.formId, - submissionId: context.submissionId, - ...context.data, - }, - }); - - this.logger.log( - `Email sent successfully for submission: ${context.submissionId}`, - ); + try { + await this.emailService.sendEmail({ + to, + from, + subject, + template, + html, + text, + data: { + formId: context.formId, + submissionId: context.submissionId, + ...context.data, + }, + }); + + this.logger.log( + `Email sent successfully for submission: ${context.submissionId}`, + ); + } catch (error) { + this.logger.warn( + `Email processor encountered an error but continuing (email service may be unavailable): ${error.message}`, + ); + // Continue without throwing - email failures should not block form submission + } } } From bd3494ffda84d531c5d8f3aadf488219a4d44d7d Mon Sep 17 00:00:00 2001 From: Akinola Raphael Date: Fri, 16 Jan 2026 14:11:06 +0100 Subject: [PATCH 5/5] chore: update email error logic --- .../implementations/email.processor.ts | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/processors/implementations/email.processor.ts b/src/processors/implementations/email.processor.ts index a2c2d5f..3f7dcce 100644 --- a/src/processors/implementations/email.processor.ts +++ b/src/processors/implementations/email.processor.ts @@ -38,29 +38,22 @@ export class EmailProcessor implements IProcessor { return; } - try { - await this.emailService.sendEmail({ - to, - from, - subject, - template, - html, - text, - data: { - formId: context.formId, - submissionId: context.submissionId, - ...context.data, - }, - }); - - this.logger.log( - `Email sent successfully for submission: ${context.submissionId}`, - ); - } catch (error) { - this.logger.warn( - `Email processor encountered an error but continuing (email service may be unavailable): ${error.message}`, - ); - // Continue without throwing - email failures should not block form submission - } + await this.emailService.sendEmail({ + to, + from, + subject, + template, + html, + text, + data: { + formId: context.formId, + submissionId: context.submissionId, + ...context.data, + }, + }); + + this.logger.log( + `Email sent successfully for submission: ${context.submissionId}`, + ); } }