From 86c2b7b10bb11df7aeadcdebca77c06187c58bf3 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolski Date: Tue, 6 Jun 2023 11:51:59 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=A7=20Add=20runtime=20migrations=20to?= =?UTF-8?q?=20schema=20v2=20(#535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add runtime migrations to schema v2 * V2 migration scripts (#536) --- apps/emails-and-messages/package.json | 1 + .../scripts/migrations/README.md | 7 + .../scripts/migrations/migration-utils.ts | 30 ++++ .../scripts/migrations/run-migration.ts | 76 +++++++++ .../scripts/migrations/run-report.ts | 97 ++++++++++++ .../app-config-metadata-manager.ts | 32 ++++ .../app-configuration/app-config-schema.ts | 13 ++ ...get-channels-assigned-to-config-id.test.ts | 100 ++++++++++++ .../get-channels-assigned-to-config-id.ts | 37 +++++ .../sendgrid-config-migration-v1-to-v2.ts | 38 +++++ .../migrations/sendgrid-config-schema-v1.ts | 29 ++++ .../migrations/sendgrid-config-schema-v2.ts | 32 ++++ .../sendgrid-transform-v1-to-v2.test.ts | 128 ++++++++++++++++ .../migrations/sendgrid-transform-v1-to-v2.ts | 33 ++++ .../configuration/sendgrid-config-schema.ts | 40 ++--- .../sendgrid-configuration.service.ts | 2 +- .../sendgrid-metadata-manager-v1.ts | 32 ++++ .../sendgrid-metadata-manager-v2.ts | 31 ++++ .../sendgrid-metadata-manager.ts | 40 +++-- .../migrations/mjml-config-schema-v1.ts | 30 ++++ .../smtp-config-migration-v1-to-v2.ts | 45 ++++++ .../migrations/smtp-config-schema-v2.ts | 37 +++++ .../smtp-transform-v1-to-v2.test.ts | 144 ++++++++++++++++++ .../migrations/smtp-transform-v1-to-v2.ts | 36 +++++ .../configuration/mjml-metadata-manager.ts | 32 ++++ .../smtp/configuration/smtp-config-schema.ts | 43 ++---- .../configuration/smtp-metadata-manager-v2.ts | 30 ++++ .../configuration/smtp-metadata-manager.ts | 40 +++-- pnpm-lock.yaml | 26 ++-- 29 files changed, 1171 insertions(+), 90 deletions(-) create mode 100644 apps/emails-and-messages/scripts/migrations/README.md create mode 100644 apps/emails-and-messages/scripts/migrations/migration-utils.ts create mode 100644 apps/emails-and-messages/scripts/migrations/run-migration.ts create mode 100644 apps/emails-and-messages/scripts/migrations/run-report.ts create mode 100644 apps/emails-and-messages/src/modules/app-configuration/app-config-metadata-manager.ts create mode 100644 apps/emails-and-messages/src/modules/app-configuration/app-config-schema.ts create mode 100644 apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.test.ts create mode 100644 apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.ts create mode 100644 apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-migration-v1-to-v2.ts create mode 100644 apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v1.ts create mode 100644 apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v2.ts create mode 100644 apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.test.ts create mode 100644 apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.ts create mode 100644 apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v1.ts create mode 100644 apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v2.ts create mode 100644 apps/emails-and-messages/src/modules/smtp/configuration/migrations/mjml-config-schema-v1.ts create mode 100644 apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-migration-v1-to-v2.ts create mode 100644 apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-schema-v2.ts create mode 100644 apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.test.ts create mode 100644 apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.ts create mode 100644 apps/emails-and-messages/src/modules/smtp/configuration/mjml-metadata-manager.ts create mode 100644 apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager-v2.ts diff --git a/apps/emails-and-messages/package.json b/apps/emails-and-messages/package.json index d8a864e..8bde7db 100644 --- a/apps/emails-and-messages/package.json +++ b/apps/emails-and-messages/package.json @@ -34,6 +34,7 @@ "@urql/exchange-auth": "^1.0.0", "@vitejs/plugin-react": "4.0.0", "clsx": "^1.2.1", + "dotenv": "^16.0.3", "graphql": "^16.6.0", "graphql-tag": "^2.12.6", "handlebars": "^4.7.7", diff --git a/apps/emails-and-messages/scripts/migrations/README.md b/apps/emails-and-messages/scripts/migrations/README.md new file mode 100644 index 0000000..3c97bfc --- /dev/null +++ b/apps/emails-and-messages/scripts/migrations/README.md @@ -0,0 +1,7 @@ +# Metadata migration scripts + +To run dry-run (check migration without mutating the data): +`npx tsx scripts/migrations/run-report.ts` + +To update date and save it: +`npx tsx scripts/migrations/run-migration.ts` diff --git a/apps/emails-and-messages/scripts/migrations/migration-utils.ts b/apps/emails-and-messages/scripts/migrations/migration-utils.ts new file mode 100644 index 0000000..8c78c31 --- /dev/null +++ b/apps/emails-and-messages/scripts/migrations/migration-utils.ts @@ -0,0 +1,30 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ + +import { createClient } from "../../src/lib/create-graphql-client"; +import { SaleorCloudAPL } from "@saleor/app-sdk/APL"; +import { createSettingsManager } from "../../src/lib/metadata-manager"; + +export const getMetadataManagerForEnv = (apiUrl: string, appToken: string, appId: string) => { + const client = createClient(apiUrl, async () => ({ + token: appToken, + })); + + return createSettingsManager(client, appId); +}; + +export const verifyRequiredEnvs = () => { + const requiredEnvs = ["SALEOR_CLOUD_TOKEN", "SALEOR_CLOUD_RESOURCE_URL", "SECRET_KEY"]; + + if (!requiredEnvs.every((env) => process.env[env])) { + throw new Error(`Missing envs: ${requiredEnvs.join(" | ")}`); + } +}; + +export const fetchCloudAplEnvs = () => { + const saleorAPL = new SaleorCloudAPL({ + token: process.env.SALEOR_CLOUD_TOKEN!, + resourceUrl: process.env.SALEOR_CLOUD_RESOURCE_URL!, + }); + + return saleorAPL.getAll(); +}; diff --git a/apps/emails-and-messages/scripts/migrations/run-migration.ts b/apps/emails-and-messages/scripts/migrations/run-migration.ts new file mode 100644 index 0000000..66e7304 --- /dev/null +++ b/apps/emails-and-messages/scripts/migrations/run-migration.ts @@ -0,0 +1,76 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ + +import * as dotenv from "dotenv"; +import { fetchCloudAplEnvs, getMetadataManagerForEnv, verifyRequiredEnvs } from "./migration-utils"; + +import { SendgridPrivateMetadataManager } from "../../src/modules/sendgrid/configuration/sendgrid-metadata-manager"; +import { SmtpPrivateMetadataManager } from "../../src/modules/smtp/configuration/smtp-metadata-manager"; + +dotenv.config(); + +const runMigration = async () => { + console.log("Starting running migration"); + + verifyRequiredEnvs(); + + console.log("Envs verified, fetching envs"); + const allEnvs = await fetchCloudAplEnvs().catch((r) => { + console.error(r); + + process.exit(1); + }); + + const report = { + smtp: [] as string[], + sendgrid: [] as string[], + none: [] as string[], + }; + + for (const env of allEnvs) { + let isSmtpMigrated = false; + let isSendgridMigrated = false; + + console.log("Working on env: ", env.saleorApiUrl); + + const metadataManager = getMetadataManagerForEnv(env.saleorApiUrl, env.token, env.appId); + + const sendgridMetadataManager = new SendgridPrivateMetadataManager( + metadataManager, + env.saleorApiUrl + ); + + const sendgridUpdatedSchema = await sendgridMetadataManager.getConfig(); + + if (sendgridUpdatedSchema) { + console.log("Migrated sendgrid configuration found, overriding"); + isSendgridMigrated = true; + await sendgridMetadataManager.setConfig(sendgridUpdatedSchema); + } + + const smtpMetadataManager = new SmtpPrivateMetadataManager(metadataManager, env.saleorApiUrl); + + const smtpUpdatedSchema = await smtpMetadataManager.getConfig(); + + if (smtpUpdatedSchema) { + console.log("Migrated smtp configuration found, overriding"); + isSmtpMigrated = true; + await smtpMetadataManager.setConfig(smtpUpdatedSchema); + } + + if (isSendgridMigrated) { + report.sendgrid.push(env.saleorApiUrl); + } + + if (isSmtpMigrated) { + report.smtp.push(env.saleorApiUrl); + } + + if (!isSmtpMigrated && !isSendgridMigrated) { + report.none.push(env.saleorApiUrl); + } + } + + console.log("Report", report); +}; + +runMigration(); diff --git a/apps/emails-and-messages/scripts/migrations/run-report.ts b/apps/emails-and-messages/scripts/migrations/run-report.ts new file mode 100644 index 0000000..0617295 --- /dev/null +++ b/apps/emails-and-messages/scripts/migrations/run-report.ts @@ -0,0 +1,97 @@ +/* eslint-disable turbo/no-undeclared-env-vars */ + +import * as dotenv from "dotenv"; +import { fetchCloudAplEnvs, getMetadataManagerForEnv, verifyRequiredEnvs } from "./migration-utils"; + +import { AppConfigPrivateMetadataManager } from "../../src/modules/app-configuration/app-config-metadata-manager"; +import { SendgridPrivateMetadataManagerV1 } from "../../src/modules/sendgrid/configuration/sendgrid-metadata-manager-v1"; +import { MjmlPrivateMetadataManager } from "../../src/modules/smtp/configuration/mjml-metadata-manager"; +import { smtpTransformV1toV2 } from "../../src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2"; +import { sendgridTransformV1toV2 } from "../../src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2"; + +dotenv.config(); + +const runReport = async () => { + console.log("Starting running report"); + + verifyRequiredEnvs(); + + console.log("Envs verified, fetching envs"); + const allEnvs = await fetchCloudAplEnvs().catch((r) => { + console.error(r); + + process.exit(1); + }); + + const report = { + smtp: [] as string[], + sendgrid: [] as string[], + none: [] as string[], + }; + + for (const env of allEnvs) { + console.log("Working on env: ", env.saleorApiUrl); + let isSmtpMigrated = false; + let isSendgridMigrated = false; + + const metadataManager = getMetadataManagerForEnv(env.saleorApiUrl, env.token, env.appId); + + const sendgridMetadataManagerV1 = new SendgridPrivateMetadataManagerV1( + metadataManager, + env.saleorApiUrl + ); + + const appMetadataManager = new AppConfigPrivateMetadataManager( + metadataManager, + env.saleorApiUrl + ); + + const appConfiguration = await appMetadataManager.getConfig(); + + const sendgridConfigurationV1 = await sendgridMetadataManagerV1.getConfig(); + + if (sendgridConfigurationV1) { + console.log("Found old sendgrid config, migrating"); + isSendgridMigrated = true; + const v2 = sendgridTransformV1toV2({ + configV1: sendgridConfigurationV1, + appConfigV1: appConfiguration, + }); + + console.log("Old config", sendgridConfigurationV1); + console.log("New config", v2); + } + + const mjmlMetadataManagerV1 = new MjmlPrivateMetadataManager(metadataManager, env.saleorApiUrl); + + const mjmlConfiguration = await mjmlMetadataManagerV1.getConfig(); + + if (mjmlConfiguration) { + console.log("Found old mjml config, migrating"); + isSmtpMigrated = true; + const v2 = smtpTransformV1toV2({ + configV1: mjmlConfiguration, + appConfigV1: appConfiguration, + }); + + console.log("Old config", mjmlConfiguration); + console.log("New config", v2); + } + + if (isSendgridMigrated) { + report.sendgrid.push(env.saleorApiUrl); + } + + if (isSmtpMigrated) { + report.smtp.push(env.saleorApiUrl); + } + + if (!isSmtpMigrated && !isSendgridMigrated) { + report.none.push(env.saleorApiUrl); + } + } + + console.log("Report", report); +}; + +runReport(); diff --git a/apps/emails-and-messages/src/modules/app-configuration/app-config-metadata-manager.ts b/apps/emails-and-messages/src/modules/app-configuration/app-config-metadata-manager.ts new file mode 100644 index 0000000..d00c093 --- /dev/null +++ b/apps/emails-and-messages/src/modules/app-configuration/app-config-metadata-manager.ts @@ -0,0 +1,32 @@ +// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION + +import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { AppConfig } from "./app-config-schema"; + +export class AppConfigPrivateMetadataManager { + private metadataKey = "app-config"; + + constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {} + + getConfig(): Promise { + return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => { + if (!data) { + return data; + } + + try { + return JSON.parse(data); + } catch (e) { + throw new Error("Invalid metadata value, cant be parsed"); + } + }); + } + + setConfig(config: AppConfig): Promise { + return this.metadataManager.set({ + key: this.metadataKey, + value: JSON.stringify(config), + domain: this.saleorApiUrl, + }); + } +} diff --git a/apps/emails-and-messages/src/modules/app-configuration/app-config-schema.ts b/apps/emails-and-messages/src/modules/app-configuration/app-config-schema.ts new file mode 100644 index 0000000..0b411a7 --- /dev/null +++ b/apps/emails-and-messages/src/modules/app-configuration/app-config-schema.ts @@ -0,0 +1,13 @@ +// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION + +export interface AppConfigurationPerChannel { + active: boolean; + mjmlConfigurationId?: string; + sendgridConfigurationId?: string; +} + +export type AppConfigurationsChannelMap = Record; + +export type AppConfig = { + configurationsPerChannel: AppConfigurationsChannelMap; +}; diff --git a/apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.test.ts b/apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.test.ts new file mode 100644 index 0000000..b949993 --- /dev/null +++ b/apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.test.ts @@ -0,0 +1,100 @@ +import { expect, describe, it } from "vitest"; +import { getChannelsAssignedToConfigId } from "./get-channels-assigned-to-config-id"; + +describe("getChannelsAssignedToConfigId", function () { + it("Do not assign to any channel, when theres no app configuration", () => { + const channels = getChannelsAssignedToConfigId("id", "sendgrid", undefined); + + expect(channels).toEqual({ + channels: [], + mode: "restrict", + override: true, + }); + }); + + it("Do not assign sendgrid configuration to any channel, when app configuration did not assigned it", () => { + const channels = getChannelsAssignedToConfigId("id", "sendgrid", { + configurationsPerChannel: { + "default-channel": { + active: true, + sendgridConfigurationId: "other-id", + mjmlConfigurationId: "id", + }, + "other-channel": { + active: true, + mjmlConfigurationId: "id", + }, + }, + }); + + expect(channels).toEqual({ + channels: [], + mode: "restrict", + override: true, + }); + }); + + it("Assign sendgrid configuration to channel, when app configuration has assigned it", () => { + const channels = getChannelsAssignedToConfigId("id", "sendgrid", { + configurationsPerChannel: { + "default-channel": { + active: true, + sendgridConfigurationId: "id", + }, + "other-channel": { + active: true, + sendgridConfigurationId: "id", + }, + }, + }); + + expect(channels).toEqual({ + channels: ["default-channel", "other-channel"], + mode: "restrict", + override: true, + }); + }); + + it("Do not assign mjml configuration to any channel, when app configuration did not assigned it", () => { + const channels = getChannelsAssignedToConfigId("id", "mjml", { + configurationsPerChannel: { + "default-channel": { + active: true, + mjmlConfigurationId: "other-id", + sendgridConfigurationId: "id", + }, + "other-channel": { + active: true, + sendgridConfigurationId: "id", + }, + }, + }); + + expect(channels).toEqual({ + channels: [], + mode: "restrict", + override: true, + }); + }); + + it("Assign mjml configuration to channel, when app configuration has assigned it", () => { + const channels = getChannelsAssignedToConfigId("id", "mjml", { + configurationsPerChannel: { + "default-channel": { + active: true, + mjmlConfigurationId: "id", + }, + "other-channel": { + active: true, + mjmlConfigurationId: "id", + }, + }, + }); + + expect(channels).toEqual({ + channels: ["default-channel", "other-channel"], + mode: "restrict", + override: true, + }); + }); +}); diff --git a/apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.ts b/apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.ts new file mode 100644 index 0000000..00958fe --- /dev/null +++ b/apps/emails-and-messages/src/modules/app-configuration/migrations/get-channels-assigned-to-config-id.ts @@ -0,0 +1,37 @@ +import { ChannelConfiguration } from "../../channels/channel-configuration-schema"; +import { AppConfig } from "../app-config-schema"; + +export const getChannelsAssignedToConfigId = ( + configId: string, + moduleName: "sendgrid" | "mjml", + appConfig?: AppConfig +): ChannelConfiguration => { + if (!appConfig) { + return { + channels: [], + mode: "restrict", + override: true, + }; + } + + const channels = []; + + if (moduleName === "sendgrid") { + for (const key in appConfig.configurationsPerChannel) { + if (appConfig.configurationsPerChannel[key].sendgridConfigurationId === configId) { + channels.push(key); + } + } + } else { + for (const key in appConfig.configurationsPerChannel) { + if (appConfig.configurationsPerChannel[key].mjmlConfigurationId === configId) { + channels.push(key); + } + } + } + return { + channels, + mode: "restrict", + override: true, + }; +}; diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-migration-v1-to-v2.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-migration-v1-to-v2.ts new file mode 100644 index 0000000..81a356e --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-migration-v1-to-v2.ts @@ -0,0 +1,38 @@ +import { AppConfigPrivateMetadataManager } from "../../../app-configuration/app-config-metadata-manager"; +import { SendgridPrivateMetadataManagerV1 } from "../sendgrid-metadata-manager-v1"; +import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { sendgridTransformV1toV2 } from "./sendgrid-transform-v1-to-v2"; +import { createLogger } from "@saleor/apps-shared"; + +const logger = createLogger({ + fn: "sendgridConfigMigrationV1ToV2", +}); + +interface SendgridConfigMigrationV1ToV1Args { + settingsManager: SettingsManager; + saleorApiUrl: string; +} + +export const sendgridConfigMigrationV1ToV2 = async ({ + settingsManager, + saleorApiUrl, +}: SendgridConfigMigrationV1ToV1Args) => { + logger.debug("Detect if theres data to migrate"); + + const appConfigManager = new AppConfigPrivateMetadataManager(settingsManager, saleorApiUrl); + const metadataManagerV1 = new SendgridPrivateMetadataManagerV1(settingsManager, saleorApiUrl); + + const configV1 = await metadataManagerV1.getConfig(); + + if (!configV1) { + logger.debug("No migration required - no previous data"); + return undefined; + } + + logger.debug("Migrating data"); + const appConfigV1 = await appConfigManager.getConfig(); + const migratedConfigurationRoot = sendgridTransformV1toV2({ configV1, appConfigV1 }); + + logger.debug("Data transformed"); + return migratedConfigurationRoot; +}; diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v1.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v1.ts new file mode 100644 index 0000000..c489b24 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v1.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import { messageEventTypes } from "../../../event-handlers/message-event-types"; + +export const sendgridEventConfigurationV1Schema = z.object({ + active: z.boolean().default(false), + eventType: z.enum(messageEventTypes), + template: z.string().optional(), +}); + +export type SendgridEventConfigurationV1 = z.infer; + +export const sendgridConfigurationV1Schema = z.object({ + id: z.string().min(1), + active: z.boolean().default(true), + configurationName: z.string().min(1), + sandboxMode: z.boolean().default(false), + senderName: z.string().optional(), + senderEmail: z.string().optional(), + apiKey: z.string().min(1), + events: z.array(sendgridEventConfigurationV1Schema), +}); + +export type SendgridConfigurationV1 = z.infer; + +export const sendgridConfigV1Schema = z.object({ + configurations: z.array(sendgridConfigurationV1Schema), +}); + +export type SendgridConfigV1 = z.infer; diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v2.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v2.ts new file mode 100644 index 0000000..2ad8798 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-config-schema-v2.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import { messageEventTypes } from "../../../event-handlers/message-event-types"; +import { channelConfigurationSchema } from "../../../channels/channel-configuration-schema"; + +export const sendgridConfigurationEventV2Schema = z.object({ + active: z.boolean().default(false), + eventType: z.enum(messageEventTypes), + template: z.string().optional(), +}); + +export type SendgridEventConfigurationV2 = z.infer; + +export const sendgridConfigurationV2Schema = z.object({ + id: z.string().min(1), + active: z.boolean().default(true), + name: z.string().min(1), + sandboxMode: z.boolean().default(false), + apiKey: z.string().min(1), + sender: z.string().min(1).optional(), + senderEmail: z.string().email().optional(), + senderName: z.string().optional(), + channels: channelConfigurationSchema, + events: z.array(sendgridConfigurationEventV2Schema), +}); + +export type SendgridConfigurationV2 = z.infer; + +export const sendgridConfigV2Schema = z.object({ + configurations: z.array(sendgridConfigurationV2Schema), +}); + +export type SendgridConfigV2 = z.infer; diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.test.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.test.ts new file mode 100644 index 0000000..515be47 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.test.ts @@ -0,0 +1,128 @@ +import { expect, describe, it } from "vitest"; +import { sendgridTransformV1toV2 } from "./sendgrid-transform-v1-to-v2"; + +describe("sendgridTransformV1toV2", function () { + it("No configurations, when no defined previously", () => { + const migratedConfig = sendgridTransformV1toV2({ + configV1: { + configurations: [], + }, + appConfigV1: undefined, + }); + + expect(migratedConfig).toEqual({ + configurations: [], + }); + }); + + it("Migrate and do not assign to any channel, when no app configuration passed", () => { + const migratedConfig = sendgridTransformV1toV2({ + configV1: { + configurations: [ + { + id: "id", + configurationName: "name", + active: true, + apiKey: "key", + sandboxMode: true, + senderEmail: "email", + senderName: "name", + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + }, + ], + }, + ], + }, + appConfigV1: undefined, + }); + + expect(migratedConfig).toEqual({ + configurations: [ + { + id: "id", + name: "name", + active: true, + apiKey: "key", + sandboxMode: true, + senderEmail: "email", + senderName: "name", + channels: { + override: true, + mode: "restrict", + channels: [], + }, + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + }, + ], + }, + ], + }); + }); + + it("Migrate and assign to channel, when app configuration is passed", () => { + const migratedConfig = sendgridTransformV1toV2({ + configV1: { + configurations: [ + { + id: "id", + configurationName: "name", + active: true, + apiKey: "key", + sandboxMode: true, + senderEmail: "email", + senderName: "name", + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + }, + ], + }, + ], + }, + appConfigV1: { + configurationsPerChannel: { + "default-channel": { + active: true, + sendgridConfigurationId: "id", + }, + }, + }, + }); + + expect(migratedConfig).toEqual({ + configurations: [ + { + id: "id", + name: "name", + active: true, + apiKey: "key", + sandboxMode: true, + senderEmail: "email", + senderName: "name", + channels: { + override: true, + mode: "restrict", + channels: ["default-channel"], + }, + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + }, + ], + }, + ], + }); + }); +}); diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.ts new file mode 100644 index 0000000..a73a612 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/migrations/sendgrid-transform-v1-to-v2.ts @@ -0,0 +1,33 @@ +import { AppConfig } from "../../../app-configuration/app-config-schema"; +import { getChannelsAssignedToConfigId } from "../../../app-configuration/migrations/get-channels-assigned-to-config-id"; +import { SendgridConfigV1 } from "./sendgrid-config-schema-v1"; +import { SendgridConfigV2 } from "./sendgrid-config-schema-v2"; + +interface SendgridTransformV1toV2Args { + configV1: SendgridConfigV1; + appConfigV1?: AppConfig; +} + +export const sendgridTransformV1toV2 = ({ configV1, appConfigV1 }: SendgridTransformV1toV2Args) => { + const migratedConfigurationRoot: SendgridConfigV2 = { + configurations: [], + }; + + configV1.configurations.forEach((config) => { + const channels = getChannelsAssignedToConfigId(config.id, "sendgrid", appConfigV1); + + migratedConfigurationRoot.configurations.push({ + id: config.id, + name: config.configurationName, + active: config.active, + apiKey: config.apiKey, + channels, + sandboxMode: config.sandboxMode, + senderEmail: config.senderEmail, + senderName: config.senderName, + events: config.events, + }); + }); + + return migratedConfigurationRoot; +}; diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-schema.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-schema.ts index 88c4643..8ceb46d 100644 --- a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-schema.ts +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-config-schema.ts @@ -1,32 +1,20 @@ -import { z } from "zod"; -import { messageEventTypes } from "../../event-handlers/message-event-types"; -import { channelConfigurationSchema } from "../../channels/channel-configuration-schema"; +import { + SendgridConfigV2, + SendgridConfigurationV2, + SendgridEventConfigurationV2, + sendgridConfigV2Schema, + sendgridConfigurationEventV2Schema, + sendgridConfigurationV2Schema, +} from "./migrations/sendgrid-config-schema-v2"; -export const sendgridConfigurationEventSchema = z.object({ - active: z.boolean().default(false), - eventType: z.enum(messageEventTypes), - template: z.string().optional(), -}); +export const sendgridConfigurationEventSchema = sendgridConfigurationEventV2Schema; -export type SendgridEventConfiguration = z.infer; +export type SendgridEventConfiguration = SendgridEventConfigurationV2; -export const sendgridConfigurationSchema = z.object({ - id: z.string().min(1), - active: z.boolean().default(true), - name: z.string().min(1), - sandboxMode: z.boolean().default(false), - apiKey: z.string().min(1), - sender: z.string().min(1).optional(), - senderEmail: z.string().email().optional(), - senderName: z.string().optional(), - channels: channelConfigurationSchema, - events: z.array(sendgridConfigurationEventSchema), -}); +export const sendgridConfigurationSchema = sendgridConfigurationV2Schema; -export type SendgridConfiguration = z.infer; +export type SendgridConfiguration = SendgridConfigurationV2; -export const sendgridConfigSchema = z.object({ - configurations: z.array(sendgridConfigurationSchema), -}); +export const sendgridConfigSchema = sendgridConfigV2Schema; -export type SendgridConfig = z.infer; +export type SendgridConfig = SendgridConfigV2; diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.service.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.service.ts index 348714a..35e13e0 100644 --- a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.service.ts +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-configuration.service.ts @@ -1,4 +1,3 @@ -import { SendgridPrivateMetadataManager } from "./sendgrid-metadata-manager"; import { createLogger } from "@saleor/apps-shared"; import { sendgridDefaultEmptyConfigurations } from "./sendgrid-default-empty-configurations"; import { @@ -9,6 +8,7 @@ import { import { MessageEventTypes } from "../../event-handlers/message-event-types"; import { generateRandomId } from "../../../lib/generate-random-id"; import { filterConfigurations } from "../../app-configuration/filter-configurations"; +import { SendgridPrivateMetadataManager } from "./sendgrid-metadata-manager"; const logger = createLogger({ service: "SendgridConfigurationService", diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v1.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v1.ts new file mode 100644 index 0000000..c76c6d1 --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v1.ts @@ -0,0 +1,32 @@ +// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION + +import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { SendgridConfigV1 } from "./migrations/sendgrid-config-schema-v1"; + +export class SendgridPrivateMetadataManagerV1 { + private metadataKey = "sendgrid-config"; + + constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {} + + getConfig(): Promise { + return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => { + if (!data) { + return data; + } + + try { + return JSON.parse(data); + } catch (e) { + throw new Error("Invalid metadata value, cant be parsed"); + } + }); + } + + setConfig(config: SendgridConfigV1): Promise { + return this.metadataManager.set({ + key: this.metadataKey, + value: JSON.stringify(config), + domain: this.saleorApiUrl, + }); + } +} diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v2.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v2.ts new file mode 100644 index 0000000..7b8570c --- /dev/null +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager-v2.ts @@ -0,0 +1,31 @@ +import { SendgridConfig } from "./sendgrid-config-schema"; +import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { SendgridConfigV2 } from "./migrations/sendgrid-config-schema-v2"; + +export class SendgridPrivateMetadataManagerV2 { + private metadataKey = "sendgrid-config-v2"; + + constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {} + + getConfig(): Promise { + return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => { + if (!data) { + return data; + } + + try { + return JSON.parse(data); + } catch (e) { + throw new Error("Invalid metadata value, cant be parsed"); + } + }); + } + + setConfig(config: SendgridConfigV2): Promise { + return this.metadataManager.set({ + key: this.metadataKey, + value: JSON.stringify(config), + domain: this.saleorApiUrl, + }); + } +} diff --git a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager.ts b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager.ts index 5c1c8d2..06732b3 100644 --- a/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager.ts +++ b/apps/emails-and-messages/src/modules/sendgrid/configuration/sendgrid-metadata-manager.ts @@ -1,23 +1,41 @@ import { SendgridConfig } from "./sendgrid-config-schema"; import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { SendgridPrivateMetadataManagerV2 } from "./sendgrid-metadata-manager-v2"; +import { sendgridConfigMigrationV1ToV2 } from "./migrations/sendgrid-config-migration-v1-to-v2"; +import { createLogger } from "@saleor/apps-shared"; + +const logger = createLogger({ + fn: "SendgridPrivateMetadataManager", +}); export class SendgridPrivateMetadataManager { - private metadataKey = "sendgrid-config"; + private metadataKey = "sendgrid-config-v2"; constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {} - getConfig(): Promise { - return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => { - if (!data) { - return data; - } + async getConfig() { + logger.debug("Fetching config in the current version"); - try { - return JSON.parse(data); - } catch (e) { - throw new Error("Invalid metadata value, cant be parsed"); - } + const currentVersionManager = new SendgridPrivateMetadataManagerV2( + this.metadataManager, + this.saleorApiUrl + ); + + const currentVersionConfig = await currentVersionManager.getConfig(); + + if (currentVersionConfig) { + // We have the current version, no need to migrate so we can return it + return currentVersionConfig; + } + + logger.debug("No config in the current version, trying to migrate from v1"); + // TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE AFTER MIGRATION + const migratedSchema = await sendgridConfigMigrationV1ToV2({ + saleorApiUrl: this.saleorApiUrl, + settingsManager: this.metadataManager, }); + + return migratedSchema; } setConfig(config: SendgridConfig): Promise { diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/migrations/mjml-config-schema-v1.ts b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/mjml-config-schema-v1.ts new file mode 100644 index 0000000..ed20da3 --- /dev/null +++ b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/mjml-config-schema-v1.ts @@ -0,0 +1,30 @@ +import { MessageEventTypes } from "../../../event-handlers/message-event-types"; + +export interface MjmlEventConfiguration { + active: boolean; + eventType: MessageEventTypes; + template: string; + subject: string; +} + +export const smtpEncryptionTypes = ["NONE", "TLS", "SSL"] as const; + +export type SmtpEncryptionType = (typeof smtpEncryptionTypes)[number]; + +export interface MjmlConfiguration { + id: string; + active: boolean; + configurationName: string; + senderName: string; + senderEmail: string; + smtpHost: string; + smtpPort: string; + smtpUser: string; + smtpPassword: string; + encryption: SmtpEncryptionType; + events: MjmlEventConfiguration[]; +} + +export type MjmlConfig = { + configurations: MjmlConfiguration[]; +}; diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-migration-v1-to-v2.ts b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-migration-v1-to-v2.ts new file mode 100644 index 0000000..7644fb6 --- /dev/null +++ b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-migration-v1-to-v2.ts @@ -0,0 +1,45 @@ +import { AppConfigPrivateMetadataManager } from "../../../app-configuration/app-config-metadata-manager"; +import { AppConfig } from "../../../app-configuration/app-config-schema"; +import { ChannelConfiguration } from "../../../channels/channel-configuration-schema"; +import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { MjmlPrivateMetadataManager } from "../mjml-metadata-manager"; +import { SmtpConfigV2 } from "./smtp-config-schema-v2"; +import { getChannelsAssignedToConfigId } from "../../../app-configuration/migrations/get-channels-assigned-to-config-id"; +import { smtpTransformV1toV2 } from "./smtp-transform-v1-to-v2"; +import { createLogger } from "@saleor/apps-shared"; + +const logger = createLogger({ + fn: "smtpConfigMigrationV1ToV2", +}); + +interface SmtpConfigMigrationV1ToV1Args { + settingsManager: SettingsManager; + saleorApiUrl: string; +} + +export const smtpConfigMigrationV1ToV2 = async ({ + settingsManager, + saleorApiUrl, +}: SmtpConfigMigrationV1ToV1Args) => { + logger.debug("Hello, I'm migrating smtp config from v1 to v2"); + + const appConfigManager = new AppConfigPrivateMetadataManager(settingsManager, saleorApiUrl); + const metadataManagerV1 = new MjmlPrivateMetadataManager(settingsManager, saleorApiUrl); + + const configV1 = await metadataManagerV1.getConfig(); + + if (!configV1) { + logger.debug("No migration required - no previous data"); + return undefined; + } + + const appConfigV1 = await appConfigManager.getConfig(); + + const migratedConfigurationRoot = smtpTransformV1toV2({ + configV1, + appConfigV1, + }); + + logger.debug("Migrated config v1 to v2!"); + return migratedConfigurationRoot; +}; diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-schema-v2.ts b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-schema-v2.ts new file mode 100644 index 0000000..e8502cb --- /dev/null +++ b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-config-schema-v2.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import { messageEventTypes } from "../../../event-handlers/message-event-types"; +import { channelConfigurationSchema } from "../../../channels/channel-configuration-schema"; + +export const smtpEncryptionTypes = ["NONE", "TLS", "SSL"] as const; + +export const smtpConfigurationEventV2Schema = z.object({ + active: z.boolean().default(false), + eventType: z.enum(messageEventTypes), + template: z.string(), + subject: z.string(), +}); + +export type SmtpEventConfigurationV2 = z.infer; + +export const smtpConfigurationV2Schema = z.object({ + id: z.string().min(1), + active: z.boolean().default(true), + name: z.string().min(1), + senderName: z.string().optional(), + senderEmail: z.string().email().min(5).optional(), + smtpHost: z.string().min(1), + smtpPort: z.string().min(1), + smtpUser: z.string().optional(), + smtpPassword: z.string().optional(), + encryption: z.enum(smtpEncryptionTypes).default("NONE"), + channels: channelConfigurationSchema, + events: z.array(smtpConfigurationEventV2Schema), +}); + +export type SmtpConfigurationV2 = z.infer; + +export const smtpConfigV2Schema = z.object({ + configurations: z.array(smtpConfigurationV2Schema), +}); + +export type SmtpConfigV2 = z.infer; diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.test.ts b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.test.ts new file mode 100644 index 0000000..796b887 --- /dev/null +++ b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.test.ts @@ -0,0 +1,144 @@ +import { expect, describe, it } from "vitest"; +import { smtpTransformV1toV2 } from "./smtp-transform-v1-to-v2"; + +describe("smtpTransformV1toV2", function () { + it("No configurations, when no defined previously", () => { + const migratedConfig = smtpTransformV1toV2({ + configV1: { + configurations: [], + }, + appConfigV1: undefined, + }); + + expect(migratedConfig).toEqual({ + configurations: [], + }); + }); + + it("Migrate and do not assign to any channel, when no app configuration passed", () => { + const migratedConfig = smtpTransformV1toV2({ + configV1: { + configurations: [ + { + id: "id", + configurationName: "name", + active: true, + encryption: "NONE", + smtpHost: "host", + smtpPort: "1234", + smtpPassword: "password", + smtpUser: "user", + senderEmail: "email", + senderName: "name", + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + subject: "subject", + }, + ], + }, + ], + }, + appConfigV1: undefined, + }); + + expect(migratedConfig).toEqual({ + configurations: [ + { + id: "id", + name: "name", + active: true, + encryption: "NONE", + smtpHost: "host", + smtpPort: "1234", + smtpPassword: "password", + smtpUser: "user", + senderEmail: "email", + senderName: "name", + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + subject: "subject", + }, + ], + channels: { + override: true, + mode: "restrict", + channels: [], + }, + }, + ], + }); + }); + + it("Migrate and assign to channel, when app configuration is passed", () => { + const migratedConfig = smtpTransformV1toV2({ + configV1: { + configurations: [ + { + id: "id", + configurationName: "name", + active: true, + encryption: "NONE", + smtpHost: "host", + smtpPort: "1234", + smtpPassword: "password", + smtpUser: "user", + senderEmail: "email", + senderName: "name", + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + subject: "subject", + }, + ], + }, + ], + }, + appConfigV1: { + configurationsPerChannel: { + "default-channel": { + active: true, + mjmlConfigurationId: "id", + }, + }, + }, + }); + + expect(migratedConfig).toEqual({ + configurations: [ + { + id: "id", + name: "name", + active: true, + encryption: "NONE", + smtpHost: "host", + smtpPort: "1234", + smtpPassword: "password", + smtpUser: "user", + senderEmail: "email", + senderName: "name", + events: [ + { + active: true, + eventType: "ORDER_CREATED", + template: "template", + subject: "subject", + }, + ], + channels: { + override: true, + mode: "restrict", + channels: ["default-channel"], + }, + }, + ], + }); + }); +}); diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.ts b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.ts new file mode 100644 index 0000000..e25a303 --- /dev/null +++ b/apps/emails-and-messages/src/modules/smtp/configuration/migrations/smtp-transform-v1-to-v2.ts @@ -0,0 +1,36 @@ +import { AppConfig } from "../../../app-configuration/app-config-schema"; +import { getChannelsAssignedToConfigId } from "../../../app-configuration/migrations/get-channels-assigned-to-config-id"; +import { MjmlConfig } from "./mjml-config-schema-v1"; +import { SmtpConfigV2 } from "./smtp-config-schema-v2"; + +interface SmtpTransformV1toV2Args { + configV1: MjmlConfig; + appConfigV1?: AppConfig; +} + +export const smtpTransformV1toV2 = ({ configV1, appConfigV1 }: SmtpTransformV1toV2Args) => { + const migratedConfigurationRoot: SmtpConfigV2 = { + configurations: [], + }; + + configV1.configurations.forEach((config) => { + const channels = getChannelsAssignedToConfigId(config.id, "mjml", appConfigV1); + + migratedConfigurationRoot.configurations.push({ + id: config.id, + name: config.configurationName, + active: config.active, + channels, + senderEmail: config.senderEmail, + senderName: config.senderName, + events: config.events, + encryption: config.encryption, + smtpHost: config.smtpHost, + smtpPort: config.smtpPort, + smtpPassword: config.smtpPassword, + smtpUser: config.smtpUser, + }); + }); + + return migratedConfigurationRoot; +}; diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/mjml-metadata-manager.ts b/apps/emails-and-messages/src/modules/smtp/configuration/mjml-metadata-manager.ts new file mode 100644 index 0000000..5903cd5 --- /dev/null +++ b/apps/emails-and-messages/src/modules/smtp/configuration/mjml-metadata-manager.ts @@ -0,0 +1,32 @@ +// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION + +import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { MjmlConfig } from "./migrations/mjml-config-schema-v1"; + +export class MjmlPrivateMetadataManager { + private metadataKey = "mjml-config"; + + constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {} + + getConfig(): Promise { + return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => { + if (!data) { + return data; + } + + try { + return JSON.parse(data); + } catch (e) { + throw new Error("Invalid metadata value, can't be parsed"); + } + }); + } + + setConfig(config: MjmlConfig): Promise { + return this.metadataManager.set({ + key: this.metadataKey, + value: JSON.stringify(config), + domain: this.saleorApiUrl, + }); + } +} diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/smtp-config-schema.ts b/apps/emails-and-messages/src/modules/smtp/configuration/smtp-config-schema.ts index 2cec6cc..8e69729 100644 --- a/apps/emails-and-messages/src/modules/smtp/configuration/smtp-config-schema.ts +++ b/apps/emails-and-messages/src/modules/smtp/configuration/smtp-config-schema.ts @@ -1,37 +1,22 @@ -import { z } from "zod"; -import { messageEventTypes } from "../../event-handlers/message-event-types"; -import { channelConfigurationSchema } from "../../channels/channel-configuration-schema"; +import { + SmtpConfigV2, + SmtpConfigurationV2, + SmtpEventConfigurationV2, + smtpConfigV2Schema, + smtpConfigurationEventV2Schema, + smtpConfigurationV2Schema, +} from "./migrations/smtp-config-schema-v2"; export const smtpEncryptionTypes = ["NONE", "TLS", "SSL"] as const; -export const smtpConfigurationEventSchema = z.object({ - active: z.boolean().default(false), - eventType: z.enum(messageEventTypes), - template: z.string(), - subject: z.string(), -}); +export const smtpConfigurationEventSchema = smtpConfigurationEventV2Schema; -export type SmtpEventConfiguration = z.infer; +export type SmtpEventConfiguration = SmtpEventConfigurationV2; -export const smtpConfigurationSchema = z.object({ - id: z.string().min(1), - active: z.boolean().default(true), - name: z.string().min(1), - senderName: z.string().optional(), - senderEmail: z.string().email().min(5).optional(), - smtpHost: z.string().min(1), - smtpPort: z.string().min(1), - smtpUser: z.string().optional(), - smtpPassword: z.string().optional(), - encryption: z.enum(smtpEncryptionTypes).default("NONE"), - channels: channelConfigurationSchema, - events: z.array(smtpConfigurationEventSchema), -}); +export const smtpConfigurationSchema = smtpConfigurationV2Schema; -export type SmtpConfiguration = z.infer; +export type SmtpConfiguration = SmtpConfigurationV2; -export const smtpConfigSchema = z.object({ - configurations: z.array(smtpConfigurationSchema), -}); +export const smtpConfigSchema = smtpConfigV2Schema; -export type SmtpConfig = z.infer; +export type SmtpConfig = SmtpConfigV2; diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager-v2.ts b/apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager-v2.ts new file mode 100644 index 0000000..62493b1 --- /dev/null +++ b/apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager-v2.ts @@ -0,0 +1,30 @@ +import { SettingsManager } from "@saleor/app-sdk/settings-manager"; +import { SmtpConfig } from "./smtp-config-schema"; + +export class SmtpPrivateMetadataManagerV2 { + private metadataKey = "smtp-config-v2"; + + constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {} + + getConfig(): Promise { + return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => { + if (!data) { + return data; + } + + try { + return JSON.parse(data); + } catch (e) { + throw new Error("Invalid metadata value, can't be parsed"); + } + }); + } + + setConfig(config: SmtpConfig): Promise { + return this.metadataManager.set({ + key: this.metadataKey, + value: JSON.stringify(config), + domain: this.saleorApiUrl, + }); + } +} diff --git a/apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager.ts b/apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager.ts index 1cd5745..50d0aa7 100644 --- a/apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager.ts +++ b/apps/emails-and-messages/src/modules/smtp/configuration/smtp-metadata-manager.ts @@ -1,23 +1,41 @@ import { SettingsManager } from "@saleor/app-sdk/settings-manager"; import { SmtpConfig } from "./smtp-config-schema"; +import { SmtpPrivateMetadataManagerV2 } from "./smtp-metadata-manager-v2"; +import { smtpConfigMigrationV1ToV2 } from "./migrations/smtp-config-migration-v1-to-v2"; +import { createLogger } from "@saleor/apps-shared"; + +const logger = createLogger({ + fn: "SmtpPrivateMetadataManager", +}); export class SmtpPrivateMetadataManager { - private metadataKey = "smtp-config"; + private metadataKey = "smtp-config-v2"; constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {} - getConfig(): Promise { - return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => { - if (!data) { - return data; - } + async getConfig() { + logger.debug("Fetching config in the current version"); - try { - return JSON.parse(data); - } catch (e) { - throw new Error("Invalid metadata value, can't be parsed"); - } + const currentVersionManager = new SmtpPrivateMetadataManagerV2( + this.metadataManager, + this.saleorApiUrl + ); + + const currentVersionConfig = await currentVersionManager.getConfig(); + + if (currentVersionConfig) { + // We have the current version, no need to migrate so we can return it + return currentVersionConfig; + } + + logger.debug("No config in the current version, trying to migrate from v1"); + // TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE AFTER MIGRATION + const migratedSchema = await smtpConfigMigrationV1ToV2({ + saleorApiUrl: this.saleorApiUrl, + settingsManager: this.metadataManager, }); + + return migratedSchema; } setConfig(config: SmtpConfig): Promise { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d3f63a..109f4a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -524,6 +524,9 @@ importers: clsx: specifier: ^1.2.1 version: 1.2.1 + dotenv: + specifier: ^16.0.3 + version: 16.0.3 graphql: specifier: ^16.6.0 version: 16.6.0 @@ -6167,7 +6170,7 @@ packages: '@graphql-tools/utils': 9.2.1(graphql@16.6.0) '@whatwg-node/fetch': 0.7.0(@types/node@18.13.0) graphql: 16.6.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - '@types/node' - encoding @@ -6194,7 +6197,7 @@ packages: '@graphql-tools/utils': 9.2.1(graphql@16.6.0) globby: 11.1.0 graphql: 16.6.0 - tslib: 2.5.0 + tslib: 2.5.2 unixify: 1.0.0 transitivePeerDependencies: - '@babel/core' @@ -6309,7 +6312,7 @@ packages: graphql: 16.6.0 is-glob: 4.0.3 micromatch: 4.0.5 - tslib: 2.5.0 + tslib: 2.5.2 unixify: 1.0.0 transitivePeerDependencies: - '@babel/core' @@ -6344,7 +6347,7 @@ packages: '@graphql-tools/utils': 9.2.1(graphql@16.6.0) '@whatwg-node/fetch': 0.7.0(@types/node@18.13.0) graphql: 16.6.0 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - '@babel/core' - '@types/node' @@ -6361,7 +6364,7 @@ packages: '@graphql-tools/utils': 9.2.1(graphql@16.6.0) globby: 11.1.0 graphql: 16.6.0 - tslib: 2.5.0 + tslib: 2.5.2 unixify: 1.0.0 dev: true @@ -6401,7 +6404,7 @@ packages: '@graphql-tools/utils': 9.2.1(graphql@16.6.0) globby: 11.1.0 graphql: 16.6.0 - tslib: 2.5.0 + tslib: 2.5.2 unixify: 1.0.0 dev: true @@ -6414,7 +6417,7 @@ packages: '@graphql-tools/utils': 9.2.1(graphql@16.6.0) graphql: 16.6.0 p-limit: 3.1.0 - tslib: 2.5.0 + tslib: 2.5.2 dev: true /@graphql-tools/merge@8.3.18(graphql@16.6.0): @@ -6492,7 +6495,7 @@ packages: jsonwebtoken: 9.0.0 lodash: 4.17.21 scuid: 1.1.0 - tslib: 2.5.0 + tslib: 2.5.2 yaml-ast-parser: 0.0.43 transitivePeerDependencies: - '@types/node' @@ -6586,7 +6589,7 @@ packages: graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 dependencies: graphql: 16.6.0 - tslib: 2.4.1 + tslib: 2.5.2 dev: true /@graphql-tools/utils@9.2.1(graphql@16.6.0): @@ -13744,7 +13747,6 @@ packages: /dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} - dev: true /dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} @@ -14436,7 +14438,7 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.22.3 aria-query: 5.1.3 array-includes: 3.1.6 array.prototype.flatmap: 1.3.1 @@ -16075,7 +16077,7 @@ packages: jiti: 1.17.1 minimatch: 4.2.3 string-env-interpolation: 1.0.1 - tslib: 2.5.0 + tslib: 2.5.2 transitivePeerDependencies: - '@types/node' - bufferutil