refactor: 🔥 migration code (#806)
This commit is contained in:
parent
5a4da7beed
commit
1b47ad22da
21 changed files with 0 additions and 898 deletions
|
@ -1,21 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const addressSchema = z.object({
|
|
||||||
country: z.string(),
|
|
||||||
zip: z.string(),
|
|
||||||
state: z.string(),
|
|
||||||
city: z.string(),
|
|
||||||
street: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const channelSchema = z.object({
|
|
||||||
providerInstanceId: z.string(),
|
|
||||||
enabled: z.boolean(),
|
|
||||||
address: addressSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ChannelV1 = z.infer<typeof channelSchema>;
|
|
||||||
|
|
||||||
const channelsV1Schema = z.record(channelSchema);
|
|
||||||
|
|
||||||
export type ChannelsV1 = z.infer<typeof channelsV1Schema>;
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
import { channelsSchema } from "../../src/modules/channel-configuration/channel-config";
|
|
||||||
|
|
||||||
export const channelsV2Schema = channelsSchema;
|
|
||||||
|
|
||||||
export type ChannelsV2 = z.infer<typeof channelsV2Schema>;
|
|
|
@ -1,31 +0,0 @@
|
||||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
|
||||||
|
|
||||||
import { SaleorCloudAPL } from "@saleor/app-sdk/APL";
|
|
||||||
import { createSettingsManager } from "../../src/modules/app/metadata-manager";
|
|
||||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
|
||||||
|
|
||||||
export const getMetadataManagerForEnv = (apiUrl: string, appToken: string, appId: string) => {
|
|
||||||
const client = createGraphQLClient({
|
|
||||||
saleorApiUrl: apiUrl,
|
|
||||||
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();
|
|
||||||
};
|
|
|
@ -1,124 +0,0 @@
|
||||||
import { saleorApp } from "../../saleor-app";
|
|
||||||
import { Logger, createLogger } from "../../src/lib/logger";
|
|
||||||
import { createSettingsManager } from "../../src/modules/app/metadata-manager";
|
|
||||||
import { TaxProvidersV1 } from "./tax-providers-config-schema-v1";
|
|
||||||
import { TaxProvidersPrivateMetadataManagerV1 } from "./tax-providers-metadata-manager-v1";
|
|
||||||
import { ChannelsV1 } from "./channels-config-schema-v1";
|
|
||||||
|
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
import { TaxChannelsPrivateMetadataManagerV1 } from "./tax-channels-metadata-manager-v1";
|
|
||||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
export const dummyChannelsV1Config: ChannelsV1 = {
|
|
||||||
"default-channel": {
|
|
||||||
providerInstanceId: "24822834-1a49-4b51-8a59-579affdb772f",
|
|
||||||
address: {
|
|
||||||
city: "San Francisco",
|
|
||||||
country: "US",
|
|
||||||
state: "CA",
|
|
||||||
street: "Sesame Street",
|
|
||||||
zip: "10001",
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
"channel-pln": {
|
|
||||||
providerInstanceId: "d15d9907-a3cb-42d2-9336-366d2366e91b",
|
|
||||||
address: {
|
|
||||||
city: "San Francisco",
|
|
||||||
country: "US",
|
|
||||||
state: "CA",
|
|
||||||
street: "Sesame Street",
|
|
||||||
zip: "10001",
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dummyTaxProvidersV1Config: TaxProvidersV1 = [
|
|
||||||
{
|
|
||||||
provider: "avatax",
|
|
||||||
id: "24822834-1a49-4b51-8a59-579affdb772f",
|
|
||||||
config: {
|
|
||||||
isAutocommit: true,
|
|
||||||
isSandbox: true,
|
|
||||||
name: "Avatalara1",
|
|
||||||
password: "password",
|
|
||||||
username: "username",
|
|
||||||
companyCode: "companyCode",
|
|
||||||
shippingTaxCode: "shippingTaxCode",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provider: "taxjar",
|
|
||||||
id: "d15d9907-a3cb-42d2-9336-366d2366e91b",
|
|
||||||
config: {
|
|
||||||
isSandbox: true,
|
|
||||||
apiKey: "apiKey",
|
|
||||||
name: "TaxJar1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// This class is used to generate dummy config for the app to check if the runtime migrations work as expected.
|
|
||||||
class DummyConfigGenerator {
|
|
||||||
private logger: Logger;
|
|
||||||
constructor(private domain: string) {
|
|
||||||
this.logger = createLogger({
|
|
||||||
name: "DummyConfigGenerator",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private getFileApl = () => {
|
|
||||||
return saleorApp.apl.getAll();
|
|
||||||
};
|
|
||||||
|
|
||||||
private generateDummyTaxProvidersConfig = (): TaxProvidersV1 => dummyTaxProvidersV1Config;
|
|
||||||
|
|
||||||
private generateDummyTaxChannelsConfig = (): ChannelsV1 => dummyChannelsV1Config;
|
|
||||||
|
|
||||||
generate = async () => {
|
|
||||||
console.log("Generating dummy config");
|
|
||||||
const apls = await this.getFileApl();
|
|
||||||
|
|
||||||
console.log({ apls }, "Apls retrieved");
|
|
||||||
|
|
||||||
const target = apls.find((apl) => apl.domain === this.domain);
|
|
||||||
|
|
||||||
if (!target) {
|
|
||||||
throw new Error(`Domain ${this.domain} not found in apls`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dummyTaxProvidersConfig = this.generateDummyTaxProvidersConfig();
|
|
||||||
const dummyTaxChannelsConfig = this.generateDummyTaxChannelsConfig();
|
|
||||||
|
|
||||||
console.log({ dummyTaxProvidersConfig, dummyTaxChannelsConfig }, "Dummy configs generated");
|
|
||||||
|
|
||||||
const client = createGraphQLClient({
|
|
||||||
saleorApiUrl: target.saleorApiUrl,
|
|
||||||
token: target.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
const metadataManager = createSettingsManager(client, target.appId);
|
|
||||||
const taxProvidersManager = new TaxProvidersPrivateMetadataManagerV1(
|
|
||||||
metadataManager,
|
|
||||||
target.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxChannelsManager = new TaxChannelsPrivateMetadataManagerV1(
|
|
||||||
metadataManager,
|
|
||||||
target.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("Setting dummy configs");
|
|
||||||
|
|
||||||
await taxProvidersManager.setConfig(dummyTaxProvidersConfig);
|
|
||||||
await taxChannelsManager.setConfig(dummyTaxChannelsConfig);
|
|
||||||
|
|
||||||
console.log("Dummy config set");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// const dummyConfigGenerator = new DummyConfigGenerator("");
|
|
||||||
|
|
||||||
// dummyConfigGenerator.generate();
|
|
|
@ -1,78 +0,0 @@
|
||||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
|
||||||
|
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
import { fetchCloudAplEnvs, getMetadataManagerForEnv, verifyRequiredEnvs } from "./migration-utils";
|
|
||||||
import { TaxProvidersV1toV2MigrationManager } from "./tax-providers-migration-v1-to-v2";
|
|
||||||
import { TaxChannelsV1toV2MigrationManager } from "./tax-channels-migration-v1-to-v2";
|
|
||||||
|
|
||||||
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 = {
|
|
||||||
taxProviders: [] as string[],
|
|
||||||
taxChannels: [] as string[],
|
|
||||||
none: [] as string[],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const env of allEnvs) {
|
|
||||||
let isTaxProvidersMigrated = false;
|
|
||||||
let isTaxChannelsMigrated = false;
|
|
||||||
|
|
||||||
console.log("Working on env: ", env.saleorApiUrl);
|
|
||||||
|
|
||||||
const metadataManager = getMetadataManagerForEnv(env.saleorApiUrl, env.token, env.appId);
|
|
||||||
|
|
||||||
const taxProvidersMigrationManager = new TaxProvidersV1toV2MigrationManager(
|
|
||||||
metadataManager,
|
|
||||||
env.saleorApiUrl,
|
|
||||||
{ mode: "migrate" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxProvidersMigratedConfig = await taxProvidersMigrationManager.migrateIfNeeded();
|
|
||||||
|
|
||||||
if (taxProvidersMigratedConfig) {
|
|
||||||
console.log("Config migrated", taxProvidersMigratedConfig);
|
|
||||||
isTaxProvidersMigrated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const taxChannelsMigrationManager = new TaxChannelsV1toV2MigrationManager(
|
|
||||||
metadataManager,
|
|
||||||
env.saleorApiUrl,
|
|
||||||
{ mode: "migrate" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxChannelsMigratedConfig = await taxChannelsMigrationManager.migrateIfNeeded();
|
|
||||||
|
|
||||||
if (taxChannelsMigratedConfig) {
|
|
||||||
console.log("Config migrated", taxChannelsMigratedConfig);
|
|
||||||
isTaxChannelsMigrated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTaxProvidersMigrated) {
|
|
||||||
report.taxProviders.push(env.saleorApiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTaxChannelsMigrated) {
|
|
||||||
report.taxChannels.push(env.saleorApiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTaxProvidersMigrated && !isTaxChannelsMigrated) {
|
|
||||||
report.none.push(env.saleorApiUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Report", report);
|
|
||||||
};
|
|
||||||
|
|
||||||
runMigration();
|
|
|
@ -1,78 +0,0 @@
|
||||||
/* eslint-disable turbo/no-undeclared-env-vars */
|
|
||||||
|
|
||||||
import * as dotenv from "dotenv";
|
|
||||||
import { fetchCloudAplEnvs, getMetadataManagerForEnv, verifyRequiredEnvs } from "./migration-utils";
|
|
||||||
import { TaxProvidersV1toV2MigrationManager } from "./tax-providers-migration-v1-to-v2";
|
|
||||||
import { TaxChannelsV1toV2MigrationManager } from "./tax-channels-migration-v1-to-v2";
|
|
||||||
|
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const runReport = 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 = {
|
|
||||||
taxProviders: [] as string[],
|
|
||||||
taxChannels: [] as string[],
|
|
||||||
none: [] as string[],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const env of allEnvs) {
|
|
||||||
let isTaxProvidersMigrated = false;
|
|
||||||
let isTaxChannelsMigrated = false;
|
|
||||||
|
|
||||||
console.log("Working on env: ", env.saleorApiUrl);
|
|
||||||
|
|
||||||
const metadataManager = getMetadataManagerForEnv(env.saleorApiUrl, env.token, env.appId);
|
|
||||||
|
|
||||||
const taxProvidersMigrationManager = new TaxProvidersV1toV2MigrationManager(
|
|
||||||
metadataManager,
|
|
||||||
env.saleorApiUrl,
|
|
||||||
{ mode: "report" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxProvidersMigratedConfig = await taxProvidersMigrationManager.migrateIfNeeded();
|
|
||||||
|
|
||||||
if (taxProvidersMigratedConfig) {
|
|
||||||
console.log("Config migrated", taxProvidersMigratedConfig);
|
|
||||||
isTaxProvidersMigrated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const taxChannelsMigrationManager = new TaxChannelsV1toV2MigrationManager(
|
|
||||||
metadataManager,
|
|
||||||
env.saleorApiUrl,
|
|
||||||
{ mode: "report" }
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxChannelsMigratedConfig = await taxChannelsMigrationManager.migrateIfNeeded();
|
|
||||||
|
|
||||||
if (taxChannelsMigratedConfig) {
|
|
||||||
console.log("Config migrated", taxChannelsMigratedConfig);
|
|
||||||
isTaxChannelsMigrated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTaxProvidersMigrated) {
|
|
||||||
report.taxProviders.push(env.saleorApiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTaxChannelsMigrated) {
|
|
||||||
report.taxChannels.push(env.saleorApiUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTaxProvidersMigrated && !isTaxChannelsMigrated) {
|
|
||||||
report.none.push(env.saleorApiUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Report", report);
|
|
||||||
};
|
|
||||||
|
|
||||||
runReport();
|
|
|
@ -1,32 +0,0 @@
|
||||||
// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION
|
|
||||||
|
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { ChannelsV1 } from "./channels-config-schema-v1";
|
|
||||||
|
|
||||||
export class TaxChannelsPrivateMetadataManagerV1 {
|
|
||||||
private metadataKey = "tax-channels";
|
|
||||||
|
|
||||||
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
|
|
||||||
|
|
||||||
getConfig(): Promise<ChannelsV1 | undefined> {
|
|
||||||
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: ChannelsV1): Promise<void> {
|
|
||||||
return this.metadataManager.set({
|
|
||||||
key: this.metadataKey,
|
|
||||||
value: JSON.stringify(config),
|
|
||||||
domain: this.saleorApiUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION
|
|
||||||
|
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { ChannelsV2 } from "./channels-config-schema-v2";
|
|
||||||
|
|
||||||
export class TaxChannelsPrivateMetadataManagerV2 {
|
|
||||||
private metadataKey = "tax-channels-v2";
|
|
||||||
|
|
||||||
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
|
|
||||||
|
|
||||||
getConfig(): Promise<ChannelsV2 | undefined> {
|
|
||||||
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: ChannelsV2): Promise<void> {
|
|
||||||
return this.metadataManager.set({
|
|
||||||
key: this.metadataKey,
|
|
||||||
value: JSON.stringify(config),
|
|
||||||
domain: this.saleorApiUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { Logger, createLogger } from "../../src/lib/logger";
|
|
||||||
import { TaxChannelsPrivateMetadataManagerV1 } from "./tax-channels-metadata-manager-v1";
|
|
||||||
import { TaxChannelsPrivateMetadataManagerV2 } from "./tax-channels-metadata-manager-v2";
|
|
||||||
import { TaxChannelsTransformV1toV2 } from "./tax-channels-transform-v1-to-v2";
|
|
||||||
|
|
||||||
export class TaxChannelsV1toV2MigrationManager {
|
|
||||||
private logger: Logger;
|
|
||||||
constructor(
|
|
||||||
private metadataManager: SettingsManager,
|
|
||||||
private saleorApiUrl: string,
|
|
||||||
private options: { mode: "report" | "migrate" } = { mode: "migrate" }
|
|
||||||
) {
|
|
||||||
this.logger = createLogger({
|
|
||||||
name: "TaxChannelsV1toV2MigrationManager",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async migrateIfNeeded() {
|
|
||||||
const taxChannelsManagerV1 = new TaxChannelsPrivateMetadataManagerV1(
|
|
||||||
this.metadataManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxChannelsManagerV2 = new TaxChannelsPrivateMetadataManagerV2(
|
|
||||||
this.metadataManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Commenting this out, because we want to overwrite the previous migrations.
|
|
||||||
* const currentConfig = await taxChannelsManagerV2.getConfig();
|
|
||||||
* if (currentConfig) {
|
|
||||||
* this.logger.info("Migration is not necessary, we have current config.");
|
|
||||||
* return currentConfig;
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
const previousChannelConfig = await taxChannelsManagerV1.getConfig();
|
|
||||||
|
|
||||||
if (!previousChannelConfig) {
|
|
||||||
this.logger.info("Previous config not found. Migration not possible.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Previous config found. Migrating...");
|
|
||||||
|
|
||||||
const transformer = new TaxChannelsTransformV1toV2();
|
|
||||||
const nextConfig = transformer.transform(previousChannelConfig);
|
|
||||||
|
|
||||||
if (this.options.mode === "migrate") {
|
|
||||||
await taxChannelsManagerV2.setConfig(nextConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextConfig;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { dummyChannelsV1Config } from "./run-generate-dummy-data";
|
|
||||||
import { TaxChannelsTransformV1toV2 } from "./tax-channels-transform-v1-to-v2";
|
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
const transformer = new TaxChannelsTransformV1toV2();
|
|
||||||
|
|
||||||
describe("TaxChannelsTransformV1toV2", () => {
|
|
||||||
it("should transform v1 to v2", () => {
|
|
||||||
const result = transformer.transform(dummyChannelsV1Config);
|
|
||||||
|
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
|
||||||
id: expect.any(String),
|
|
||||||
config: {
|
|
||||||
providerConnectionId: "24822834-1a49-4b51-8a59-579affdb772f",
|
|
||||||
slug: "default-channel",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: expect.any(String),
|
|
||||||
config: {
|
|
||||||
providerConnectionId: "d15d9907-a3cb-42d2-9336-366d2366e91b",
|
|
||||||
slug: "channel-pln",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array if no channels are provided", () => {
|
|
||||||
const result = transformer.transform({});
|
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { createId } from "../../src/lib/utils";
|
|
||||||
import { ChannelsV1 } from "./channels-config-schema-v1";
|
|
||||||
import { ChannelsV2 } from "./channels-config-schema-v2";
|
|
||||||
|
|
||||||
export class TaxChannelsTransformV1toV2 {
|
|
||||||
transform(channels: ChannelsV1): ChannelsV2 {
|
|
||||||
return Object.keys(channels).map((slug) => {
|
|
||||||
const channel = channels[slug];
|
|
||||||
|
|
||||||
return {
|
|
||||||
// * There was no id in v1, so we need to generate it
|
|
||||||
id: createId(),
|
|
||||||
config: {
|
|
||||||
providerConnectionId: channel.providerInstanceId,
|
|
||||||
slug,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const avataxConfigSchema = z.object({
|
|
||||||
name: z.string().min(1, { message: "Name requires at least one character." }),
|
|
||||||
username: z.string().min(1, { message: "Username requires at least one character." }),
|
|
||||||
password: z.string().min(1, { message: "Password requires at least one character." }),
|
|
||||||
isSandbox: z.boolean(),
|
|
||||||
companyCode: z.string().optional(),
|
|
||||||
isAutocommit: z.boolean(),
|
|
||||||
shippingTaxCode: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const avataxInstanceConfigV1Schema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
provider: z.literal("avatax"),
|
|
||||||
config: avataxConfigSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type AvataxInstanceConfigV1 = z.infer<typeof avataxInstanceConfigV1Schema>;
|
|
||||||
|
|
||||||
const taxJarConfigSchema = z.object({
|
|
||||||
name: z.string().min(1, { message: "Name requires at least one character." }),
|
|
||||||
apiKey: z.string().min(1, { message: "API Key requires at least one character." }),
|
|
||||||
isSandbox: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const taxJarInstanceConfigV1Schema = z.object({
|
|
||||||
id: z.string(),
|
|
||||||
provider: z.literal("taxjar"),
|
|
||||||
config: taxJarConfigSchema,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TaxJarInstanceConfigV1 = z.infer<typeof taxJarInstanceConfigV1Schema>;
|
|
||||||
|
|
||||||
const taxProviderV1Schema = taxJarInstanceConfigV1Schema.or(avataxInstanceConfigV1Schema);
|
|
||||||
|
|
||||||
export type TaxProviderV1 = z.infer<typeof taxProviderV1Schema>;
|
|
||||||
const taxProvidersV1Schema = z.array(taxProviderV1Schema);
|
|
||||||
|
|
||||||
export type TaxProvidersV1 = z.infer<typeof taxProvidersV1Schema>;
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { z } from "zod";
|
|
||||||
import { providerConnectionsSchema } from "../../src/modules/provider-connections/provider-connections";
|
|
||||||
|
|
||||||
const taxProvidersV2Schema = providerConnectionsSchema;
|
|
||||||
|
|
||||||
export type TaxProvidersV2 = z.infer<typeof taxProvidersV2Schema>;
|
|
|
@ -1,32 +0,0 @@
|
||||||
// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION
|
|
||||||
|
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { TaxProvidersV1 } from "./tax-providers-config-schema-v1";
|
|
||||||
|
|
||||||
export class TaxProvidersPrivateMetadataManagerV1 {
|
|
||||||
private metadataKey = "tax-providers";
|
|
||||||
|
|
||||||
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
|
|
||||||
|
|
||||||
getConfig(): Promise<TaxProvidersV1 | undefined> {
|
|
||||||
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: TaxProvidersV1): Promise<void> {
|
|
||||||
return this.metadataManager.set({
|
|
||||||
key: this.metadataKey,
|
|
||||||
value: JSON.stringify(config),
|
|
||||||
domain: this.saleorApiUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// TODO: MIGRATION CODE FROM CONFIG VERSION V1. REMOVE THIS FILE AFTER MIGRATION
|
|
||||||
|
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { TaxProvidersV2 } from "./tax-providers-config-schema-v2";
|
|
||||||
import { TAX_PROVIDER_KEY } from "../../src/modules/provider-connections/public-provider-connections.service";
|
|
||||||
|
|
||||||
export class TaxProvidersPrivateMetadataManagerV2 {
|
|
||||||
private metadataKey = TAX_PROVIDER_KEY;
|
|
||||||
|
|
||||||
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
|
|
||||||
|
|
||||||
getConfig(): Promise<TaxProvidersV2 | undefined> {
|
|
||||||
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: TaxProvidersV2): Promise<void> {
|
|
||||||
return this.metadataManager.set({
|
|
||||||
key: this.metadataKey,
|
|
||||||
value: JSON.stringify(config),
|
|
||||||
domain: this.saleorApiUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|
||||||
import { Logger, createLogger } from "../../src/lib/logger";
|
|
||||||
import { TaxProvidersPrivateMetadataManagerV1 } from "./tax-providers-metadata-manager-v1";
|
|
||||||
import { TaxProvidersPrivateMetadataManagerV2 } from "./tax-providers-metadata-manager-v2";
|
|
||||||
import { TaxProvidersV1ToV2Transformer } from "./tax-providers-transform-v1-to-v2";
|
|
||||||
import { TaxChannelsPrivateMetadataManagerV1 } from "./tax-channels-metadata-manager-v1";
|
|
||||||
|
|
||||||
export class TaxProvidersV1toV2MigrationManager {
|
|
||||||
private logger: Logger;
|
|
||||||
constructor(
|
|
||||||
private metadataManager: SettingsManager,
|
|
||||||
private saleorApiUrl: string,
|
|
||||||
private options: { mode: "report" | "migrate" } = { mode: "migrate" }
|
|
||||||
) {
|
|
||||||
this.logger = createLogger({
|
|
||||||
name: "TaxProvidersV1toV2MigrationManager",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async migrateIfNeeded() {
|
|
||||||
const taxProvidersManagerV1 = new TaxProvidersPrivateMetadataManagerV1(
|
|
||||||
this.metadataManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
const taxProvidersManagerV2 = new TaxProvidersPrivateMetadataManagerV2(
|
|
||||||
this.metadataManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const taxChannelsManagerV1 = new TaxChannelsPrivateMetadataManagerV1(
|
|
||||||
this.metadataManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Commenting this out, because we want to overwrite the previous migrations.
|
|
||||||
* const currentTaxProvidersConfig = await taxProvidersManagerV2.getConfig();
|
|
||||||
* if (currentTaxProvidersConfig) {
|
|
||||||
* this.logger.info("Migration is not necessary, the config is up to date.");
|
|
||||||
* return;
|
|
||||||
* }
|
|
||||||
* this.logger.info("Current config not found.");
|
|
||||||
*/
|
|
||||||
|
|
||||||
const previousTaxProvidersConfig = await taxProvidersManagerV1.getConfig();
|
|
||||||
const previousChannelConfig = await taxChannelsManagerV1.getConfig();
|
|
||||||
|
|
||||||
if (!previousTaxProvidersConfig || !previousChannelConfig) {
|
|
||||||
this.logger.info(
|
|
||||||
{ previousChannelConfig, previousTaxProvidersConfig },
|
|
||||||
"Previous config not found. Migration not possible."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Previous config found. Migrating...");
|
|
||||||
|
|
||||||
const transformer = new TaxProvidersV1ToV2Transformer();
|
|
||||||
const nextConfig = transformer.transform(previousTaxProvidersConfig, previousChannelConfig);
|
|
||||||
|
|
||||||
if (this.options.mode === "migrate") {
|
|
||||||
await taxProvidersManagerV2.setConfig(nextConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextConfig;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
import { dummyChannelsV1Config, dummyTaxProvidersV1Config } from "./run-generate-dummy-data";
|
|
||||||
import { TaxProvidersV1ToV2Transformer } from "./tax-providers-transform-v1-to-v2";
|
|
||||||
import { describe, expect, it } from "vitest";
|
|
||||||
|
|
||||||
const transformer = new TaxProvidersV1ToV2Transformer();
|
|
||||||
|
|
||||||
describe("TaxProvidersV1ToV2Transformer", () => {
|
|
||||||
it("should transform v1 to v2", () => {
|
|
||||||
const result = transformer.transform(dummyTaxProvidersV1Config, dummyChannelsV1Config);
|
|
||||||
|
|
||||||
expect(result).toEqual([
|
|
||||||
{
|
|
||||||
id: "24822834-1a49-4b51-8a59-579affdb772f",
|
|
||||||
provider: "avatax",
|
|
||||||
config: {
|
|
||||||
name: "Avatalara1",
|
|
||||||
isSandbox: true,
|
|
||||||
isAutocommit: true,
|
|
||||||
credentials: {
|
|
||||||
username: "username",
|
|
||||||
password: "password",
|
|
||||||
},
|
|
||||||
companyCode: "companyCode",
|
|
||||||
shippingTaxCode: "shippingTaxCode",
|
|
||||||
address: {
|
|
||||||
city: "San Francisco",
|
|
||||||
country: "US",
|
|
||||||
state: "CA",
|
|
||||||
street: "Sesame Street",
|
|
||||||
zip: "10001",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "d15d9907-a3cb-42d2-9336-366d2366e91b",
|
|
||||||
provider: "taxjar",
|
|
||||||
config: {
|
|
||||||
name: "TaxJar1",
|
|
||||||
isSandbox: true,
|
|
||||||
credentials: {
|
|
||||||
apiKey: "apiKey",
|
|
||||||
},
|
|
||||||
address: {
|
|
||||||
city: "San Francisco",
|
|
||||||
country: "US",
|
|
||||||
state: "CA",
|
|
||||||
street: "Sesame Street",
|
|
||||||
zip: "10001",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should return empty array if no channels and providers are provided", () => {
|
|
||||||
const result = transformer.transform([], {});
|
|
||||||
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,91 +0,0 @@
|
||||||
import { AvataxConnection } from "../../src/modules/avatax/avatax-connection-schema";
|
|
||||||
import { TaxJarConnection } from "../../src/modules/taxjar/taxjar-connection-schema";
|
|
||||||
import { ChannelV1, ChannelsV1 } from "./channels-config-schema-v1";
|
|
||||||
import {
|
|
||||||
AvataxInstanceConfigV1,
|
|
||||||
TaxJarInstanceConfigV1,
|
|
||||||
TaxProvidersV1,
|
|
||||||
} from "./tax-providers-config-schema-v1";
|
|
||||||
import { TaxProvidersV2 } from "./tax-providers-config-schema-v2";
|
|
||||||
|
|
||||||
export class TaxProvidersV1ToV2Transformer {
|
|
||||||
private findTaxProviderChannelConfig = (id: string, channelsConfig: ChannelsV1): ChannelV1 => {
|
|
||||||
const channel = Object.values(channelsConfig).find(
|
|
||||||
(channel) => channel.providerInstanceId === id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!channel) {
|
|
||||||
throw new Error(`Channel with id ${id} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return channel;
|
|
||||||
};
|
|
||||||
|
|
||||||
private transformAvataxInstance = (
|
|
||||||
instance: AvataxInstanceConfigV1,
|
|
||||||
channel: ChannelV1
|
|
||||||
): AvataxConnection => {
|
|
||||||
return {
|
|
||||||
id: instance.id,
|
|
||||||
provider: "avatax",
|
|
||||||
config: {
|
|
||||||
name: instance.config.name,
|
|
||||||
address: {
|
|
||||||
city: channel.address.city,
|
|
||||||
country: channel.address.country,
|
|
||||||
state: channel.address.state,
|
|
||||||
street: channel.address.street,
|
|
||||||
zip: channel.address.zip,
|
|
||||||
},
|
|
||||||
credentials: {
|
|
||||||
password: instance.config.password,
|
|
||||||
username: instance.config.username,
|
|
||||||
},
|
|
||||||
isAutocommit: instance.config.isAutocommit,
|
|
||||||
isSandbox: instance.config.isSandbox,
|
|
||||||
companyCode: instance.config.companyCode,
|
|
||||||
shippingTaxCode: instance.config.shippingTaxCode,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
private transformTaxJarInstance = (
|
|
||||||
instance: TaxJarInstanceConfigV1,
|
|
||||||
channel: ChannelV1
|
|
||||||
): TaxJarConnection => {
|
|
||||||
return {
|
|
||||||
id: instance.id,
|
|
||||||
provider: "taxjar",
|
|
||||||
config: {
|
|
||||||
name: instance.config.name,
|
|
||||||
address: {
|
|
||||||
city: channel.address.city,
|
|
||||||
country: channel.address.country,
|
|
||||||
state: channel.address.state,
|
|
||||||
street: channel.address.street,
|
|
||||||
zip: channel.address.zip,
|
|
||||||
},
|
|
||||||
credentials: {
|
|
||||||
apiKey: instance.config.apiKey,
|
|
||||||
},
|
|
||||||
isSandbox: instance.config.isSandbox,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
transform = (taxProvidersConfig: TaxProvidersV1, channelsConfig: ChannelsV1): TaxProvidersV2 => {
|
|
||||||
return taxProvidersConfig.map((instance) => {
|
|
||||||
const channel = this.findTaxProviderChannelConfig(instance.id, channelsConfig);
|
|
||||||
|
|
||||||
if (instance.provider === "avatax") {
|
|
||||||
return this.transformAvataxInstance(instance, channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (instance.provider === "taxjar") {
|
|
||||||
return this.transformTaxJarInstance(instance, channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unknown provider `);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager";
|
import { EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager";
|
||||||
import { TaxProvidersV1toV2MigrationManager } from "../../../../scripts/migrations/tax-providers-migration-v1-to-v2";
|
|
||||||
import { createLogger, Logger } from "../../../lib/logger";
|
import { createLogger, Logger } from "../../../lib/logger";
|
||||||
import { CrudSettingsManager } from "../../crud-settings/crud-settings.service";
|
import { CrudSettingsManager } from "../../crud-settings/crud-settings.service";
|
||||||
import {
|
import {
|
||||||
|
@ -38,26 +37,6 @@ export class AvataxConnectionRepository {
|
||||||
|
|
||||||
async getAll(): Promise<AvataxConnection[]> {
|
async getAll(): Promise<AvataxConnection[]> {
|
||||||
const { data } = await this.crudSettingsManager.readAll();
|
const { data } = await this.crudSettingsManager.readAll();
|
||||||
/*
|
|
||||||
* * migration logic start
|
|
||||||
* // todo: remove after migration
|
|
||||||
*/
|
|
||||||
const migrationManager = new TaxProvidersV1toV2MigrationManager(
|
|
||||||
this.settingsManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const migratedConfig = await migrationManager.migrateIfNeeded();
|
|
||||||
|
|
||||||
if (migratedConfig) {
|
|
||||||
this.logger.info("Config migrated", migratedConfig);
|
|
||||||
return this.filterAvataxConnections(migratedConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Config is up to date, no need to migrate.");
|
|
||||||
/*
|
|
||||||
* * migration logic end
|
|
||||||
*/
|
|
||||||
|
|
||||||
const connections = providerConnectionsSchema.parse(data);
|
const connections = providerConnectionsSchema.parse(data);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { ChannelConfigProperties } from "./channel-config";
|
||||||
import { ChannelConfigurationRepository } from "./channel-configuration-repository";
|
import { ChannelConfigurationRepository } from "./channel-configuration-repository";
|
||||||
import { ChannelsFetcher } from "./channel-fetcher";
|
import { ChannelsFetcher } from "./channel-fetcher";
|
||||||
import { ChannelConfigurationMerger } from "./channel-configuration-merger";
|
import { ChannelConfigurationMerger } from "./channel-configuration-merger";
|
||||||
import { TaxChannelsV1toV2MigrationManager } from "../../../scripts/migrations/tax-channels-migration-v1-to-v2";
|
|
||||||
import { EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager";
|
import { EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager";
|
||||||
import { Logger, createLogger } from "../../lib/logger";
|
import { Logger, createLogger } from "../../lib/logger";
|
||||||
import { createSettingsManager } from "../app/metadata-manager";
|
import { createSettingsManager } from "../app/metadata-manager";
|
||||||
|
@ -30,19 +29,6 @@ export class ChannelConfigurationService {
|
||||||
async getAll() {
|
async getAll() {
|
||||||
const channelsFetcher = new ChannelsFetcher(this.client);
|
const channelsFetcher = new ChannelsFetcher(this.client);
|
||||||
|
|
||||||
const migrationManager = new TaxChannelsV1toV2MigrationManager(
|
|
||||||
this.settingsManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const migratedConfig = await migrationManager.migrateIfNeeded();
|
|
||||||
|
|
||||||
if (migratedConfig) {
|
|
||||||
this.logger.info("Config migrated", migratedConfig);
|
|
||||||
return migratedConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Config is up to date, no need to migrate.");
|
|
||||||
const channels = await channelsFetcher.fetchChannels();
|
const channels = await channelsFetcher.fetchChannels();
|
||||||
|
|
||||||
const channelConfiguration = await this.configurationRepository.getAll();
|
const channelConfiguration = await this.configurationRepository.getAll();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager";
|
import { EncryptedMetadataManager } from "@saleor/app-sdk/settings-manager";
|
||||||
import { TaxProvidersV1toV2MigrationManager } from "../../../../scripts/migrations/tax-providers-migration-v1-to-v2";
|
|
||||||
import { createLogger, Logger } from "../../../lib/logger";
|
import { createLogger, Logger } from "../../../lib/logger";
|
||||||
import { CrudSettingsManager } from "../../crud-settings/crud-settings.service";
|
import { CrudSettingsManager } from "../../crud-settings/crud-settings.service";
|
||||||
import {
|
import {
|
||||||
|
@ -34,26 +33,6 @@ export class TaxJarConnectionRepository {
|
||||||
|
|
||||||
async getAll(): Promise<TaxJarConnection[]> {
|
async getAll(): Promise<TaxJarConnection[]> {
|
||||||
const { data } = await this.crudSettingsManager.readAll();
|
const { data } = await this.crudSettingsManager.readAll();
|
||||||
/*
|
|
||||||
* * migration logic start
|
|
||||||
* // todo: remove after migration
|
|
||||||
*/
|
|
||||||
const migrationManager = new TaxProvidersV1toV2MigrationManager(
|
|
||||||
this.settingsManager,
|
|
||||||
this.saleorApiUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
const migratedConfig = await migrationManager.migrateIfNeeded();
|
|
||||||
|
|
||||||
if (migratedConfig) {
|
|
||||||
this.logger.info("Config migrated", migratedConfig);
|
|
||||||
return this.filterTaxJarConnections(migratedConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info("Config is up to date, no need to migrate.");
|
|
||||||
/*
|
|
||||||
* * migration logic end
|
|
||||||
*/
|
|
||||||
|
|
||||||
const connections = providerConnectionsSchema.parse(data);
|
const connections = providerConnectionsSchema.parse(data);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue