Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions api/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/**
* Category of the error - this is likely to match the service it came from
*/
export type ErrorTypes = "WEBHOOK" | "SES" | "NOTIFY" | "QUEUE" | "GENERIC";
export type ErrorTypes = "WEBHOOK" | "SES" | "NOTIFY" | "QUEUE" | "ORBIT" | "GENERIC";

/**
* Error code for the matching ErrorType.
Expand All @@ -26,13 +26,14 @@ type WebhookErrorCode = "VALIDATION";
type SESErrorCode = "MISSING_ANSWER" | "PROCESS_VALIDATION" | "UNKNOWN";
type NotifyErrorCode = "PROCESS_VALIDATION" | "UNKNOWN";
type QueueErrorCode = "SES_PROCESS_ERROR" | "NOTIFY_PROCESS_ERROR" | "NOTIFY_SEND_ERROR" | "SES_SEND_ERROR";
type OrbitErrorCode = "ORBIT_ERROR";

type GenericErrorCode = "UNKNOWN" | "RATE_LIMIT_EXCEEDED";

/**
* Union of all the different ErrorCode.
*/
export type ErrorCode = WebhookErrorCode | SESErrorCode | NotifyErrorCode | QueueErrorCode | GenericErrorCode;
export type ErrorCode = WebhookErrorCode | SESErrorCode | NotifyErrorCode | QueueErrorCode | OrbitErrorCode | GenericErrorCode;

/**
* {@ErrorRecord} uses `Record`, which means every key passed into the generic, must be implemented
Expand Down Expand Up @@ -68,17 +69,23 @@ const QUEUE: ErrorRecord<QueueErrorCode> = {
SES_SEND_ERROR: "unable to queue SES_SEND_ERROR",
};

const ORBIT: ErrorRecord<OrbitErrorCode> = {
ORBIT_ERROR: "orbit error from case mgmt",
};

type ErrorRecords = {
WEBHOOK: typeof WEBHOOK;
SES: typeof SES;
NOTIFY: typeof NOTIFY;
QUEUE: typeof QUEUE;
GENERIC: typeof GENERIC;
ORBIT: typeof ORBIT;
};
export const ERRORS: ErrorRecords = {
WEBHOOK,
SES,
NOTIFY,
QUEUE,
GENERIC,
ORBIT,
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ import { isFieldType } from "../../../../utils";
import { getPostForCertifyCopy } from "../../utils/getPost";
import { CertifyCopyProcessQueueData, PaymentData } from "../types";
import { PaymentViewModel } from "../utils/PaymentViewModel";
import { CaseService } from "../types";
import { reorderSectionsWithNewName } from "../utils/reorderSectionsWithNewName";
import { order, remap } from "./mappings";
import { createRemapper } from "../utils/createRemapper";
import { CertifyCopyProcessQueueDataInput } from "../types";
import { getPostEmailAddress } from "../../utils/getPostEmailAddress";
import { QueueService } from "../../QueueService";
import logger, { Logger } from "pino";
import { SESCaseService } from "../types/CaseService";

export class CertifyCopyCaseService implements CaseService {
export class CertifyCopyCaseService implements SESCaseService {
logger: Logger;
queueService: QueueService;
templates: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import * as handlebars from "handlebars";
import { isFieldType } from "../../../../utils";
import { ConsularLetterProcessQueueData, PaymentData } from "../types";
import { PaymentViewModel } from "../utils/PaymentViewModel";
import { CaseService } from "../types";
import { reorderSectionsWithNewName } from "../utils/reorderSectionsWithNewName";
import { order, remap } from "./mappings";
import { createRemapper } from "../utils/createRemapper";
import { ConsularLetterProcessQueueDataInput } from "../types";
import { getPostEmailAddress } from "../../utils/getPostEmailAddress";
import { QueueService } from "../../QueueService";
import logger, { Logger } from "pino";
import { SESCaseService } from "../types/CaseService";

export class ConsularLetterCaseService implements CaseService {
export class ConsularLetterCaseService implements SESCaseService {
logger: Logger;
queueService: QueueService;
templates: {
Expand Down
174 changes: 23 additions & 151 deletions api/src/middlewares/services/CaseService/marriage/MarriageCaseService.ts
Original file line number Diff line number Diff line change
@@ -1,165 +1,37 @@
import { FormField } from "../../../../types/FormField";
import marriageSubmissionTemplate from "./marriageSubmissionTemplate";
import { MarriageFormType } from "../../../../types/FormDataBody";
import { remappers } from "./remappers";
import { getAnswerOrThrow } from "../utils/getAnswerOrThrow";
import { reorderers } from "./reorderers";
import { getApplicationTypeName } from "../utils/getApplicationTypeName";
import { answersHashMap } from "../../helpers";
import config from "config";
import * as handlebars from "handlebars";
import { isFieldType } from "../../../../utils";
import { getPostForMarriage } from "../../utils/getPost";
import { MarriageProcessQueueData, PaymentData, CaseService } from "../types";
import { PaymentViewModel } from "../utils/PaymentViewModel";
import { MarriageProcessQueueDataInput } from "../types";
import { getPostEmailAddress } from "../../utils/getPostEmailAddress";
import logger, { Logger } from "pino";
import { QueueService } from "../../QueueService";
import { AnswersHashMap } from "../../../../types/AnswersHashMap";
import { MarriageTemplateType } from "../../utils/types";
import { OrbitCaseService } from "../types/CaseService";
import { ApplicationError } from "../../../../ApplicationError";

export class MarriageCaseService implements CaseService {
export class MarriageCaseService implements OrbitCaseService {
logger: Logger;
queueService: QueueService;
templates: {
SES: HandlebarsTemplateDelegate;
Notify: Record<"postAlert", string>;
};

constructor({ queueService }) {
this.queueService = queueService;
this.templates = {
SES: MarriageCaseService.createTemplate(marriageSubmissionTemplate),
Notify: {
postAlert: config.get<string>("Notify.Template.postNotification"),
},
};
this.logger = logger().child({ service: "SES" });
}

buildProcessQueueData(input: MarriageProcessQueueDataInput): MarriageProcessQueueData {
const { fields, reference, metadata, type } = input;
return {
fields,
metadata: {
reference,
payment: metadata.pay,
type: type,
postal: metadata.postal,
},
};
}

/**
* This will add all the parameters needed to process the email to the queue. The NOTIFY_PROCESS queue will pick up
* this message and make a post request to notarial-api/forms/emails/staff
*/
async sendToProcessQueue(data: MarriageProcessQueueData) {
return await this.queueService.sendToQueue("SES_PROCESS", data);
}

async sendEmail(data: MarriageProcessQueueData) {
const jobData = this.buildJobData(data);
return await this.queueService.sendToQueue("SES_SEND", jobData);
}

getEmailBody(data: { fields: FormField[]; payment?: PaymentData; reference: string; postal?: boolean }, type: MarriageTemplateType) {
const { fields, payment, reference, postal } = data;
const remapperName = postal ? `${type}Postal` : type;

const remapFields = remappers[remapperName];
const remapped = remapFields(fields);

const { information } = remapped;

const reorderer = reorderers[remapperName];
const reordered = reorderer(remapped);

const country = getAnswerOrThrow(information, "country");
const post = getPostForMarriage(country, information.post?.answer);
let oathType, jurats;
if ((type === "affirmation" || type === "cni") && !postal) {
oathType = getAnswerOrThrow(information, "oathType");
jurats = getAnswerOrThrow(information, "jurats");
}
return this.templates.SES({
post,
type: getApplicationTypeName(type),
reference,
payment,
country,
oathType,
jurats,
certifyPassport: information.certifyPassport?.answer ?? false,
questions: reordered,
});
this.logger = logger().child({ service: "MarriageCaseService" });
}

