diff --git a/.changeset/famous-trees-work.md b/.changeset/famous-trees-work.md new file mode 100644 index 0000000..76024bf --- /dev/null +++ b/.changeset/famous-trees-work.md @@ -0,0 +1,5 @@ +--- +"saleor-app-invoices": patch +--- + +Improved error handling in Webhook INVOICE_CREATED. Now Sentry will gather additional breadcrumbs for better debugging. No PII is logged diff --git a/apps/invoices/next.config.js b/apps/invoices/next.config.js index 6c8dd36..6f81e6a 100644 --- a/apps/invoices/next.config.js +++ b/apps/invoices/next.config.js @@ -1,7 +1,8 @@ const { withSentryConfig } = require("@sentry/nextjs"); -const isSentryPropertiesInEnvironment = - process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG; +const isSentryPropertiesInEnvironment = Boolean( + process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG +); /** @type {import('next').NextConfig} */ const nextConfig = { @@ -25,4 +26,6 @@ const configWithSentry = withSentryConfig( } ); + module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig; + diff --git a/apps/invoices/sentry.client.config.ts b/apps/invoices/sentry.client.config.ts index 6eef515..82b1af4 100644 --- a/apps/invoices/sentry.client.config.ts +++ b/apps/invoices/sentry.client.config.ts @@ -9,22 +9,10 @@ import pkg from "./package.json"; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - - // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 0.5, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: false, - replaysOnErrorSampleRate: 1.0, - - /* - * This sets the sample rate to be 10%. You may want this to be 100% while - * in development and sample at a lower rate in production - */ replaysSessionSampleRate: 0.1, - - // You can remove this option if you're not planning to use the Sentry Session Replay feature: integrations: [ new Sentry.Replay({ // Additional Replay configuration goes in here, for example: diff --git a/apps/invoices/sentry.edge.config.ts b/apps/invoices/sentry.edge.config.ts index ca77fff..dc56da8 100644 --- a/apps/invoices/sentry.edge.config.ts +++ b/apps/invoices/sentry.edge.config.ts @@ -10,11 +10,7 @@ import pkg from "./package.json"; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - - // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 0.5, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: false, environment: process.env.SENTRY_ENVIRONMENT, release: `${pkg.name}@${pkg.version}`, diff --git a/apps/invoices/sentry.server.config.ts b/apps/invoices/sentry.server.config.ts index fb1faea..9bdc76e 100644 --- a/apps/invoices/sentry.server.config.ts +++ b/apps/invoices/sentry.server.config.ts @@ -9,11 +9,7 @@ import pkg from "./package.json"; Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - - // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 0.5, - - // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: false, environment: process.env.SENTRY_ENVIRONMENT, release: `${pkg.name}@${pkg.version}`, diff --git a/apps/invoices/src/modules/app-configuration/app-configuration-router.ts b/apps/invoices/src/modules/app-configuration/app-configuration-router.ts index b268df2..b904812 100644 --- a/apps/invoices/src/modules/app-configuration/app-configuration-router.ts +++ b/apps/invoices/src/modules/app-configuration/app-configuration-router.ts @@ -7,6 +7,7 @@ import { AppConfigV2MetadataManager } from "./schema-v2/app-config-v2-metadata-m import { GetAppConfigurationV2Service } from "./schema-v2/get-app-configuration.v2.service"; import { ConfigV1ToV2MigrationService } from "./schema-v2/config-v1-to-v2-migration.service"; import { AddressV2Schema } from "./schema-v2/app-config-schema.v2"; +import { AppConfigV2 } from "./schema-v2/app-config"; const UpsertAddressSchema = z.object({ address: AddressV2Schema, @@ -19,19 +20,8 @@ export const appConfigurationRouter = router({ logger.debug("appConfigurationRouterV2.fetch called"); - const appConfigV2 = await new GetAppConfigurationV2Service(ctx).getConfiguration(); - - /** - * MIGRATION CODE START - remove when metadata migrated - */ - if (!appConfigV2) { - const migrationService = new ConfigV1ToV2MigrationService(ctx.apiClient, ctx.saleorApiUrl); - - return migrationService.migrate().then((config) => config.getChannelsOverrides()); - } - /** - * MIGRATION CODE END - */ + const appConfigV2 = + (await new GetAppConfigurationV2Service(ctx).getConfiguration()) ?? new AppConfigV2(); return appConfigV2.getChannelsOverrides(); }), @@ -41,23 +31,8 @@ export const appConfigurationRouter = router({ }) .input(UpsertAddressSchema) .mutation(async ({ ctx, input }) => { - const appConfigV2 = await new GetAppConfigurationV2Service(ctx).getConfiguration(); - - /** - * MIGRATION CODE START - remove when metadata migrated - */ - if (!appConfigV2) { - const migrationService = new ConfigV1ToV2MigrationService(ctx.apiClient, ctx.saleorApiUrl); - - await migrationService.migrate((config) => - config.upsertOverride(input.channelSlug, input.address) - ); - - return; - } - /** - * MIGRATION CODE END - */ + const appConfigV2 = + (await new GetAppConfigurationV2Service(ctx).getConfiguration()) ?? new AppConfigV2(); appConfigV2.upsertOverride(input.channelSlug, input.address); @@ -75,21 +50,8 @@ export const appConfigurationRouter = router({ }) ) .mutation(async ({ ctx, input }) => { - const appConfigV2 = await new GetAppConfigurationV2Service(ctx).getConfiguration(); - - /** - * MIGRATION CODE START - remove when metadata migrated - */ - if (!appConfigV2) { - const migrationService = new ConfigV1ToV2MigrationService(ctx.apiClient, ctx.saleorApiUrl); - - await migrationService.migrate((config) => config.removeOverride(input.channelSlug)); - - return; - } - /** - * MIGRATION CODE END - */ + const appConfigV2 = + (await new GetAppConfigurationV2Service(ctx).getConfiguration()) ?? new AppConfigV2(); appConfigV2.removeOverride(input.channelSlug); diff --git a/apps/invoices/src/modules/app-configuration/ui/address-form.tsx b/apps/invoices/src/modules/app-configuration/ui/address-form.tsx index 9536023..fc8b764 100644 --- a/apps/invoices/src/modules/app-configuration/ui/address-form.tsx +++ b/apps/invoices/src/modules/app-configuration/ui/address-form.tsx @@ -181,8 +181,6 @@ export const ConnectedAddressForm = (props: Props) => { push("/configuration"); }, [push]); - console.log(addressData); - if (channelOverrideConfigQuery.isLoading) { return Loading; } diff --git a/apps/invoices/src/pages/api/webhooks/invoice-requested.ts b/apps/invoices/src/pages/api/webhooks/invoice-requested.ts index b1c0e25..376cb9e 100644 --- a/apps/invoices/src/pages/api/webhooks/invoice-requested.ts +++ b/apps/invoices/src/pages/api/webhooks/invoice-requested.ts @@ -26,6 +26,9 @@ import { import { ConfigV1ToV2MigrationService } from "../../../modules/app-configuration/schema-v2/config-v1-to-v2-migration.service"; import { shopInfoQueryToAddressShape } from "../../../modules/shop-info/shop-info-query-to-address-shape"; +import * as Sentry from "@sentry/nextjs"; +import { AppConfigV2 } from "../../../modules/app-configuration/schema-v2/app-config"; + const OrderPayload = gql` fragment Address on Address { id @@ -158,6 +161,10 @@ export const handler: NextWebhookApiHandler = a const { authData, payload, baseUrl } = context; const logger = createLogger({ domain: authData.saleorApiUrl, url: baseUrl }); + Sentry.configureScope((s) => { + s.setTag("saleorApiUrl", authData.saleorApiUrl); + }); + const order = payload.order; logger.info({ orderId: order.id }, "Received event INVOICE_REQUESTED"); @@ -172,6 +179,14 @@ export const handler: NextWebhookApiHandler = a InvoiceNumberGenerationStrategy.localizedDate("en-US") // todo connect locale -> where from? ); + Sentry.addBreadcrumb({ + message: "Calculated invoice name", + data: { + invoiceName: invoiceName, + }, + level: "debug", + }); + logger.debug({ invoiceName }, "Generated invoice name"); try { @@ -189,22 +204,19 @@ export const handler: NextWebhookApiHandler = a logger.debug({ tempPdfLocation }, "Resolved PDF location for temporary files"); - let appConfigV2 = await new GetAppConfigurationV2Service({ - saleorApiUrl: authData.saleorApiUrl, - apiClient: client, - }).getConfiguration(); + Sentry.addBreadcrumb({ + message: "Calculated invoice file location", + data: { + invoiceFile: tempPdfLocation, + }, + level: "debug", + }); - /** - * MIGRATION CODE START - remove when metadata migrated - */ - if (!appConfigV2) { - const migrationService = new ConfigV1ToV2MigrationService(client, authData.saleorApiUrl); - - appConfigV2 = await migrationService.migrate(); - } - /** - * MIGRATION CODE END - */ + let appConfigV2 = + (await new GetAppConfigurationV2Service({ + saleorApiUrl: authData.saleorApiUrl, + apiClient: client, + }).getConfiguration()) ?? new AppConfigV2(); const address: AddressV2Shape | null = appConfigV2.getChannelsOverrides()[order.channel.slug] ?? @@ -213,6 +225,11 @@ export const handler: NextWebhookApiHandler = a if (!address) { // todo disable webhook + Sentry.addBreadcrumb({ + message: "Address not configured", + level: "debug", + }); + return res.status(200).end("App not configured"); } @@ -226,25 +243,49 @@ export const handler: NextWebhookApiHandler = a .catch((err) => { logger.error(err, "Error generating invoice"); + Sentry.captureException(err); + return res.status(500).json({ error: "Error generating invoice", }); }); + Sentry.addBreadcrumb({ + message: "Generated invoice file", + level: "debug", + }); + const uploader = new SaleorInvoiceUploader(client); const uploadedFileUrl = await uploader.upload(tempPdfLocation, `${invoiceName}.pdf`); - logger.info({ uploadedFileUrl }, "Uploaded file to storage, will notify Saleor now"); + Sentry.addBreadcrumb({ + message: "Uploaded file to Saleor", + level: "debug", + }); + + logger.info("Uploaded file to storage, will notify Saleor now"); + logger.debug({ uploadedFileUrl }); await new InvoiceCreateNotifier(client).notifyInvoiceCreated( orderId, invoiceName, uploadedFileUrl ); + + Sentry.addBreadcrumb({ + message: "Notified Saleor about invoice creation", + level: "debug", + data: { + orderId, + invoiceName, + }, + }); } catch (e) { logger.error(e); + Sentry.captureException(e); + return res.status(500).json({ error: (e as any)?.message ?? "Error", });