Use configuration service
This commit is contained in:
parent
d23e85a850
commit
eb9bd700ca
23 changed files with 446 additions and 390 deletions
|
@ -9,3 +9,12 @@ export const appConfigInputSchema = z.object({
|
|||
})
|
||||
),
|
||||
});
|
||||
|
||||
export const appChannelConfigurationInputSchema = z.object({
|
||||
channel: z.string(),
|
||||
configuration: z.object({
|
||||
active: z.boolean(),
|
||||
mjmlConfigurationId: z.string().optional(),
|
||||
sendgridConfigurationId: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,23 +1,57 @@
|
|||
import { PrivateMetadataAppConfigurator } from "./app-configurator";
|
||||
import { createSettingsManager } from "./metadata-manager";
|
||||
import { logger as pinoLogger } from "../../lib/logger";
|
||||
import { appConfigInputSchema } from "./app-config-input-schema";
|
||||
import { GetAppConfigurationService } from "./get-app-configuration.service";
|
||||
import {
|
||||
appChannelConfigurationInputSchema,
|
||||
appConfigInputSchema,
|
||||
} from "./app-config-input-schema";
|
||||
import { AppConfigurationService } from "./get-app-configuration.service";
|
||||
import { router } from "../trpc/trpc-server";
|
||||
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
||||
import { z } from "zod";
|
||||
|
||||
// Allow access only for the dashboard users and attaches the
|
||||
// configuration service to the context
|
||||
const protectedWithConfigurationService = protectedClientProcedure.use(({ next, ctx }) =>
|
||||
next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
configurationService: new AppConfigurationService({
|
||||
apiClient: ctx.apiClient,
|
||||
saleorApiUrl: ctx.saleorApiUrl,
|
||||
}),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export const appConfigurationRouter = router({
|
||||
fetch: protectedClientProcedure.query(async ({ ctx, input }) => {
|
||||
getChannelConfiguration: protectedWithConfigurationService
|
||||
.input(z.object({ channelSlug: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
logger.debug("Get Channel Configuration called");
|
||||
|
||||
return await ctx.configurationService.getChannelConfiguration(input.channelSlug);
|
||||
}),
|
||||
|
||||
setChannelConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(appChannelConfigurationInputSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
logger.debug("Set channel configuration called");
|
||||
|
||||
await ctx.configurationService.setChannelConfiguration(input);
|
||||
}),
|
||||
fetch: protectedWithConfigurationService.query(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug("appConfigurationRouter.fetch called");
|
||||
|
||||
return new GetAppConfigurationService({
|
||||
return new AppConfigurationService({
|
||||
apiClient: ctx.apiClient,
|
||||
saleorApiUrl: ctx.saleorApiUrl,
|
||||
}).getConfiguration();
|
||||
}),
|
||||
setAndReplace: protectedClientProcedure
|
||||
setAndReplace: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(appConfigInputSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
|
@ -25,12 +59,7 @@ export const appConfigurationRouter = router({
|
|||
|
||||
logger.debug(input, "appConfigurationRouter.setAndReplace called with input");
|
||||
|
||||
const appConfigurator = new PrivateMetadataAppConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
|
||||
await appConfigurator.setConfig(input);
|
||||
await ctx.configurationService.setConfigurationRoot(input);
|
||||
|
||||
return null;
|
||||
}),
|
||||
|
|
|
@ -1,65 +1,91 @@
|
|||
import { PrivateMetadataAppConfigurator } from "./app-configurator";
|
||||
import { createSettingsManager } from "./metadata-manager";
|
||||
import { ChannelsFetcher } from "../channels/channels-fetcher";
|
||||
import { ShopInfoFetcher } from "../shop-info/shop-info-fetcher";
|
||||
import { FallbackAppConfig } from "./fallback-app-config";
|
||||
import { Client } from "urql";
|
||||
import { logger as pinoLogger } from "../../lib/logger";
|
||||
import { AppConfig, AppConfigurationPerChannel } from "./app-config";
|
||||
import { getDefaultEmptyAppConfiguration } from "./app-config-container";
|
||||
|
||||
// todo test
|
||||
export class GetAppConfigurationService {
|
||||
constructor(
|
||||
private settings: {
|
||||
apiClient: Client;
|
||||
saleorApiUrl: string;
|
||||
}
|
||||
) {}
|
||||
const logger = pinoLogger.child({
|
||||
service: "AppConfigurationService",
|
||||
});
|
||||
|
||||
export class AppConfigurationService {
|
||||
private configurationData?: AppConfig;
|
||||
private metadataConfigurator: PrivateMetadataAppConfigurator;
|
||||
|
||||
constructor(args: { apiClient: Client; saleorApiUrl: string; initialData?: AppConfig }) {
|
||||
this.metadataConfigurator = new PrivateMetadataAppConfigurator(
|
||||
createSettingsManager(args.apiClient),
|
||||
args.saleorApiUrl
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch configuration from Saleor API and cache it
|
||||
private async pullConfiguration() {
|
||||
logger.debug("Fetch configuration from Saleor API");
|
||||
|
||||
const config = await this.metadataConfigurator.getConfig();
|
||||
this.configurationData = config;
|
||||
}
|
||||
|
||||
// Push configuration to Saleor API
|
||||
private async pushConfiguration() {
|
||||
logger.debug("Push configuration to Saleor API");
|
||||
|
||||
await this.metadataConfigurator.setConfig(this.configurationData!);
|
||||
}
|
||||
|
||||
async getConfiguration() {
|
||||
const logger = pinoLogger.child({
|
||||
service: "GetAppConfigurationService",
|
||||
saleorApiUrl: this.settings.saleorApiUrl,
|
||||
});
|
||||
logger.debug("Get configuration");
|
||||
|
||||
const { saleorApiUrl, apiClient } = this.settings;
|
||||
if (!this.configurationData) {
|
||||
logger.debug("No configuration found in cache. Will fetch it from Saleor API");
|
||||
await this.pullConfiguration();
|
||||
}
|
||||
|
||||
const appConfigurator = new PrivateMetadataAppConfigurator(
|
||||
createSettingsManager(apiClient),
|
||||
saleorApiUrl
|
||||
);
|
||||
|
||||
const savedAppConfig = (await appConfigurator.getConfig()) ?? null;
|
||||
const savedAppConfig = this.configurationData ?? null;
|
||||
|
||||
logger.debug(savedAppConfig, "Retrieved app config from Metadata. Will return it");
|
||||
|
||||
if (savedAppConfig) {
|
||||
return savedAppConfig;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("App config not found in metadata. Will create default config now.");
|
||||
// Saves configuration to Saleor API and cache it
|
||||
async setConfigurationRoot(config: AppConfig) {
|
||||
logger.debug("Set configuration");
|
||||
|
||||
const channelsFetcher = new ChannelsFetcher(apiClient);
|
||||
const shopInfoFetcher = new ShopInfoFetcher(apiClient);
|
||||
this.configurationData = config;
|
||||
await this.pushConfiguration();
|
||||
}
|
||||
|
||||
const [channels, shopAppConfiguration] = await Promise.all([
|
||||
channelsFetcher.fetchChannels(),
|
||||
shopInfoFetcher.fetchShopInfo(),
|
||||
]);
|
||||
// Returns channel configuration if existing. Otherwise returns default empty one
|
||||
async getChannelConfiguration(channel: string) {
|
||||
logger.debug("Get channel configuration");
|
||||
const configurations = await this.getConfiguration();
|
||||
if (!configurations) {
|
||||
return getDefaultEmptyAppConfiguration();
|
||||
}
|
||||
|
||||
logger.debug(channels, "Fetched channels");
|
||||
logger.debug(shopAppConfiguration, "Fetched shop app configuration");
|
||||
const channelConfiguration = configurations.configurationsPerChannel[channel];
|
||||
return channelConfiguration || getDefaultEmptyAppConfiguration();
|
||||
}
|
||||
|
||||
const appConfig = FallbackAppConfig.createFallbackConfigFromExistingShopAndChannels(
|
||||
channels ?? [],
|
||||
shopAppConfiguration
|
||||
);
|
||||
async setChannelConfiguration({
|
||||
channel,
|
||||
configuration,
|
||||
}: {
|
||||
channel: string;
|
||||
configuration: AppConfigurationPerChannel;
|
||||
}) {
|
||||
logger.debug("Set channel configuration");
|
||||
let configurations = await this.getConfiguration();
|
||||
if (!configurations) {
|
||||
configurations = { configurationsPerChannel: {} };
|
||||
}
|
||||
|
||||
logger.debug(appConfig, "Created a fallback AppConfig. Will save it.");
|
||||
|
||||
await appConfigurator.setConfig(appConfig);
|
||||
|
||||
logger.info("Saved initial AppConfig");
|
||||
|
||||
return appConfig;
|
||||
configurations.configurationsPerChannel[channel] = configuration;
|
||||
await this.setConfigurationRoot(configurations);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ export const AppConfigurationForm = (props: AppConfigurationFormProps) => {
|
|||
<SwitchSelectorButton
|
||||
value={button.value.toString()}
|
||||
onClick={() => onChange(button.value)}
|
||||
activeTab={value.toString()}
|
||||
activeTab={value?.toString() || "false"}
|
||||
key={button.label}
|
||||
>
|
||||
{button.label}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { EditIcon, IconButton, makeStyles } from "@saleor/macaw-ui";
|
||||
import { AppConfigContainer } from "../app-config-container";
|
||||
import { AppConfigurationForm } from "./app-configuration-form";
|
||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||
import { AppColumnsLayout } from "../../ui/app-columns-layout";
|
||||
|
@ -28,96 +27,88 @@ const useStyles = makeStyles((theme) => {
|
|||
export const ChannelsConfigurationTab = () => {
|
||||
const styles = useStyles();
|
||||
const { appBridge } = useAppBridge();
|
||||
const [mjmlConfigurationsListData, setMjmlConfigurationsListData] = useState<
|
||||
{ label: string; value: string }[]
|
||||
>([]);
|
||||
|
||||
const [sendgridConfigurationsListData, setSendgridConfigurationsListData] = useState<
|
||||
{ label: string; value: string }[]
|
||||
>([]);
|
||||
|
||||
const { data: configurationData, refetch: refetchConfig } =
|
||||
trpcClient.appConfiguration.fetch.useQuery();
|
||||
|
||||
trpcClient.mjmlConfiguration.getConfigurations.useQuery(
|
||||
{},
|
||||
{
|
||||
onSuccess(data) {
|
||||
setMjmlConfigurationsListData(
|
||||
data.map((configuration) => ({
|
||||
value: configuration.id,
|
||||
label: configuration.configurationName,
|
||||
}))
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
trpcClient.sendgridConfiguration.fetch.useQuery(undefined, {
|
||||
onSuccess(data) {
|
||||
const keys = Object.keys(data.availableConfigurations);
|
||||
|
||||
setSendgridConfigurationsListData(
|
||||
keys.map((key) => ({
|
||||
value: key,
|
||||
label: data.availableConfigurations[key].configurationName,
|
||||
}))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: channels,
|
||||
isLoading: isChannelsLoading,
|
||||
isSuccess: isChannelsFetchSuccess,
|
||||
} = trpcClient.channels.fetch.useQuery();
|
||||
|
||||
const { mutate, error: saveError } = trpcClient.appConfiguration.setAndReplace.useMutation({
|
||||
onSuccess() {
|
||||
refetchConfig();
|
||||
appBridge?.dispatch(
|
||||
actions.Notification({
|
||||
title: "Success",
|
||||
text: "Saved app configuration",
|
||||
status: "success",
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const [activeChannelSlug, setActiveChannelSlug] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isChannelsFetchSuccess) {
|
||||
setActiveChannelSlug(channels[0].slug ?? null);
|
||||
}
|
||||
}, [isChannelsFetchSuccess, channels]);
|
||||
const { data: channelsData, isLoading: isChannelsDataLoading } =
|
||||
trpcClient.channels.fetch.useQuery(undefined, {
|
||||
onSuccess: (data) => {
|
||||
if (data?.length) {
|
||||
setActiveChannelSlug(data[0].slug);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const activeChannel = useMemo(() => {
|
||||
try {
|
||||
return channels!.find((c) => c.slug === activeChannelSlug)!;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}, [channels, activeChannelSlug]);
|
||||
const {
|
||||
data: configurationData,
|
||||
refetch: refetchConfig,
|
||||
isLoading: isConfigurationDataLoading,
|
||||
} = trpcClient.appConfiguration.getChannelConfiguration.useQuery(
|
||||
{
|
||||
channelSlug: activeChannelSlug!,
|
||||
},
|
||||
{ enabled: !!activeChannelSlug }
|
||||
);
|
||||
|
||||
if (isChannelsLoading) {
|
||||
const { data: mjmlConfigurations, isLoading: isMjmlQueryLoading } =
|
||||
trpcClient.mjmlConfiguration.getConfigurations.useQuery({});
|
||||
|
||||
const mjmlConfigurationsListData = useMemo(() => {
|
||||
return (
|
||||
mjmlConfigurations?.map((configuration) => ({
|
||||
value: configuration.id,
|
||||
label: configuration.configurationName,
|
||||
})) ?? []
|
||||
);
|
||||
}, [mjmlConfigurations]);
|
||||
|
||||
const { data: sendgridConfigurations, isLoading: isSendgridQueryLoading } =
|
||||
trpcClient.sendgridConfiguration.fetch.useQuery();
|
||||
|
||||
const sendgridConfigurationsListData = useMemo(() => {
|
||||
if (!sendgridConfigurations) {
|
||||
return [];
|
||||
}
|
||||
const keys = Object.keys(sendgridConfigurations.availableConfigurations ?? {}) || [];
|
||||
|
||||
return (
|
||||
keys.map((key) => ({
|
||||
value: key,
|
||||
label: sendgridConfigurations.availableConfigurations[key].configurationName,
|
||||
})) ?? []
|
||||
);
|
||||
}, [sendgridConfigurations]);
|
||||
|
||||
const { mutate: mutateAppChannelConfiguration, error: saveError } =
|
||||
trpcClient.appConfiguration.setChannelConfiguration.useMutation({
|
||||
onSuccess() {
|
||||
refetchConfig();
|
||||
appBridge?.dispatch(
|
||||
actions.Notification({
|
||||
title: "Success",
|
||||
text: "Saved app configuration",
|
||||
status: "success",
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const activeChannel = channelsData?.find((c) => c.slug === activeChannelSlug);
|
||||
|
||||
if (isChannelsDataLoading) {
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
|
||||
if (!channels?.length) {
|
||||
if (!channelsData?.length) {
|
||||
return <div>NO CHANNELS</div>;
|
||||
}
|
||||
|
||||
if (!activeChannel) {
|
||||
return <div>Error. No channel available</div>;
|
||||
}
|
||||
const isFormDataLoading =
|
||||
isConfigurationDataLoading || isMjmlQueryLoading || isSendgridQueryLoading;
|
||||
|
||||
return (
|
||||
<AppColumnsLayout>
|
||||
<SideMenu
|
||||
title="Channels"
|
||||
selectedItemId={activeChannel.slug}
|
||||
selectedItemId={activeChannel?.slug}
|
||||
headerToolbar={
|
||||
<IconButton
|
||||
variant="secondary"
|
||||
|
@ -133,31 +124,32 @@ export const ChannelsConfigurationTab = () => {
|
|||
</IconButton>
|
||||
}
|
||||
onClick={(id) => setActiveChannelSlug(id)}
|
||||
items={channels.map((c) => ({ label: c.name, id: c.slug })) || []}
|
||||
items={channelsData.map((c) => ({ label: c.name, id: c.slug })) || []}
|
||||
/>
|
||||
{activeChannel ? (
|
||||
<div className={styles.configurationColumn}>
|
||||
<AppConfigurationForm
|
||||
channelID={activeChannel.id}
|
||||
key={activeChannelSlug}
|
||||
channelSlug={activeChannel.slug}
|
||||
mjmlConfigurationChoices={mjmlConfigurationsListData}
|
||||
sendgridConfigurationChoices={sendgridConfigurationsListData}
|
||||
onSubmit={async (data) => {
|
||||
const newConfig = AppConfigContainer.setChannelAppConfiguration(configurationData)(
|
||||
activeChannel.slug
|
||||
)(data);
|
||||
|
||||
mutate(newConfig);
|
||||
}}
|
||||
initialData={AppConfigContainer.getChannelAppConfiguration(configurationData)(
|
||||
activeChannel.slug
|
||||
)}
|
||||
channelName={activeChannel?.name ?? activeChannelSlug}
|
||||
/>
|
||||
{saveError && <span>{saveError.message}</span>}
|
||||
</div>
|
||||
) : null}
|
||||
<div className={styles.configurationColumn}>
|
||||
{!activeChannel || isFormDataLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<>
|
||||
<AppConfigurationForm
|
||||
channelID={activeChannel.id}
|
||||
key={activeChannelSlug}
|
||||
channelSlug={activeChannel.slug}
|
||||
mjmlConfigurationChoices={mjmlConfigurationsListData}
|
||||
sendgridConfigurationChoices={sendgridConfigurationsListData}
|
||||
onSubmit={async (data) => {
|
||||
mutateAppChannelConfiguration({
|
||||
channel: activeChannel.slug,
|
||||
configuration: data,
|
||||
});
|
||||
}}
|
||||
initialData={configurationData}
|
||||
channelName={activeChannel?.name ?? activeChannelSlug}
|
||||
/>
|
||||
{saveError && <span>{saveError.message}</span>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</AppColumnsLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { ChannelsFetcher } from "./channels-fetcher";
|
||||
import { ChannelFragment } from "../../../generated/graphql";
|
||||
import { createClient } from "../../lib/create-graphq-client";
|
||||
import { createClient } from "../../lib/create-graphql-client";
|
||||
import { router } from "../trpc/trpc-server";
|
||||
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
||||
|
||||
export const channelsRouter = router({
|
||||
fetch: protectedClientProcedure.query(async ({ ctx, input }): Promise<ChannelFragment[]> => {
|
||||
fetch: protectedClientProcedure.query(async ({ ctx }) => {
|
||||
const client = createClient(ctx.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: ctx.appToken })
|
||||
);
|
||||
|
||||
const fetcher = new ChannelsFetcher(client);
|
||||
|
||||
return fetcher.fetchChannels().then((channels) => channels ?? []);
|
||||
return await fetcher.fetchChannels().then((channels) => channels ?? []);
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { AuthData } from "@saleor/app-sdk/APL";
|
||||
import { Client } from "urql";
|
||||
import { logger as pinoLogger } from "../../lib/logger";
|
||||
import { AppConfigurationService } from "../app-configuration/get-app-configuration.service";
|
||||
import { MjmlConfigurationService } from "../mjml/configuration/get-mjml-configuration.service";
|
||||
import { sendMjml } from "../mjml/send-mjml";
|
||||
import { sendSendgrid } from "../sendgrid/send-sendgrid";
|
||||
import { appRouter } from "../trpc/trpc-app-router";
|
||||
import { MessageEventTypes } from "./message-event-types";
|
||||
|
||||
interface SendEventMessagesArgs {
|
||||
|
@ -11,6 +13,7 @@ interface SendEventMessagesArgs {
|
|||
event: MessageEventTypes;
|
||||
authData: AuthData;
|
||||
payload: any;
|
||||
client: Client;
|
||||
}
|
||||
|
||||
export const sendEventMessages = async ({
|
||||
|
@ -19,6 +22,7 @@ export const sendEventMessages = async ({
|
|||
event,
|
||||
authData,
|
||||
payload,
|
||||
client,
|
||||
}: SendEventMessagesArgs) => {
|
||||
const logger = pinoLogger.child({
|
||||
fn: "sendEventMessages",
|
||||
|
@ -26,16 +30,12 @@ export const sendEventMessages = async ({
|
|||
|
||||
logger.debug("Function called");
|
||||
|
||||
// get app configuration
|
||||
const caller = appRouter.createCaller({
|
||||
appId: authData.appId,
|
||||
const appConfigurationService = new AppConfigurationService({
|
||||
apiClient: client,
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
token: authData.token,
|
||||
ssr: true,
|
||||
});
|
||||
|
||||
const appConfigurations = await caller.appConfiguration.fetch();
|
||||
const channelAppConfiguration = appConfigurations.configurationsPerChannel[channel];
|
||||
const channelAppConfiguration = await appConfigurationService.getChannelConfiguration(channel);
|
||||
|
||||
if (!channelAppConfiguration) {
|
||||
logger.warn("App has no configuration for this channel");
|
||||
|
@ -50,18 +50,27 @@ export const sendEventMessages = async ({
|
|||
|
||||
if (channelAppConfiguration.mjmlConfigurationId) {
|
||||
logger.debug("Channel has assigned MJML configuration");
|
||||
const mjmlStatus = await sendMjml({
|
||||
authData,
|
||||
channel,
|
||||
event,
|
||||
payload,
|
||||
recipientEmail,
|
||||
mjmlConfigurationId: channelAppConfiguration.mjmlConfigurationId,
|
||||
|
||||
const mjmlConfigurationService = new MjmlConfigurationService({
|
||||
apiClient: client,
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
});
|
||||
|
||||
if (mjmlStatus?.errors.length) {
|
||||
logger.error("MJML errors");
|
||||
logger.error(mjmlStatus?.errors);
|
||||
const mjmlConfiguration = await mjmlConfigurationService.getConfiguration({
|
||||
id: channelAppConfiguration.mjmlConfigurationId,
|
||||
});
|
||||
if (mjmlConfiguration) {
|
||||
const mjmlStatus = await sendMjml({
|
||||
event,
|
||||
payload,
|
||||
recipientEmail,
|
||||
mjmlConfiguration,
|
||||
});
|
||||
|
||||
if (mjmlStatus?.errors.length) {
|
||||
logger.error("MJML errors");
|
||||
logger.error(mjmlStatus?.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
const sendgridStatus = await sendSendgrid({
|
||||
|
|
|
@ -1,36 +1,107 @@
|
|||
import { PrivateMetadataMjmlConfigurator } from "./mjml-configurator";
|
||||
import { MjmlConfigurator, PrivateMetadataMjmlConfigurator } from "./mjml-configurator";
|
||||
import { Client } from "urql";
|
||||
import { logger as pinoLogger } from "../../../lib/logger";
|
||||
import { createSettingsManager } from "../../app-configuration/metadata-manager";
|
||||
import { MjmlConfig, MjmlConfiguration } from "./mjml-config";
|
||||
import { FilterConfigurationsArgs, MjmlConfigContainer } from "./mjml-config-container";
|
||||
|
||||
// todo test
|
||||
export class GetMjmlConfigurationService {
|
||||
constructor(
|
||||
private settings: {
|
||||
apiClient: Client;
|
||||
saleorApiUrl: string;
|
||||
}
|
||||
) {}
|
||||
const logger = pinoLogger.child({
|
||||
service: "MjmlConfigurationService",
|
||||
});
|
||||
|
||||
async getConfiguration() {
|
||||
const logger = pinoLogger.child({
|
||||
service: "GetMjmlConfigurationService",
|
||||
saleorApiUrl: this.settings.saleorApiUrl,
|
||||
});
|
||||
export class MjmlConfigurationService {
|
||||
private configurationData?: MjmlConfig;
|
||||
private metadataConfigurator: MjmlConfigurator;
|
||||
|
||||
const { saleorApiUrl, apiClient } = this.settings;
|
||||
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(apiClient),
|
||||
saleorApiUrl
|
||||
constructor(args: { apiClient: Client; saleorApiUrl: string; initialData?: MjmlConfig }) {
|
||||
this.metadataConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(args.apiClient),
|
||||
args.saleorApiUrl
|
||||
);
|
||||
|
||||
const savedMjmlConfig = (await mjmlConfigurator.getConfig()) ?? null;
|
||||
|
||||
logger.debug(savedMjmlConfig, "Retrieved MJML config from Metadata. Will return it");
|
||||
|
||||
if (savedMjmlConfig) {
|
||||
return savedMjmlConfig;
|
||||
if (args.initialData) {
|
||||
this.configurationData = args.initialData;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch configuration from Saleor API and cache it
|
||||
private async pullConfiguration() {
|
||||
logger.debug("Fetch configuration from Saleor API");
|
||||
|
||||
const config = await this.metadataConfigurator.getConfig();
|
||||
this.configurationData = config;
|
||||
}
|
||||
|
||||
// Push configuration to Saleor API
|
||||
private async pushConfiguration() {
|
||||
logger.debug("Push configuration to Saleor API");
|
||||
|
||||
await this.metadataConfigurator.setConfig(this.configurationData!);
|
||||
}
|
||||
|
||||
// Returns configuration from cache or fetches it from Saleor API
|
||||
async getConfigurationRoot() {
|
||||
logger.debug("Get configuration root");
|
||||
|
||||
if (this.configurationData) {
|
||||
logger.debug("Using cached configuration");
|
||||
return this.configurationData;
|
||||
}
|
||||
|
||||
// No cached data, fetch it from Saleor API
|
||||
await this.pullConfiguration();
|
||||
|
||||
if (!this.configurationData) {
|
||||
logger.warn("No configuration found in Saleor API");
|
||||
return;
|
||||
}
|
||||
|
||||
return this.configurationData;
|
||||
}
|
||||
|
||||
// Saves configuration to Saleor API and cache it
|
||||
async setConfigurationRoot(config: MjmlConfig) {
|
||||
logger.debug("Set configuration root");
|
||||
|
||||
this.configurationData = config;
|
||||
await this.pushConfiguration();
|
||||
}
|
||||
|
||||
async getConfiguration({ id }: { id: string }) {
|
||||
logger.debug("Get configuration");
|
||||
return MjmlConfigContainer.getConfiguration(await this.getConfigurationRoot())({ id });
|
||||
}
|
||||
|
||||
async getConfigurations(filter: FilterConfigurationsArgs) {
|
||||
logger.debug("Get configuration");
|
||||
return MjmlConfigContainer.getConfigurations(await this.getConfigurationRoot())(filter);
|
||||
}
|
||||
|
||||
async createConfiguration(config: Omit<MjmlConfiguration, "id" | "events">) {
|
||||
logger.debug("Create configuration");
|
||||
const updatedConfigurationRoot = MjmlConfigContainer.createConfiguration(
|
||||
await this.getConfigurationRoot()
|
||||
)(config);
|
||||
await this.setConfigurationRoot(updatedConfigurationRoot);
|
||||
|
||||
return updatedConfigurationRoot.configurations[
|
||||
updatedConfigurationRoot.configurations.length - 1
|
||||
];
|
||||
}
|
||||
|
||||
async updateConfiguration(config: MjmlConfiguration) {
|
||||
logger.debug("Update configuration");
|
||||
const updatedConfigurationRoot = MjmlConfigContainer.updateConfiguration(
|
||||
await this.getConfigurationRoot()
|
||||
)(config);
|
||||
this.setConfigurationRoot(updatedConfigurationRoot);
|
||||
}
|
||||
|
||||
async deleteConfiguration({ id }: { id: string }) {
|
||||
logger.debug("Delete configuration");
|
||||
const updatedConfigurationRoot = MjmlConfigContainer.deleteConfiguration(
|
||||
await this.getConfigurationRoot()
|
||||
)({ id });
|
||||
this.setConfigurationRoot(updatedConfigurationRoot);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ import { messageEventTypes } from "../../event-handlers/message-event-types";
|
|||
import { MjmlConfig as MjmlConfigurationRoot, MjmlConfiguration } from "./mjml-config";
|
||||
import { defaultMjmlTemplates, defaultMjmlSubjectTemplates } from "../default-templates";
|
||||
|
||||
const getDefaultEventsConfiguration = (): MjmlConfiguration["events"] =>
|
||||
export const generateMjmlConfigurationId = () => Date.now().toString();
|
||||
|
||||
export const getDefaultEventsConfiguration = (): MjmlConfiguration["events"] =>
|
||||
messageEventTypes.map((eventType) => ({
|
||||
active: true,
|
||||
eventType: eventType,
|
||||
|
@ -41,7 +43,7 @@ const getConfiguration =
|
|||
return mjmlConfigRoot.configurations.find((c) => c.id === id);
|
||||
};
|
||||
|
||||
interface FilterConfigurationsArgs {
|
||||
export interface FilterConfigurationsArgs {
|
||||
ids?: string[];
|
||||
active?: boolean;
|
||||
}
|
||||
|
@ -74,7 +76,7 @@ const createConfiguration =
|
|||
// for creating a new configurations, the ID has to be generated
|
||||
const newConfiguration = {
|
||||
...mjmlConfiguration,
|
||||
id: Date.now().toString(),
|
||||
id: generateMjmlConfigurationId(),
|
||||
events: getDefaultEventsConfiguration(),
|
||||
};
|
||||
mjmlConfigNormalized.configurations.unshift(newConfiguration);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { PrivateMetadataMjmlConfigurator } from "./mjml-configurator";
|
||||
import { logger as pinoLogger } from "../../../lib/logger";
|
||||
import {
|
||||
mjmlCreateConfigurationSchema,
|
||||
|
@ -9,134 +8,96 @@ import {
|
|||
mjmlUpdateEventConfigurationInputSchema,
|
||||
mjmlUpdateOrCreateConfigurationSchema,
|
||||
} from "./mjml-config-input-schema";
|
||||
import { GetMjmlConfigurationService } from "./get-mjml-configuration.service";
|
||||
import { MjmlConfigurationService } from "./get-mjml-configuration.service";
|
||||
import { router } from "../../trpc/trpc-server";
|
||||
import { protectedClientProcedure } from "../../trpc/protected-client-procedure";
|
||||
import { createSettingsManager } from "../../app-configuration/metadata-manager";
|
||||
import { z } from "zod";
|
||||
import { compileMjml } from "../compile-mjml";
|
||||
import Handlebars from "handlebars";
|
||||
import { MjmlConfigContainer } from "./mjml-config-container";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
// Allow access only for the dashboard users and attaches the
|
||||
// configuration service to the context
|
||||
const protectedWithConfigurationService = protectedClientProcedure.use(({ next, ctx }) =>
|
||||
next({
|
||||
ctx: {
|
||||
...ctx,
|
||||
configurationService: new MjmlConfigurationService({
|
||||
apiClient: ctx.apiClient,
|
||||
saleorApiUrl: ctx.saleorApiUrl,
|
||||
}),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export const mjmlConfigurationRouter = router({
|
||||
fetch: protectedClientProcedure.query(async ({ ctx }) => {
|
||||
fetch: protectedWithConfigurationService.query(async ({ ctx }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug("mjmlConfigurationRouter.fetch called");
|
||||
|
||||
return new GetMjmlConfigurationService({
|
||||
apiClient: ctx.apiClient,
|
||||
saleorApiUrl: ctx.saleorApiUrl,
|
||||
}).getConfiguration();
|
||||
return ctx.configurationService.getConfigurationRoot();
|
||||
}),
|
||||
getConfiguration: protectedClientProcedure
|
||||
getConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(mjmlGetConfigurationInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug(input, "mjmlConfigurationRouter.get called");
|
||||
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
|
||||
const configRoot = await mjmlConfigurator.getConfig();
|
||||
return MjmlConfigContainer.getConfiguration(configRoot)(input);
|
||||
return ctx.configurationService.getConfiguration(input);
|
||||
}),
|
||||
getConfigurations: protectedClientProcedure
|
||||
getConfigurations: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(mjmlGetConfigurationsInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
logger.debug(input, "mjmlConfigurationRouter.getConfigurations called");
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
const configRoot = await mjmlConfigurator.getConfig();
|
||||
return MjmlConfigContainer.getConfigurations(configRoot)(input);
|
||||
return ctx.configurationService.getConfigurations(input);
|
||||
}),
|
||||
createConfiguration: protectedClientProcedure
|
||||
createConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(mjmlCreateConfigurationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
logger.debug(input, "mjmlConfigurationRouter.create called");
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
|
||||
const configRoot = await mjmlConfigurator.getConfig();
|
||||
const newConfigurationRoot = MjmlConfigContainer.createConfiguration(configRoot)(input);
|
||||
|
||||
await mjmlConfigurator.setConfig(newConfigurationRoot);
|
||||
|
||||
return null;
|
||||
return await ctx.configurationService.createConfiguration(input);
|
||||
}),
|
||||
deleteConfiguration: protectedClientProcedure
|
||||
deleteConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(mjmlDeleteConfigurationInputSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
logger.debug(input, "mjmlConfigurationRouter.delete called");
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
|
||||
const configRoot = await mjmlConfigurator.getConfig();
|
||||
const newConfigurationRoot = MjmlConfigContainer.deleteConfiguration(configRoot)(input);
|
||||
|
||||
await mjmlConfigurator.setConfig(newConfigurationRoot);
|
||||
|
||||
await ctx.configurationService.deleteConfiguration(input);
|
||||
return null;
|
||||
}),
|
||||
updateOrCreateConfiguration: protectedClientProcedure
|
||||
updateOrCreateConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(mjmlUpdateOrCreateConfigurationSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const logger = pinoLogger.child({ saleorApiUrl: ctx.saleorApiUrl });
|
||||
|
||||
logger.debug(input, "mjmlConfigurationRouter.update or create called");
|
||||
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
|
||||
const configRoot = await mjmlConfigurator.getConfig();
|
||||
|
||||
const { id } = input;
|
||||
if (!!id) {
|
||||
const existingConfiguration = MjmlConfigContainer.getConfiguration(configRoot)({ id });
|
||||
if (!id) {
|
||||
return await ctx.configurationService.createConfiguration(input);
|
||||
} else {
|
||||
const existingConfiguration = await ctx.configurationService.getConfiguration({ id });
|
||||
if (!existingConfiguration) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Configuration not found",
|
||||
});
|
||||
}
|
||||
// checking typeof id is not enough to satisfy typescript, so need to override id field issue
|
||||
const configuration = {
|
||||
id,
|
||||
...input,
|
||||
events: existingConfiguration.events,
|
||||
};
|
||||
|
||||
const newConfigurationRoot =
|
||||
MjmlConfigContainer.updateConfiguration(configRoot)(configuration);
|
||||
await mjmlConfigurator.setConfig(newConfigurationRoot);
|
||||
await ctx.configurationService.updateConfiguration(configuration);
|
||||
return configuration;
|
||||
} else {
|
||||
const newConfigurationRoot = MjmlConfigContainer.createConfiguration(configRoot)(input);
|
||||
await mjmlConfigurator.setConfig(newConfigurationRoot);
|
||||
return newConfigurationRoot.configurations[newConfigurationRoot.configurations.length - 1];
|
||||
}
|
||||
}),
|
||||
getEventConfiguration: protectedClientProcedure
|
||||
getEventConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(mjmlGetEventConfigurationInputSchema)
|
||||
.query(async ({ ctx, input }) => {
|
||||
|
@ -144,22 +105,17 @@ export const mjmlConfigurationRouter = router({
|
|||
|
||||
logger.debug(input, "mjmlConfigurationRouter.getEventConfiguration or create called");
|
||||
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
|
||||
const configRoot = await mjmlConfigurator.getConfig();
|
||||
|
||||
const configuration = MjmlConfigContainer.getConfiguration(configRoot)({
|
||||
const configuration = await ctx.configurationService.getConfiguration({
|
||||
id: input.configurationId,
|
||||
});
|
||||
|
||||
if (!configuration) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Configuration not found",
|
||||
});
|
||||
}
|
||||
|
||||
const event = configuration.events.find((e) => e.eventType === input.eventType);
|
||||
if (!event) {
|
||||
throw new TRPCError({
|
||||
|
@ -169,7 +125,7 @@ export const mjmlConfigurationRouter = router({
|
|||
}
|
||||
return event;
|
||||
}),
|
||||
updateEventConfiguration: protectedClientProcedure
|
||||
updateEventConfiguration: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(mjmlUpdateEventConfigurationInputSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
|
@ -177,22 +133,17 @@ export const mjmlConfigurationRouter = router({
|
|||
|
||||
logger.debug(input, "mjmlConfigurationRouter.updateEventConfiguration or create called");
|
||||
|
||||
const mjmlConfigurator = new PrivateMetadataMjmlConfigurator(
|
||||
createSettingsManager(ctx.apiClient),
|
||||
ctx.saleorApiUrl
|
||||
);
|
||||
|
||||
const configRoot = await mjmlConfigurator.getConfig();
|
||||
|
||||
const configuration = MjmlConfigContainer.getConfiguration(configRoot)({
|
||||
const configuration = await ctx.configurationService.getConfiguration({
|
||||
id: input.configurationId,
|
||||
});
|
||||
|
||||
if (!configuration) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Configuration not found",
|
||||
});
|
||||
}
|
||||
|
||||
const eventIndex = configuration.events.findIndex((e) => e.eventType === input.eventType);
|
||||
configuration.events[eventIndex] = {
|
||||
active: input.active,
|
||||
|
@ -200,13 +151,11 @@ export const mjmlConfigurationRouter = router({
|
|||
template: input.template,
|
||||
subject: input.subject,
|
||||
};
|
||||
const newConfigurationRoot =
|
||||
MjmlConfigContainer.updateConfiguration(configRoot)(configuration);
|
||||
await mjmlConfigurator.setConfig(newConfigurationRoot);
|
||||
await ctx.configurationService.updateConfiguration(configuration);
|
||||
return configuration;
|
||||
}),
|
||||
|
||||
renderTemplate: protectedClientProcedure
|
||||
renderTemplate: protectedWithConfigurationService
|
||||
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
|
||||
.input(
|
||||
z.object({
|
||||
|
|
|
@ -116,19 +116,25 @@ export const MjmlConfigurationTab = ({ configurationId }: MjmlConfigurationTabPr
|
|||
items={configurations?.map((c) => ({ label: c.configurationName, id: c.id })) || []}
|
||||
/>
|
||||
<div className={styles.configurationColumn}>
|
||||
<MjmlConfigurationForm
|
||||
onConfigurationSaved={() => refetchConfigurations()}
|
||||
initialData={configuration || getDefaultEmptyConfiguration()}
|
||||
configurationId={configurationId}
|
||||
/>
|
||||
{!!configurationId && !!configuration && (
|
||||
<MjmlTemplatesCard
|
||||
configurationId={configurationId}
|
||||
configuration={configuration}
|
||||
onEventChanged={() => {
|
||||
refetchConfigurations();
|
||||
}}
|
||||
/>
|
||||
{configurationsIsLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<>
|
||||
<MjmlConfigurationForm
|
||||
onConfigurationSaved={() => refetchConfigurations()}
|
||||
initialData={configuration || getDefaultEmptyConfiguration()}
|
||||
configurationId={configurationId}
|
||||
/>
|
||||
{!!configurationId && !!configuration && (
|
||||
<MjmlTemplatesCard
|
||||
configurationId={configurationId}
|
||||
configuration={configuration}
|
||||
onEventChanged={() => {
|
||||
refetchConfigurations();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</AppColumnsLayout>
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { AuthData } from "@saleor/app-sdk/APL";
|
||||
import { appRouter } from "../trpc/trpc-app-router";
|
||||
import { logger as pinoLogger } from "../../lib/logger";
|
||||
|
||||
interface GetMjmlSettingsArgs {
|
||||
authData: AuthData;
|
||||
channel: string;
|
||||
configurationId: string;
|
||||
}
|
||||
|
||||
export const getActiveMjmlSettings = async ({
|
||||
authData,
|
||||
channel,
|
||||
configurationId,
|
||||
}: GetMjmlSettingsArgs) => {
|
||||
const logger = pinoLogger.child({
|
||||
fn: "getMjmlSettings",
|
||||
channel,
|
||||
});
|
||||
|
||||
const caller = appRouter.createCaller({
|
||||
appId: authData.appId,
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
token: authData.token,
|
||||
ssr: true,
|
||||
});
|
||||
|
||||
const configuration = await caller.mjmlConfiguration.getConfiguration({
|
||||
id: configurationId,
|
||||
});
|
||||
|
||||
if (!configuration) {
|
||||
logger.warn(`The MJML configuration with id ${configurationId} does not exist`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configuration.active) {
|
||||
logger.warn(`The MJML configuration ${configuration.configurationName} is not active`);
|
||||
return;
|
||||
}
|
||||
|
||||
return configuration;
|
||||
};
|
|
@ -1,16 +1,13 @@
|
|||
import { logger as pinoLogger } from "../../lib/logger";
|
||||
import { AuthData } from "@saleor/app-sdk/APL";
|
||||
import { getActiveMjmlSettings } from "./get-active-mjml-settings";
|
||||
import { compileMjml } from "./compile-mjml";
|
||||
import { compileHandlebarsTemplate } from "./compile-handlebars-template";
|
||||
import { sendEmailWithSmtp } from "./send-email-with-smtp";
|
||||
import { MessageEventTypes } from "../event-handlers/message-event-types";
|
||||
import { htmlToPlaintext } from "./html-to-plaintext";
|
||||
import { MjmlConfiguration } from "./configuration/mjml-config";
|
||||
|
||||
interface SendMjmlArgs {
|
||||
authData: AuthData;
|
||||
mjmlConfigurationId: string;
|
||||
channel: string;
|
||||
mjmlConfiguration: MjmlConfiguration;
|
||||
recipientEmail: string;
|
||||
event: MessageEventTypes;
|
||||
payload: any;
|
||||
|
@ -24,36 +21,17 @@ export interface EmailServiceResponse {
|
|||
}
|
||||
|
||||
export const sendMjml = async ({
|
||||
authData,
|
||||
channel,
|
||||
payload,
|
||||
recipientEmail,
|
||||
event,
|
||||
mjmlConfigurationId,
|
||||
mjmlConfiguration,
|
||||
}: SendMjmlArgs) => {
|
||||
const logger = pinoLogger.child({
|
||||
fn: "sendMjml",
|
||||
event,
|
||||
});
|
||||
|
||||
const settings = await getActiveMjmlSettings({
|
||||
authData,
|
||||
channel,
|
||||
configurationId: mjmlConfigurationId,
|
||||
});
|
||||
|
||||
if (!settings) {
|
||||
logger.debug("No active settings, skipping");
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
message: "No active settings",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const eventSettings = settings.events.find((e) => e.eventType === event);
|
||||
const eventSettings = mjmlConfiguration.events.find((e) => e.eventType === event);
|
||||
if (!eventSettings) {
|
||||
logger.debug("No active settings for this event, skipping");
|
||||
return {
|
||||
|
@ -154,13 +132,13 @@ export const sendMjml = async ({
|
|||
mailData: {
|
||||
text: emailBodyPlaintext,
|
||||
html: emailBodyHtml,
|
||||
from: `${settings.senderName} <${settings.senderEmail}>`,
|
||||
from: `${mjmlConfiguration.senderName} <${mjmlConfiguration.senderEmail}>`,
|
||||
to: recipientEmail,
|
||||
subject: emailSubject,
|
||||
},
|
||||
smtpSettings: {
|
||||
host: settings.smtpHost,
|
||||
port: parseInt(settings.smtpPort, 10),
|
||||
host: mjmlConfiguration.smtpHost,
|
||||
port: parseInt(mjmlConfiguration.smtpPort, 10),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { TRPCError } from "@trpc/server";
|
|||
import { ProtectedHandlerError } from "@saleor/app-sdk/handlers/next";
|
||||
import { saleorApp } from "../../saleor-app";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { createClient } from "../../lib/create-graphq-client";
|
||||
import { createClient } from "../../lib/create-graphql-client";
|
||||
|
||||
const attachAppToken = middleware(async ({ ctx, next }) => {
|
||||
logger.debug("attachAppToken middleware");
|
||||
|
|
|
@ -21,7 +21,6 @@ export const CodeEditor = ({ initialTemplate, onChange, value, language }: Props
|
|||
|
||||
const handleOnChange = useCallback(
|
||||
(value?: string) => {
|
||||
console.log("ON CHANGE");
|
||||
onChange(value ?? "");
|
||||
},
|
||||
[value]
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
OrderDetailsFragmentDoc,
|
||||
} from "../../../../generated/graphql";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { createClient } from "../../../lib/create-graphql-client";
|
||||
|
||||
const InvoiceSentWebhookPayload = gql`
|
||||
${OrderDetailsFragmentDoc}
|
||||
|
@ -71,10 +72,14 @@ const handler: NextWebhookApiHandler<InvoiceSentWebhookPayloadFragment> = async
|
|||
}
|
||||
|
||||
const channel = order.channel.slug;
|
||||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
await sendEventMessages({
|
||||
authData,
|
||||
channel,
|
||||
client,
|
||||
event: "INVOICE_SENT",
|
||||
payload: { order: payload.order },
|
||||
recipientEmail,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
OrderDetailsFragmentDoc,
|
||||
} from "../../../../generated/graphql";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { createClient } from "../../../lib/create-graphql-client";
|
||||
|
||||
const OrderCancelledWebhookPayload = gql`
|
||||
${OrderDetailsFragmentDoc}
|
||||
|
@ -62,10 +63,14 @@ const handler: NextWebhookApiHandler<OrderCancelledWebhookPayloadFragment> = asy
|
|||
}
|
||||
|
||||
const channel = order.channel.slug;
|
||||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
await sendEventMessages({
|
||||
authData,
|
||||
channel,
|
||||
client,
|
||||
event: "ORDER_CANCELLED",
|
||||
payload: { order: payload.order },
|
||||
recipientEmail,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
OrderDetailsFragmentDoc,
|
||||
} from "../../../../generated/graphql";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { createClient } from "../../../lib/create-graphql-client";
|
||||
|
||||
const OrderConfirmedWebhookPayload = gql`
|
||||
${OrderDetailsFragmentDoc}
|
||||
|
@ -63,10 +64,14 @@ const handler: NextWebhookApiHandler<OrderConfirmedWebhookPayloadFragment> = asy
|
|||
}
|
||||
|
||||
const channel = order.channel.slug;
|
||||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
await sendEventMessages({
|
||||
authData,
|
||||
channel,
|
||||
client,
|
||||
event: "ORDER_CONFIRMED",
|
||||
payload: { order: payload.order },
|
||||
recipientEmail,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { OrderDetailsFragment, OrderDetailsFragmentDoc } from "./../../../../generated/graphql";
|
||||
import { OrderDetailsFragmentDoc } from "./../../../../generated/graphql";
|
||||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import { gql } from "urql";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import { logger as pinoLogger } from "../../../lib/logger";
|
||||
import { OrderCreatedWebhookPayloadFragment } from "../../../../generated/graphql";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { createClient } from "../../../lib/create-graphql-client";
|
||||
|
||||
const OrderCreatedWebhookPayload = gql`
|
||||
${OrderDetailsFragmentDoc}
|
||||
|
@ -60,10 +61,14 @@ const handler: NextWebhookApiHandler<OrderCreatedWebhookPayloadFragment> = async
|
|||
}
|
||||
|
||||
const channel = order.channel.slug;
|
||||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
await sendEventMessages({
|
||||
authData,
|
||||
channel,
|
||||
client,
|
||||
event: "ORDER_CREATED",
|
||||
payload: { order: payload.order },
|
||||
recipientEmail,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
OrderFulfilledWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { createClient } from "../../../lib/create-graphql-client";
|
||||
|
||||
const OrderFulfilledWebhookPayload = gql`
|
||||
${OrderDetailsFragmentDoc}
|
||||
|
@ -63,9 +64,13 @@ const handler: NextWebhookApiHandler<OrderFulfilledWebhookPayloadFragment> = asy
|
|||
}
|
||||
|
||||
const channel = order.channel.slug;
|
||||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
await sendEventMessages({
|
||||
authData,
|
||||
client,
|
||||
channel,
|
||||
event: "ORDER_FULFILLED",
|
||||
payload: { order: payload.order },
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
OrderFullyPaidWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { sendEventMessages } from "../../../modules/event-handlers/send-event-messages";
|
||||
import { createClient } from "../../../lib/create-graphql-client";
|
||||
|
||||
const OrderFullyPaidWebhookPayload = gql`
|
||||
${OrderDetailsFragmentDoc}
|
||||
|
@ -63,10 +64,14 @@ const handler: NextWebhookApiHandler<OrderFullyPaidWebhookPayloadFragment> = asy
|
|||
}
|
||||
|
||||
const channel = order.channel.slug;
|
||||
const client = createClient(authData.saleorApiUrl, async () =>
|
||||
Promise.resolve({ token: authData.token })
|
||||
);
|
||||
|
||||
await sendEventMessages({
|
||||
authData,
|
||||
channel,
|
||||
client,
|
||||
event: "ORDER_FULLY_PAID",
|
||||
payload: { order: payload.order },
|
||||
recipientEmail,
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { trpcClient } from "../../../../../modules/trpc/trpc-client";
|
||||
|
||||
import { checkMessageEventType } from "../../../../../modules/event-handlers/check-message-event-type";
|
||||
import { parseMessageEventType } from "../../../../../modules/event-handlers/parse-message-event-type";
|
||||
import { ConfigurationPageBaseLayout } from "../../../../../modules/ui/configuration-page-base-layout";
|
||||
import { EventConfigurationForm } from "../../../../../modules/mjml/configuration/ui/mjml-event-configuration-form";
|
||||
import { LoadingIndicator } from "../../../../../modules/ui/loading-indicator";
|
||||
|
@ -13,7 +13,7 @@ const EventConfigurationPage: NextPage = () => {
|
|||
|
||||
const configurationId = router.query.configurationId as string;
|
||||
const eventTypeFromQuery = router.query.eventType as string | undefined;
|
||||
const eventType = checkMessageEventType(eventTypeFromQuery);
|
||||
const eventType = parseMessageEventType(eventTypeFromQuery);
|
||||
|
||||
const {
|
||||
data: configuration,
|
||||
|
|
Loading…
Reference in a new issue