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