diff --git a/api.planx.uk/hasura/metadata/index.ts b/api.planx.uk/hasura/metadata/index.ts index c9f7e79364..14829616b7 100644 --- a/api.planx.uk/hasura/metadata/index.ts +++ b/api.planx.uk/hasura/metadata/index.ts @@ -25,13 +25,18 @@ type RequiredScheduledEventArgs = Pick< "webhook" | "schedule_at" | "comment" | "payload" >; +export interface ScheduledEventResponse { + message: "success"; + event_id: string; +} + /** * POST a request to the Hasura Metadata API * https://hasura.io/docs/latest/graphql/core/api-reference/metadata-api/index/ */ const postToMetadataAPI = async ( body: ScheduledEvent, -): Promise> => { +): Promise> => { try { return await Axios.post( process.env.HASURA_METADATA_URL!, diff --git a/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts b/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts index d9eac45774..cb75bf84b1 100644 --- a/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts +++ b/api.planx.uk/inviteToPay/createPaymentSendEvents.test.ts @@ -9,6 +9,11 @@ const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction< typeof createScheduledEvent >; +const mockScheduledEventResponse = { + message: "success", + event_id: "abc123", +} as const; + describe("Create payment send events webhook", () => { const ENDPOINT = "/webhooks/hasura/create-payment-send-events"; @@ -83,7 +88,7 @@ describe("Create payment send events webhook", () => { }); it("returns a 200 on successful event setup", async () => { - mockedCreateScheduledEvent.mockResolvedValue("test-event-id"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await supertest(app) .post(ENDPOINT) @@ -91,13 +96,15 @@ describe("Create payment send events webhook", () => { .send({ payload: { sessionId: "123" } }) .expect(200) .then((response) => { - expect(response.body).toMatchObject({ email: "test-event-id" }); + expect(response.body).toMatchObject({ + email: mockScheduledEventResponse, + }); }); }); it("passes the correct arguments along to createScheduledEvent", async () => { const body = { createdAt: new Date(), payload: { sessionId: "123" } }; - mockedCreateScheduledEvent.mockResolvedValue("test-event-id"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await supertest(app) .post(ENDPOINT) @@ -105,7 +112,9 @@ describe("Create payment send events webhook", () => { .send(body) .expect(200) .then((response) => { - expect(response.body).toMatchObject({ email: "test-event-id" }); + expect(response.body).toMatchObject({ + email: mockScheduledEventResponse, + }); }); const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0]; diff --git a/api.planx.uk/inviteToPay/createPaymentSendEvents.ts b/api.planx.uk/inviteToPay/createPaymentSendEvents.ts index 3b34947ec1..7e2b7e05ef 100644 --- a/api.planx.uk/inviteToPay/createPaymentSendEvents.ts +++ b/api.planx.uk/inviteToPay/createPaymentSendEvents.ts @@ -3,7 +3,10 @@ import { NextFunction, Request, Response } from "express"; import { gql } from "graphql-request"; import { $admin } from "../client"; import { adminGraphQLClient as adminClient } from "../hasura"; -import { createScheduledEvent } from "../hasura/metadata"; +import { + ScheduledEventResponse, + createScheduledEvent, +} from "../hasura/metadata"; import { getMostRecentPublishedFlow } from "../helpers"; import { Flow, Node, Team } from "../types"; @@ -14,9 +17,9 @@ enum Destination { } interface CombinedResponse { - bops?: Record; - uniform?: Record; - email?: Record; + bops?: ScheduledEventResponse; + uniform?: ScheduledEventResponse; + email?: ScheduledEventResponse; } // Create "One-off Scheduled Events" in Hasura when a payment request is paid diff --git a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts b/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts deleted file mode 100644 index 1d50affdc3..0000000000 --- a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { addDays } from "date-fns"; -import { Request, Response, NextFunction } from "express"; - -import { createScheduledEvent } from "../../../hasura/metadata"; -import { - DAYS_UNTIL_EXPIRY, - REMINDER_DAYS_FROM_EXPIRY, -} from "../../../saveAndReturn/utils"; - -/** - * Create "reminder" events for a lowcal_session record - */ -const createReminderEvent = async ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - try { - const { createdAt, payload } = req.body; - if (!createdAt || !payload) - return next({ - status: 400, - message: "Required value missing", - }); - const response = await Promise.all( - REMINDER_DAYS_FROM_EXPIRY.map((day: number) => - createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/reminder", - schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY - day), - payload: payload, - comment: `reminder_${payload.sessionId}_${day}day`, - }), - ), - ); - res.json(response); - } catch (error) { - return next({ - error, - message: `Failed to create reminder event. Error: ${error}`, - }); - } -}; - -/** - * Create an "expiry" event for a lowcal_session record - */ -const createExpiryEvent = async ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - try { - const { createdAt, payload } = req.body; - if (!createdAt || !payload) - return next({ - status: 400, - message: "Required value missing", - }); - const response = await createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/expiry", - schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY), - payload: payload, - comment: `expiry_${payload.sessionId}`, - }); - res.json(response); - } catch (error) { - return next({ - error, - message: `Failed to create expiry event. Error: ${ - (error as Error).message - }`, - }); - } -}; - -export { createReminderEvent, createExpiryEvent }; diff --git a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts b/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts deleted file mode 100644 index 30a387b228..0000000000 --- a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { addDays } from "date-fns"; -import { Request, Response, NextFunction } from "express"; - -import { createScheduledEvent } from "../../../hasura/metadata"; -import { - DAYS_UNTIL_EXPIRY, - REMINDER_DAYS_FROM_EXPIRY, -} from "../../../saveAndReturn/utils"; - -/** - * Create two "invitation" events for a payments_request record: one for the nominee and one for the agent - */ -const createPaymentInvitationEvents = async ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - try { - const { createdAt, payload } = req.body; - if (!createdAt || !payload) - return next({ - status: 400, - message: "Required value missing", - }); - const response = await Promise.all([ - createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay", - schedule_at: createdAt, - payload: payload, - comment: `payment_invitation_${payload.paymentRequestId}`, - }), - createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay-agent", - schedule_at: createdAt, - payload: payload, - comment: `payment_invitation_agent_${payload.paymentRequestId}`, - }), - ]); - res.json(response); - } catch (error) { - return next({ - error, - message: `Failed to create payment invitation events. Error: ${error}`, - }); - } -}; - -/** - * Create "reminder" events for a payment_requests record: one for the nominee and one for the agent - */ -const createPaymentReminderEvents = async ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - try { - const { createdAt, payload } = req.body; - if (!createdAt || !payload) - return next({ - status: 400, - message: "Required value missing", - }); - const applicantResponse = await Promise.all( - REMINDER_DAYS_FROM_EXPIRY.map((day: number) => - createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder", - schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY - day), - payload: payload, - comment: `payment_reminder_${payload.paymentRequestId}_${day}day`, - }), - ), - ); - const agentResponse = await Promise.all( - REMINDER_DAYS_FROM_EXPIRY.map((day: number) => - createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder-agent", - schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY - day), - payload: payload, - comment: `payment_reminder_agent_${payload.paymentRequestId}_${day}day`, - }), - ), - ); - res.json([...applicantResponse, ...agentResponse]); - } catch (error) { - return next({ - error, - message: `Failed to create payment reminder events. Error: ${error}`, - }); - } -}; - -/** - * Create two "expiry" events for a payment_requests record: one for the nominee and one for the agent - */ -const createPaymentExpiryEvents = async ( - req: Request, - res: Response, - next: NextFunction, -): Promise => { - try { - const { createdAt, payload } = req.body; - if (!createdAt || !payload) - return next({ - status: 400, - message: "Required value missing", - }); - const response = await Promise.all([ - createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry", - schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY), - payload: payload, - comment: `payment_expiry_${payload.paymentRequestId}`, - }), - createScheduledEvent({ - webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry-agent", - schedule_at: addDays(Date.parse(createdAt), DAYS_UNTIL_EXPIRY), - payload: payload, - comment: `payment_expiry_agent_${payload.paymentRequestId}`, - }), - ]); - res.json(response); - } catch (error) { - return next({ - error, - message: `Failed to create payment expiry events. Error: ${ - (error as Error).message - }`, - }); - } -}; - -export { - createPaymentInvitationEvents, - createPaymentReminderEvents, - createPaymentExpiryEvents, -}; diff --git a/api.planx.uk/modules/webhooks/controller.ts b/api.planx.uk/modules/webhooks/controller.ts index 8b98358f89..1621159778 100644 --- a/api.planx.uk/modules/webhooks/controller.ts +++ b/api.planx.uk/modules/webhooks/controller.ts @@ -1,6 +1,19 @@ import { ServerError } from "../../errors"; -import { sendSlackNotification } from "./sendNotification/service"; -import { SendSlackNotification } from "./sendNotification/types"; +import { CreateSessionEventController } from "./service/lowcalSessionEvents/schema"; +import { + createSessionExpiryEvent, + createSessionReminderEvent, +} from "./service/lowcalSessionEvents"; +import { SendSlackNotification } from "./service/sendNotification/types"; +import { sendSlackNotification } from "./service/sendNotification"; +import { CreatePaymentEventController } from "./service/paymentRequestEvents/schema"; +import { + createPaymentExpiryEvents, + createPaymentInvitationEvents, + createPaymentReminderEvents, +} from "./service/paymentRequestEvents"; +import { SanitiseApplicationData } from "./service/sanitiseApplicationData/types"; +import { sanitiseApplicationData } from "./service/sanitiseApplicationData"; export const sendSlackNotificationController: SendSlackNotification = async ( req, @@ -29,3 +42,94 @@ export const sendSlackNotificationController: SendSlackNotification = async ( ); } }; + +export const createPaymentInvitationEventsController: CreatePaymentEventController = + async (req, res, next) => { + try { + const response = await createPaymentInvitationEvents(req.body); + res.json(response); + } catch (error) { + return next( + new ServerError({ + message: `Failed to create payment invitation events`, + cause: error, + }), + ); + } + }; + +export const createPaymentReminderEventsController: CreatePaymentEventController = + async (req, res, next) => { + try { + const response = await createPaymentReminderEvents(req.body); + res.json(response); + } catch (error) { + return next( + new ServerError({ + message: "Failed to create payment reminder events", + cause: error, + }), + ); + } + }; + +export const createPaymentExpiryEventsController: CreatePaymentEventController = + async (req, res, next) => { + try { + const response = await createPaymentExpiryEvents(req.body); + res.json(response); + } catch (error) { + return next( + new ServerError({ + message: "Failed to create payment expiry events", + cause: error, + }), + ); + } + }; + +export const createSessionReminderEventController: CreateSessionEventController = + async (req, res, next) => { + try { + const response = await createSessionReminderEvent(req.body); + res.json(response); + } catch (error) { + return next( + new ServerError({ + message: "Failed to create session reminder event", + cause: error, + }), + ); + } + }; + +export const createSessionExpiryEventController: CreateSessionEventController = + async (req, res, next) => { + try { + const response = await createSessionExpiryEvent(req.body); + res.json(response); + } catch (error) { + return next( + new ServerError({ + message: "Failed to create session expiry event", + cause: error, + }), + ); + } + }; + +export const sanitiseApplicationDataController: SanitiseApplicationData = + async (_req, res, next) => { + try { + const { operationFailed, results } = await sanitiseApplicationData(); + if (operationFailed) res.status(500); + return res.json(results); + } catch (error) { + return next( + new ServerError({ + message: "Failed to sanitise application data", + cause: error, + }), + ); + } + }; diff --git a/api.planx.uk/modules/webhooks/docs.yaml b/api.planx.uk/modules/webhooks/docs.yaml index 837e64a8ea..b200378244 100644 --- a/api.planx.uk/modules/webhooks/docs.yaml +++ b/api.planx.uk/modules/webhooks/docs.yaml @@ -14,7 +14,7 @@ components: type: string required: - sessionId - BopsSubmissionSchema: + BopsSubmission: type: object properties: body: @@ -37,7 +37,7 @@ components: type: string required: - body - UniformSubmissionSchema: + UniformSubmission: type: object properties: body: @@ -63,7 +63,7 @@ components: type: string required: - body - EmailSubmissionSchema: + EmailSubmission: type: object properties: body: @@ -92,11 +92,37 @@ components: type: string required: - body - SendSlackNotificationSchema: + SendSlackNotification: oneOf: - - $ref: "#/components/schemas/BopsSubmissionSchema" - - $ref: "#/components/schemas/UniformSubmissionSchema" - - $ref: "#/components/schemas/EmailSubmissionSchema" + - $ref: "#/components/schemas/BopsSubmission" + - $ref: "#/components/schemas/UniformSubmission" + - $ref: "#/components/schemas/EmailSubmission" + CreatePaymentEvent: + type: object + properties: + createdAt: + required: true + type: string + format: date-time + payload: + required: true + type: object + properties: + paymentRequestId: + type: string + CreateSessionEvent: + type: object + properties: + createdAt: + required: true + type: string + format: date-time + payload: + required: true + type: object + properties: + sessionId: + type: string responses: SlackNotificationSuccessMessage: content: @@ -111,6 +137,56 @@ components: message: type: string required: true + ScheduledEvent: + content: + application/json: + schema: + type: array + items: + type: object + properties: + message: + type: string + enum: ["success"] + event_id: + type: string + description: Internal Hasura ID of the generated event + OperationResultSuccess: + content: + application/json: + schema: + type: array + items: + type: object + properties: + operationName: + type: string + status: + type: string + enum: + - success + count: + type: integer + OperationResultFailure: + content: + application/json: + schema: + type: array + items: + type: object + properties: + operationName: + type: string + status: + type: string + enum: + - success + - failure + count: + type: integer + errorMessage: + type: string + required: false paths: /webhooks/hasura/sendSlackNotification: post: @@ -127,9 +203,94 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/SendSlackNotificationSchema" + $ref: "#/components/schemas/SendSlackNotification" responses: "200": $ref: "#/components/responses/SlackNotificationSuccessMessage" "500": $ref: "#/components/responses/ErrorMessage" + /webhooks/hasura/create-payment-invitation-events: + post: + tags: ["webhooks"] + summary: Create payment invitation events + description: Setup events which will trigger payment invitation emails to be generated + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreatePaymentEvent" + responses: + "200": + $ref: "#/components/responses/ScheduledEvent" + "500": + $ref: "#/components/responses/ErrorMessage" + /webhooks/hasura/create-payment-reminder-events: + post: + tags: ["webhooks"] + summary: Create payment reminder events + description: Setup events which will trigger payment reminder emails to be generated + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreatePaymentEvent" + responses: + "200": + $ref: "#/components/responses/ScheduledEvent" + "500": + $ref: "#/components/responses/ErrorMessage" + /webhooks/hasura/create-payment-expiry-events: + post: + tags: ["webhooks"] + summary: Create payment expiry events + description: Setup events which will trigger payment expiry emails to be generated + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreatePaymentEvent" + responses: + "200": + $ref: "#/components/responses/ScheduledEvent" + "500": + $ref: "#/components/responses/ErrorMessage" + /webhooks/hasura/create-reminder-event: + post: + tags: ["webhooks"] + summary: Create session reminder event + description: Setup events which will trigger session reminder emails to be generated + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateSessionEvent" + responses: + "200": + $ref: "#/components/responses/ScheduledEvent" + "500": + $ref: "#/components/responses/ErrorMessage" + /webhooks/hasura/create-expiry-event: + post: + tags: ["webhooks"] + summary: Create session expiry event + description: Setup events which will trigger session expiry email to be generated + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateSessionEvent" + responses: + "200": + $ref: "#/components/responses/ScheduledEvent" + "500": + $ref: "#/components/responses/ErrorMessage" + /webhooks/hasura/sanitise-application-data: + post: + tags: ["webhooks"] + summary: Sanitise application data + description: Called by Hasura cron job "sanitise_application_data" on a nightly basis + responses: + "200": + $ref: "#/components/responses/OperationResultSuccess" + "500": + $ref: "#/components/responses/OperationResultFailure" diff --git a/api.planx.uk/modules/webhooks/routes.ts b/api.planx.uk/modules/webhooks/routes.ts index c98421b7e3..dc7c1bbbcd 100644 --- a/api.planx.uk/modules/webhooks/routes.ts +++ b/api.planx.uk/modules/webhooks/routes.ts @@ -1,40 +1,59 @@ import { Router } from "express"; import { useHasuraAuth } from "../auth/middleware"; import { createPaymentSendEvents } from "../../inviteToPay/createPaymentSendEvents"; -import { sanitiseApplicationData } from "./_old/sanitiseApplicationData"; -import { - createExpiryEvent, - createReminderEvent, -} from "./_old/lowcalSessionEvents"; -import { - createPaymentExpiryEvents, - createPaymentInvitationEvents, - createPaymentReminderEvents, -} from "./_old/paymentRequestEvents"; import { validate } from "../../shared/middleware/validate"; -import { sendSlackNotificationController } from "./controller"; -import { sendSlackNotificationSchema } from "./sendNotification/schema"; +import { + createPaymentExpiryEventsController, + createPaymentInvitationEventsController, + createPaymentReminderEventsController, + createSessionExpiryEventController, + createSessionReminderEventController, + sanitiseApplicationDataController, + sendSlackNotificationController, +} from "./controller"; +import { sendSlackNotificationSchema } from "./service/sendNotification/schema"; +import { createPaymentEventSchema } from "./service/paymentRequestEvents/schema"; +import { createSessionEventSchema } from "./service/lowcalSessionEvents/schema"; const router = Router(); router.use("/hasura", useHasuraAuth); -router.post("/hasura/create-reminder-event", createReminderEvent); -router.post("/hasura/create-expiry-event", createExpiryEvent); router.post( "/hasura/create-payment-invitation-events", - createPaymentInvitationEvents, + validate(createPaymentEventSchema), + createPaymentInvitationEventsController, ); router.post( "/hasura/create-payment-reminder-events", - createPaymentReminderEvents, + validate(createPaymentEventSchema), + createPaymentReminderEventsController, +); +router.post( + "/hasura/create-payment-expiry-events", + validate(createPaymentEventSchema), + createPaymentExpiryEventsController, ); -router.post("/hasura/create-payment-expiry-events", createPaymentExpiryEvents); -router.post("/hasura/create-payment-send-events", createPaymentSendEvents); router.post( "/hasura/send-slack-notification", validate(sendSlackNotificationSchema), sendSlackNotificationController, ); -router.post("/hasura/sanitise-application-data", sanitiseApplicationData); +router.post( + "/hasura/create-reminder-event", + validate(createSessionEventSchema), + createSessionReminderEventController, +); +router.post( + "/hasura/create-expiry-event", + validate(createSessionEventSchema), + createSessionExpiryEventController, +); +router.post( + "/hasura/sanitise-application-data", + sanitiseApplicationDataController, +); + +// TODO: Convert to the new API module structure +router.post("/hasura/create-payment-send-events", createPaymentSendEvents); export default router; diff --git a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.test.ts similarity index 76% rename from api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts rename to api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.test.ts index 0cddfe272b..8f557a4686 100644 --- a/api.planx.uk/modules/webhooks/_old/lowcalSessionEvents.test.ts +++ b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.test.ts @@ -1,14 +1,19 @@ import supertest from "supertest"; -import app from "../../../server"; -import { createScheduledEvent } from "../../../hasura/metadata"; +import app from "../../../../server"; +import { createScheduledEvent } from "../../../../hasura/metadata"; const { post } = supertest(app); -jest.mock("../../../hasura/metadata"); +jest.mock("../../../../hasura/metadata"); const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction< typeof createScheduledEvent >; +const mockScheduledEventResponse = { + message: "success", + event_id: "abc123", +} as const; + describe("Create reminder event webhook", () => { const ENDPOINT = "/webhooks/hasura/create-reminder-event"; @@ -33,15 +38,16 @@ describe("Create reminder event webhook", () => { .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(400) - .then((response) => - expect(response.body.error).toEqual("Required value missing"), - ); + .then((response) => { + expect(response.body).toHaveProperty("issues"); + expect(response.body).toHaveProperty("name", "ZodError"); + }); } }); it("returns a 200 on successful event setup", async () => { const body = { createdAt: new Date(), payload: { sessionId: "123" } }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) @@ -50,20 +56,26 @@ describe("Create reminder event webhook", () => { .then((response) => { // it's queued up x2 reminders for 7 days and 1 day from expiry expect(response.body).toHaveLength(2); - expect(response.body).toStrictEqual(["test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); }); it("passes the correct arguments along to createScheduledEvent", async () => { const body = { createdAt: new Date(), payload: { sessionId: "123" } }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(200) .then((response) => { - expect(response.body).toStrictEqual(["test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0]; @@ -92,7 +104,9 @@ describe("Create reminder event webhook", () => { .send(body) .expect(500) .then((response) => { - expect(response.body.error).toMatch(/Failed to create reminder event/); + expect(response.body.error).toMatch( + /Failed to create session reminder event/, + ); }); }); }); @@ -121,35 +135,36 @@ describe("Create expiry event webhook", () => { .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(400) - .then((response) => - expect(response.body.error).toEqual("Required value missing"), - ); + .then((response) => { + expect(response.body).toHaveProperty("issues"); + expect(response.body).toHaveProperty("name", "ZodError"); + }); } }); it("returns a 200 on successful event setup", async () => { const body = { createdAt: new Date(), payload: { sessionId: "123" } }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(200) .then((response) => { - expect(response.body).toBe("test"); + expect(response.body).toStrictEqual([mockScheduledEventResponse]); }); }); it("passes the correct arguments along to createScheduledEvent", async () => { const body = { createdAt: new Date(), payload: { sessionId: "123" } }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(200) .then((response) => { - expect(response.body).toBe("test"); + expect(response.body).toStrictEqual([mockScheduledEventResponse]); }); const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0]; expect(mockArgs.webhook).toBe("{{HASURA_PLANX_API_URL}}/send-email/expiry"); @@ -166,7 +181,9 @@ describe("Create expiry event webhook", () => { .send(body) .expect(500) .then((response) => { - expect(response.body.error).toMatch(/Failed to create expiry event/); + expect(response.body.error).toMatch( + /Failed to create session expiry event/, + ); }); }); }); diff --git a/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.ts b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.ts new file mode 100644 index 0000000000..b8ec4cf072 --- /dev/null +++ b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/index.ts @@ -0,0 +1,44 @@ +import { addDays } from "date-fns"; + +import { createScheduledEvent } from "../../../../hasura/metadata"; +import { + DAYS_UNTIL_EXPIRY, + REMINDER_DAYS_FROM_EXPIRY, +} from "../../../../saveAndReturn/utils"; +import { CreateSessionEvent } from "./schema"; + +/** + * Create "reminder" events for a lowcal_session record + */ +export const createSessionReminderEvent = async ({ + createdAt, + payload, +}: CreateSessionEvent) => { + const response = await Promise.all( + REMINDER_DAYS_FROM_EXPIRY.map((day: number) => + createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/reminder", + schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY - day), + payload: payload, + comment: `reminder_${payload.sessionId}_${day}day`, + }), + ), + ); + return response; +}; + +/** + * Create an "expiry" event for a lowcal_session record + */ +export const createSessionExpiryEvent = async ({ + createdAt, + payload, +}: CreateSessionEvent) => { + const response = await createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/expiry", + schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY), + payload: payload, + comment: `expiry_${payload.sessionId}`, + }); + return [response]; +}; diff --git a/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts new file mode 100644 index 0000000000..53e73f0b06 --- /dev/null +++ b/api.planx.uk/modules/webhooks/service/lowcalSessionEvents/schema.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { ValidatedRequestHandler } from "../../../../shared/middleware/validate"; +import { ScheduledEventResponse } from "../../../../hasura/metadata"; + +export const createSessionEventSchema = z.object({ + body: z.object({ + createdAt: z.string().transform((val) => new Date(val)), + payload: z.object({ + sessionId: z.string(), + }), + }), +}); + +export type CreateSessionEvent = z.infer< + typeof createSessionEventSchema +>["body"]; + +export type CreateSessionEventController = ValidatedRequestHandler< + typeof createSessionEventSchema, + ScheduledEventResponse[] +>; diff --git a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.test.ts similarity index 80% rename from api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts rename to api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.test.ts index 282ca011a5..20beeefd98 100644 --- a/api.planx.uk/modules/webhooks/_old/paymentRequestEvents.test.ts +++ b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.test.ts @@ -1,14 +1,20 @@ import supertest from "supertest"; -import app from "../../../server"; -import { createScheduledEvent } from "../../../hasura/metadata"; +import app from "../../../../server"; +import { createScheduledEvent } from "../../../../hasura/metadata"; +import { CreatePaymentEvent } from "./schema"; const { post } = supertest(app); -jest.mock("../../../hasura/metadata"); +jest.mock("../../../../hasura/metadata"); const mockedCreateScheduledEvent = createScheduledEvent as jest.MockedFunction< typeof createScheduledEvent >; +const mockScheduledEventResponse = { + message: "success", + event_id: "abc123", +} as const; + describe("Create payment invitation events webhook", () => { const ENDPOINT = "/webhooks/hasura/create-payment-invitation-events"; @@ -33,18 +39,19 @@ describe("Create payment invitation events webhook", () => { .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(400) - .then((response) => - expect(response.body.error).toEqual("Required value missing"), - ); + .then((response) => { + expect(response.body).toHaveProperty("issues"); + expect(response.body).toHaveProperty("name", "ZodError"); + }); } }); it("returns a 200 on successful event setup", async () => { - const body = { + const body: CreatePaymentEvent = { createdAt: new Date(), payload: { paymentRequestId: "123" }, }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) @@ -53,7 +60,10 @@ describe("Create payment invitation events webhook", () => { .then((response) => { // it's queued up x2 invitations: one for the payee and one for the agent expect(response.body).toHaveLength(2); - expect(response.body).toStrictEqual(["test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); }); @@ -62,7 +72,7 @@ describe("Create payment invitation events webhook", () => { createdAt: new Date(), payload: { paymentRequestId: "123" }, }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) @@ -70,7 +80,10 @@ describe("Create payment invitation events webhook", () => { .expect(200) .then((response) => { expect(response.body).toHaveLength(2); - expect(response.body).toStrictEqual(["test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0]; @@ -135,9 +148,10 @@ describe("Create payment reminder events webhook", () => { .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(400) - .then((response) => - expect(response.body.error).toEqual("Required value missing"), - ); + .then((response) => { + expect(response.body).toHaveProperty("issues"); + expect(response.body).toHaveProperty("name", "ZodError"); + }); } }); @@ -146,7 +160,7 @@ describe("Create payment reminder events webhook", () => { createdAt: new Date(), payload: { paymentRequestId: "123" }, }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) @@ -155,7 +169,12 @@ describe("Create payment reminder events webhook", () => { .then((response) => { // it's queued up x4 reminders: 2 for the payee and 2 for the agent at 7 days and 1 day from expiry expect(response.body).toHaveLength(4); - expect(response.body).toStrictEqual(["test", "test", "test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); }); @@ -164,7 +183,7 @@ describe("Create payment reminder events webhook", () => { createdAt: new Date(), payload: { paymentRequestId: "123" }, }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) @@ -172,7 +191,12 @@ describe("Create payment reminder events webhook", () => { .expect(200) .then((response) => { expect(response.body).toHaveLength(4); - expect(response.body).toStrictEqual(["test", "test", "test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0]; @@ -255,9 +279,10 @@ describe("Create payment expiry events webhook", () => { .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(400) - .then((response) => - expect(response.body.error).toEqual("Required value missing"), - ); + .then((response) => { + expect(response.body).toHaveProperty("issues"); + expect(response.body).toHaveProperty("name", "ZodError"); + }); } }); @@ -266,16 +291,19 @@ describe("Create payment expiry events webhook", () => { createdAt: new Date(), payload: { paymentRequestId: "123" }, }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .send(body) .expect(200) .then((response) => { - // it's queued up x2 expirys: one for the payee and one for the agent + // it's queued up x2 expiries: one for the payee and one for the agent expect(response.body).toHaveLength(2); - expect(response.body).toStrictEqual(["test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); }); @@ -284,7 +312,7 @@ describe("Create payment expiry events webhook", () => { createdAt: new Date(), payload: { paymentRequestId: "123" }, }; - mockedCreateScheduledEvent.mockResolvedValue("test"); + mockedCreateScheduledEvent.mockResolvedValue(mockScheduledEventResponse); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) @@ -292,7 +320,10 @@ describe("Create payment expiry events webhook", () => { .expect(200) .then((response) => { expect(response.body).toHaveLength(2); - expect(response.body).toStrictEqual(["test", "test"]); + expect(response.body).toStrictEqual([ + mockScheduledEventResponse, + mockScheduledEventResponse, + ]); }); const mockArgs = mockedCreateScheduledEvent.mock.calls[0][0]; diff --git a/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.ts b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.ts new file mode 100644 index 0000000000..9679c7931e --- /dev/null +++ b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/index.ts @@ -0,0 +1,86 @@ +import { addDays } from "date-fns"; + +import { createScheduledEvent } from "../../../../hasura/metadata"; +import { + DAYS_UNTIL_EXPIRY, + REMINDER_DAYS_FROM_EXPIRY, +} from "../../../../saveAndReturn/utils"; +import { CreatePaymentEvent } from "./schema"; + +/** + * Create two "invitation" events for a payments_request record: one for the nominee and one for the agent + */ +export const createPaymentInvitationEvents = async ({ + createdAt, + payload, +}: CreatePaymentEvent) => { + const response = await Promise.all([ + createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay", + schedule_at: createdAt, + payload: payload, + comment: `payment_invitation_${payload.paymentRequestId}`, + }), + createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/invite-to-pay-agent", + schedule_at: createdAt, + payload: payload, + comment: `payment_invitation_agent_${payload.paymentRequestId}`, + }), + ]); + return response; +}; + +/** + * Create "reminder" events for a payment_requests record: one for the nominee and one for the agent + */ +export const createPaymentReminderEvents = async ({ + createdAt, + payload, +}: CreatePaymentEvent) => { + const applicantResponse = await Promise.all( + REMINDER_DAYS_FROM_EXPIRY.map((day: number) => + createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder", + schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY - day), + payload: payload, + comment: `payment_reminder_${payload.paymentRequestId}_${day}day`, + }), + ), + ); + const agentResponse = await Promise.all( + REMINDER_DAYS_FROM_EXPIRY.map((day: number) => + createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-reminder-agent", + schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY - day), + payload: payload, + comment: `payment_reminder_agent_${payload.paymentRequestId}_${day}day`, + }), + ), + ); + return [...applicantResponse, ...agentResponse]; +}; + +/** + * Create two "expiry" events for a payment_requests record: one for the nominee and one for the agent + */ +export const createPaymentExpiryEvents = async ({ + createdAt, + payload, +}: CreatePaymentEvent) => { + const response = await Promise.all([ + createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry", + schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY), + payload: payload, + comment: `payment_expiry_${payload.paymentRequestId}`, + }), + createScheduledEvent({ + webhook: "{{HASURA_PLANX_API_URL}}/send-email/payment-expiry-agent", + schedule_at: addDays(createdAt, DAYS_UNTIL_EXPIRY), + payload: payload, + comment: `payment_expiry_agent_${payload.paymentRequestId}`, + }), + ]); + return response; +}; diff --git a/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts new file mode 100644 index 0000000000..5b7a48404c --- /dev/null +++ b/api.planx.uk/modules/webhooks/service/paymentRequestEvents/schema.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { ValidatedRequestHandler } from "../../../../shared/middleware/validate"; +import { ScheduledEventResponse } from "../../../../hasura/metadata"; + +export const createPaymentEventSchema = z.object({ + body: z.object({ + createdAt: z.string().transform((val) => new Date(val)), + payload: z.object({ + paymentRequestId: z.string(), + }), + }), +}); + +export type CreatePaymentEvent = z.infer< + typeof createPaymentEventSchema +>["body"]; + +export type CreatePaymentEventController = ValidatedRequestHandler< + typeof createPaymentEventSchema, + ScheduledEventResponse[] +>; diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.test.ts similarity index 96% rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.test.ts index 96da6ca4b3..a4930c9178 100644 --- a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.test.ts +++ b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.test.ts @@ -30,13 +30,15 @@ describe("Sanitise application data webhook", () => { it("returns a 500 if an unhandled error is thrown whilst running operations", async () => { const mockOperationHandler = jest.spyOn(operations, "operationHandler"); - mockOperationHandler.mockRejectedValueOnce(new Error("Unhandled error!")); + mockOperationHandler.mockRejectedValueOnce("Unhandled error!"); await post(ENDPOINT) .set({ Authorization: process.env.HASURA_PLANX_API_KEY }) .expect(500) .then((response) => - expect(response.body.error).toMatch(/Unhandled error!/), + expect(response.body.error).toMatch( + /Failed to sanitise application data/, + ), ); }); diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.ts similarity index 61% rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.ts index 9454104edf..af9cecf7c3 100644 --- a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/index.ts +++ b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/index.ts @@ -9,35 +9,19 @@ import { getFormattedEnvironment } from "../../../../helpers"; * Called by Hasura cron job `sanitise_application_data` on a nightly basis * See hasura.planx.uk/metadata/cron_triggers.yaml */ -export const sanitiseApplicationData = async ( - _req: Request, - res: Response, - next: NextFunction, -): Promise => { +export const sanitiseApplicationData = async () => { const operations = getOperations(); const results: OperationResult[] = []; - try { - for (const operation of operations) { - const result = await operationHandler(operation); - results.push(result); - } - } catch (error) { - // Unhandled error, flag with Airbrake - return next({ - error, - message: `Failed to sanitise application data. ${ - (error as Error).message - }`, - }); - } - const operationFailed = results.find((result) => result.status === "failure"); - if (operationFailed) { - await postToSlack(results); - res.status(500); + for (const operation of operations) { + const result = await operationHandler(operation); + results.push(result); } - return res.json(results); + const operationFailed = results.some((result) => result.status === "failure"); + if (operationFailed) await postToSlack(results); + + return { operationFailed, results }; }; export const postToSlack = async (results: OperationResult[]) => { diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/mocks/queries.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/mocks/queries.ts similarity index 100% rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/mocks/queries.ts rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/mocks/queries.ts diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.test.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.test.ts similarity index 100% rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.test.ts rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.test.ts diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.ts similarity index 100% rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/operations.ts rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/operations.ts diff --git a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/types.ts similarity index 53% rename from api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts rename to api.planx.uk/modules/webhooks/service/sanitiseApplicationData/types.ts index 55bd0d8bc5..88b9df2804 100644 --- a/api.planx.uk/modules/webhooks/_old/sanitiseApplicationData/types.d.ts +++ b/api.planx.uk/modules/webhooks/service/sanitiseApplicationData/types.ts @@ -1,3 +1,6 @@ +import { z } from "zod"; +import { ValidatedRequestHandler } from "../../../../shared/middleware/validate"; + export interface OperationResult { operationName: string; status: "processing" | "success" | "failure"; @@ -8,3 +11,8 @@ export interface OperationResult { export type QueryResult = string[]; export type Operation = () => Promise; + +export type SanitiseApplicationData = ValidatedRequestHandler< + z.ZodUndefined, + OperationResult[] +>; diff --git a/api.planx.uk/modules/webhooks/sendNotification/index.test.ts b/api.planx.uk/modules/webhooks/service/sendNotification/index.test.ts similarity index 98% rename from api.planx.uk/modules/webhooks/sendNotification/index.test.ts rename to api.planx.uk/modules/webhooks/service/sendNotification/index.test.ts index e73c115371..d3a4d25a54 100644 --- a/api.planx.uk/modules/webhooks/sendNotification/index.test.ts +++ b/api.planx.uk/modules/webhooks/service/sendNotification/index.test.ts @@ -1,8 +1,8 @@ import supertest from "supertest"; -import app from "../../../server"; +import app from "../../../../server"; import SlackNotify from "slack-notify"; import { BOPSBody, EmailBody, UniformBody } from "./types"; -import { $admin } from "../../../client"; +import { $admin } from "../../../../client"; import { CoreDomainClient } from "@opensystemslab/planx-core"; const mockSessionWithFee = { @@ -35,7 +35,7 @@ const mockSessionWithResubmissionExemption = { }, }; -jest.mock("../../../client"); +jest.mock("../../../../client"); const mockAdmin = jest.mocked($admin); const mockSend = jest.fn(); @@ -208,10 +208,8 @@ describe("Send Slack notifications endpoint", () => { ); expect(mockSend).toHaveBeenCalledTimes(1); expect(mockAdmin.session.find).toHaveBeenCalledTimes(1); - expect(response.body.message).toBe("Posted to Slack"); expect(response.body.data).toMatch(/abc123/); - expect(response.body.data).toMatch(/test-council/); }); }); diff --git a/api.planx.uk/modules/webhooks/sendNotification/service.ts b/api.planx.uk/modules/webhooks/service/sendNotification/index.ts similarity index 97% rename from api.planx.uk/modules/webhooks/sendNotification/service.ts rename to api.planx.uk/modules/webhooks/service/sendNotification/index.ts index be8678a0e9..e0dfd36947 100644 --- a/api.planx.uk/modules/webhooks/sendNotification/service.ts +++ b/api.planx.uk/modules/webhooks/service/sendNotification/index.ts @@ -7,7 +7,7 @@ import { EventType, UniformEventData, } from "./types"; -import { $admin } from "../../../client"; +import { $admin } from "../../../../client"; export const sendSlackNotification = async ( data: EventData, diff --git a/api.planx.uk/modules/webhooks/sendNotification/schema.ts b/api.planx.uk/modules/webhooks/service/sendNotification/schema.ts similarity index 100% rename from api.planx.uk/modules/webhooks/sendNotification/schema.ts rename to api.planx.uk/modules/webhooks/service/sendNotification/schema.ts diff --git a/api.planx.uk/modules/webhooks/sendNotification/types.ts b/api.planx.uk/modules/webhooks/service/sendNotification/types.ts similarity index 92% rename from api.planx.uk/modules/webhooks/sendNotification/types.ts rename to api.planx.uk/modules/webhooks/service/sendNotification/types.ts index cd1e293bf9..1575f45429 100644 --- a/api.planx.uk/modules/webhooks/sendNotification/types.ts +++ b/api.planx.uk/modules/webhooks/service/sendNotification/types.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { ValidatedRequestHandler } from "../../../shared/middleware/validate"; +import { ValidatedRequestHandler } from "../../../../shared/middleware/validate"; import { bopsSubmissionSchema, emailSubmissionSchema, diff --git a/api.planx.uk/send/createSendEvents.ts b/api.planx.uk/send/createSendEvents.ts index ed611c121f..8f5c12af4e 100644 --- a/api.planx.uk/send/createSendEvents.ts +++ b/api.planx.uk/send/createSendEvents.ts @@ -1,10 +1,13 @@ import { NextFunction, Request, Response } from "express"; -import { createScheduledEvent } from "../hasura/metadata"; +import { + ScheduledEventResponse, + createScheduledEvent, +} from "../hasura/metadata"; interface CombinedResponse { - bops?: Record; - uniform?: Record; - email?: Record; + bops?: ScheduledEventResponse; + uniform?: ScheduledEventResponse; + email?: ScheduledEventResponse; } // Create "One-off Scheduled Events" in Hasura from Send component for selected destinations diff --git a/api.planx.uk/shared/middleware/validate.ts b/api.planx.uk/shared/middleware/validate.ts index ce647f2f16..854a37938e 100644 --- a/api.planx.uk/shared/middleware/validate.ts +++ b/api.planx.uk/shared/middleware/validate.ts @@ -20,6 +20,7 @@ export const validate = }); return next(); } catch (error) { + console.error(error); return res.status(400).json(error); } };