From ce64d3e6f96b851c8365617bf6ec013e69ed6d3e Mon Sep 17 00:00:00 2001 From: Lukasz Ostrowski Date: Fri, 17 May 2024 14:08:22 +0200 Subject: [PATCH] Apply use case error handling to webhook handlers --- .../src/pages/api/webhooks/gift-card-sent.ts | 51 ++++++++++++++++--- .../src/pages/api/webhooks/invoice-sent.ts | 51 ++++++++++++++++--- apps/smtp/src/pages/api/webhooks/notify.ts | 49 +++++++++++++++--- .../src/pages/api/webhooks/order-cancelled.ts | 51 ++++++++++++++++--- .../src/pages/api/webhooks/order-confirmed.ts | 51 ++++++++++++++++--- .../src/pages/api/webhooks/order-created.ts | 51 ++++++++++++++++--- .../src/pages/api/webhooks/order-fulfilled.ts | 51 ++++++++++++++++--- .../pages/api/webhooks/order-fully-paid.ts | 51 ++++++++++++++++--- .../src/pages/api/webhooks/order-refunded.ts | 51 ++++++++++++++++--- 9 files changed, 386 insertions(+), 71 deletions(-) diff --git a/apps/smtp/src/pages/api/webhooks/gift-card-sent.ts b/apps/smtp/src/pages/api/webhooks/gift-card-sent.ts index c6f4aeb894..4b61bd6ab6 100644 --- a/apps/smtp/src/pages/api/webhooks/gift-card-sent.ts +++ b/apps/smtp/src/pages/api/webhooks/gift-card-sent.ts @@ -5,6 +5,8 @@ import { GiftCardSentWebhookPayloadFragment } from "../../../../generated/graphq import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const GiftCardSentWebhookPayload = gql` fragment GiftCardSentWebhookPayload on GiftCardSent { @@ -103,14 +105,47 @@ const handler: NextWebhookApiHandler = async const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "GIFT_CARD_SENT", - payload, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "GIFT_CARD_SENT", + payload, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel(giftCardSentWebhook.createHandler(handler), "/api/webhooks/gift-card-sent"); diff --git a/apps/smtp/src/pages/api/webhooks/invoice-sent.ts b/apps/smtp/src/pages/api/webhooks/invoice-sent.ts index 1bd306482c..fd1ac69d62 100644 --- a/apps/smtp/src/pages/api/webhooks/invoice-sent.ts +++ b/apps/smtp/src/pages/api/webhooks/invoice-sent.ts @@ -8,6 +8,8 @@ import { import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const InvoiceSentWebhookPayload = gql` ${OrderDetailsFragmentDoc} @@ -84,14 +86,47 @@ const handler: NextWebhookApiHandler = async const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "INVOICE_SENT", - payload: { order: payload.order }, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "INVOICE_SENT", + payload: { order: payload.order }, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel(invoiceSentWebhook.createHandler(handler), "api/webhooks/invoice-sent"); diff --git a/apps/smtp/src/pages/api/webhooks/notify.ts b/apps/smtp/src/pages/api/webhooks/notify.ts index 694efecd6f..a3c44fcb70 100644 --- a/apps/smtp/src/pages/api/webhooks/notify.ts +++ b/apps/smtp/src/pages/api/webhooks/notify.ts @@ -4,6 +4,8 @@ import { notifyEventMapping, NotifySubscriptionPayload } from "../../../lib/noti import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; /* * The Notify webhook is triggered on multiple Saleor events. @@ -48,14 +50,47 @@ const handler: NextWebhookApiHandler = async (req, re const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event, - payload: payload.payload, - recipientEmail, - }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event, + payload: payload.payload, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); - return res.status(200).json({ message: "The event has been handled" }); + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel(notifyWebhook.createHandler(handler), "api/webhooks/notify"); diff --git a/apps/smtp/src/pages/api/webhooks/order-cancelled.ts b/apps/smtp/src/pages/api/webhooks/order-cancelled.ts index 8ad830b234..2423025926 100644 --- a/apps/smtp/src/pages/api/webhooks/order-cancelled.ts +++ b/apps/smtp/src/pages/api/webhooks/order-cancelled.ts @@ -8,6 +8,8 @@ import { import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const OrderCancelledWebhookPayload = gql` ${OrderDetailsFragmentDoc} @@ -67,14 +69,47 @@ const handler: NextWebhookApiHandler = asy const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "ORDER_CANCELLED", - payload: { order: payload.order }, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "ORDER_CANCELLED", + payload: { order: payload.order }, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel( diff --git a/apps/smtp/src/pages/api/webhooks/order-confirmed.ts b/apps/smtp/src/pages/api/webhooks/order-confirmed.ts index ff35db7b80..004c77e086 100644 --- a/apps/smtp/src/pages/api/webhooks/order-confirmed.ts +++ b/apps/smtp/src/pages/api/webhooks/order-confirmed.ts @@ -8,6 +8,8 @@ import { import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const OrderConfirmedWebhookPayload = gql` ${OrderDetailsFragmentDoc} @@ -68,14 +70,47 @@ const handler: NextWebhookApiHandler = asy const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "ORDER_CONFIRMED", - payload: { order: payload.order }, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "ORDER_CONFIRMED", + payload: { order: payload.order }, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel( diff --git a/apps/smtp/src/pages/api/webhooks/order-created.ts b/apps/smtp/src/pages/api/webhooks/order-created.ts index 976283a08c..d1f21a41f3 100644 --- a/apps/smtp/src/pages/api/webhooks/order-created.ts +++ b/apps/smtp/src/pages/api/webhooks/order-created.ts @@ -6,6 +6,8 @@ import { OrderCreatedWebhookPayloadFragment } from "../../../../generated/graphq import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const OrderCreatedWebhookPayload = gql` ${OrderDetailsFragmentDoc} @@ -65,14 +67,47 @@ const handler: NextWebhookApiHandler = async const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "ORDER_CREATED", - payload: { order: payload.order }, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "ORDER_CREATED", + payload: { order: payload.order }, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel(orderCreatedWebhook.createHandler(handler), "api/webhooks/order-created"); diff --git a/apps/smtp/src/pages/api/webhooks/order-fulfilled.ts b/apps/smtp/src/pages/api/webhooks/order-fulfilled.ts index 5b769dfee1..ecde0aab95 100644 --- a/apps/smtp/src/pages/api/webhooks/order-fulfilled.ts +++ b/apps/smtp/src/pages/api/webhooks/order-fulfilled.ts @@ -8,6 +8,8 @@ import { import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const OrderFulfilledWebhookPayload = gql` ${OrderDetailsFragmentDoc} @@ -68,14 +70,47 @@ const handler: NextWebhookApiHandler = asy const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "ORDER_FULFILLED", - payload: { order: payload.order }, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "ORDER_FULFILLED", + payload: { order: payload.order }, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel( diff --git a/apps/smtp/src/pages/api/webhooks/order-fully-paid.ts b/apps/smtp/src/pages/api/webhooks/order-fully-paid.ts index da2b4a4e8f..a5c75de888 100644 --- a/apps/smtp/src/pages/api/webhooks/order-fully-paid.ts +++ b/apps/smtp/src/pages/api/webhooks/order-fully-paid.ts @@ -8,6 +8,8 @@ import { import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const OrderFullyPaidWebhookPayload = gql` ${OrderDetailsFragmentDoc} @@ -68,14 +70,47 @@ const handler: NextWebhookApiHandler = asy const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "ORDER_FULLY_PAID", - payload: { order: payload.order }, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "ORDER_FULLY_PAID", + payload: { order: payload.order }, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel( diff --git a/apps/smtp/src/pages/api/webhooks/order-refunded.ts b/apps/smtp/src/pages/api/webhooks/order-refunded.ts index c1383df0e8..364790cfec 100644 --- a/apps/smtp/src/pages/api/webhooks/order-refunded.ts +++ b/apps/smtp/src/pages/api/webhooks/order-refunded.ts @@ -8,6 +8,8 @@ import { saleorApp } from "../../../saleor-app"; import { withOtel } from "@saleor/apps-otel"; import { createLogger } from "../../../logger"; import { SendEventMessagesUseCaseFactory } from "../../../modules/event-handlers/use-case/send-event-messages.use-case.factory"; +import { SendEventMessagesUseCase } from "../../../modules/event-handlers/use-case/send-event-messages.use-case"; +import { captureException } from "@sentry/nextjs"; const OrderRefundedWebhookPayload = gql` ${OrderDetailsFragmentDoc} @@ -67,14 +69,47 @@ const handler: NextWebhookApiHandler = asyn const useCase = useCaseFactory.createFromAuthData(authData); - await useCase.sendEventMessages({ - channelSlug: channel, - event: "ORDER_REFUNDED", - payload: { order: payload.order }, - recipientEmail, - }); - - return res.status(200).json({ message: "The event has been handled" }); + return useCase + .sendEventMessages({ + channelSlug: channel, + event: "ORDER_REFUNDED", + payload: { order: payload.order }, + recipientEmail, + }) + .then((result) => + result.match( + (r) => { + logger.info("Successfully sent email(s)"); + + return res.status(200).json({ message: "The event has been handled" }); + }, + (err) => { + switch (err[0].constructor) { + case SendEventMessagesUseCase.ServerError: { + logger.error("Failed to send email(s) [server error]", { error: err }); + + return res.status(500).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.ClientError: { + logger.info("Failed to send email(s) [client error]", { error: err }); + + return res.status(400).json({ message: "Failed to send email" }); + } + case SendEventMessagesUseCase.NoOpError: { + logger.error("Sending emails aborted [no op]", { error: err }); + + return res.status(200).json({ message: "The event has been handled [no op]" }); + } + default: { + logger.error("Failed to send email(s) [server error]", { error: err }); + captureException(new Error("Unhandled useCase error", { cause: err })); + + return res.status(500).json({ message: "Failed to send email [unhandled]" }); + } + } + }, + ), + ); }; export default withOtel(orderRefundedWebhook.createHandler(handler), "api/webhooks/order-refunded");