buildJobData(data: MarriageProcessQueueData) {
const { fields, metadata } = data;
const { reference, payment, type, postal } = metadata;
const answers = answersHashMap(fields);
let paymentViewModel: PaymentData | undefined;

async send(data): Promise<string> {
/**
* TODO: This can be implemented to POST to Case Management Service, or handled by the form runner directly.
* In which case this method should be a "no-op", or unimplemented entirely.
*/
try {
paymentViewModel = PaymentViewModel(payment, answers.country as string);
} catch (e) {
this.logger.warn(`Payment details for ${reference} could not be parsed. Payment details will not be shown on the email.`);
}

const country = answers.country as string;
const templateType = this.getTemplateType(answers, type);
const emailBody = this.getEmailBody({ fields, payment: paymentViewModel, reference, postal }, templateType);
const post = getPostForMarriage(country, answers.post as string);
const onCompleteJob = this.getPostAlertData(country, post, reference);
return {
subject: `Local marriage application - ${post} – ${reference}`,
body: emailBody,
attachments: fields.filter(isFieldType("file")),
reference,
metadata: {
reference,
type,
},
onComplete: {
queue: "NOTIFY_SEND",
...(onCompleteJob && { job: onCompleteJob }),
},
};
}

getPostAlertData(country: string, post: string, reference: string) {
const emailAddress = getPostEmailAddress(post);
if (!emailAddress) {
this.logger.error(
{ code: "UNRECOGNISED_SERVICE_APPLICATION" },
`No email address found for the specified post – ${country} - ${post} – reference ${reference}.`
);
return;
// const { reference } = postRequest(data);
// This reference will be shown to the user on the application complete page.
// return reference;
} catch (err) {
// handle response errors etc.
if (err.response) {
// add additional logging:
this.logger.error({ err }, "Case management api responded with an error");
// This error will be logged, ensure that `ORBIT_ERROR` has been added to alerting.
throw new ApplicationError("ORBIT", "ORBIT_ERROR", err.response.statusCode);
}

throw new ApplicationError("ORBIT", "ORBIT_ERROR", 500);
}

return {
template: this.templates.Notify.postAlert,
emailAddress,
reference,
options: {
personalisation: {
post,
reference,
},
reference,
},
};
}

getTemplateType(answers: AnswersHashMap, type: MarriageFormType): MarriageTemplateType {
if (answers.service) {
return answers.service as MarriageTemplateType;
}
return type;
}

private static createTemplate(template: string) {
return handlebars.compile(template);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { isFieldType } from "../../../../utils";
import { getPostForRequestDocument } from "../../utils/getPost";
import { CertifyCopyProcessQueueData, PaymentData } from "../types";
import { PaymentViewModel } from "../utils/PaymentViewModel";
import { CaseService } from "../types";
import { reorderSectionsWithNewName } from "../utils/reorderSectionsWithNewName";
import { order, remap } from "./mappings";
import { createRemapper } from "../utils/createRemapper";
Expand All @@ -16,8 +15,9 @@ import { getPostEmailAddress } from "../../utils/getPostEmailAddress";
import { QueueService } from "../../QueueService";
import logger, { Logger } from "pino";
import { RequestDocumentAnswersHashmap } from "../../../../types/AnswersHashMap";
import { SESCaseService } from "../types/CaseService";

export class RequestDocumentCaseService implements CaseService {
export class RequestDocumentCaseService implements SESCaseService {
logger: Logger;
queueService: QueueService;
templates: {
Expand Down
11 changes: 10 additions & 1 deletion api/src/middlewares/services/CaseService/types/CaseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@ import { PaymentData } from "./PaymentData";
import { ProcessQueueDataInput } from "./ProcessQueueDataInput";
import { Logger } from "pino";

export interface CaseService {
export type CaseService = OrbitCaseService | SESCaseService;

export interface OrbitCaseService {
logger: Logger;
queueService: QueueService;

send(data): Promise<string>;
}

export interface SESCaseService {
logger: Logger;
queueService: QueueService;

Expand Down
Loading