📧 Introduce new event - Gift Card Sent (#661)
* Introduce new event - Gift Card Sent * Add feature flag service and use it with gift card event * Add saleor version check on install
This commit is contained in:
parent
1405deaf66
commit
6250095a4e
36 changed files with 10423 additions and 15443 deletions
6
.changeset/real-years-return.md
Normal file
6
.changeset/real-years-return.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
"saleor-app-emails-and-messages": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Added support for new event - Gift Card Sent. The event is available for Saleor version 3.13 and above.
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -74,6 +74,6 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"saleor": {
|
"saleor": {
|
||||||
"schemaVersion": "3.11.7"
|
"schemaVersion": "3.13"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,6 @@ export const generateRandomId = () => {
|
||||||
const offsetInMinutes = date.getTimezoneOffset();
|
const offsetInMinutes = date.getTimezoneOffset();
|
||||||
const randomDate = date.setMinutes(date.getMinutes() + offsetInMinutes).valueOf();
|
const randomDate = date.setMinutes(date.getMinutes() + offsetInMinutes).valueOf();
|
||||||
const randomString = (Math.random() + 1).toString(36).substring(7);
|
const randomString = (Math.random() + 1).toString(36).substring(7);
|
||||||
|
|
||||||
return `${randomDate}${randomString}`;
|
return `${randomDate}${randomString}`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,7 +32,7 @@ export function setBackendErrors<T extends FieldValues = FieldValues>({
|
||||||
|
|
||||||
notifyError(
|
notifyError(
|
||||||
"Could not save the configuration",
|
"Could not save the configuration",
|
||||||
isFieldErrorSet ? "Submitted form contain errors" : "Error saving configuration",
|
isFieldErrorSet ? "Submitted form contain errors" : error.message,
|
||||||
formErrorMessage
|
formErrorMessage
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
import { router } from "../trpc/trpc-server";
|
||||||
|
import { protectedWithConfigurationServices } from "../trpc/protected-client-procedure-with-services";
|
||||||
|
|
||||||
|
export const appConfigurationRouter = router({
|
||||||
|
featureFlags: protectedWithConfigurationServices.query(async ({ ctx }) => {
|
||||||
|
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||||
|
|
||||||
|
logger.debug("appConfigurationRouter.featureFlags called");
|
||||||
|
return await ctx.featureFlagService.getFeatureFlags();
|
||||||
|
}),
|
||||||
|
});
|
|
@ -7,6 +7,7 @@ import {
|
||||||
OrderFulfilledWebhookPayloadFragment,
|
OrderFulfilledWebhookPayloadFragment,
|
||||||
OrderFullyPaidWebhookPayloadFragment,
|
OrderFullyPaidWebhookPayloadFragment,
|
||||||
InvoiceSentWebhookPayloadFragment,
|
InvoiceSentWebhookPayloadFragment,
|
||||||
|
GiftCardSentWebhookPayloadFragment,
|
||||||
} from "../../../generated/graphql";
|
} from "../../../generated/graphql";
|
||||||
import { NotifyEventPayload } from "../../pages/api/webhooks/notify";
|
import { NotifyEventPayload } from "../../pages/api/webhooks/notify";
|
||||||
|
|
||||||
|
@ -247,6 +248,33 @@ const accountDeletePayload: NotifyEventPayload = {
|
||||||
logo_url: "",
|
logo_url: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: UPDATE WITH BETTER DATA
|
||||||
|
const giftCardSentPayload: GiftCardSentWebhookPayloadFragment = {
|
||||||
|
channel: "default_channel",
|
||||||
|
sentToEmail: "user@example.com",
|
||||||
|
giftCard: {
|
||||||
|
code: "XXXX",
|
||||||
|
tags: [],
|
||||||
|
created: "2021-03-16T13:12:00+00:00",
|
||||||
|
currentBalance: {
|
||||||
|
amount: 100,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
id: "R2lmdENhcmQ6MjI=",
|
||||||
|
initialBalance: {
|
||||||
|
amount: 100,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
isActive: true,
|
||||||
|
lastUsedOn: null,
|
||||||
|
displayCode: "XXXX-XXXX-XXXX-XXXX",
|
||||||
|
last4CodeChars: "XXXX",
|
||||||
|
expiryDate: "2021-03-16T13:12:00+00:00",
|
||||||
|
usedByEmail: null,
|
||||||
|
usedBy: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const examplePayloads: Record<MessageEventTypes, any> = {
|
export const examplePayloads: Record<MessageEventTypes, any> = {
|
||||||
ORDER_CREATED: orderCreatedPayload,
|
ORDER_CREATED: orderCreatedPayload,
|
||||||
ORDER_CONFIRMED: orderConfirmedPayload,
|
ORDER_CONFIRMED: orderConfirmedPayload,
|
||||||
|
@ -254,6 +282,7 @@ export const examplePayloads: Record<MessageEventTypes, any> = {
|
||||||
ORDER_FULFILLED: orderFulfilledPayload,
|
ORDER_FULFILLED: orderFulfilledPayload,
|
||||||
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
ORDER_FULLY_PAID: orderFullyPaidPayload,
|
||||||
INVOICE_SENT: invoiceSentPayload,
|
INVOICE_SENT: invoiceSentPayload,
|
||||||
|
GIFT_CARD_SENT: giftCardSentPayload,
|
||||||
ACCOUNT_CONFIRMATION: accountConfirmationPayload,
|
ACCOUNT_CONFIRMATION: accountConfirmationPayload,
|
||||||
ACCOUNT_PASSWORD_RESET: accountPasswordResetPayload,
|
ACCOUNT_PASSWORD_RESET: accountPasswordResetPayload,
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: accountChangeEmailRequestPayload,
|
ACCOUNT_CHANGE_EMAIL_REQUEST: accountChangeEmailRequestPayload,
|
||||||
|
|
|
@ -10,6 +10,7 @@ export const messageEventTypes = [
|
||||||
"ACCOUNT_CHANGE_EMAIL_REQUEST",
|
"ACCOUNT_CHANGE_EMAIL_REQUEST",
|
||||||
"ACCOUNT_CHANGE_EMAIL_CONFIRM",
|
"ACCOUNT_CHANGE_EMAIL_CONFIRM",
|
||||||
"ACCOUNT_DELETE",
|
"ACCOUNT_DELETE",
|
||||||
|
"GIFT_CARD_SENT",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type MessageEventTypes = (typeof messageEventTypes)[number];
|
export type MessageEventTypes = (typeof messageEventTypes)[number];
|
||||||
|
@ -21,6 +22,7 @@ export const messageEventTypesLabels: Record<MessageEventTypes, string> = {
|
||||||
ORDER_CANCELLED: "Order cancelled",
|
ORDER_CANCELLED: "Order cancelled",
|
||||||
ORDER_FULLY_PAID: "Order fully paid",
|
ORDER_FULLY_PAID: "Order fully paid",
|
||||||
INVOICE_SENT: "Invoice sent",
|
INVOICE_SENT: "Invoice sent",
|
||||||
|
GIFT_CARD_SENT: "Gift card sent",
|
||||||
ACCOUNT_CONFIRMATION: "Customer account confirmation",
|
ACCOUNT_CONFIRMATION: "Customer account confirmation",
|
||||||
ACCOUNT_PASSWORD_RESET: "Customer account password reset request",
|
ACCOUNT_PASSWORD_RESET: "Customer account password reset request",
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: "Customer account change email request",
|
ACCOUNT_CHANGE_EMAIL_REQUEST: "Customer account change email request",
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { MessageEventTypes } from "./message-event-types";
|
||||||
import { SmtpPrivateMetadataManager } from "../smtp/configuration/smtp-metadata-manager";
|
import { SmtpPrivateMetadataManager } from "../smtp/configuration/smtp-metadata-manager";
|
||||||
import { createSettingsManager } from "../../lib/metadata-manager";
|
import { createSettingsManager } from "../../lib/metadata-manager";
|
||||||
import { SendgridPrivateMetadataManager } from "../sendgrid/configuration/sendgrid-metadata-manager";
|
import { SendgridPrivateMetadataManager } from "../sendgrid/configuration/sendgrid-metadata-manager";
|
||||||
|
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
||||||
|
|
||||||
interface SendEventMessagesArgs {
|
interface SendEventMessagesArgs {
|
||||||
recipientEmail: string;
|
recipientEmail: string;
|
||||||
|
@ -33,11 +34,16 @@ export const sendEventMessages = async ({
|
||||||
|
|
||||||
logger.debug("Function called");
|
logger.debug("Function called");
|
||||||
|
|
||||||
|
const featureFlagService = new FeatureFlagService({
|
||||||
|
client,
|
||||||
|
});
|
||||||
|
|
||||||
const smtpConfigurationService = new SmtpConfigurationService({
|
const smtpConfigurationService = new SmtpConfigurationService({
|
||||||
metadataManager: new SmtpPrivateMetadataManager(
|
metadataManager: new SmtpPrivateMetadataManager(
|
||||||
createSettingsManager(client, authData.appId),
|
createSettingsManager(client, authData.appId),
|
||||||
authData.saleorApiUrl
|
authData.saleorApiUrl
|
||||||
),
|
),
|
||||||
|
featureFlagService,
|
||||||
});
|
});
|
||||||
|
|
||||||
const availableSmtpConfigurations = await smtpConfigurationService.getConfigurations({
|
const availableSmtpConfigurations = await smtpConfigurationService.getConfigurations({
|
||||||
|
@ -66,6 +72,7 @@ export const sendEventMessages = async ({
|
||||||
createSettingsManager(client, authData.appId),
|
createSettingsManager(client, authData.appId),
|
||||||
authData.saleorApiUrl
|
authData.saleorApiUrl
|
||||||
),
|
),
|
||||||
|
featureFlagService,
|
||||||
});
|
});
|
||||||
|
|
||||||
const availableSendgridConfigurations = await sendgridConfigurationService.getConfigurations({
|
const availableSendgridConfigurations = await sendgridConfigurationService.getConfigurations({
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { it, describe, expect, afterEach, vi } from "vitest";
|
||||||
|
import { FeatureFlagService } from "./feature-flag-service";
|
||||||
|
import * as fetchSaleorVersionExports from "./fetch-saleor-version";
|
||||||
|
|
||||||
|
import { Client } from "urql";
|
||||||
|
|
||||||
|
describe("FeatureFlagService", function () {
|
||||||
|
const createMockedClient = () => ({} as Client);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("No API calls, when version is passed to the constructor", async () => {
|
||||||
|
const passedVersion = "3.13.0";
|
||||||
|
|
||||||
|
const service = new FeatureFlagService({
|
||||||
|
client: createMockedClient(),
|
||||||
|
saleorVersion: passedVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
const versionFetchSpy = vi
|
||||||
|
.spyOn(fetchSaleorVersionExports, "fetchSaleorVersion")
|
||||||
|
.mockResolvedValue("XXXX");
|
||||||
|
|
||||||
|
expect(await service.getSaleorVersion()).toEqual(passedVersion);
|
||||||
|
expect(versionFetchSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Use cached version, when once fetched", async () => {
|
||||||
|
const fetchedVersion = "3.13.0";
|
||||||
|
|
||||||
|
const service = new FeatureFlagService({
|
||||||
|
client: createMockedClient(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const versionFetchSpy = vi
|
||||||
|
.spyOn(fetchSaleorVersionExports, "fetchSaleorVersion")
|
||||||
|
.mockResolvedValue(fetchedVersion);
|
||||||
|
|
||||||
|
expect(await service.getSaleorVersion()).toEqual(fetchedVersion);
|
||||||
|
expect(versionFetchSpy).toHaveBeenCalledOnce();
|
||||||
|
|
||||||
|
// Request version once again - should be cached
|
||||||
|
expect(await service.getSaleorVersion()).toEqual(fetchedVersion);
|
||||||
|
expect(versionFetchSpy).toHaveBeenCalledOnce();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Client } from "urql";
|
||||||
|
import { FeatureFlagsState, getFeatureFlags } from "./get-feature-flags";
|
||||||
|
import { fetchSaleorVersion } from "./fetch-saleor-version";
|
||||||
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
|
|
||||||
|
const logger = createLogger({
|
||||||
|
name: "FeatureFlagService",
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Manages state of feature flags, based on Saleor version.
|
||||||
|
* If `saleorVersion` is not provided, it will be fetched from the API on first call.
|
||||||
|
* `saleorVersion` is expected to be in Semver format, e.g. "3.13.0"
|
||||||
|
*/
|
||||||
|
export class FeatureFlagService {
|
||||||
|
private client: Client;
|
||||||
|
private saleorVersion?: string;
|
||||||
|
|
||||||
|
constructor(args: { client: Client; saleorVersion?: string }) {
|
||||||
|
this.client = args.client;
|
||||||
|
this.saleorVersion = args.saleorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSaleorVersion = async (): Promise<string> => {
|
||||||
|
if (!this.saleorVersion) {
|
||||||
|
logger.debug("No cached value, fetching version from the API");
|
||||||
|
this.saleorVersion = await fetchSaleorVersion(this.client);
|
||||||
|
}
|
||||||
|
return this.saleorVersion;
|
||||||
|
};
|
||||||
|
|
||||||
|
public getFeatureFlags = async (): Promise<FeatureFlagsState> => {
|
||||||
|
logger.debug("Checking feature flags");
|
||||||
|
const saleorVersion = await this.getSaleorVersion();
|
||||||
|
const flags = getFeatureFlags({ saleorVersion });
|
||||||
|
|
||||||
|
logger.debug({ flags }, "Feature flags checked");
|
||||||
|
return flags;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Client, gql } from "urql";
|
||||||
|
import { FetchSaleorVersionDocument, FetchSaleorVersionQuery } from "../../../generated/graphql";
|
||||||
|
|
||||||
|
gql`
|
||||||
|
query FetchSaleorVersion {
|
||||||
|
shop {
|
||||||
|
version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export async function fetchSaleorVersion(client: Client): Promise<string> {
|
||||||
|
const { error, data } = await client
|
||||||
|
.query<FetchSaleorVersionQuery>(FetchSaleorVersionDocument, {})
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
if (error || !data?.shop.version) {
|
||||||
|
throw new Error("Can't fetch Saleor version");
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.shop.version;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { it, describe, expect } from "vitest";
|
||||||
|
import { getFeatureFlags } from "./get-feature-flags";
|
||||||
|
|
||||||
|
describe("getFeatureFlags", function () {
|
||||||
|
it("Flag should be turned off, when using too old version", () => {
|
||||||
|
expect(getFeatureFlags({ saleorVersion: "3.10.0" }).giftCardSentEvent).toEqual(false);
|
||||||
|
});
|
||||||
|
it("Flag should be turned on, when using version with feature support", () => {
|
||||||
|
expect(getFeatureFlags({ saleorVersion: "3.13.0" }).giftCardSentEvent).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { SaleorVersionCompatibilityValidator } from "@saleor/apps-shared";
|
||||||
|
|
||||||
|
export const featureFlags = ["giftCardSentEvent"] as const;
|
||||||
|
|
||||||
|
export type FeatureFlag = (typeof featureFlags)[number];
|
||||||
|
|
||||||
|
export type FeatureFlagsState = Record<FeatureFlag, boolean>;
|
||||||
|
|
||||||
|
interface GetFeatureFlagsArgs {
|
||||||
|
saleorVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns list of feature flags based on Saleor version.
|
||||||
|
* `saleorVersion` is expected to be in Semver format, e.g. "3.13.0"
|
||||||
|
*/
|
||||||
|
export const getFeatureFlags = ({ saleorVersion }: GetFeatureFlagsArgs): FeatureFlagsState => {
|
||||||
|
return {
|
||||||
|
giftCardSentEvent: new SaleorVersionCompatibilityValidator(">=3.13").isValid(saleorVersion),
|
||||||
|
};
|
||||||
|
};
|
|
@ -26,17 +26,22 @@ export const throwTrpcErrorFromConfigurationServiceError = (
|
||||||
case "CONFIGURATION_NOT_FOUND":
|
case "CONFIGURATION_NOT_FOUND":
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
message: "Configuration not found",
|
message: "Configuration not found.",
|
||||||
});
|
});
|
||||||
case "EVENT_CONFIGURATION_NOT_FOUND":
|
case "EVENT_CONFIGURATION_NOT_FOUND":
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
message: "Event configuration not found",
|
message: "Event configuration not found.",
|
||||||
});
|
});
|
||||||
case "CANT_FETCH":
|
case "CANT_FETCH":
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Can't fetch configuration",
|
message: "Can't fetch configuration.",
|
||||||
|
});
|
||||||
|
case "WRONG_SALEOR_VERSION":
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "Feature you are trying to use is not supported in this version of Saleor.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,7 +85,7 @@ export const sendgridConfigurationRouter = router({
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
createConfiguration: protectedWithConfigurationServices
|
createConfiguration: protectedWithConfigurationServices
|
||||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"], updateWebhooks: true })
|
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||||
.input(sendgridCreateConfigurationInputSchema)
|
.input(sendgridCreateConfigurationInputSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||||
|
@ -241,20 +246,17 @@ export const sendgridConfigurationRouter = router({
|
||||||
|
|
||||||
logger.debug(input, "sendgridConfigurationRouter.updateEventArray called");
|
logger.debug(input, "sendgridConfigurationRouter.updateEventArray called");
|
||||||
|
|
||||||
return await Promise.all(
|
try {
|
||||||
input.events.map(async (event) => {
|
const configuration = await ctx.sendgridConfigurationService.getConfiguration({
|
||||||
const { eventType, ...eventConfiguration } = event;
|
id: input.configurationId,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
await ctx.sendgridConfigurationService.updateConfiguration({
|
||||||
return await ctx.sendgridConfigurationService.updateEventConfiguration({
|
...configuration,
|
||||||
configurationId: input.configurationId,
|
events: input.events,
|
||||||
eventType,
|
});
|
||||||
eventConfiguration,
|
} catch (e) {
|
||||||
});
|
throwTrpcErrorFromConfigurationServiceError(e);
|
||||||
} catch (e) {
|
}
|
||||||
throwTrpcErrorFromConfigurationServiceError(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { SendgridConfigurationService } from "./sendgrid-configuration.service";
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||||
import { SendgridPrivateMetadataManager } from "./sendgrid-metadata-manager";
|
import { SendgridPrivateMetadataManager } from "./sendgrid-metadata-manager";
|
||||||
import { SendgridConfig } from "./sendgrid-config-schema";
|
import { SendgridConfig } from "./sendgrid-config-schema";
|
||||||
|
import { Client } from "urql";
|
||||||
|
import { FeatureFlagService } from "../../feature-flag-service/feature-flag-service";
|
||||||
|
|
||||||
const mockSaleorApiUrl = "https://demo.saleor.io/graphql/";
|
const mockSaleorApiUrl = "https://demo.saleor.io/graphql/";
|
||||||
|
|
||||||
|
@ -79,6 +81,11 @@ const validConfig: SendgridConfig = {
|
||||||
eventType: "ACCOUNT_DELETE",
|
eventType: "ACCOUNT_DELETE",
|
||||||
template: undefined,
|
template: undefined,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
eventType: "GIFT_CARD_SENT",
|
||||||
|
template: undefined,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
sender: "1",
|
sender: "1",
|
||||||
senderEmail: "no-reply@example.com",
|
senderEmail: "no-reply@example.com",
|
||||||
|
@ -170,6 +177,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
||||||
|
|
||||||
new SendgridConfigurationService({
|
new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -187,6 +198,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(validConfig);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(validConfig);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -209,6 +224,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(emptyConfigRoot);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(emptyConfigRoot);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -230,6 +249,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
|
|
||||||
// Service initialized with empty configuration
|
// Service initialized with empty configuration
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: emptyConfigRoot,
|
initialData: emptyConfigRoot,
|
||||||
});
|
});
|
||||||
|
@ -244,6 +267,28 @@ describe("SendgridConfigurationService", function () {
|
||||||
expect(await service.getConfigurationRoot());
|
expect(await service.getConfigurationRoot());
|
||||||
expect(getConfigMock).toBeCalledTimes(0);
|
expect(getConfigMock).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Operation should be rejected, when attempting to save event not available according to feature flag", async () => {
|
||||||
|
const configurator = new SendgridPrivateMetadataManager(
|
||||||
|
null as unknown as SettingsManager,
|
||||||
|
mockSaleorApiUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
// Service initialized with empty configuration
|
||||||
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.12.0", // Feature flag is available since 3.13.0
|
||||||
|
}),
|
||||||
|
metadataManager: configurator,
|
||||||
|
initialData: emptyConfigRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set configuration
|
||||||
|
await expect(async () => await service.setConfigurationRoot(validConfig)).rejects.toThrow(
|
||||||
|
"Gift card sent event is not supported for this Saleor version"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getConfiguration", () => {
|
describe("getConfiguration", () => {
|
||||||
|
@ -254,6 +299,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -270,6 +319,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -288,6 +341,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: emptyConfigRoot,
|
initialData: emptyConfigRoot,
|
||||||
});
|
});
|
||||||
|
@ -302,6 +359,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -323,6 +384,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: emptyConfigRoot,
|
initialData: emptyConfigRoot,
|
||||||
});
|
});
|
||||||
|
@ -351,6 +416,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -381,6 +450,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -404,6 +477,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -433,6 +510,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -456,6 +537,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -475,6 +560,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -506,6 +595,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -538,6 +631,10 @@ describe("SendgridConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SendgridConfigurationService({
|
const service = new SendgridConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { MessageEventTypes } from "../../event-handlers/message-event-types";
|
||||||
import { generateRandomId } from "../../../lib/generate-random-id";
|
import { generateRandomId } from "../../../lib/generate-random-id";
|
||||||
import { filterConfigurations } from "../../app-configuration/filter-configurations";
|
import { filterConfigurations } from "../../app-configuration/filter-configurations";
|
||||||
import { SendgridPrivateMetadataManager } from "./sendgrid-metadata-manager";
|
import { SendgridPrivateMetadataManager } from "./sendgrid-metadata-manager";
|
||||||
|
import { FeatureFlagService } from "../../feature-flag-service/feature-flag-service";
|
||||||
|
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
service: "SendgridConfigurationService",
|
service: "SendgridConfigurationService",
|
||||||
|
@ -18,7 +19,8 @@ export type SendgridConfigurationServiceErrorType =
|
||||||
| "OTHER"
|
| "OTHER"
|
||||||
| "CONFIGURATION_NOT_FOUND"
|
| "CONFIGURATION_NOT_FOUND"
|
||||||
| "EVENT_CONFIGURATION_NOT_FOUND"
|
| "EVENT_CONFIGURATION_NOT_FOUND"
|
||||||
| "CANT_FETCH";
|
| "CANT_FETCH"
|
||||||
|
| "WRONG_SALEOR_VERSION";
|
||||||
|
|
||||||
export interface ConfigurationPartial extends Partial<SendgridConfiguration> {
|
export interface ConfigurationPartial extends Partial<SendgridConfiguration> {
|
||||||
id: SendgridConfiguration["id"];
|
id: SendgridConfiguration["id"];
|
||||||
|
@ -45,16 +47,20 @@ export interface FilterConfigurationsArgs {
|
||||||
export class SendgridConfigurationService {
|
export class SendgridConfigurationService {
|
||||||
private configurationData?: SendgridConfig;
|
private configurationData?: SendgridConfig;
|
||||||
private metadataConfigurator: SendgridPrivateMetadataManager;
|
private metadataConfigurator: SendgridPrivateMetadataManager;
|
||||||
|
private featureFlagService: FeatureFlagService;
|
||||||
|
|
||||||
constructor(args: {
|
constructor(args: {
|
||||||
metadataManager: SendgridPrivateMetadataManager;
|
metadataManager: SendgridPrivateMetadataManager;
|
||||||
initialData?: SendgridConfig;
|
initialData?: SendgridConfig;
|
||||||
|
featureFlagService: FeatureFlagService;
|
||||||
}) {
|
}) {
|
||||||
this.metadataConfigurator = args.metadataManager;
|
this.metadataConfigurator = args.metadataManager;
|
||||||
|
|
||||||
if (args.initialData) {
|
if (args.initialData) {
|
||||||
this.configurationData = args.initialData;
|
this.configurationData = args.initialData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.featureFlagService = args.featureFlagService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,10 +107,35 @@ export class SendgridConfigurationService {
|
||||||
return this.configurationData;
|
return this.configurationData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private containActiveGiftCardEvent(config: SendgridConfig) {
|
||||||
|
for (const configuration of config.configurations) {
|
||||||
|
const giftCardSentEvent = configuration.events.find(
|
||||||
|
(event) => event.eventType === "GIFT_CARD_SENT"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (giftCardSentEvent?.active) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Saves configuration to Saleor API and cache it
|
// Saves configuration to Saleor API and cache it
|
||||||
async setConfigurationRoot(config: SendgridConfig) {
|
async setConfigurationRoot(config: SendgridConfig) {
|
||||||
logger.debug("Set configuration root");
|
logger.debug("Validate configuration before sending it to the Saleor API");
|
||||||
|
const availableFeatures = await this.featureFlagService.getFeatureFlags();
|
||||||
|
|
||||||
|
if (!availableFeatures.giftCardSentEvent && this.containActiveGiftCardEvent(config)) {
|
||||||
|
logger.error(
|
||||||
|
"Attempt to enable gift card sent event for unsupported Saleor version. Aborting configuration update."
|
||||||
|
);
|
||||||
|
throw new SendgridConfigurationServiceError(
|
||||||
|
"Gift card sent event is not supported for this Saleor version",
|
||||||
|
"WRONG_SALEOR_VERSION"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Set configuration root");
|
||||||
this.configurationData = config;
|
this.configurationData = config;
|
||||||
await this.pushConfiguration();
|
await this.pushConfiguration();
|
||||||
}
|
}
|
||||||
|
@ -179,7 +210,7 @@ export class SendgridConfigurationService {
|
||||||
|
|
||||||
updatedConfigRoot.configurations[configurationIndex] = updatedConfiguration;
|
updatedConfigRoot.configurations[configurationIndex] = updatedConfiguration;
|
||||||
|
|
||||||
this.setConfigurationRoot(updatedConfigRoot);
|
await this.setConfigurationRoot(updatedConfigRoot);
|
||||||
|
|
||||||
return updatedConfiguration;
|
return updatedConfiguration;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +230,7 @@ export class SendgridConfigurationService {
|
||||||
(configuration) => configuration.id !== id
|
(configuration) => configuration.id !== id
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setConfigurationRoot(updatedConfigRoot);
|
await this.setConfigurationRoot(updatedConfigRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,7 @@ export const fetchTemplates =
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Could not fetch available Sendgrid templates");
|
console.error("Could not fetch available Sendgrid templates");
|
||||||
return [];
|
return [];
|
||||||
|
@ -33,6 +34,7 @@ export const fetchTemplates =
|
||||||
value: r.id.toString(),
|
value: r.id.toString(),
|
||||||
label: r.name,
|
label: r.name,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
return templates;
|
return templates;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not parse the response from Sendgrid", e);
|
console.error("Could not parse the response from Sendgrid", e);
|
||||||
|
@ -55,6 +57,7 @@ export const fetchSenders =
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Could not fetch available Sendgrid senders");
|
console.error("Could not fetch available Sendgrid senders");
|
||||||
return [];
|
return [];
|
||||||
|
@ -70,6 +73,7 @@ export const fetchSenders =
|
||||||
nickname: r.nickname,
|
nickname: r.nickname,
|
||||||
from_email: r.from_email,
|
from_email: r.from_email,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
return senders;
|
return senders;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Could not parse the response from Sendgrid", e);
|
console.error("Could not parse the response from Sendgrid", e);
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import {
|
import { SendgridConfiguration } from "../configuration/sendgrid-config-schema";
|
||||||
SendgridConfiguration,
|
|
||||||
SendgridEventConfiguration,
|
|
||||||
} from "../configuration/sendgrid-config-schema";
|
|
||||||
import { BoxWithBorder } from "../../../components/box-with-border";
|
import { BoxWithBorder } from "../../../components/box-with-border";
|
||||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
import { Box, Button, Text, Tooltip } from "@saleor/macaw-ui/next";
|
||||||
import { defaultPadding } from "../../../components/ui-defaults";
|
import { defaultPadding } from "../../../components/ui-defaults";
|
||||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
import { trpcClient } from "../../trpc/trpc-client";
|
import { trpcClient } from "../../trpc/trpc-client";
|
||||||
|
@ -30,6 +27,8 @@ interface SendgridEventsSectionProps {
|
||||||
export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionProps) => {
|
export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionProps) => {
|
||||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||||
|
|
||||||
|
const { data: featureFlags } = trpcClient.app.featureFlags.useQuery();
|
||||||
|
|
||||||
// Sort events by displayed label
|
// Sort events by displayed label
|
||||||
const eventsSorted = configuration.events.sort((a, b) =>
|
const eventsSorted = configuration.events.sort((a, b) =>
|
||||||
messageEventTypesLabels[a.eventType].localeCompare(messageEventTypesLabels[b.eventType])
|
messageEventTypesLabels[a.eventType].localeCompare(messageEventTypesLabels[b.eventType])
|
||||||
|
@ -92,23 +91,43 @@ export const SendgridEventsSection = ({ configuration }: SendgridEventsSectionPr
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{eventsSorted.map((event, index) => (
|
{eventsSorted.map((event, index) => {
|
||||||
<Table.Row key={event.eventType}>
|
const isUnsupported =
|
||||||
<Table.Cell>
|
!featureFlags?.giftCardSentEvent && event.eventType === "GIFT_CARD_SENT";
|
||||||
<input type="checkbox" {...register(`events.${index}.active`)} />
|
|
||||||
</Table.Cell>
|
return (
|
||||||
<Table.Cell>
|
<Table.Row key={event.eventType}>
|
||||||
<Text>{messageEventTypesLabels[event.eventType]}</Text>
|
<Table.Cell>
|
||||||
</Table.Cell>
|
<Tooltip>
|
||||||
<Table.Cell>
|
<Tooltip.Trigger>
|
||||||
<Select
|
<input
|
||||||
control={control}
|
type="checkbox"
|
||||||
name={`events.${index}.template`}
|
{...register(`events.${index}.active`)}
|
||||||
options={templateChoices}
|
disabled={isUnsupported}
|
||||||
/>
|
/>
|
||||||
</Table.Cell>
|
</Tooltip.Trigger>
|
||||||
</Table.Row>
|
{isUnsupported && (
|
||||||
))}
|
<Tooltip.Content side="left">
|
||||||
|
Event is available in Saleor version 3.13 and above only.
|
||||||
|
<Tooltip.Arrow />
|
||||||
|
</Tooltip.Content>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Text>{messageEventTypesLabels[event.eventType]}</Text>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Select
|
||||||
|
control={control}
|
||||||
|
name={`events.${index}.template`}
|
||||||
|
options={templateChoices}
|
||||||
|
disabled={isUnsupported}
|
||||||
|
/>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table.Container>
|
</Table.Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -72,21 +72,6 @@ export const smtpUpdateEventSchema = smtpConfigurationEventSchema.merge(
|
||||||
|
|
||||||
export type SmtpUpdateEvent = z.infer<typeof smtpUpdateEventSchema>;
|
export type SmtpUpdateEvent = z.infer<typeof smtpUpdateEventSchema>;
|
||||||
|
|
||||||
export const smtpUpdateEventActiveStatusInputSchema = smtpConfigurationEventSchema
|
|
||||||
.pick({
|
|
||||||
active: true,
|
|
||||||
eventType: true,
|
|
||||||
})
|
|
||||||
.merge(
|
|
||||||
smtpConfigurationSchema.pick({
|
|
||||||
id: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export type SmtpUpdateEventActiveStatusInput = z.infer<
|
|
||||||
typeof smtpUpdateEventActiveStatusInputSchema
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const smtpGetEventConfigurationInputSchema = smtpConfigurationIdInputSchema.merge(
|
export const smtpGetEventConfigurationInputSchema = smtpConfigurationIdInputSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
eventType: z.enum(messageEventTypes),
|
eventType: z.enum(messageEventTypes),
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
smtpGetConfigurationsInputSchema,
|
smtpGetConfigurationsInputSchema,
|
||||||
smtpGetEventConfigurationInputSchema,
|
smtpGetEventConfigurationInputSchema,
|
||||||
smtpUpdateBasicInformationSchema,
|
smtpUpdateBasicInformationSchema,
|
||||||
smtpUpdateEventActiveStatusInputSchema,
|
|
||||||
smtpUpdateEventArraySchema,
|
smtpUpdateEventArraySchema,
|
||||||
smtpUpdateEventSchema,
|
smtpUpdateEventSchema,
|
||||||
smtpUpdateSenderSchema,
|
smtpUpdateSenderSchema,
|
||||||
|
@ -29,17 +28,22 @@ export const throwTrpcErrorFromConfigurationServiceError = (
|
||||||
case "CONFIGURATION_NOT_FOUND":
|
case "CONFIGURATION_NOT_FOUND":
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
message: "Configuration not found",
|
message: "Configuration not found.",
|
||||||
});
|
});
|
||||||
case "EVENT_CONFIGURATION_NOT_FOUND":
|
case "EVENT_CONFIGURATION_NOT_FOUND":
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "NOT_FOUND",
|
code: "NOT_FOUND",
|
||||||
message: "Event configuration not found",
|
message: "Event configuration not found.",
|
||||||
});
|
});
|
||||||
case "CANT_FETCH":
|
case "CANT_FETCH":
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: "Can't fetch configuration",
|
message: "Can't fetch configuration.",
|
||||||
|
});
|
||||||
|
case "WRONG_SALEOR_VERSION":
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "Feature you are trying to use is not supported in this version of Saleor.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +88,7 @@ export const smtpConfigurationRouter = router({
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
createConfiguration: protectedWithConfigurationServices
|
createConfiguration: protectedWithConfigurationServices
|
||||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"], updateWebhooks: true })
|
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||||
.input(smtpCreateConfigurationInputSchema)
|
.input(smtpCreateConfigurationInputSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
||||||
|
@ -95,7 +99,11 @@ export const smtpConfigurationRouter = router({
|
||||||
...input,
|
...input,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await ctx.smtpConfigurationService.createConfiguration(newConfiguration);
|
try {
|
||||||
|
return await ctx.smtpConfigurationService.createConfiguration(newConfiguration);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("erroro", e);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
deleteConfiguration: protectedWithConfigurationServices
|
deleteConfiguration: protectedWithConfigurationServices
|
||||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"], updateWebhooks: true })
|
.meta({ requiredClientPermissions: ["MANAGE_APPS"], updateWebhooks: true })
|
||||||
|
@ -260,25 +268,6 @@ export const smtpConfigurationRouter = router({
|
||||||
throwTrpcErrorFromConfigurationServiceError(e);
|
throwTrpcErrorFromConfigurationServiceError(e);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
updateEventActiveStatus: protectedWithConfigurationServices
|
|
||||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"], updateWebhooks: true })
|
|
||||||
.input(smtpUpdateEventActiveStatusInputSchema)
|
|
||||||
.mutation(async ({ ctx, input }) => {
|
|
||||||
const logger = createLogger({ saleorApiUrl: ctx.saleorApiUrl });
|
|
||||||
|
|
||||||
logger.debug(input, "mjmlConfigurationRouter.updateEventActiveStatus called");
|
|
||||||
try {
|
|
||||||
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
|
||||||
configurationId: input.id,
|
|
||||||
eventType: input.eventType,
|
|
||||||
eventConfiguration: {
|
|
||||||
active: input.active,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
throwTrpcErrorFromConfigurationServiceError(e);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
updateEventArray: protectedWithConfigurationServices
|
updateEventArray: protectedWithConfigurationServices
|
||||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"], updateWebhooks: true })
|
.meta({ requiredClientPermissions: ["MANAGE_APPS"], updateWebhooks: true })
|
||||||
.input(smtpUpdateEventArraySchema)
|
.input(smtpUpdateEventArraySchema)
|
||||||
|
@ -287,20 +276,17 @@ export const smtpConfigurationRouter = router({
|
||||||
|
|
||||||
logger.debug(input, "smtpConfigurationRouter.updateEventArray called");
|
logger.debug(input, "smtpConfigurationRouter.updateEventArray called");
|
||||||
|
|
||||||
return await Promise.all(
|
try {
|
||||||
input.events.map(async (event) => {
|
const configuration = await ctx.smtpConfigurationService.getConfiguration({
|
||||||
const { eventType, ...eventConfiguration } = event;
|
id: input.configurationId,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
await ctx.smtpConfigurationService.updateConfiguration({
|
||||||
return await ctx.smtpConfigurationService.updateEventConfiguration({
|
...configuration,
|
||||||
configurationId: input.configurationId,
|
events: input.events,
|
||||||
eventType,
|
});
|
||||||
eventConfiguration,
|
} catch (e) {
|
||||||
});
|
throwTrpcErrorFromConfigurationServiceError(e);
|
||||||
} catch (e) {
|
}
|
||||||
throwTrpcErrorFromConfigurationServiceError(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { SmtpConfigurationService } from "./smtp-configuration.service";
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||||
import { SmtpPrivateMetadataManager } from "./smtp-metadata-manager";
|
import { SmtpPrivateMetadataManager } from "./smtp-metadata-manager";
|
||||||
import { SmtpConfig } from "./smtp-config-schema";
|
import { SmtpConfig } from "./smtp-config-schema";
|
||||||
|
import { FeatureFlagService } from "../../feature-flag-service/feature-flag-service";
|
||||||
|
import { Client } from "urql";
|
||||||
|
|
||||||
const mockSaleorApiUrl = "https://demo.saleor.io/graphql/";
|
const mockSaleorApiUrl = "https://demo.saleor.io/graphql/";
|
||||||
|
|
||||||
|
@ -91,6 +93,12 @@ const validConfig: SmtpConfig = {
|
||||||
template: "template",
|
template: "template",
|
||||||
subject: "Account deletion",
|
subject: "Account deletion",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
eventType: "GIFT_CARD_SENT",
|
||||||
|
template: "template",
|
||||||
|
subject: "Gift card sent",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
smtpUser: "John",
|
smtpUser: "John",
|
||||||
smtpPassword: "securepassword",
|
smtpPassword: "securepassword",
|
||||||
|
@ -196,6 +204,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
||||||
|
|
||||||
new SmtpConfigurationService({
|
new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -213,6 +225,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(validConfig);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(validConfig);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -235,6 +251,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(emptyConfigRoot);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(emptyConfigRoot);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -256,6 +276,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
|
|
||||||
// Service initialized with empty configuration
|
// Service initialized with empty configuration
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: emptyConfigRoot,
|
initialData: emptyConfigRoot,
|
||||||
});
|
});
|
||||||
|
@ -270,6 +294,28 @@ describe("SmtpConfigurationService", function () {
|
||||||
expect(await service.getConfigurationRoot());
|
expect(await service.getConfigurationRoot());
|
||||||
expect(getConfigMock).toBeCalledTimes(0);
|
expect(getConfigMock).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Operation should be rejected, when attempting to save event not available according to feature flag", async () => {
|
||||||
|
const configurator = new SmtpPrivateMetadataManager(
|
||||||
|
null as unknown as SettingsManager,
|
||||||
|
mockSaleorApiUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
// Service initialized with empty configuration
|
||||||
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.12.0", // This version does not support Gift Card event
|
||||||
|
}),
|
||||||
|
metadataManager: configurator,
|
||||||
|
initialData: emptyConfigRoot,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set configuration
|
||||||
|
await expect(async () => await service.setConfigurationRoot(validConfig)).rejects.toThrow(
|
||||||
|
"Gift card sent event is not supported for this Saleor version"
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getConfiguration", () => {
|
describe("getConfiguration", () => {
|
||||||
|
@ -280,6 +326,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -296,6 +346,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -314,6 +368,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: emptyConfigRoot,
|
initialData: emptyConfigRoot,
|
||||||
});
|
});
|
||||||
|
@ -328,6 +386,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -349,6 +411,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: emptyConfigRoot,
|
initialData: emptyConfigRoot,
|
||||||
});
|
});
|
||||||
|
@ -378,6 +444,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -408,6 +478,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
const getConfigMock = vi.spyOn(configurator, "getConfig").mockResolvedValue(undefined);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -431,6 +505,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -460,6 +538,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -483,6 +565,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -502,6 +588,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
);
|
);
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -533,6 +623,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
@ -565,6 +659,10 @@ describe("SmtpConfigurationService", function () {
|
||||||
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
const setConfigMock = vi.spyOn(configurator, "setConfig").mockResolvedValue();
|
||||||
|
|
||||||
const service = new SmtpConfigurationService({
|
const service = new SmtpConfigurationService({
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
metadataManager: configurator,
|
metadataManager: configurator,
|
||||||
initialData: { ...validConfig },
|
initialData: { ...validConfig },
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { MessageEventTypes } from "../../event-handlers/message-event-types";
|
||||||
import { generateRandomId } from "../../../lib/generate-random-id";
|
import { generateRandomId } from "../../../lib/generate-random-id";
|
||||||
import { smtpDefaultEmptyConfigurations } from "./smtp-default-empty-configurations";
|
import { smtpDefaultEmptyConfigurations } from "./smtp-default-empty-configurations";
|
||||||
import { filterConfigurations } from "../../app-configuration/filter-configurations";
|
import { filterConfigurations } from "../../app-configuration/filter-configurations";
|
||||||
|
import { FeatureFlagService } from "../../feature-flag-service/feature-flag-service";
|
||||||
|
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
service: "SmtpConfigurationService",
|
service: "SmtpConfigurationService",
|
||||||
|
@ -14,7 +15,8 @@ export type SmtpConfigurationServiceErrorType =
|
||||||
| "OTHER"
|
| "OTHER"
|
||||||
| "CONFIGURATION_NOT_FOUND"
|
| "CONFIGURATION_NOT_FOUND"
|
||||||
| "EVENT_CONFIGURATION_NOT_FOUND"
|
| "EVENT_CONFIGURATION_NOT_FOUND"
|
||||||
| "CANT_FETCH";
|
| "CANT_FETCH"
|
||||||
|
| "WRONG_SALEOR_VERSION";
|
||||||
|
|
||||||
export interface ConfigurationPartial extends Partial<SmtpConfiguration> {
|
export interface ConfigurationPartial extends Partial<SmtpConfiguration> {
|
||||||
id: SmtpConfiguration["id"];
|
id: SmtpConfiguration["id"];
|
||||||
|
@ -41,13 +43,20 @@ export interface FilterConfigurationsArgs {
|
||||||
export class SmtpConfigurationService {
|
export class SmtpConfigurationService {
|
||||||
private configurationData?: SmtpConfig;
|
private configurationData?: SmtpConfig;
|
||||||
private metadataConfigurator: SmtpPrivateMetadataManager;
|
private metadataConfigurator: SmtpPrivateMetadataManager;
|
||||||
|
private featureFlagService: FeatureFlagService;
|
||||||
|
|
||||||
constructor(args: { metadataManager: SmtpPrivateMetadataManager; initialData?: SmtpConfig }) {
|
constructor(args: {
|
||||||
|
metadataManager: SmtpPrivateMetadataManager;
|
||||||
|
initialData?: SmtpConfig;
|
||||||
|
featureFlagService: FeatureFlagService;
|
||||||
|
}) {
|
||||||
this.metadataConfigurator = args.metadataManager;
|
this.metadataConfigurator = args.metadataManager;
|
||||||
|
|
||||||
if (args.initialData) {
|
if (args.initialData) {
|
||||||
this.configurationData = args.initialData;
|
this.configurationData = args.initialData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.featureFlagService = args.featureFlagService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,10 +103,35 @@ export class SmtpConfigurationService {
|
||||||
return this.configurationData;
|
return this.configurationData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private containActiveGiftCardEvent(config: SmtpConfig) {
|
||||||
|
for (const configuration of config.configurations) {
|
||||||
|
const giftCardSentEvent = configuration.events.find(
|
||||||
|
(event) => event.eventType === "GIFT_CARD_SENT"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (giftCardSentEvent?.active) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Saves configuration to Saleor API and cache it
|
// Saves configuration to Saleor API and cache it
|
||||||
async setConfigurationRoot(config: SmtpConfig) {
|
async setConfigurationRoot(config: SmtpConfig) {
|
||||||
logger.debug("Set configuration root");
|
logger.debug("Validate configuration before sending it to the Saleor API");
|
||||||
|
const availableFeatures = await this.featureFlagService.getFeatureFlags();
|
||||||
|
|
||||||
|
if (!availableFeatures.giftCardSentEvent && this.containActiveGiftCardEvent(config)) {
|
||||||
|
logger.error(
|
||||||
|
"Attempt to enable gift card sent event for unsupported Saleor version. Aborting configuration update."
|
||||||
|
);
|
||||||
|
throw new SmtpConfigurationServiceError(
|
||||||
|
"Gift card sent event is not supported for this Saleor version",
|
||||||
|
"WRONG_SALEOR_VERSION"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Set configuration root");
|
||||||
this.configurationData = config;
|
this.configurationData = config;
|
||||||
await this.pushConfiguration();
|
await this.pushConfiguration();
|
||||||
}
|
}
|
||||||
|
@ -169,7 +203,7 @@ export class SmtpConfigurationService {
|
||||||
|
|
||||||
updatedConfigRoot.configurations[configurationIndex] = updatedConfiguration;
|
updatedConfigRoot.configurations[configurationIndex] = updatedConfiguration;
|
||||||
|
|
||||||
this.setConfigurationRoot(updatedConfigRoot);
|
await this.setConfigurationRoot(updatedConfigRoot);
|
||||||
|
|
||||||
return updatedConfiguration;
|
return updatedConfiguration;
|
||||||
}
|
}
|
||||||
|
@ -189,7 +223,7 @@ export class SmtpConfigurationService {
|
||||||
(configuration) => configuration.id !== id
|
(configuration) => configuration.id !== id
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setConfigurationRoot(updatedConfigRoot);
|
await this.setConfigurationRoot(updatedConfigRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -170,6 +170,22 @@ const defaultInvoiceSentMjmlTemplate = `<mjml>
|
||||||
</mj-body>
|
</mj-body>
|
||||||
</mjml>`;
|
</mjml>`;
|
||||||
|
|
||||||
|
// TODO: Improve the template
|
||||||
|
const defaultGiftCardSentMjmlTemplate = `<mjml>
|
||||||
|
<mj-body>
|
||||||
|
<mj-section>
|
||||||
|
<mj-column>
|
||||||
|
<mj-text font-size="16px">
|
||||||
|
Hi!
|
||||||
|
</mj-text>
|
||||||
|
<mj-text>
|
||||||
|
Heres your gift card
|
||||||
|
</mj-text>
|
||||||
|
</mj-column>
|
||||||
|
</mj-section>
|
||||||
|
</mj-body>
|
||||||
|
</mjml>`;
|
||||||
|
|
||||||
const defaultAccountConfirmationMjmlTemplate = `<mjml>
|
const defaultAccountConfirmationMjmlTemplate = `<mjml>
|
||||||
<mj-body>
|
<mj-body>
|
||||||
<mj-section>
|
<mj-section>
|
||||||
|
@ -258,29 +274,31 @@ const defaultAccountDeleteMjmlTemplate = `<mjml>
|
||||||
</mjml>`;
|
</mjml>`;
|
||||||
|
|
||||||
export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
export const defaultMjmlTemplates: Record<MessageEventTypes, string> = {
|
||||||
|
ACCOUNT_CHANGE_EMAIL_CONFIRM: defaultAccountChangeEmailConfirmationMjmlTemplate,
|
||||||
|
ACCOUNT_CHANGE_EMAIL_REQUEST: defaultAccountChangeEmailRequestMjmlTemplate,
|
||||||
|
ACCOUNT_CONFIRMATION: defaultAccountConfirmationMjmlTemplate,
|
||||||
|
ACCOUNT_DELETE: defaultAccountDeleteMjmlTemplate,
|
||||||
|
ACCOUNT_PASSWORD_RESET: defaultAccountPasswordResetMjmlTemplate,
|
||||||
|
GIFT_CARD_SENT: defaultGiftCardSentMjmlTemplate,
|
||||||
|
INVOICE_SENT: defaultInvoiceSentMjmlTemplate,
|
||||||
|
ORDER_CANCELLED: defaultOrderCancelledMjmlTemplate,
|
||||||
|
ORDER_CONFIRMED: defaultOrderConfirmedMjmlTemplate,
|
||||||
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
ORDER_CREATED: defaultOrderCreatedMjmlTemplate,
|
||||||
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
ORDER_FULFILLED: defaultOrderFulfilledMjmlTemplate,
|
||||||
ORDER_CONFIRMED: defaultOrderConfirmedMjmlTemplate,
|
|
||||||
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
ORDER_FULLY_PAID: defaultOrderFullyPaidMjmlTemplate,
|
||||||
ORDER_CANCELLED: defaultOrderCancelledMjmlTemplate,
|
|
||||||
INVOICE_SENT: defaultInvoiceSentMjmlTemplate,
|
|
||||||
ACCOUNT_CONFIRMATION: defaultAccountConfirmationMjmlTemplate,
|
|
||||||
ACCOUNT_PASSWORD_RESET: defaultAccountPasswordResetMjmlTemplate,
|
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: defaultAccountChangeEmailRequestMjmlTemplate,
|
|
||||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: defaultAccountChangeEmailConfirmationMjmlTemplate,
|
|
||||||
ACCOUNT_DELETE: defaultAccountDeleteMjmlTemplate,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
export const defaultMjmlSubjectTemplates: Record<MessageEventTypes, string> = {
|
||||||
|
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Email change confirmation",
|
||||||
|
ACCOUNT_CHANGE_EMAIL_REQUEST: "Email change request",
|
||||||
|
ACCOUNT_CONFIRMATION: "Account activation",
|
||||||
|
ACCOUNT_DELETE: "Account deletion",
|
||||||
|
ACCOUNT_PASSWORD_RESET: "Password reset request",
|
||||||
|
GIFT_CARD_SENT: "Gift card",
|
||||||
|
INVOICE_SENT: "New invoice has been created",
|
||||||
|
ORDER_CANCELLED: "Order {{ order.number }} has been cancelled",
|
||||||
|
ORDER_CONFIRMED: "Order {{ order.number }} has been confirmed",
|
||||||
ORDER_CREATED: "Order {{ order.number }} has been created",
|
ORDER_CREATED: "Order {{ order.number }} has been created",
|
||||||
ORDER_FULFILLED: "Order {{ order.number }} has been fulfilled",
|
ORDER_FULFILLED: "Order {{ order.number }} has been fulfilled",
|
||||||
ORDER_CONFIRMED: "Order {{ order.number }} has been confirmed",
|
|
||||||
ORDER_FULLY_PAID: "Order {{ order.number }} has been fully paid",
|
ORDER_FULLY_PAID: "Order {{ order.number }} has been fully paid",
|
||||||
ORDER_CANCELLED: "Order {{ order.number }} has been cancelled",
|
|
||||||
INVOICE_SENT: "New invoice has been created",
|
|
||||||
ACCOUNT_CONFIRMATION: "Account activation",
|
|
||||||
ACCOUNT_PASSWORD_RESET: "Password reset request",
|
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: "Email change request",
|
|
||||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: "Email change confirmation",
|
|
||||||
ACCOUNT_DELETE: "Account deletion",
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { SmtpConfiguration } from "../configuration/smtp-config-schema";
|
import { SmtpConfiguration } from "../configuration/smtp-config-schema";
|
||||||
import { BoxWithBorder } from "../../../components/box-with-border";
|
import { BoxWithBorder } from "../../../components/box-with-border";
|
||||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
import { Box, Button, Text, Tooltip } from "@saleor/macaw-ui/next";
|
||||||
import { defaultPadding } from "../../../components/ui-defaults";
|
import { defaultPadding } from "../../../components/ui-defaults";
|
||||||
import { SectionWithDescription } from "../../../components/section-with-description";
|
import { SectionWithDescription } from "../../../components/section-with-description";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
@ -28,6 +28,8 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
||||||
const { notifySuccess, notifyError } = useDashboardNotification();
|
const { notifySuccess, notifyError } = useDashboardNotification();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { data: featureFlags } = trpcClient.app.featureFlags.useQuery();
|
||||||
|
|
||||||
// Sort events by displayed label
|
// Sort events by displayed label
|
||||||
const eventsSorted = configuration.events.sort((a, b) =>
|
const eventsSorted = configuration.events.sort((a, b) =>
|
||||||
messageEventTypesLabels[a.eventType].localeCompare(messageEventTypesLabels[b.eventType])
|
messageEventTypesLabels[a.eventType].localeCompare(messageEventTypesLabels[b.eventType])
|
||||||
|
@ -84,29 +86,49 @@ export const SmtpEventsSection = ({ configuration }: SmtpEventsSectionProps) =>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
</Table.Header>
|
</Table.Header>
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{eventsSorted.map((event, index) => (
|
{eventsSorted.map((event, index) => {
|
||||||
<Table.Row key={event.eventType}>
|
const isUnsupported =
|
||||||
<Table.Cell>
|
!featureFlags?.giftCardSentEvent && event.eventType === "GIFT_CARD_SENT";
|
||||||
<input type="checkbox" {...register(`events.${index}.active`)} />
|
|
||||||
</Table.Cell>
|
return (
|
||||||
<Table.Cell>
|
<Table.Row key={event.eventType}>
|
||||||
<Text>{messageEventTypesLabels[event.eventType]}</Text>
|
<Table.Cell>
|
||||||
</Table.Cell>
|
<Tooltip>
|
||||||
<Table.Cell>
|
<Tooltip.Trigger>
|
||||||
<Button
|
<input
|
||||||
variant="tertiary"
|
type="checkbox"
|
||||||
size="small"
|
{...register(`events.${index}.active`)}
|
||||||
onClick={() => {
|
disabled={isUnsupported}
|
||||||
router.push(
|
/>
|
||||||
smtpUrls.eventConfiguration(configuration.id, event.eventType)
|
</Tooltip.Trigger>
|
||||||
);
|
{isUnsupported && (
|
||||||
}}
|
<Tooltip.Content side="left">
|
||||||
>
|
Event is available in Saleor version 3.13 and above only.
|
||||||
Edit template
|
<Tooltip.Arrow />
|
||||||
</Button>
|
</Tooltip.Content>
|
||||||
</Table.Cell>
|
)}
|
||||||
</Table.Row>
|
</Tooltip>
|
||||||
))}
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Text>{messageEventTypesLabels[event.eventType]}</Text>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
router.push(
|
||||||
|
smtpUrls.eventConfiguration(configuration.id, event.eventType)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
disabled={isUnsupported}
|
||||||
|
>
|
||||||
|
Edit template
|
||||||
|
</Button>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table.Container>
|
</Table.Container>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { SmtpPrivateMetadataManager } from "../smtp/configuration/smtp-metadata-
|
||||||
import { syncWebhookStatus } from "../webhook-management/sync-webhook-status";
|
import { syncWebhookStatus } from "../webhook-management/sync-webhook-status";
|
||||||
import { protectedClientProcedure } from "./protected-client-procedure";
|
import { protectedClientProcedure } from "./protected-client-procedure";
|
||||||
import { WebhookManagementService } from "../webhook-management/webhook-management-service";
|
import { WebhookManagementService } from "../webhook-management/webhook-management-service";
|
||||||
|
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
||||||
|
|
||||||
const logger = createLogger({ name: "protectedWithConfigurationServices middleware" });
|
const logger = createLogger({ name: "protectedWithConfigurationServices middleware" });
|
||||||
|
|
||||||
|
@ -19,11 +20,21 @@ const logger = createLogger({ name: "protectedWithConfigurationServices middlewa
|
||||||
*/
|
*/
|
||||||
export const protectedWithConfigurationServices = protectedClientProcedure.use(
|
export const protectedWithConfigurationServices = protectedClientProcedure.use(
|
||||||
async ({ next, ctx, meta }) => {
|
async ({ next, ctx, meta }) => {
|
||||||
|
/*
|
||||||
|
* TODO: When App Bridge will add Saleor Version do the context,
|
||||||
|
* extract it from there and pass it to the service constructor.
|
||||||
|
* It will reduce additional call to the API.
|
||||||
|
*/
|
||||||
|
const featureFlagService = new FeatureFlagService({
|
||||||
|
client: ctx.apiClient,
|
||||||
|
});
|
||||||
|
|
||||||
const smtpConfigurationService = new SmtpConfigurationService({
|
const smtpConfigurationService = new SmtpConfigurationService({
|
||||||
metadataManager: new SmtpPrivateMetadataManager(
|
metadataManager: new SmtpPrivateMetadataManager(
|
||||||
createSettingsManager(ctx.apiClient, ctx.appId!),
|
createSettingsManager(ctx.apiClient, ctx.appId!),
|
||||||
ctx.saleorApiUrl
|
ctx.saleorApiUrl
|
||||||
),
|
),
|
||||||
|
featureFlagService,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sendgridConfigurationService = new SendgridConfigurationService({
|
const sendgridConfigurationService = new SendgridConfigurationService({
|
||||||
|
@ -31,19 +42,25 @@ export const protectedWithConfigurationServices = protectedClientProcedure.use(
|
||||||
createSettingsManager(ctx.apiClient, ctx.appId!),
|
createSettingsManager(ctx.apiClient, ctx.appId!),
|
||||||
ctx.saleorApiUrl
|
ctx.saleorApiUrl
|
||||||
),
|
),
|
||||||
|
featureFlagService,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await next({
|
const result = await next({
|
||||||
ctx: {
|
ctx: {
|
||||||
smtpConfigurationService,
|
smtpConfigurationService,
|
||||||
sendgridConfigurationService,
|
sendgridConfigurationService,
|
||||||
|
featureFlagService,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (meta?.updateWebhooks) {
|
if (meta?.updateWebhooks) {
|
||||||
logger.debug("Updating webhooks");
|
logger.debug("Updating webhooks");
|
||||||
|
|
||||||
const webhookManagementService = new WebhookManagementService(ctx.baseUrl, ctx.apiClient);
|
const webhookManagementService = new WebhookManagementService({
|
||||||
|
appBaseUrl: ctx.baseUrl,
|
||||||
|
client: ctx.apiClient,
|
||||||
|
featureFlagService: featureFlagService,
|
||||||
|
});
|
||||||
|
|
||||||
await syncWebhookStatus({
|
await syncWebhookStatus({
|
||||||
sendgridConfigurationService,
|
sendgridConfigurationService,
|
||||||
|
|
|
@ -2,8 +2,10 @@ import { channelsRouter } from "../channels/channels.router";
|
||||||
import { router } from "./trpc-server";
|
import { router } from "./trpc-server";
|
||||||
import { smtpConfigurationRouter } from "../smtp/configuration/smtp-configuration.router";
|
import { smtpConfigurationRouter } from "../smtp/configuration/smtp-configuration.router";
|
||||||
import { sendgridConfigurationRouter } from "../sendgrid/configuration/sendgrid-configuration.router";
|
import { sendgridConfigurationRouter } from "../sendgrid/configuration/sendgrid-configuration.router";
|
||||||
|
import { appConfigurationRouter } from "../app-configuration/app-configuration.router";
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
|
app: appConfigurationRouter,
|
||||||
channels: channelsRouter,
|
channels: channelsRouter,
|
||||||
smtpConfiguration: smtpConfigurationRouter,
|
smtpConfiguration: smtpConfigurationRouter,
|
||||||
sendgridConfiguration: sendgridConfigurationRouter,
|
sendgridConfiguration: sendgridConfigurationRouter,
|
||||||
|
|
|
@ -177,6 +177,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -194,6 +195,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -216,6 +218,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -246,6 +249,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -282,6 +286,7 @@ describe("getWebhookStatusesFromConfigurations", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,13 +13,15 @@ export const getWebhookStatusesFromConfigurations = ({
|
||||||
smtpConfigurations: SmtpConfiguration[];
|
smtpConfigurations: SmtpConfiguration[];
|
||||||
sendgridConfigurations: SendgridConfiguration[];
|
sendgridConfigurations: SendgridConfiguration[];
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: this dict should be generated in one place instead of manually edited
|
||||||
const statuses: Record<AppWebhook, boolean> = {
|
const statuses: Record<AppWebhook, boolean> = {
|
||||||
|
giftCardSentWebhook: false,
|
||||||
invoiceSentWebhook: false,
|
invoiceSentWebhook: false,
|
||||||
notifyWebhook: false,
|
notifyWebhook: false,
|
||||||
orderCancelledWebhook: false,
|
orderCancelledWebhook: false,
|
||||||
orderConfirmedWebhook: false,
|
orderConfirmedWebhook: false,
|
||||||
orderFulfilledWebhook: false,
|
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
|
orderFulfilledWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ describe("syncWebhookStatus", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getWebhooksStatusMock = vi
|
const getWebhooksStatusMock = vi
|
||||||
|
@ -76,6 +77,7 @@ describe("syncWebhookStatus", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await syncWebhookStatus({
|
await syncWebhookStatus({
|
||||||
|
@ -98,6 +100,7 @@ describe("syncWebhookStatus", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getWebhooksStatusMock = vi
|
const getWebhooksStatusMock = vi
|
||||||
|
@ -110,6 +113,7 @@ describe("syncWebhookStatus", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await syncWebhookStatus({
|
await syncWebhookStatus({
|
||||||
|
@ -132,6 +136,7 @@ describe("syncWebhookStatus", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getWebhooksStatusMock = vi
|
const getWebhooksStatusMock = vi
|
||||||
|
@ -144,6 +149,7 @@ describe("syncWebhookStatus", function () {
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
await syncWebhookStatus({
|
await syncWebhookStatus({
|
||||||
|
|
|
@ -5,8 +5,8 @@ import * as operationExports from "./api-operations";
|
||||||
import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql";
|
import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql";
|
||||||
import { invoiceSentWebhook } from "../../pages/api/webhooks/invoice-sent";
|
import { invoiceSentWebhook } from "../../pages/api/webhooks/invoice-sent";
|
||||||
import { orderCancelledWebhook } from "../../pages/api/webhooks/order-cancelled";
|
import { orderCancelledWebhook } from "../../pages/api/webhooks/order-cancelled";
|
||||||
|
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
||||||
const mockSaleorApiUrl = "https://demo.saleor.io/graphql/";
|
import { giftCardSentWebhook } from "../../pages/api/webhooks/gift-card-sent";
|
||||||
|
|
||||||
describe("WebhookManagementService", function () {
|
describe("WebhookManagementService", function () {
|
||||||
const mockedClient = {} as Client;
|
const mockedClient = {} as Client;
|
||||||
|
@ -16,10 +16,14 @@ describe("WebhookManagementService", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("API should be called, when getWebhooks is used", async () => {
|
it("API should be called, when getWebhooks is used", async () => {
|
||||||
const webhookManagementService = new WebhookManagementService(
|
const webhookManagementService = new WebhookManagementService({
|
||||||
"https://example.com",
|
client: mockedClient,
|
||||||
mockedClient
|
appBaseUrl: "https://example.com",
|
||||||
);
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
const fetchAppWebhooksMock = vi.spyOn(operationExports, "fetchAppWebhooks").mockResolvedValue([
|
const fetchAppWebhooksMock = vi.spyOn(operationExports, "fetchAppWebhooks").mockResolvedValue([
|
||||||
{
|
{
|
||||||
|
@ -43,11 +47,15 @@ describe("WebhookManagementService", function () {
|
||||||
expect(fetchAppWebhooksMock).toBeCalledTimes(1);
|
expect(fetchAppWebhooksMock).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Webhook statuses should be active, when whists in API and active", async () => {
|
it("Webhook statuses should be active, when webhook is created in the API and set to active", async () => {
|
||||||
const webhookManagementService = new WebhookManagementService(
|
const webhookManagementService = new WebhookManagementService({
|
||||||
"https://example.com",
|
client: mockedClient,
|
||||||
mockedClient
|
appBaseUrl: "https://example.com",
|
||||||
);
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
const fetchAppWebhooksMock = vi.spyOn(operationExports, "fetchAppWebhooks").mockResolvedValue([
|
const fetchAppWebhooksMock = vi.spyOn(operationExports, "fetchAppWebhooks").mockResolvedValue([
|
||||||
{
|
{
|
||||||
|
@ -76,15 +84,20 @@ describe("WebhookManagementService", function () {
|
||||||
orderCreatedWebhook: false,
|
orderCreatedWebhook: false,
|
||||||
orderFulfilledWebhook: false,
|
orderFulfilledWebhook: false,
|
||||||
orderFullyPaidWebhook: false,
|
orderFullyPaidWebhook: false,
|
||||||
|
giftCardSentWebhook: false,
|
||||||
});
|
});
|
||||||
expect(fetchAppWebhooksMock).toBeCalledTimes(1);
|
expect(fetchAppWebhooksMock).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Webhook should be created using the API, when requested", async () => {
|
it("Webhook should be created using the API, when requested", async () => {
|
||||||
const webhookManagementService = new WebhookManagementService(
|
const webhookManagementService = new WebhookManagementService({
|
||||||
"https://example.com",
|
client: mockedClient,
|
||||||
mockedClient
|
appBaseUrl: "https://example.com",
|
||||||
);
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
const createAppWebhookMock = vi.spyOn(operationExports, "createAppWebhook").mockResolvedValue({
|
const createAppWebhookMock = vi.spyOn(operationExports, "createAppWebhook").mockResolvedValue({
|
||||||
id: "1",
|
id: "1",
|
||||||
|
@ -113,11 +126,33 @@ describe("WebhookManagementService", function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Should throw error, when attempting to create gift card sent webhook in unsupported environment", async () => {
|
||||||
|
const webhookManagementService = new WebhookManagementService({
|
||||||
|
client: mockedClient,
|
||||||
|
appBaseUrl: "https://example.com",
|
||||||
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.12.0", // Gift card sent webhook is supported from 3.13.0
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
async () =>
|
||||||
|
await webhookManagementService.createWebhook({
|
||||||
|
webhook: "giftCardSentWebhook",
|
||||||
|
})
|
||||||
|
).rejects.toThrow("Gift card event is not supported in this environment");
|
||||||
|
});
|
||||||
|
|
||||||
it("Webhook should be deleted using the API, when requested", async () => {
|
it("Webhook should be deleted using the API, when requested", async () => {
|
||||||
const webhookManagementService = new WebhookManagementService(
|
const webhookManagementService = new WebhookManagementService({
|
||||||
"https://example.com",
|
client: mockedClient,
|
||||||
mockedClient
|
appBaseUrl: "https://example.com",
|
||||||
);
|
featureFlagService: new FeatureFlagService({
|
||||||
|
client: {} as Client,
|
||||||
|
saleorVersion: "3.14.0",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
vi.spyOn(operationExports, "fetchAppWebhooks").mockResolvedValue([
|
vi.spyOn(operationExports, "fetchAppWebhooks").mockResolvedValue([
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,25 +10,30 @@ import { notifyWebhook } from "../../pages/api/webhooks/notify";
|
||||||
import { MessageEventTypes } from "../event-handlers/message-event-types";
|
import { MessageEventTypes } from "../event-handlers/message-event-types";
|
||||||
import { createLogger } from "@saleor/apps-shared";
|
import { createLogger } from "@saleor/apps-shared";
|
||||||
import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql";
|
import { WebhookEventTypeAsyncEnum } from "../../../generated/graphql";
|
||||||
|
import { giftCardSentWebhook } from "../../pages/api/webhooks/gift-card-sent";
|
||||||
|
import { FeatureFlagService } from "../feature-flag-service/feature-flag-service";
|
||||||
|
import { FeatureFlagsState } from "../feature-flag-service/get-feature-flags";
|
||||||
|
|
||||||
export const AppWebhooks = {
|
export const AppWebhooks = {
|
||||||
orderCreatedWebhook,
|
giftCardSentWebhook,
|
||||||
orderFulfilledWebhook,
|
|
||||||
orderConfirmedWebhook,
|
|
||||||
orderCancelledWebhook,
|
|
||||||
orderFullyPaidWebhook,
|
|
||||||
invoiceSentWebhook,
|
invoiceSentWebhook,
|
||||||
notifyWebhook,
|
notifyWebhook,
|
||||||
|
orderCancelledWebhook,
|
||||||
|
orderConfirmedWebhook,
|
||||||
|
orderCreatedWebhook,
|
||||||
|
orderFulfilledWebhook,
|
||||||
|
orderFullyPaidWebhook,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppWebhook = keyof typeof AppWebhooks;
|
export type AppWebhook = keyof typeof AppWebhooks;
|
||||||
|
|
||||||
export const eventToWebhookMapping: Record<MessageEventTypes, AppWebhook> = {
|
export const eventToWebhookMapping: Record<MessageEventTypes, AppWebhook> = {
|
||||||
|
ACCOUNT_CHANGE_EMAIL_CONFIRM: "notifyWebhook",
|
||||||
|
ACCOUNT_CHANGE_EMAIL_REQUEST: "notifyWebhook",
|
||||||
ACCOUNT_CONFIRMATION: "notifyWebhook",
|
ACCOUNT_CONFIRMATION: "notifyWebhook",
|
||||||
ACCOUNT_DELETE: "notifyWebhook",
|
ACCOUNT_DELETE: "notifyWebhook",
|
||||||
ACCOUNT_PASSWORD_RESET: "notifyWebhook",
|
ACCOUNT_PASSWORD_RESET: "notifyWebhook",
|
||||||
ACCOUNT_CHANGE_EMAIL_REQUEST: "notifyWebhook",
|
GIFT_CARD_SENT: "giftCardSentWebhook",
|
||||||
ACCOUNT_CHANGE_EMAIL_CONFIRM: "notifyWebhook",
|
|
||||||
INVOICE_SENT: "invoiceSentWebhook",
|
INVOICE_SENT: "invoiceSentWebhook",
|
||||||
ORDER_CANCELLED: "orderCancelledWebhook",
|
ORDER_CANCELLED: "orderCancelledWebhook",
|
||||||
ORDER_CONFIRMED: "orderConfirmedWebhook",
|
ORDER_CONFIRMED: "orderConfirmedWebhook",
|
||||||
|
@ -42,7 +47,19 @@ const logger = createLogger({
|
||||||
});
|
});
|
||||||
|
|
||||||
export class WebhookManagementService {
|
export class WebhookManagementService {
|
||||||
constructor(private appBaseUrl: string, private client: Client) {}
|
private appBaseUrl: string;
|
||||||
|
private client: Client;
|
||||||
|
private featureFlagService: FeatureFlagService;
|
||||||
|
|
||||||
|
constructor(args: {
|
||||||
|
appBaseUrl: string;
|
||||||
|
client: Client;
|
||||||
|
featureFlagService: FeatureFlagService;
|
||||||
|
}) {
|
||||||
|
this.appBaseUrl = args.appBaseUrl;
|
||||||
|
this.client = args.client;
|
||||||
|
this.featureFlagService = args.featureFlagService;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns list of webhooks registered for the App in the Saleor instance
|
// Returns list of webhooks registered for the App in the Saleor instance
|
||||||
public async getWebhooks() {
|
public async getWebhooks() {
|
||||||
|
@ -70,6 +87,13 @@ export class WebhookManagementService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createWebhook({ webhook }: { webhook: AppWebhook }) {
|
public async createWebhook({ webhook }: { webhook: AppWebhook }) {
|
||||||
|
const flags = await this.featureFlagService.getFeatureFlags();
|
||||||
|
|
||||||
|
if (!flags.giftCardSentEvent && webhook === "giftCardSentWebhook") {
|
||||||
|
logger.error(`Attempt to activate Gift Card Sent webhook despite unsupported environment`);
|
||||||
|
throw new Error("Gift card event is not supported in this environment");
|
||||||
|
}
|
||||||
|
|
||||||
const webhookManifest = AppWebhooks[webhook].getWebhookManifest(this.appBaseUrl);
|
const webhookManifest = AppWebhooks[webhook].getWebhookManifest(this.appBaseUrl);
|
||||||
|
|
||||||
const asyncWebhooks = webhookManifest.asyncEvents;
|
const asyncWebhooks = webhookManifest.asyncEvents;
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next";
|
import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next";
|
||||||
|
|
||||||
import { saleorApp } from "../../saleor-app";
|
import { REQUIRED_SALEOR_VERSION, saleorApp } from "../../saleor-app";
|
||||||
|
import {
|
||||||
|
SaleorVersionCompatibilityValidator,
|
||||||
|
createGraphQLClient,
|
||||||
|
createLogger,
|
||||||
|
} from "@saleor/apps-shared";
|
||||||
|
import { fetchSaleorVersion } from "../../modules/feature-flag-service/fetch-saleor-version";
|
||||||
|
|
||||||
const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN;
|
const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN;
|
||||||
|
|
||||||
|
@ -21,4 +27,54 @@ export default createAppRegisterHandler({
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
async onRequestVerified(req, { authData: { token, saleorApiUrl }, respondWithError }) {
|
||||||
|
const logger = createLogger({
|
||||||
|
name: "onRequestVerified",
|
||||||
|
});
|
||||||
|
|
||||||
|
let saleorVersion: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const client = createGraphQLClient({
|
||||||
|
saleorApiUrl: saleorApiUrl,
|
||||||
|
token: token,
|
||||||
|
});
|
||||||
|
|
||||||
|
saleorVersion = await fetchSaleorVersion(client);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const message = (e as Error)?.message ?? "Unknown error";
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
{ message, saleorApiUrl },
|
||||||
|
"Error during fetching saleor version in onRequestVerified handler"
|
||||||
|
);
|
||||||
|
|
||||||
|
throw respondWithError({
|
||||||
|
message: "Couldn't communicate with Saleor API",
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saleorVersion) {
|
||||||
|
logger.warn({ saleorApiUrl }, "No version returned from Saleor API");
|
||||||
|
throw respondWithError({
|
||||||
|
message: "Saleor version couldn't be fetched from the API",
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVersionValid = new SaleorVersionCompatibilityValidator(REQUIRED_SALEOR_VERSION).isValid(
|
||||||
|
saleorVersion
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isVersionValid) {
|
||||||
|
logger.info({ saleorApiUrl }, "Rejecting installation due to incompatible Saleor version");
|
||||||
|
throw respondWithError({
|
||||||
|
message: `Saleor version (${saleorVersion}) is not compatible with this app version (${REQUIRED_SALEOR_VERSION})`,
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Saleor version validated successfully");
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||||
|
import { gql } from "urql";
|
||||||
|
import { saleorApp } from "../../../saleor-app";
|
||||||
|
import { createLogger, createGraphQLClient } from "@saleor/apps-shared";
|
||||||
|
import { GiftCardSentWebhookPayloadFragment } from "../../../../generated/graphql";
|
||||||
|
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||||
|
|
||||||
|
const GiftCardSentWebhookPayload = gql`
|
||||||
|
fragment GiftCardSentWebhookPayload on GiftCardSent {
|
||||||
|
giftCard {
|
||||||
|
id
|
||||||
|
code
|
||||||
|
displayCode
|
||||||
|
last4CodeChars
|
||||||
|
created
|
||||||
|
usedByEmail
|
||||||
|
isActive
|
||||||
|
initialBalance {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
currentBalance {
|
||||||
|
currency
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
tags {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
expiryDate
|
||||||
|
lastUsedOn
|
||||||
|
usedBy {
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sentToEmail
|
||||||
|
channel
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const GiftCardSentGraphqlSubscription = gql`
|
||||||
|
${GiftCardSentWebhookPayload}
|
||||||
|
subscription GiftCardSent {
|
||||||
|
event {
|
||||||
|
...GiftCardSentWebhookPayload
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const giftCardSentWebhook = new SaleorAsyncWebhook<GiftCardSentWebhookPayloadFragment>({
|
||||||
|
name: "Gift card sent in Saleor",
|
||||||
|
webhookPath: "api/webhooks/gift-card-sent",
|
||||||
|
asyncEvent: "GIFT_CARD_SENT",
|
||||||
|
apl: saleorApp.apl,
|
||||||
|
subscriptionQueryAst: GiftCardSentGraphqlSubscription,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handler: NextWebhookApiHandler<GiftCardSentWebhookPayloadFragment> = async (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
context
|
||||||
|
) => {
|
||||||
|
const logger = createLogger({
|
||||||
|
webhook: giftCardSentWebhook.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug("Webhook received");
|
||||||
|
|
||||||
|
const { payload, authData } = context;
|
||||||
|
const { giftCard } = payload;
|
||||||
|
|
||||||
|
if (!giftCard) {
|
||||||
|
logger.error("No gift card data payload");
|
||||||
|
return res.status(200).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientEmail = payload.sentToEmail;
|
||||||
|
|
||||||
|
if (!recipientEmail?.length) {
|
||||||
|
logger.error(`The gift card ${giftCard.id} had no email recipient set. Aborting.`);
|
||||||
|
return res
|
||||||
|
.status(200)
|
||||||
|
.json({ error: "Email recipient has not been specified in the event payload." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = payload.channel;
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
logger.error("No channel specified in payload");
|
||||||
|
return res.status(200).end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createGraphQLClient({
|
||||||
|
saleorApiUrl: authData.saleorApiUrl,
|
||||||
|
token: authData.token,
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendEventMessages({
|
||||||
|
authData,
|
||||||
|
channel,
|
||||||
|
client,
|
||||||
|
event: "GIFT_CARD_SENT",
|
||||||
|
payload,
|
||||||
|
recipientEmail,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ message: "The event has been handled" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default giftCardSentWebhook.createHandler(handler);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
};
|
|
@ -33,3 +33,5 @@ switch (aplType) {
|
||||||
export const saleorApp = new SaleorApp({
|
export const saleorApp = new SaleorApp({
|
||||||
apl,
|
apl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const REQUIRED_SALEOR_VERSION = ">=3.11.7 <4";
|
||||||
|
|
|
@ -3,12 +3,14 @@ const semver = require("semver");
|
||||||
export class SaleorVersionCompatibilityValidator {
|
export class SaleorVersionCompatibilityValidator {
|
||||||
constructor(private appRequiredVersion: string) {}
|
constructor(private appRequiredVersion: string) {}
|
||||||
|
|
||||||
validateOrThrow(saleorVersion: string) {
|
isValid(saleorVersion: string) {
|
||||||
const versionIsValid = semver.satisfies(saleorVersion, this.appRequiredVersion, {
|
return semver.satisfies(saleorVersion, this.appRequiredVersion, {
|
||||||
includePrerelease: true,
|
includePrerelease: true,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!versionIsValid) {
|
validateOrThrow(saleorVersion: string) {
|
||||||
|
if (!this.isValid(saleorVersion)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Your Saleor version (${saleorVersion}) doesn't match App's required version (semver: ${this.appRequiredVersion})`
|
`Your Saleor version (${saleorVersion}) doesn't match App's required version (semver: ${this.appRequiredVersion})`
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue