Skip to content

Commit c8af861

Browse files
committed
Router gaurd and BE validation
1 parent 34922da commit c8af861

File tree

8 files changed

+208
-20
lines changed

8 files changed

+208
-20
lines changed

src/ECER.Clients.RegistryPortal/ECER.Clients.RegistryPortal.Server/Applications/ApplicationsEndpoints.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void Register(IEndpointRouteBuilder endpointRouteBuilder)
5757
{
5858
return TypedResults.NotFound();
5959
}
60-
if (!result.IsSuccess && result.Error == SubmissionError.DraftApplicationValidationFailed)
60+
if (!result.IsSuccess && result.Error == SubmissionError.DraftApplicationValidationFailed || result.Error == SubmissionError.SubmittedApplicationAlreadyExists)
6161
{
6262
var problemDetails = new ProblemDetails
6363
{
@@ -67,6 +67,7 @@ public void Register(IEndpointRouteBuilder endpointRouteBuilder)
6767
};
6868
return TypedResults.BadRequest(problemDetails);
6969
}
70+
7071
return TypedResults.Ok(new SubmitApplicationResponse(mapper.Map<Application>(result.Application)));
7172
})
7273
.WithOpenApi("Submit an application", string.Empty, "application_post")

src/ECER.Clients.RegistryPortal/ecer.clients.registryportal.client/src/components/pages/Dashboard.vue

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,7 @@ export default defineComponent({
307307
return (
308308
this.applicationStore.applicationStatus === undefined ||
309309
this.applicationStore.applicationStatus === "Draft" ||
310-
this.applicationStore.applicationStatus === "Submitted" ||
311-
this.applicationStore.applicationStatus === "Ready" ||
312-
this.applicationStore.applicationStatus === "InProgress" ||
313-
this.applicationStore.applicationStatus === "PendingQueue" ||
314-
this.applicationStore.applicationStatus === "Pending" ||
315-
this.applicationStore.applicationStatus === "Escalated"
310+
this.applicationStore.hasSubmittedApplication
316311
);
317312
},
318313
showTransferCard(): boolean {

src/ECER.Clients.RegistryPortal/ecer.clients.registryportal.client/src/router.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useCertificationStore } from "./store/certification";
77
import { useFormStore } from "./store/form";
88
import { useMessageStore } from "./store/message";
99
import { useWizardStore } from "./store/wizard";
10+
import { useConfigStore } from "./store/config";
1011

1112
const router = createRouter({
1213
history: createWebHistory(),
@@ -390,4 +391,13 @@ router.beforeEach((to, _, next) => {
390391
} else next();
391392
});
392393

394+
// Gaurd to prevent users from accessing /application if they have already submitted an application
395+
router.beforeEach((to, _, next) => {
396+
const applicationStore = useApplicationStore();
397+
if (to.path === "/application" && applicationStore.hasSubmittedApplication) {
398+
console.warn("User has already submitted an application, redirecting to home page.");
399+
next({ path: "/" });
400+
} else next();
401+
});
402+
393403
export default router;

src/ECER.Clients.RegistryPortal/ecer.clients.registryportal.client/src/store/application.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,17 @@ export const useApplicationStore = defineStore("application", {
7979
hasDraftApplication(state): boolean {
8080
return state.draftApplication.id !== undefined;
8181
},
82+
hasSubmittedApplication(state): boolean {
83+
return (
84+
state.application?.status === "Submitted" ||
85+
state.application?.status === "Ready" ||
86+
state.application?.status === "InProgress" ||
87+
state.application?.status === "PendingQueue" ||
88+
state.application?.status === "Pending" ||
89+
state.application?.status === "PendingPSPConsultationNeeded" ||
90+
state.application?.status === "Escalated"
91+
);
92+
},
8293
hasApplication(state): boolean {
8394
return state.application !== null;
8495
},
@@ -302,18 +313,14 @@ export const useApplicationStore = defineStore("application", {
302313

303314
// One year renewal explanation letter
304315
if (oneYearRenewalExplanationId && oneYearRenewalExplanationOtherId) {
305-
this.draftApplication.oneYearRenewalExplanationChoice =
306-
wizardStore.wizardData[wizardStore.wizardConfig.steps.oneYearRenewalExplanation.form.inputs.oneYearRenewalExplanation.id];
307-
this.draftApplication.renewalExplanationOther =
308-
wizardStore.wizardData[wizardStore.wizardConfig.steps.oneYearRenewalExplanation.form.inputs.renewalExplanationOther.id];
316+
this.draftApplication.oneYearRenewalExplanationChoice = wizardStore.wizardData[oneYearRenewalExplanationId];
317+
this.draftApplication.renewalExplanationOther = wizardStore.wizardData[oneYearRenewalExplanationOtherId];
309318
}
310319

311320
// Five year renewal explanation letter
312321
if (fiveYearRenewalExplanationId && fiveYearRenewalExplanationOtherId) {
313-
this.draftApplication.fiveYearRenewalExplanationChoice =
314-
wizardStore.wizardData[wizardStore.wizardConfig.steps.fiveYearRenewalExplanation.form.inputs.fiveYearRenewalExplanation.id];
315-
this.draftApplication.renewalExplanationOther =
316-
wizardStore.wizardData[wizardStore.wizardConfig.steps.fiveYearRenewalExplanation.form.inputs.renewalExplanationOther.id];
322+
this.draftApplication.fiveYearRenewalExplanationChoice = wizardStore.wizardData[fiveYearRenewalExplanationId];
323+
this.draftApplication.renewalExplanationOther = wizardStore.wizardData[fiveYearRenewalExplanationOtherId];
317324
}
318325

319326
// Character References step data

src/ECER.Clients.RegistryPortal/ecer.clients.registryportal.client/src/types/openapi.d.ts

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ declare namespace Components {
4242
clientAuthenticationMethods?: {
4343
[name: string]: OidcAuthenticationSettings;
4444
} | null;
45+
icraFeatureEnabled?: boolean;
4546
}
4647
export type ApplicationOrigin = "Manual" | "Oracle" | "Portal";
4748
export type ApplicationStatus =
@@ -58,6 +59,7 @@ declare namespace Components {
5859
| "Ready"
5960
| "InProgress"
6061
| "PendingQueue"
62+
| "PendingPSPConsultationNeeded"
6163
| "ReconsiderationDecision"
6264
| "AppealDecision";
6365
export type ApplicationStatusReasonDetail =
@@ -85,7 +87,7 @@ declare namespace Components {
8587
*/
8688
id?: string | null;
8789
}
88-
export type ApplicationTypes = "New" | "Renewal" | "LabourMobility";
90+
export type ApplicationTypes = "New" | "Renewal" | "LabourMobility" | "ICRA";
8991
/**
9092
* delete draft application response
9193
*/
@@ -114,6 +116,7 @@ declare namespace Components {
114116
hasOtherName?: boolean | null;
115117
}
116118
export type CertificatePDFGeneration = "No" | "Requested" | "Yes";
119+
export type CertificateStatus = "Valid" | "Expired";
117120
export type CertificateStatusCode = "Active" | "Cancelled" | "Expired" | "Inactive" | "Renewed" | "Reprinted" | "Suspended";
118121
export interface Certification {
119122
id?: string | null;
@@ -276,6 +279,7 @@ declare namespace Components {
276279
countryId?: string | null;
277280
countryName?: string | null;
278281
countryCode?: string | null;
282+
isICRA?: boolean;
279283
}
280284
export type CourseOutlineOptions = "UploadNow" | "RegistryAlreadyHas";
281285
export interface DefaultContent {
@@ -309,6 +313,9 @@ declare namespace Components {
309313
export interface DraftApplicationResponse {
310314
application?: Application;
311315
}
316+
export interface DraftICRAEligibilityResponse {
317+
eligibility?: ICRAEligibility;
318+
}
312319
export type EducationOrigin = "InsideBC" | "OutsideBC" | "OutsideofCanada";
313320
export type EducationRecognition = "Recognized" | "NotRecognized";
314321
export interface FileInfo {
@@ -349,6 +356,16 @@ declare namespace Components {
349356
[name: string]: string[];
350357
} | null;
351358
}
359+
export interface ICRAEligibility {
360+
id?: string | null;
361+
applicantId?: string | null;
362+
portalStage?: string | null;
363+
signedDate?: string | null; // date-time
364+
createdOn?: string | null; // date-time
365+
status?: ICRAStatus;
366+
internationalCertifications?: InternationalCertification[] | null;
367+
}
368+
export type ICRAStatus = "Active" | "Draft" | "Eligible" | "Inactive" | "Ineligible" | "InReview" | "ReadyforReview" | "Submitted";
352369
export interface IdentificationType {
353370
id?: string | null;
354371
name?: string | null;
@@ -363,6 +380,26 @@ declare namespace Components {
363380
size?: string | null;
364381
}
365382
export type InitiatedFrom = "Investigation" | "PortalUser" | "Registry";
383+
export interface InternationalCertification {
384+
id?: string | null;
385+
otherFirstName?: string | null;
386+
otherMiddleName?: string | null;
387+
otherLastName?: string | null;
388+
hasOtherName?: boolean;
389+
countryId?: string | null;
390+
nameOfRegulatoryAuthority?: string | null;
391+
emailOfRegulatoryAuthority?: string | null;
392+
phoneOfRegulatoryAuthority?: string | null;
393+
websiteOfRegulatoryAuthority?: string | null;
394+
onlineCertificateValidationToolOfRegulatoryAuthority?: string | null;
395+
certificateStatus?: CertificateStatus;
396+
certificateTitle?: string | null;
397+
issueDate?: string | null; // date-time
398+
expiryDate?: string | null; // date-time
399+
files?: FileInfo[] | null;
400+
deletedFiles?: string[] | null;
401+
newFiles?: string[] | null;
402+
}
366403
export type InviteType = "CharacterReference" | "WorkExperienceReference";
367404
export type LikertScale = "Yes" | "No";
368405
export interface OidcAuthenticationSettings {
@@ -506,6 +543,9 @@ declare namespace Components {
506543
export interface SaveDraftApplicationRequest {
507544
draftApplication?: DraftApplication;
508545
}
546+
export interface SaveDraftICRAEligibilityRequest {
547+
eligibility?: ICRAEligibility;
548+
}
509549
/**
510550
* Send Message Request
511551
*/
@@ -653,7 +693,9 @@ declare namespace Components {
653693
| "Rejected"
654694
| "Submitted"
655695
| "UnderReview"
656-
| "WaitingforResponse";
696+
| "WaitingforResponse"
697+
| "ICRAEligibilitySubmitted"
698+
| "EligibilityResponseSubmitted";
657699
export interface WorkExperienceReference {
658700
lastName?: string | null;
659701
emailAddress?: string | null;
@@ -991,6 +1033,36 @@ declare namespace Paths {
9911033
export interface $404 {}
9921034
}
9931035
}
1036+
namespace IcraGet {
1037+
namespace Parameters {
1038+
export type ByStatus = Components.Schemas.ICRAStatus[];
1039+
export type Id = string;
1040+
}
1041+
export interface PathParameters {
1042+
id?: Parameters.Id;
1043+
}
1044+
export interface QueryParameters {
1045+
byStatus?: Parameters.ByStatus;
1046+
}
1047+
namespace Responses {
1048+
export type $200 = Components.Schemas.ICRAEligibility[];
1049+
export type $400 = Components.Schemas.HttpValidationProblemDetails;
1050+
}
1051+
}
1052+
namespace IcraPut {
1053+
namespace Parameters {
1054+
export type Id = string;
1055+
}
1056+
export interface PathParameters {
1057+
id?: Parameters.Id;
1058+
}
1059+
export type RequestBody = Components.Schemas.SaveDraftICRAEligibilityRequest;
1060+
namespace Responses {
1061+
export type $200 = Components.Schemas.DraftICRAEligibilityResponse;
1062+
export type $400 = Components.Schemas.HttpValidationProblemDetails;
1063+
export interface $404 {}
1064+
}
1065+
}
9941066
namespace IdentificationTypesGet {
9951067
namespace Parameters {
9961068
export type ById = string;
@@ -1298,6 +1370,22 @@ export interface OperationMethods {
12981370
data?: Paths.ReferenceOptout.RequestBody,
12991371
config?: AxiosRequestConfig,
13001372
): OperationResponse<Paths.ReferenceOptout.Responses.$200>;
1373+
/**
1374+
* icra_get - Handles icra queries
1375+
*/
1376+
"icra_get"(
1377+
parameters?: Parameters<Paths.IcraGet.QueryParameters & Paths.IcraGet.PathParameters> | null,
1378+
data?: any,
1379+
config?: AxiosRequestConfig,
1380+
): OperationResponse<Paths.IcraGet.Responses.$200>;
1381+
/**
1382+
* icra_put - Save a draft icra eligibility for the current user
1383+
*/
1384+
"icra_put"(
1385+
parameters?: Parameters<Paths.IcraPut.PathParameters> | null,
1386+
data?: Paths.IcraPut.RequestBody,
1387+
config?: AxiosRequestConfig,
1388+
): OperationResponse<Paths.IcraPut.Responses.$200>;
13011389
/**
13021390
* files_certificate_get - Handles fetching certificate PDF's
13031391
*/
@@ -1649,6 +1737,24 @@ export interface PathsDictionary {
16491737
config?: AxiosRequestConfig,
16501738
): OperationResponse<Paths.ReferenceOptout.Responses.$200>;
16511739
};
1740+
["/api/icra/{id}"]: {
1741+
/**
1742+
* icra_put - Save a draft icra eligibility for the current user
1743+
*/
1744+
"put"(
1745+
parameters?: Parameters<Paths.IcraPut.PathParameters> | null,
1746+
data?: Paths.IcraPut.RequestBody,
1747+
config?: AxiosRequestConfig,
1748+
): OperationResponse<Paths.IcraPut.Responses.$200>;
1749+
/**
1750+
* icra_get - Handles icra queries
1751+
*/
1752+
"get"(
1753+
parameters?: Parameters<Paths.IcraGet.QueryParameters & Paths.IcraGet.PathParameters> | null,
1754+
data?: any,
1755+
config?: AxiosRequestConfig,
1756+
): OperationResponse<Paths.IcraGet.Responses.$200>;
1757+
};
16521758
["/api/files/certificate/{certificateId}"]: {
16531759
/**
16541760
* files_certificate_get - Handles fetching certificate PDF's

src/ECER.Managers.Registry.Contract/Applications/Contract.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ public enum OneYearRenewalexplanations
185185
public enum SubmissionError
186186
{
187187
DraftApplicationNotFound,
188-
DraftApplicationValidationFailed
188+
DraftApplicationValidationFailed,
189+
SubmittedApplicationAlreadyExists,
189190
}
190191

191192
public enum ApplicationStatus

src/ECER.Managers.Registry/ApplicationHandlers.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,31 @@ public async Task<ApplicationSubmissionResult> Handle(SubmitApplicationCommand r
139139
{
140140
ArgumentNullException.ThrowIfNull(request);
141141

142-
var applications = await applicationRepository.Query(new ApplicationQuery
142+
var draftApplications = await applicationRepository.Query(new ApplicationQuery
143143
{
144144
ById = request.applicationId,
145145
ByApplicantId = request.userId,
146146
ByStatus = [Resources.Documents.Applications.ApplicationStatus.Draft]
147147
}, cancellationToken);
148148

149-
var draftApplicationResults = new ApplicationsQueryResults(mapper.Map<IEnumerable<Contract.Applications.Application>>(applications)!);
149+
var submittedApplications = await applicationRepository.Query(new ApplicationQuery
150+
{
151+
ByApplicantId = request.userId,
152+
ByStatus = new List<Resources.Documents.Applications.ApplicationStatus>
153+
{
154+
Resources.Documents.Applications.ApplicationStatus.Submitted,
155+
Resources.Documents.Applications.ApplicationStatus.Ready,
156+
Resources.Documents.Applications.ApplicationStatus.Escalated,
157+
Resources.Documents.Applications.ApplicationStatus.Pending,
158+
Resources.Documents.Applications.ApplicationStatus.InProgress,
159+
Resources.Documents.Applications.ApplicationStatus.PendingPSPConsultationNeeded,
160+
Resources.Documents.Applications.ApplicationStatus.PendingQueue,
161+
}
162+
}, cancellationToken);
163+
164+
var draftApplicationResults = new ApplicationsQueryResults(mapper.Map<IEnumerable<Contract.Applications.Application>>(draftApplications)!);
165+
var submittedApplicationResults = new ApplicationsQueryResults(mapper.Map<IEnumerable<Contract.Applications.Application>>(submittedApplications)!);
166+
150167
if (!draftApplicationResults.Items.Any())
151168
{
152169
return new ApplicationSubmissionResult() { Application = null, Error = SubmissionError.DraftApplicationNotFound, ValidationErrors = new List<string>() { "draft application does not exist" } };
@@ -169,6 +186,12 @@ public async Task<ApplicationSubmissionResult> Handle(SubmitApplicationCommand r
169186
{
170187
return new ApplicationSubmissionResult() { Application = null, Error = SubmissionError.DraftApplicationNotFound, ValidationErrors = new List<string>() { "draft application does not exist" } };
171188
}
189+
190+
if (submittedApplicationResults.Items.Any())
191+
{
192+
return new ApplicationSubmissionResult() { Application = null, Error = SubmissionError.SubmittedApplicationAlreadyExists, ValidationErrors = new List<string>() { "submitted application already exists" } };
193+
}
194+
172195
return new ApplicationSubmissionResult() { Application = mapper.Map<IEnumerable<Contract.Applications.Application>>(freshApplications)!.FirstOrDefault() };
173196
}
174197

0 commit comments

Comments
 (0)