From fb0d30fce4ea8bf16e20978ceedf2b28d35be797 Mon Sep 17 00:00:00 2001 From: Milos Despotovic Date: Mon, 15 Jun 2026 10:06:29 -0700 Subject: [PATCH 01/24] Update Access & Info branch name --- api/emailLayout.js | 6 +++--- .../choose-identity/choose-identity.component.html | 2 +- web/src/app/route-components/landing/landing.component.html | 2 +- .../static-contact-block.component.html | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/emailLayout.js b/api/emailLayout.js index e02ca403..1307d0c0 100644 --- a/api/emailLayout.js +++ b/api/emailLayout.js @@ -467,11 +467,11 @@ function ConfirmationEmailLayout() {

Please do not reply to this email, this is an acknowledgment of payment.

If you have any questions or concerns, please contact us directly at the contact information noted below.

Regards,

-

FOI Operations

+

Access & Information Management Branch


Ministry of Citizens’ Services | - FOI Operations + Access & Information Management Branch

PO Box 9569 Stn Prov Govt Victoria BC V8W 9V1 @@ -484,7 +484,7 @@ function ConfirmationEmailLayout() {

FOI.Requests@gov.bc.ca

250-387-1321 or 1-833-283-8200 - (ask for FOI Operations) + (ask for Access & Information Management Branch)

` diff --git a/web/src/app/route-components/choose-identity/choose-identity.component.html b/web/src/app/route-components/choose-identity/choose-identity.component.html index f1f9cebb..8b6cfb96 100644 --- a/web/src/app/route-components/choose-identity/choose-identity.component.html +++ b/web/src/app/route-components/choose-identity/choose-identity.component.html @@ -36,7 +36,7 @@

Information Collection Notice

- Personal information from your BC Services Card is collected under s.26 of the Freedom of Information and Protection of Privacy Act. The information you provide will be only be used in confirming your identity. Upon pressing the [Login with BC Services Card] button, you are confirming that you have reviewed this statement. If you have any questions about the collection and use of this information, please contact FOI Operations at PO Box 9569, Stn Prov Govt, Victoria BC, V8W 9V1 or at 250-387-1321 + Personal information from your BC Services Card is collected under s.26 of the Freedom of Information and Protection of Privacy Act. The information you provide will be only be used in confirming your identity. Upon pressing the [Login with BC Services Card] button, you are confirming that you have reviewed this statement. If you have any questions about the collection and use of this information, please contact the Access & Information Management Branch at PO Box 9569, Stn Prov Govt, Victoria BC, V8W 9V1 or at 250-387-1321

diff --git a/web/src/app/route-components/landing/landing.component.html b/web/src/app/route-components/landing/landing.component.html index 4b8cfa9e..5e3b2edd 100644 --- a/web/src/app/route-components/landing/landing.component.html +++ b/web/src/app/route-components/landing/landing.component.html @@ -71,7 +71,7 @@

How are records stored?

>Freedom of Information and Protection of Privacy Act and will be used only for the purpose of responding to your request. If you have any questions about the - collection, use or disclosure of this information, please call FOI Operations at 250-387-1321. + collection, use or disclosure of this information, please call the Access & Information Management Branch at 250-387-1321.

You may make a request for access to records without using this form, provided you do so in writing. Including diff --git a/web/src/app/utils-components/static-contact-block/static-contact-block.component.html b/web/src/app/utils-components/static-contact-block/static-contact-block.component.html index 39b5eab8..05d98783 100644 --- a/web/src/app/utils-components/static-contact-block/static-contact-block.component.html +++ b/web/src/app/utils-components/static-contact-block/static-contact-block.component.html @@ -1,5 +1,5 @@

Contact information

-

FOI Operations can answer your questions about requesting records from government.

+

The Access & Information Management Branch can answer your questions about requesting records from government.

Phone: From e57b3baa3def8d628401fc658b6e13e7b71d6ba7 Mon Sep 17 00:00:00 2001 From: Nijjar Date: Mon, 6 Jul 2026 12:47:13 -0700 Subject: [PATCH 02/24] fix: restrict personal request route and ministry selection --- .../ministry-confirmation.component.html | 8 + .../ministry-confirmation.component.ts | 137 ++++++++++++------ .../request-topic.component.html | 17 ++- .../request-topic.component.scss | 5 + .../request-topic/request-topic.component.ts | 50 ++++++- 5 files changed, 162 insertions(+), 55 deletions(-) diff --git a/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html b/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html index 6a4149da..4a0ddff4 100644 --- a/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html +++ b/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html @@ -4,6 +4,14 @@

What ministry or agency has the records you are looking for?

*A non-refundable application fee of $10 is required for all General FOI requests, for every public body selected.
+ +

+ For personal FOI Requests you may only submit to one Ministry at a time. If you need to submit multiple + different + personal requests you can submit a new request. +

+
+
diff --git a/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.ts b/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.ts index 9266c4e9..47f7fbf6 100644 --- a/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.ts +++ b/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.ts @@ -27,61 +27,108 @@ export class MinistryConfirmationComponent implements OnInit { isENVministry: boolean = false; constructor(private fb: FormBuilder, private dataService: DataService, private route: Router) { } - mcfdOnlyTopicValues: string[] = [ - "adoption", - "childprotectionchild", - "childprotectionparent", - "fosterparent", - "youthincarechild", - "youthincareparent", - ]; - - isMcfdOnlyRequest: boolean = false; - readonly mcfdMinistryCode: string = "MCF"; - - private hasMcfdOnlyTopicSelected(): boolean { - const currentUrl = this.route.url || ""; + readonly personalTopicMinistryCodeMap: { [key: string]: string } = { + publicServiceEmployment: "PSA", + correctionalFacility: "PSSG", + incomeAssistance: "MSD", + adoption: "MCF", + childprotectionchild: "MCF", + childprotectionparent: "MCF", + fosterparent: "MCF", + youthincarechild: "MCF", + youthincareparent: "MCF", + }; + + readonly otherPersonalTopicValue: string = "anotherTopic"; + + isPersonalRequest: boolean = false; + isLockedPersonalMinistryRequest: boolean = false; + isOtherPersonalRequest: boolean = false; + lockedPersonalMinistryCode: string = null; + + private getRequestType(): string { + const requestType = this.foiRequest?.requestData?.requestType; + return requestType?.requestType || requestType; + } + + private getCurrentRequestTopicValue(): string { const currentRequestTopic = this.foiRequest?.requestData?.requestTopic?.value; - const matchesTopicSpecificRoute = this.mcfdOnlyTopicValues.some((topicValue) => - currentUrl.includes(`/${topicValue}/ministry-confirmation`) - ); + if (currentRequestTopic) { + return currentRequestTopic; + } - const matchesCurrentRequestTopic = - !!currentRequestTopic && this.mcfdOnlyTopicValues.includes(currentRequestTopic); + const currentUrl = this.route.url || ""; + const personalRouteMatch = currentUrl.match(/\/personal\/([^\/]+)\/ministry-confirmation/); - return matchesTopicSpecificRoute || matchesCurrentRequestTopic; + return personalRouteMatch ? personalRouteMatch[1] : null; } + private applyPersonalMinistryRules(ministries: any[]): any[] { + if (this.isLockedPersonalMinistryRequest) { + ministries.forEach((m) => { + const isLockedMinistry = m.code === this.lockedPersonalMinistryCode; + m.selected = isLockedMinistry; + m.defaulted = isLockedMinistry; + m.disabled = true; + }); - private isMcfdMinistry(m: any): boolean { - return m?.code === this.mcfdMinistryCode; + return ministries; + } + + if (this.isOtherPersonalRequest) { + return this.applyOtherPersonalMinistryRules(ministries); + } + + ministries.forEach((m) => { + m.disabled = false; + }); + + return ministries; } - private applyMcfdOnlyMinistryRules(ministries: any[]): any[] { - if (!this.isMcfdOnlyRequest) { + private applyOtherPersonalMinistryRules(ministries: any[]): any[] { + const selectedMinistries = ministries.filter((m) => m.selected); + + if (selectedMinistries.length > 1) { + const selectedMinistry = selectedMinistries[0]; + ministries.forEach((m) => { - m.disabled = false; + m.selected = m.code === selectedMinistry.code; }); - return ministries; } + const hasSelectedMinistry = ministries.some((m) => m.selected); + ministries.forEach((m) => { - const isMcfd = this.isMcfdMinistry(m); - m.selected = isMcfd; - m.defaulted = isMcfd; - m.disabled = !isMcfd; + m.defaulted = false; + m.disabled = hasSelectedMinistry && !m.selected; }); return ministries; } + private refreshSelectedMinistryFlags(ministries: any[]): void { + this.isEAOministry = !!ministries.find((m) => m.code === "EAO" && m.selected); + this.isENVministry = !!ministries.find((m) => m.code === "ENV" && m.selected); + this.isforestministry = !!ministries.find((m) => m.code === "FOR" && m.selected); + } + + + ngOnInit() { this.foiRequest = this.dataService.getCurrentState(this.targetKey); this.defaultMinistry = this.foiRequest.requestData[this.targetKey].defaultMinistry; let selectedMinistry = this.foiRequest.requestData[this.targetKey].selectedMinistry; this.requiresPayment = this.foiRequest.requestData.requestType.requestType === "general"; - this.isMcfdOnlyRequest = this.hasMcfdOnlyTopicSelected(); + const currentRequestTopicValue = this.getCurrentRequestTopicValue(); + + this.isPersonalRequest = this.getRequestType() === "personal" || this.route.url.includes("/personal/"); + this.lockedPersonalMinistryCode = this.isPersonalRequest + ? this.personalTopicMinistryCodeMap[currentRequestTopicValue] + : null; + this.isLockedPersonalMinistryRequest = !!this.lockedPersonalMinistryCode; + this.isOtherPersonalRequest = this.isPersonalRequest && currentRequestTopicValue === this.otherPersonalTopicValue; // Fetch Ministries from the data service. this.ministries$ = this.dataService.getMinistries().pipe( @@ -106,7 +153,11 @@ export class MinistryConfirmationComponent implements OnInit { this.isforestministry = false; } }); - return this.applyMcfdOnlyMinistryRules(ministries); + + ministries = this.applyPersonalMinistryRules(ministries); + this.refreshSelectedMinistryFlags(ministries); + + return ministries; }), map((ministries) => { this.base.continueDisabled = !ministries.find((m) => m.selected); @@ -126,25 +177,17 @@ export class MinistryConfirmationComponent implements OnInit { } selectMinistry(m: any) { - if (this.isMcfdOnlyRequest) { + if (this.isLockedPersonalMinistryRequest) { return; } + m.selected = !m.selected; - if (m.code === "EAO" && m.selected === true) { - this.isEAOministry = true; - } else if (m.code === "EAO" && m.selected === false) { - this.isEAOministry = false; - } - if (m.code === "ENV" && m.selected === true) { - this.isENVministry = true; - } else if (m.code === "ENV" && m.selected === false) { - this.isENVministry = false; - } - if (m.code === "FOR" && m.selected === true) { - this.isforestministry = true; - } else if (m.code === "FOR" && m.selected === false) { - this.isforestministry = false; + + if (this.isOtherPersonalRequest) { + this.applyOtherPersonalMinistryRules(this.ministries); } + + this.refreshSelectedMinistryFlags(this.ministries); this.setContinueDisabled(); } diff --git a/web/src/app/route-components/request-topic/request-topic.component.html b/web/src/app/route-components/request-topic/request-topic.component.html index 2836c48b..a06fe1b5 100644 --- a/web/src/app/route-components/request-topic/request-topic.component.html +++ b/web/src/app/route-components/request-topic/request-topic.component.html @@ -1,21 +1,22 @@

What kinds of records are you looking for?

-
+
-

Select the corresponding categories of records you are requesting (the more you select the larger your resulting request will be)

+

Select the corresponding categories of records you are requesting (the more you select the larger your + resulting request will be)

Are you looking for these types of records?

-
+
Police Records from a Police visit?
@@ -42,8 +43,10 @@
Your own health records?

-
Court Records and Transcripts?
- We do not hold these records. You can get copies of your court records at https://justice.gov.bc.ca/cso/index.do +
Court Records and Transcripts?
+ We do not hold these records. You can get copies of your court records at https://justice.gov.bc.ca/cso/index.do
diff --git a/web/src/app/route-components/request-topic/request-topic.component.scss b/web/src/app/route-components/request-topic/request-topic.component.scss index 342a5485..b2849b7b 100644 --- a/web/src/app/route-components/request-topic/request-topic.component.scss +++ b/web/src/app/route-components/request-topic/request-topic.component.scss @@ -18,4 +18,9 @@ .optiontext{ display: inline; padding-left: 5px;; +} + +.disabled-topic-option { + opacity: 0.5; + cursor: not-allowed; } \ No newline at end of file diff --git a/web/src/app/route-components/request-topic/request-topic.component.ts b/web/src/app/route-components/request-topic/request-topic.component.ts index 7604e221..d40c8c9a 100644 --- a/web/src/app/route-components/request-topic/request-topic.component.ts +++ b/web/src/app/route-components/request-topic/request-topic.component.ts @@ -29,6 +29,50 @@ export class RequestTopicComponent implements OnInit { "youthincarechild", "youthincareparent", ]; + + childrenAndFamilyTopicValues: string[] = [ + "adoption", + "childprotectionchild", + "childprotectionparent", + "fosterparent", + "youthincarechild", + "youthincareparent", + ]; + + singleRouteTopicValues: string[] = [ + "publicServiceEmployment", + "correctionalFacility", + "incomeAssistance", + "anotherTopic", + ]; + + private isChildrenAndFamilyTopic(topic: any): boolean { + return this.childrenAndFamilyTopicValues.includes(topic?.value); + } + + private getSelectedTopics(): any[] { + return this.topics ? this.topics.filter((topic) => topic.selected) : []; + } + + isTopicDisabled(topic: any): boolean { + const selectedTopics = this.getSelectedTopics(); + + if (selectedTopics.length === 0) { + return false; + } + + const selectedChildrenAndFamily = selectedTopics.some((selectedTopic) => + this.isChildrenAndFamilyTopic(selectedTopic) + ); + + if (selectedChildrenAndFamily) { + return !this.isChildrenAndFamilyTopic(topic); + } + + const selectedTopic = selectedTopics[0]; + return topic.value !== selectedTopic.value; + } + constructor( private fb: FormBuilder, private dataService: DataService, @@ -98,6 +142,10 @@ export class RequestTopicComponent implements OnInit { } selecttopic(item: any, _checked) { + if (this.isTopicDisabled(item) && !item.selected) { + return; + } + item.selected = !item.selected; let current = this.foiRequest.requestData.selectedtopics.find((st) => st.value === item.value); const itemindex: number = this.foiRequest.requestData.selectedtopics.indexOf(current); @@ -158,4 +206,4 @@ export class RequestTopicComponent implements OnInit { this.foiRequest.requestData.selectedtopics = this.yourselftopics; this.base.goFoiBack(); } -} +} \ No newline at end of file From aaddd296b94ce0608bc7ab5cf7bbcf7981434578 Mon Sep 17 00:00:00 2001 From: Nijjar Date: Thu, 9 Jul 2026 15:11:34 -0700 Subject: [PATCH 03/24] fix: update personal ministry selection copy --- .../ministry-confirmation.component.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html b/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html index 4a0ddff4..98be7e82 100644 --- a/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html +++ b/web/src/app/route-components/ministry-confirmation/ministry-confirmation.component.html @@ -6,9 +6,8 @@

- For personal FOI Requests you may only submit to one Ministry at a time. If you need to submit multiple - different - personal requests you can submit a new request. + Personal FOI requests can only be submitted to one ministry at a time. If you need to make a request to multiple + ministries, please submit a separate request for each ministry.

From 4118a5a40b3cbb25cdcd86c0614709dc31b8e6f6 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Tue, 30 Jun 2026 14:06:15 -0700 Subject: [PATCH 04/24] Merge pull request #523 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Duplicate Raw Request Creation Fixes --- api/apiCustomFunctions.js | 3 +++ .../payment-complete.component.ts | 2 ++ .../review-submit-complete.component.ts | 8 +------- .../review-submit/review-submit.component.ts | 16 ++++++++++------ web/src/app/services/data.service.ts | 10 ++++++++++ web/src/app/transom-api-client.service.ts | 2 ++ 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index 726b5534..ed39b0e7 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -16,6 +16,9 @@ const MAX_ATTACH_MB = 5; const maxAttachBytes = MAX_ATTACH_MB * 1024 * 1024; const submitFoiRequest = async (server, req, res, next) => { + console.count("COUNT: submitFoiRequest API CALL"); + console.log("submitFoiRequest API DATA", req); + console.trace('TRACE: submitFoiRequest API CALL'); const apiUrl = `${foiRequestAPIBackend}/foirawrequests`; diff --git a/web/src/app/route-components/payment-complete/payment-complete.component.ts b/web/src/app/route-components/payment-complete/payment-complete.component.ts index 5d019ecb..264691e7 100644 --- a/web/src/app/route-components/payment-complete/payment-complete.component.ts +++ b/web/src/app/route-components/payment-complete/payment-complete.component.ts @@ -110,6 +110,8 @@ export class PaymentCompleteComponent implements OnInit { } submitEmail() { + console.count('COUNT: SUBMIT PAYMENT EMAIL COMPONENT CALLED'); + console.trace('TRACE: SUBMIT PAYMENT EMAIL COMPONENT'); this.dataService.submitRequest(this.authToken, null, this.foiRequest, true).subscribe( (result) => { if (!result.EmailSuccess) { diff --git a/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts b/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts index 3064e078..10eab5f3 100644 --- a/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts +++ b/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts @@ -26,13 +26,7 @@ export class ReviewSubmitCompleteComponent implements OnInit { this.foiRequest.requestData.requestType.youthincareparent = []; // Clear the current state! - const blankState: FoiRequest = { - requestData: {}, - }; - this.dataService.setCurrentState(blankState); - this.dataService.removeChildFileAttachment(); - this.dataService.removePersonFileAttachment(); - this.dataService.removeAdoptionFileAttachment(); + this.dataService.clearState(); this.dataService.removeAuthToken(); } diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 415e0bd4..d6f9b5fa 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -61,6 +61,13 @@ export class ReviewSubmitComponent implements OnInit { } doContinue() { + console.count("COUNT: REQUEST SUBMIT COMPONENT CALLED"); + if (this.isBusy) { + console.trace('TRACE: REQUEST SUBMIT COMPONENT BLOCKED'); + return; + } + console.trace('TRACE: REQUEST SUBMIT COMPONENT ALLOWED'); + this.isBusy = true; this.dataService.submitRequest(this.authToken, this.captchaNonce, this.foiRequest).subscribe( (result) => { @@ -68,9 +75,11 @@ export class ReviewSubmitComponent implements OnInit { this.dataService.setCurrentState(this.foiRequest); this.dataService.saveAuthToken(this.authToken); - this.isBusy = false; + // this.isBusy = false; // If the user is authenticated, logout the user if (this.keycloakService.isAuthenticated()) { + // Clear request state so a duplicate submission cannot resubmit the same data + this.dataService.clearState(); this.keycloakService.logout(); } else { this.base.goFoiForward(); @@ -82,11 +91,6 @@ export class ReviewSubmitComponent implements OnInit { alert("Temporarily unable to submit your request. Please try again in a few minutes."); this.captchaComponent.forceRefresh(); this.captchaComplete = false; - // if (this.keycloakService.isAuthenticated()) { - // this.keycloakService.logout(); - // } else { - // this.base.goFoiForward(); - // } } ); } diff --git a/web/src/app/services/data.service.ts b/web/src/app/services/data.service.ts index 19748a8e..f4872c7b 100644 --- a/web/src/app/services/data.service.ts +++ b/web/src/app/services/data.service.ts @@ -143,6 +143,14 @@ export class DataService { return foi; } + clearState() { + const blankState: FoiRequest = { requestData: {} }; + this.setCurrentState(blankState); + this.removeChildFileAttachment(); + this.removePersonFileAttachment(); + this.removeAdoptionFileAttachment(); + } + setChildFileAttachment(f: File): Observable { return new Observable((observer) => { const reader: FileReader = new FileReader(); @@ -293,6 +301,8 @@ export class DataService { * @param foiRequest - A structure containing the complete request */ submitRequest(authToken: string, nonce: string, foiRequest: FoiRequest, sendEmailOnly?: boolean): Observable { + console.count('COUNT: submitRequest FUNCTION'); + console.trace('TRACE: submitRequest FUNCTION'); this.apiClient.setHeader("Authorization", "Bearer " + authToken); if (nonce) { diff --git a/web/src/app/transom-api-client.service.ts b/web/src/app/transom-api-client.service.ts index 97d8ea95..a68001f2 100644 --- a/web/src/app/transom-api-client.service.ts +++ b/web/src/app/transom-api-client.service.ts @@ -94,6 +94,8 @@ export class TransomApiClientService { * @param body The body to post to the request. */ postFoiRequest(foiRequest: FoiRequest, sendEmailOnly?: boolean): Observable { + console.count('COUNT: postFoiRequest FUNCTION'); + console.trace('TRACE: postFoiRequest FUNCTION'); const functionName = sendEmailOnly ? "submitFoiRequestEmail" : "submitFoiRequest"; const url = this.baseUrl + `/fx/${functionName}`; From 96e50ea3889ce5d72063fe78430a0a511a0d5443 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Tue, 30 Jun 2026 14:34:01 -0700 Subject: [PATCH 05/24] Merge pull request #524 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Log removal --- api/apiCustomFunctions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index ed39b0e7..9d6426fa 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -17,7 +17,6 @@ const maxAttachBytes = MAX_ATTACH_MB * 1024 * 1024; const submitFoiRequest = async (server, req, res, next) => { console.count("COUNT: submitFoiRequest API CALL"); - console.log("submitFoiRequest API DATA", req); console.trace('TRACE: submitFoiRequest API CALL'); const apiUrl = `${foiRequestAPIBackend}/foirawrequests`; From 841997e486805068f7894c05f5e0ab3bad1a72ac Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Tue, 30 Jun 2026 15:32:03 -0700 Subject: [PATCH 06/24] Merge pull request #525 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Log Adjustment for SubmitFOIRequest --- api/apiCustomFunctions.js | 8 ++++++-- .../payment-complete/payment-complete.component.ts | 8 ++++++-- .../review-submit/review-submit.component.ts | 10 +++++++--- web/src/app/services/data.service.ts | 8 ++++++-- web/src/app/transom-api-client.service.ts | 8 ++++++-- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index 9d6426fa..49057f5b 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -16,8 +16,12 @@ const MAX_ATTACH_MB = 5; const maxAttachBytes = MAX_ATTACH_MB * 1024 * 1024; const submitFoiRequest = async (server, req, res, next) => { - console.count("COUNT: submitFoiRequest API CALL"); - console.trace('TRACE: submitFoiRequest API CALL'); + console.log( + '[API]', + new Date().toISOString(), + 'submitFoiRequest API called' + ); + console.trace('submitFoiRequest API'); const apiUrl = `${foiRequestAPIBackend}/foirawrequests`; diff --git a/web/src/app/route-components/payment-complete/payment-complete.component.ts b/web/src/app/route-components/payment-complete/payment-complete.component.ts index 264691e7..d28fcdc3 100644 --- a/web/src/app/route-components/payment-complete/payment-complete.component.ts +++ b/web/src/app/route-components/payment-complete/payment-complete.component.ts @@ -110,8 +110,12 @@ export class PaymentCompleteComponent implements OnInit { } submitEmail() { - console.count('COUNT: SUBMIT PAYMENT EMAIL COMPONENT CALLED'); - console.trace('TRACE: SUBMIT PAYMENT EMAIL COMPONENT'); + console.log( + '[PAYMENT-COMPLETED COMPONENT]', + new Date().toISOString(), + 'Payment completed request submitted' + ); + console.trace('PAYMENT EMAIL COMPONENT'); this.dataService.submitRequest(this.authToken, null, this.foiRequest, true).subscribe( (result) => { if (!result.EmailSuccess) { diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index d6f9b5fa..4c832448 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -61,12 +61,16 @@ export class ReviewSubmitComponent implements OnInit { } doContinue() { - console.count("COUNT: REQUEST SUBMIT COMPONENT CALLED"); + console.log( + '[REVIEW-SUBMIT COMPONENT]', + new Date().toISOString(), + 'Submitting request' + ); if (this.isBusy) { - console.trace('TRACE: REQUEST SUBMIT COMPONENT BLOCKED'); + console.trace('REQUEST-SUBMIT COMPONENT BLOCKED'); return; } - console.trace('TRACE: REQUEST SUBMIT COMPONENT ALLOWED'); + console.trace('REQUEST-SUBMIT COMPONENT ALLOWED'); this.isBusy = true; this.dataService.submitRequest(this.authToken, this.captchaNonce, this.foiRequest).subscribe( diff --git a/web/src/app/services/data.service.ts b/web/src/app/services/data.service.ts index f4872c7b..3402c9f8 100644 --- a/web/src/app/services/data.service.ts +++ b/web/src/app/services/data.service.ts @@ -301,8 +301,12 @@ export class DataService { * @param foiRequest - A structure containing the complete request */ submitRequest(authToken: string, nonce: string, foiRequest: FoiRequest, sendEmailOnly?: boolean): Observable { - console.count('COUNT: submitRequest FUNCTION'); - console.trace('TRACE: submitRequest FUNCTION'); + console.log( + '[DATA SERVICE]', + new Date().toISOString(), + 'submitRequest called' + ); + console.trace('submitRequest FUNCTION'); this.apiClient.setHeader("Authorization", "Bearer " + authToken); if (nonce) { diff --git a/web/src/app/transom-api-client.service.ts b/web/src/app/transom-api-client.service.ts index a68001f2..4e7b92c1 100644 --- a/web/src/app/transom-api-client.service.ts +++ b/web/src/app/transom-api-client.service.ts @@ -94,8 +94,12 @@ export class TransomApiClientService { * @param body The body to post to the request. */ postFoiRequest(foiRequest: FoiRequest, sendEmailOnly?: boolean): Observable { - console.count('COUNT: postFoiRequest FUNCTION'); - console.trace('TRACE: postFoiRequest FUNCTION'); + console.log( + '[TRANSOM LAYER]', + new Date().toISOString(), + 'postFoiRequest called' + ); + console.trace('postFoiRequest FUNCTION'); const functionName = sendEmailOnly ? "submitFoiRequestEmail" : "submitFoiRequest"; const url = this.baseUrl + `/fx/${functionName}`; From d0f0c5e037b9b6327555cc21028525cae2712d16 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Thu, 2 Jul 2026 09:28:09 -0700 Subject: [PATCH 07/24] Merge pull request #526 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Adjustments to state clear logic --- .../route-components/review-submit/review-submit.component.ts | 3 ++- web/src/app/services/data.service.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 4c832448..5a58dd8b 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -82,8 +82,9 @@ export class ReviewSubmitComponent implements OnInit { // this.isBusy = false; // If the user is authenticated, logout the user if (this.keycloakService.isAuthenticated()) { + const requestType = this.foiRequest.requestData.requestType; // Clear request state so a duplicate submission cannot resubmit the same data - this.dataService.clearState(); + this.dataService.clearState({requestType: requestType}); this.keycloakService.logout(); } else { this.base.goFoiForward(); diff --git a/web/src/app/services/data.service.ts b/web/src/app/services/data.service.ts index 3402c9f8..ffab93e9 100644 --- a/web/src/app/services/data.service.ts +++ b/web/src/app/services/data.service.ts @@ -143,8 +143,8 @@ export class DataService { return foi; } - clearState() { - const blankState: FoiRequest = { requestData: {} }; + clearState(newData = {}) { + const blankState: FoiRequest = { requestData: newData }; this.setCurrentState(blankState); this.removeChildFileAttachment(); this.removePersonFileAttachment(); From f687be6b7485ad03d714757fe315a3ff84ea1d34 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Fri, 17 Jul 2026 11:50:54 -0700 Subject: [PATCH 08/24] Merge pull request #530 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Adjustments to submission of request --- api/apiCustomFunctions.js | 19 ++++++++++++++++-- .../payment-complete.component.ts | 4 +++- .../review-submit/review-submit.component.ts | 20 +++++++++++++++++-- web/src/app/transom-api-client.service.ts | 3 ++- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index 49057f5b..e4d316e3 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -54,9 +54,9 @@ const submitFoiRequest = async (server, req, res, next) => { console.log("calling RAW FOI Request"); const response = await requestAPI.invokeRequestAPI(JSON.stringify(data.params), apiUrl); - console.log(`API response = ${response.status}`); + console.log(`API response = DATA: ${response.data}, STATUS: ${response.status}`); - if(response.status === 200 && response.data.status ) { + if(response.status === 200 && response.data.status) { console.log(`response id: ${response.data.id}`); // if request needs payment, return earlier to prevent sending email as it will be sent after payment. if(needsPayment) { @@ -76,6 +76,15 @@ const submitFoiRequest = async (server, req, res, next) => { }); } + else if (response.status === 409) { + // Handle duplicate request + console.log(response.data.message); + res.send({ + EmailSuccess: false, + message: response.data.message, + pendingPayment: false + }); + } else { req.log.info('Failed:', response); const unavailable = new restifyErrors.ServiceUnavailableError('Service is unavailable.'); @@ -93,6 +102,12 @@ const submitFoiRequest = async (server, req, res, next) => { } const submitFoiRequestEmail = async (server, req, res, next) => { + console.log( + '[API]', + new Date().toISOString(), + 'submitFoiRequestEmail API called' + ); + console.trace('submitFoiRequestEmail API'); req.params.requestData = JSON.parse(req.params.requestData); diff --git a/web/src/app/route-components/payment-complete/payment-complete.component.ts b/web/src/app/route-components/payment-complete/payment-complete.component.ts index d28fcdc3..f73cc995 100644 --- a/web/src/app/route-components/payment-complete/payment-complete.component.ts +++ b/web/src/app/route-components/payment-complete/payment-complete.component.ts @@ -117,7 +117,9 @@ export class PaymentCompleteComponent implements OnInit { ); console.trace('PAYMENT EMAIL COMPONENT'); this.dataService.submitRequest(this.authToken, null, this.foiRequest, true).subscribe( - (result) => { + (response) => { + console.log("submitFoiRequest Response: ", response) + const result = response.body; if (!result.EmailSuccess) { alert("Temporarily unable to complete your request. Please contact us to complete your request."); } diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 5a58dd8b..185f3752 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -74,12 +74,28 @@ export class ReviewSubmitComponent implements OnInit { this.isBusy = true; this.dataService.submitRequest(this.authToken, this.captchaNonce, this.foiRequest).subscribe( - (result) => { + (response) => { + const result = response.body; + console.log("submitFoiRequest Response: ", response) + // Handle duplicate request + if (response.status === 409) { + alert( + `${result.message}.\n\n` + + "This request was already submitted.\n\n" + + "To prevent duplicates, you cannot resubmit the same request for 30 minutes.\n\n" + + "Go back to the homepage and start a new request." + ); + // this.captchaComponent.forceRefresh(); + // this.captchaComplete = false; + // this.isBusy = false; + return; + } + this.foiRequest.requestData.requestId = result.id; this.dataService.setCurrentState(this.foiRequest); this.dataService.saveAuthToken(this.authToken); - // this.isBusy = false; + // If the user is authenticated, logout the user if (this.keycloakService.isAuthenticated()) { const requestType = this.foiRequest.requestData.requestType; diff --git a/web/src/app/transom-api-client.service.ts b/web/src/app/transom-api-client.service.ts index 4e7b92c1..1e7af9c9 100644 --- a/web/src/app/transom-api-client.service.ts +++ b/web/src/app/transom-api-client.service.ts @@ -111,7 +111,8 @@ export class TransomApiClientService { } const obs = this.http.post(url, body, { - headers: this.headers + headers: this.headers, + observe: 'response' }); return this.handleResponse(obs); } From 5df8b4f5ce99c5d1961c5ee28b0871748d52440a Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Fri, 17 Jul 2026 13:14:22 -0700 Subject: [PATCH 09/24] Merge pull request #533 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Further Logging Adjustments --- api/apiCustomFunctions.js | 2 +- .../payment-complete/payment-complete.component.ts | 4 +--- .../review-submit/review-submit.component.ts | 7 +++---- web/src/app/transom-api-client.service.ts | 3 +-- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index e4d316e3..ebe724cb 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -54,7 +54,7 @@ const submitFoiRequest = async (server, req, res, next) => { console.log("calling RAW FOI Request"); const response = await requestAPI.invokeRequestAPI(JSON.stringify(data.params), apiUrl); - console.log(`API response = DATA: ${response.data}, STATUS: ${response.status}`); + console.log(`API response = DATA: ${JSON.stringify(response.data)}, STATUS: ${response.status}`); if(response.status === 200 && response.data.status) { console.log(`response id: ${response.data.id}`); diff --git a/web/src/app/route-components/payment-complete/payment-complete.component.ts b/web/src/app/route-components/payment-complete/payment-complete.component.ts index f73cc995..d28fcdc3 100644 --- a/web/src/app/route-components/payment-complete/payment-complete.component.ts +++ b/web/src/app/route-components/payment-complete/payment-complete.component.ts @@ -117,9 +117,7 @@ export class PaymentCompleteComponent implements OnInit { ); console.trace('PAYMENT EMAIL COMPONENT'); this.dataService.submitRequest(this.authToken, null, this.foiRequest, true).subscribe( - (response) => { - console.log("submitFoiRequest Response: ", response) - const result = response.body; + (result) => { if (!result.EmailSuccess) { alert("Temporarily unable to complete your request. Please contact us to complete your request."); } diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 185f3752..3efbdd03 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -74,11 +74,10 @@ export class ReviewSubmitComponent implements OnInit { this.isBusy = true; this.dataService.submitRequest(this.authToken, this.captchaNonce, this.foiRequest).subscribe( - (response) => { - const result = response.body; - console.log("submitFoiRequest Response: ", response) + (result) => { + console.log("submitFoiRequest Response: ", result) // Handle duplicate request - if (response.status === 409) { + if (!result.status) { alert( `${result.message}.\n\n` + "This request was already submitted.\n\n" + diff --git a/web/src/app/transom-api-client.service.ts b/web/src/app/transom-api-client.service.ts index 1e7af9c9..4e7b92c1 100644 --- a/web/src/app/transom-api-client.service.ts +++ b/web/src/app/transom-api-client.service.ts @@ -111,8 +111,7 @@ export class TransomApiClientService { } const obs = this.http.post(url, body, { - headers: this.headers, - observe: 'response' + headers: this.headers }); return this.handleResponse(obs); } From 87c59e9a820bf7c5b694b2c109f50dfb9da7c864 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Fri, 17 Jul 2026 13:57:33 -0700 Subject: [PATCH 10/24] Merge pull request #534 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Logging Adjustments --- .../payment-complete/payment-complete.component.ts | 4 +++- .../review-submit/review-submit.component.ts | 7 ++++--- web/src/app/transom-api-client.service.ts | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/web/src/app/route-components/payment-complete/payment-complete.component.ts b/web/src/app/route-components/payment-complete/payment-complete.component.ts index d28fcdc3..c1f0f86f 100644 --- a/web/src/app/route-components/payment-complete/payment-complete.component.ts +++ b/web/src/app/route-components/payment-complete/payment-complete.component.ts @@ -117,7 +117,9 @@ export class PaymentCompleteComponent implements OnInit { ); console.trace('PAYMENT EMAIL COMPONENT'); this.dataService.submitRequest(this.authToken, null, this.foiRequest, true).subscribe( - (result) => { + (response) => { + console.log("submitFoiRequest Response: ", response); + const result = response.body; if (!result.EmailSuccess) { alert("Temporarily unable to complete your request. Please contact us to complete your request."); } diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 3efbdd03..787eebd3 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -74,10 +74,11 @@ export class ReviewSubmitComponent implements OnInit { this.isBusy = true; this.dataService.submitRequest(this.authToken, this.captchaNonce, this.foiRequest).subscribe( - (result) => { - console.log("submitFoiRequest Response: ", result) + (response) => { + const result = response.body; + console.log("submitFoiRequest Response: ", response); // Handle duplicate request - if (!result.status) { + if (response.status === 409) { alert( `${result.message}.\n\n` + "This request was already submitted.\n\n" + diff --git a/web/src/app/transom-api-client.service.ts b/web/src/app/transom-api-client.service.ts index 4e7b92c1..1e7af9c9 100644 --- a/web/src/app/transom-api-client.service.ts +++ b/web/src/app/transom-api-client.service.ts @@ -111,7 +111,8 @@ export class TransomApiClientService { } const obs = this.http.post(url, body, { - headers: this.headers + headers: this.headers, + observe: 'response' }); return this.handleResponse(obs); } From b729b172ac79459e3058d97cbe57d7cd05da7d6f Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Fri, 17 Jul 2026 16:54:21 -0700 Subject: [PATCH 11/24] Merge pull request #535 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Moved handling of duplicate requests to error block --- api/apiCustomFunctions.js | 19 ++++++------ .../review-submit/review-submit.component.ts | 31 ++++++++++--------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index ebe724cb..9bad0f86 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -75,23 +75,22 @@ const submitFoiRequest = async (server, req, res, next) => { pendingPayment: false }); - } - else if (response.status === 409) { + } else { + req.log.info('Failed:', response); + const unavailable = new restifyErrors.ServiceUnavailableError('Service is unavailable.'); + return next(unavailable); + } + } + catch(error){ + if (error.response.status === 409) { // Handle duplicate request - console.log(response.data.message); + console.log(error.response.data.message); res.send({ EmailSuccess: false, message: response.data.message, pendingPayment: false }); } - else { - req.log.info('Failed:', response); - const unavailable = new restifyErrors.ServiceUnavailableError('Service is unavailable.'); - return next(unavailable); - } - } - catch(error){ console.log(`${error}`); console.log("FOI API STATUS:", error.response.status); console.log("FOI API DATA:", error.response.data) diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 787eebd3..a22e640b 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -77,20 +77,7 @@ export class ReviewSubmitComponent implements OnInit { (response) => { const result = response.body; console.log("submitFoiRequest Response: ", response); - // Handle duplicate request - if (response.status === 409) { - alert( - `${result.message}.\n\n` + - "This request was already submitted.\n\n" + - "To prevent duplicates, you cannot resubmit the same request for 30 minutes.\n\n" + - "Go back to the homepage and start a new request." - ); - // this.captchaComponent.forceRefresh(); - // this.captchaComplete = false; - // this.isBusy = false; - return; - } - + this.foiRequest.requestData.requestId = result.id; this.dataService.setCurrentState(this.foiRequest); this.dataService.saveAuthToken(this.authToken); @@ -107,8 +94,22 @@ export class ReviewSubmitComponent implements OnInit { } }, (error) => { - this.isBusy = false; + console.log("VERIFY", error) + // Handle duplicate request + if (error.status === 409) { + alert( + `${error.message}.\n\n` + + "This request was already submitted.\n\n" + + "To prevent duplicates, you cannot resubmit the same request for 30 minutes.\n\n" + + "Go back to the homepage and start a new request." + ); + // this.captchaComponent.forceRefresh(); + // this.captchaComplete = false; + // this.isBusy = false; + return; + } + this.isBusy = false; alert("Temporarily unable to submit your request. Please try again in a few minutes."); this.captchaComponent.forceRefresh(); this.captchaComplete = false; From 08c9b47cfbfad05df2ca0a799218ec695646266e Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Fri, 17 Jul 2026 17:22:36 -0700 Subject: [PATCH 12/24] Merge pull request #536 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Bug Fix for duplicate handling in error block --- api/apiCustomFunctions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index 9bad0f86..1a7e5148 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -84,12 +84,12 @@ const submitFoiRequest = async (server, req, res, next) => { catch(error){ if (error.response.status === 409) { // Handle duplicate request - console.log(error.response.data.message); res.send({ EmailSuccess: false, - message: response.data.message, + message: error.response.data.message, pendingPayment: false }); + return; } console.log(`${error}`); console.log("FOI API STATUS:", error.response.status); From c6562cd03690228f54635a4a9085de1ef99f4341 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Fri, 17 Jul 2026 17:48:39 -0700 Subject: [PATCH 13/24] Merge pull request #537 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Bug Fix for duplicate handling in error block (Part 2) --- api/apiCustomFunctions.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index 1a7e5148..e1f7c387 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -81,22 +81,19 @@ const submitFoiRequest = async (server, req, res, next) => { return next(unavailable); } } - catch(error){ + catch(error) { + console.log(`${error}`); + console.log("FOI API STATUS:", error.response.status); + console.log("FOI API DATA:", error.response.data); + req.log.info('Failed:', error); + if (error.response.status === 409) { // Handle duplicate request - res.send({ - EmailSuccess: false, - message: error.response.data.message, - pendingPayment: false - }); - return; + const unavailable = new restifyErrors.ConflictError(error.response.data.message); + } else { + const unavailable = new restifyErrors.ServiceUnavailableError(error.message || 'Service is unavailable.'); } - console.log(`${error}`); - console.log("FOI API STATUS:", error.response.status); - console.log("FOI API DATA:", error.response.data) - req.log.info('Failed:', error); - const unavailable = new restifyErrors.ServiceUnavailableError(error.message || 'Service is unavailable.'); - return next(unavailable); + return next(unavailable); } } From c34218bccaa67576fbae3a09217c1ed6be9eed38 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Fri, 17 Jul 2026 17:56:29 -0700 Subject: [PATCH 14/24] Merge pull request #538 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Bug Fix duplicate handling in error block (part 3) --- api/apiCustomFunctions.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/apiCustomFunctions.js b/api/apiCustomFunctions.js index e1f7c387..f9f910af 100644 --- a/api/apiCustomFunctions.js +++ b/api/apiCustomFunctions.js @@ -86,12 +86,12 @@ const submitFoiRequest = async (server, req, res, next) => { console.log("FOI API STATUS:", error.response.status); console.log("FOI API DATA:", error.response.data); req.log.info('Failed:', error); - + let unavailable = ""; if (error.response.status === 409) { // Handle duplicate request - const unavailable = new restifyErrors.ConflictError(error.response.data.message); + unavailable = new restifyErrors.ConflictError(error.response.data.message); } else { - const unavailable = new restifyErrors.ServiceUnavailableError(error.message || 'Service is unavailable.'); + unavailable = new restifyErrors.ServiceUnavailableError(error.message || 'Service is unavailable.'); } return next(unavailable); } From 438011281a2577b8fee036cf0938035a6b1b2966 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Tue, 21 Jul 2026 12:07:07 -0700 Subject: [PATCH 15/24] Merge pull request #539 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Send user to homepage and clear state on duplicate request data --- .../review-submit/review-submit.component.ts | 12 +++++------- web/src/app/utils-components/base/base.component.ts | 4 ++++ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index a22e640b..6aafd3d2 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -98,14 +98,12 @@ export class ReviewSubmitComponent implements OnInit { // Handle duplicate request if (error.status === 409) { alert( - `${error.message}.\n\n` + - "This request was already submitted.\n\n" + - "To prevent duplicates, you cannot resubmit the same request for 30 minutes.\n\n" + - "Go back to the homepage and start a new request." + "Duplicate request identified. Request was not processed.\n\n" + + "This request has already been submitted. To prevent duplicates, you cannot resubmit the same request for 30 minutes.\n\n" + + "Go back to the homepage and start a new request to try again." ); - // this.captchaComponent.forceRefresh(); - // this.captchaComplete = false; - // this.isBusy = false; + this.dataService.clearState(); + this.base.goHome(); return; } diff --git a/web/src/app/utils-components/base/base.component.ts b/web/src/app/utils-components/base/base.component.ts index 9bd8e130..03625be4 100644 --- a/web/src/app/utils-components/base/base.component.ts +++ b/web/src/app/utils-components/base/base.component.ts @@ -74,6 +74,10 @@ export class BaseComponent implements OnInit { // } } + goHome() { + this.router.navigate(['']) + } + goSkipForward() { this.foiRouter.progress({ direction: 2 }); } From 0870770e684c5223bbc39c228ace352a8a4cfe80 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Wed, 22 Jul 2026 16:44:41 -0700 Subject: [PATCH 16/24] Merge pull request #540 from bcgov/dev-AH-FOIMOD-2470 Ticket 2470: Keycloak Logout changes + added logout on duplicate request if KC user exists --- .../review-submit/review-submit.component.ts | 18 ++++++++++-------- web/src/app/services/keycloak.service.ts | 11 ++++++----- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 6aafd3d2..4acd3755 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -85,16 +85,13 @@ export class ReviewSubmitComponent implements OnInit { // If the user is authenticated, logout the user if (this.keycloakService.isAuthenticated()) { - const requestType = this.foiRequest.requestData.requestType; - // Clear request state so a duplicate submission cannot resubmit the same data - this.dataService.clearState({requestType: requestType}); - this.keycloakService.logout(); + this.dataService.clearState() + this.keycloakService.logout(window.location.origin + '/personal/submit-complete'); } else { this.base.goFoiForward(); } }, (error) => { - console.log("VERIFY", error) // Handle duplicate request if (error.status === 409) { alert( @@ -103,10 +100,15 @@ export class ReviewSubmitComponent implements OnInit { "Go back to the homepage and start a new request to try again." ); this.dataService.clearState(); - this.base.goHome(); - return; + if (this.keycloakService.isAuthenticated()) { + this.keycloakService.logout(window.location.origin); + return; + } else { + this.base.goHome(); + return; + } } - + this.isBusy = false; alert("Temporarily unable to submit your request. Please try again in a few minutes."); this.captchaComponent.forceRefresh(); diff --git a/web/src/app/services/keycloak.service.ts b/web/src/app/services/keycloak.service.ts index 274c4929..dde1b0be 100644 --- a/web/src/app/services/keycloak.service.ts +++ b/web/src/app/services/keycloak.service.ts @@ -121,24 +121,25 @@ export class KeycloakService { return token !== undefined && token.sub !== undefined; } - logout() { + logout(redirectUrl: string) { if (this.isAuthenticated()) { if (!this.keycloakAuth) { this.keycloakAuth = new Keycloak(this.keycloakConfig); this.keycloakAuth.init({token: sessionStorage.getItem('KC_TOKEN'), onLoad: 'check-sso'}) .then(authenticated => { if (authenticated) { - this.logoutUser(); + this.logoutUser(redirectUrl); } }); } else { - this.logoutUser(); + this.logoutUser(redirectUrl); } } } - logoutUser() { - const redirectUrl = window.location.origin + '/general/submit-complete'; + logoutUser(redirectUrl: string) { + sessionStorage.removeItem('KC_TOKEN'); + sessionStorage.removeItem('KC_REFRESH'); this.keycloakAuth.logout({ redirectUri: redirectUrl }); } } From e0759b55ed7a5f0916ce6b6003b80abc7163831f Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Thu, 23 Jul 2026 11:52:19 -0700 Subject: [PATCH 17/24] removal of log from payment-completed component --- .../payment-complete/payment-complete.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/app/route-components/payment-complete/payment-complete.component.ts b/web/src/app/route-components/payment-complete/payment-complete.component.ts index c1f0f86f..07a014c7 100644 --- a/web/src/app/route-components/payment-complete/payment-complete.component.ts +++ b/web/src/app/route-components/payment-complete/payment-complete.component.ts @@ -118,7 +118,6 @@ export class PaymentCompleteComponent implements OnInit { console.trace('PAYMENT EMAIL COMPONENT'); this.dataService.submitRequest(this.authToken, null, this.foiRequest, true).subscribe( (response) => { - console.log("submitFoiRequest Response: ", response); const result = response.body; if (!result.EmailSuccess) { alert("Temporarily unable to complete your request. Please contact us to complete your request."); From 0494fd07f7c0b32c5d5f8ebe6d503b3cbdd61598 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Thu, 23 Jul 2026 11:53:22 -0700 Subject: [PATCH 18/24] Remove debug log from request-submit component Removed console log for submitFoiRequest response. --- .../route-components/review-submit/review-submit.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index 4acd3755..e9f6d397 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -76,7 +76,6 @@ export class ReviewSubmitComponent implements OnInit { this.dataService.submitRequest(this.authToken, this.captchaNonce, this.foiRequest).subscribe( (response) => { const result = response.body; - console.log("submitFoiRequest Response: ", response); this.foiRequest.requestData.requestId = result.id; this.dataService.setCurrentState(this.foiRequest); From 969b7641e295f5d79ca3e0b3fbbeb472612fa7e8 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Mon, 27 Jul 2026 12:53:23 -0700 Subject: [PATCH 19/24] Merge pull request #542 from bcgov/dev-AH-FOIMOD-2470-qafixes Release 25.13 (Ticket 2470): QA FIXES --- .../review-submit-complete/review-submit-complete.component.ts | 3 ++- web/src/app/services/keycloak.service.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts b/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts index 10eab5f3..b27c946a 100644 --- a/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts +++ b/web/src/app/route-components/review-submit-complete/review-submit-complete.component.ts @@ -31,7 +31,8 @@ export class ReviewSubmitCompleteComponent implements OnInit { } submitAnotherRequest() { - this.router.navigate(["getting-started2"]); + // this.router.navigate(["getting-started2"]); + window.location.href = '/'; return false; } } diff --git a/web/src/app/services/keycloak.service.ts b/web/src/app/services/keycloak.service.ts index dde1b0be..c6e30898 100644 --- a/web/src/app/services/keycloak.service.ts +++ b/web/src/app/services/keycloak.service.ts @@ -139,7 +139,7 @@ export class KeycloakService { logoutUser(redirectUrl: string) { sessionStorage.removeItem('KC_TOKEN'); - sessionStorage.removeItem('KC_REFRESH'); + // sessionStorage.removeItem('KC_REFRESH'); this.keycloakAuth.logout({ redirectUri: redirectUrl }); } } From 78fe7e857fea4d4ae28ce99aaa16fc66f03d54bf Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Mon, 27 Jul 2026 13:00:43 -0700 Subject: [PATCH 20/24] Merge pull request #543 from bcgov/dev-AH-FOIMOD-2470-qafixes Ticket 2470: Removal of logout sessions storage changes --- web/src/app/services/keycloak.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/services/keycloak.service.ts b/web/src/app/services/keycloak.service.ts index c6e30898..ff1873aa 100644 --- a/web/src/app/services/keycloak.service.ts +++ b/web/src/app/services/keycloak.service.ts @@ -138,7 +138,7 @@ export class KeycloakService { } logoutUser(redirectUrl: string) { - sessionStorage.removeItem('KC_TOKEN'); + // sessionStorage.removeItem('KC_TOKEN'); // sessionStorage.removeItem('KC_REFRESH'); this.keycloakAuth.logout({ redirectUri: redirectUrl }); } From f5a3409de973637012e19a2498a2c1b2c2a708d6 Mon Sep 17 00:00:00 2001 From: Aman Hundal Date: Tue, 28 Jul 2026 10:38:06 -0700 Subject: [PATCH 21/24] Merge pull request #544 from bcgov/dev-AH-FOIMOD-2470-qafixes Ticket 2470: Added back removed logout code --- web/src/app/services/keycloak.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/app/services/keycloak.service.ts b/web/src/app/services/keycloak.service.ts index ff1873aa..dde1b0be 100644 --- a/web/src/app/services/keycloak.service.ts +++ b/web/src/app/services/keycloak.service.ts @@ -138,8 +138,8 @@ export class KeycloakService { } logoutUser(redirectUrl: string) { - // sessionStorage.removeItem('KC_TOKEN'); - // sessionStorage.removeItem('KC_REFRESH'); + sessionStorage.removeItem('KC_TOKEN'); + sessionStorage.removeItem('KC_REFRESH'); this.keycloakAuth.logout({ redirectUri: redirectUrl }); } } From c66a10c0b092b11bcec771735ca7798e6c348013 Mon Sep 17 00:00:00 2001 From: Hundal Date: Tue, 28 Jul 2026 13:19:44 -0700 Subject: [PATCH 22/24] adjusted redirect url for logout on duplicate request handling --- .../route-components/review-submit/review-submit.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/route-components/review-submit/review-submit.component.ts b/web/src/app/route-components/review-submit/review-submit.component.ts index e9f6d397..9b9a0687 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.ts +++ b/web/src/app/route-components/review-submit/review-submit.component.ts @@ -100,7 +100,7 @@ export class ReviewSubmitComponent implements OnInit { ); this.dataService.clearState(); if (this.keycloakService.isAuthenticated()) { - this.keycloakService.logout(window.location.origin); + this.keycloakService.logout(window.location.origin + '/'); return; } else { this.base.goHome(); From 9f8d32f4d8a3fbdc5f142b280bbf92d2cac6bb56 Mon Sep 17 00:00:00 2001 From: Adam Kroon Date: Tue, 7 Jul 2026 10:39:59 -0700 Subject: [PATCH 23/24] Merge pull request #528 from bcgov/adamkroonbcgov-patch-3 Update Public Form API to Node 22 for CGroups Compliance --- api/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index 7d855f7a..c0ca06c4 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,5 +1,5 @@ # Use the official Node.js image from the Docker Hub -FROM node:20-alpine AS build +FROM node:22-alpine AS build # Set the working directory inside the container WORKDIR /app @@ -19,4 +19,4 @@ RUN chgrp -R 0 /app && \ USER node -ENTRYPOINT ["./container-entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["./container-entrypoint.sh"] From 72f1e3da56192b849b08f45f986a59c4098aa75d Mon Sep 17 00:00:00 2001 From: Nijjar Date: Wed, 29 Jul 2026 23:50:52 -0700 Subject: [PATCH 24/24] fix(5183): restore captcha audio playback --- api/apiCaptcha.js | 164 +++++++----------- .../review-submit.component.html | 2 +- .../captcha/captcha.component.ts | 17 +- 3 files changed, 80 insertions(+), 103 deletions(-) diff --git a/api/apiCaptcha.js b/api/apiCaptcha.js index e11997dc..b0b390ba 100644 --- a/api/apiCaptcha.js +++ b/api/apiCaptcha.js @@ -3,11 +3,8 @@ var svgCaptcha = require('svg-captcha'); var jwt = require('jsonwebtoken'); -var wav = require('wav'); var text2wav = require('text2wav'); var arrayBufferToBuffer = require('arraybuffer-to-buffer'); -var streamifier = require('streamifier'); -var lame = require('lamejs'); function captchaInit(options) { @@ -33,7 +30,7 @@ function captchaInit(options) { return decrypted; } - function createCaptcha(nonce){ + function createCaptcha(nonce) { var captcha = svgCaptcha.create({ size: 6, // size of random string ignoreChars: '0Oo1iIl', // filter out some characters like 0o1i @@ -92,105 +89,78 @@ function captchaInit(options) { } function getAudio(body, req) { - return new Promise(function(resolve, reject) { - try { - // pull out encrypted answer - var validation = body.validation; - - // decrypt payload to get captcha text - var decryptedBody = decrypt(validation); - - // Insert leading text and commas to slow down reader - var captchaCharArray = decryptedBody.answer.toString().split(''); - let language = 'en'; - if (body.translation) { - if (typeof body.translation == 'string') { - if (voicePromptLanguageMap.hasOwnProperty(body.translation)) { - language = body.translation; + return new Promise(function (resolve, reject) { + try { + // pull out encrypted answer + var validation = body.validation; + + // decrypt payload to get captcha text + var decryptedBody = decrypt(validation); + + // Insert leading text and commas to slow down reader + var captchaCharArray = decryptedBody.answer.toString().split(''); + let language = 'en'; + if (body.translation) { + if (typeof body.translation == 'string') { + if (voicePromptLanguageMap.hasOwnProperty(body.translation)) { + language = body.translation; + } + } else if ( + body.translation === true && + req && + req.headers['accept-language'] + ) { + let lang = req.headers['accept-language'] + .split(',') + .map(e => e.split(';')[0].split('-')[0]) + .find(e => voicePromptLanguageMap.hasOwnProperty(e)); + if (lang) { + language = lang; + } } - } else if ( - body.translation === true && - req && - req.headers['accept-language'] + } + var spokenCatpcha = voicePromptLanguageMap[language] + ': '; + for (var i = 0; i < captchaCharArray.length; i++) { + spokenCatpcha += captchaCharArray[i] + ', '; + } + getMp3DataUriFromText(spokenCatpcha, language).then(function ( + audioDataUri ) { - let lang = req.headers['accept-language'] - .split(',') - .map(e => e.split(';')[0].split('-')[0]) - .find(e => voicePromptLanguageMap.hasOwnProperty(e)); - if (lang) { - language = lang; + if (!audioDataUri) { + resolve({ + error: 'unknown' + }); + return; } - } - } - var spokenCatpcha = voicePromptLanguageMap[language] + ': '; - for (var i = 0; i < captchaCharArray.length; i++) { - spokenCatpcha += captchaCharArray[i] + ', '; - } - getMp3DataUriFromText(spokenCatpcha, language).then(function( - audioDataUri - ) { - // Now pass back the full payload , + + // Now pass back the full payload. + resolve({ + audio: audioDataUri + }); + }); + } catch (e) { + console.log('Error getting audio:', e); resolve({ - audio: audioDataUri + error: 'unknown' }); - }); - } catch (e) { - console.log('Error getting audio:', e); - resolve({ - error: 'unknown' - }); - } - }); + } + }); } - //////////////////////////////////////////////////////// /* * Audio routines */ //////////////////////////////////////////////////////// function getMp3DataUriFromText(text, language = 'en') { - return new Promise(async function(resolve) { - // init wave reader, used to convert WAV to PCM - var reader = new wav.Reader(); - - // we have to wait for the "format" event before we can start encoding - reader.on('format', function(format) { - // init encoder - var encoder = new lame.Mp3Encoder(format); - - // Pipe Wav reader to the encoder and capture the output stream - // As the stream is encoded, convert the mp3 array buffer chunks into base64 string with mime type - var dataUri = 'data:audio/mp3;base64,'; - encoder.on('data', function(arrayBuffer) { - if (!dataUri) { - return; - } - dataUri += arrayBuffer.toString('base64'); - // by observation encoder hung before finish due to event loop being empty - // setTimeout injects an event to mitigate the issue - setTimeout(() => {}, 0); - }); - - // When encoding is complete, callback with data uri - encoder.on('finish', function() { - resolve(dataUri); - dataUri = undefined; - }); - reader.pipe(encoder); - }); - - // Generate audio, Base64 encoded WAV in DataUri format including mime type header - text2wav(text, { voice: language }).then(function(audioArrayBuffer) { - // convert to buffer + return text2wav(text, { voice: language }) + .then(function (audioArrayBuffer) { var audioBuffer = arrayBufferToBuffer(audioArrayBuffer); - - // Convert ArrayBuffer to Streamable type for input to the encoder - var audioStream = streamifier.createReadStream(audioBuffer); - - // once all events setup we can the pipeline - audioStream.pipe(reader); + return 'data:audio/wav;base64,' + audioBuffer.toString('base64'); + }) + .catch(function (error) { + console.log('Error generating audio CAPTCHA:', error); + return null; }); - }); } function verifyJWTResponse(token, nonce) { @@ -208,29 +178,29 @@ function captchaInit(options) { } return { - getCaptcha: function(req, res, next) { + getCaptcha: function (req, res, next) { var fullReponse = createCaptcha(req.body.nonce); res.send(fullReponse); next(); }, - verifyCaptcha: function(req, res, next) { + verifyCaptcha: function (req, res, next) { var ret = verifyCaptchaInternal(req.body); res.send(ret); next(); }, - getCaptchaAudio: function(req, res, next) { - getAudio(req.body, req).then(function(ret) { + getCaptchaAudio: function (req, res, next) { + getAudio(req.body, req).then(function (ret) { res.send(ret); next(); }); }, - verifyJWTResponseMiddleware: function(req, res, next) { + verifyJWTResponseMiddleware: function (req, res, next) { // if there is a captcha header , it shud be validated by captcha verifier - if(req.isAuthorised && req.userDetails && !req.headers[CAPTCHA_NONCE_HEADER]) { + if (req.isAuthorised && req.userDetails && !req.headers[CAPTCHA_NONCE_HEADER]) { console.log('Request is already validated'); return next(); } - + var token = req.headers[CAPTCHA_TOKEN_HEADER.toLowerCase()] || ''; token = token.replace('Bearer ', ''); var nonce = req.headers[CAPTCHA_NONCE_HEADER]; @@ -239,7 +209,7 @@ function captchaInit(options) { console.log('Captcha validated in apiCaptcha'); next(); } else { - res. send(401, 'Not Authorized'); + res.send(401, 'Not Authorized'); next('Invalid Captcha Token'); } } diff --git a/web/src/app/route-components/review-submit/review-submit.component.html b/web/src/app/route-components/review-submit/review-submit.component.html index 789a8ada..d2a96e2b 100644 --- a/web/src/app/route-components/review-submit/review-submit.component.html +++ b/web/src/app/route-components/review-submit/review-submit.component.html @@ -26,7 +26,7 @@

Review your request

diff --git a/web/src/app/utils-components/captcha/captcha.component.ts b/web/src/app/utils-components/captcha/captcha.component.ts index 02380516..6503e817 100644 --- a/web/src/app/utils-components/captcha/captcha.component.ts +++ b/web/src/app/utils-components/captcha/captcha.component.ts @@ -21,7 +21,6 @@ import { CaptchaDataService } from "src/app/services/captcha-data.service"; export class CaptchaComponent implements AfterViewInit, OnInit { @ViewChild("image", { static: true }) imageContainer: ElementRef; @ViewChild("answer", { static: true }) userAnswerRef: ElementRef; - @ViewChild("audioElement", { static: true }) audioElement: ElementRef; @Input("apiBaseUrl") apiBaseUrl: string; @Input("nonce") nonce: string; @Output() onValidToken = new EventEmitter(); @@ -51,7 +50,7 @@ export class CaptchaComponent implements AfterViewInit, OnInit { public fetchingAudioInProgress = false; - constructor(private dataService: CaptchaDataService, private cd: ChangeDetectorRef, private ngZone: NgZone) {} + constructor(private dataService: CaptchaDataService, private cd: ChangeDetectorRef, private ngZone: NgZone) { } ngOnInit() { this.forceRefresh.bind(this); @@ -161,12 +160,19 @@ export class CaptchaComponent implements AfterViewInit, OnInit { public playAudio(playImmediately: boolean = true) { if (this.audio && this.audio.length > 0) { - this.audioElement.nativeElement.play(); + this.playAudioDataUri(); } else { this.fetchAudio(playImmediately); } } + private playAudioDataUri() { + const audio = new Audio(this.audio); + audio.play().catch((error) => { + console.log("Error playing audio CAPTCHA: %o", error); + }); + } + private fetchAudio(playImmediately: boolean = false) { if (!this.fetchingAudioInProgress) { this.fetchingAudioInProgress = true; @@ -175,8 +181,9 @@ export class CaptchaComponent implements AfterViewInit, OnInit { this.fetchingAudioInProgress = false; this.audio = response.body.audio; this.cd.detectChanges(); - if (playImmediately) { - this.audioElement.nativeElement.play(); + + if (playImmediately && this.audio && this.audio.length > 0) { + this.playAudioDataUri(); } }, (error) => {