Compare commits
1 commit
main
...
domain-mod
Author | SHA1 | Date | |
---|---|---|---|
![]() |
204ab41168 |
6 changed files with 4567 additions and 4050 deletions
|
@ -62,6 +62,7 @@
|
||||||
"@types/react": "^18.0.27",
|
"@types/react": "^18.0.27",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
"@types/rimraf": "^3.0.2",
|
"@types/rimraf": "^3.0.2",
|
||||||
|
"@types/semver": "^7.3.13",
|
||||||
"@vitejs/plugin-react": "^3.0.0",
|
"@vitejs/plugin-react": "^3.0.0",
|
||||||
"@vitest/coverage-c8": "^0.28.4",
|
"@vitest/coverage-c8": "^0.28.4",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
|
@ -70,7 +71,6 @@
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"typescript": "4.9.5",
|
"typescript": "4.9.5",
|
||||||
"vite": "^4.2.1",
|
"vite": "^4.2.1",
|
||||||
"vitest": "^0.30.1",
|
"vitest": "^0.30.1"
|
||||||
"@types/semver": "^7.3.13"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { describe, test, expect } from "vitest";
|
||||||
|
import { ConfigModel } from "./config-v3";
|
||||||
|
|
||||||
|
describe("configv3", () => {
|
||||||
|
test("Constructs", () => {
|
||||||
|
const instance = new ConfigModel();
|
||||||
|
|
||||||
|
expect(instance).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Serializes", () => {
|
||||||
|
const instance = new ConfigModel({
|
||||||
|
overrides: {
|
||||||
|
usd: {
|
||||||
|
channel: {
|
||||||
|
slug: "usd",
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
city: "Krakow",
|
||||||
|
cityArea: "krowodrza",
|
||||||
|
country: "poland",
|
||||||
|
streetAddress1: "Some street",
|
||||||
|
streetAddress2: "",
|
||||||
|
postalCode: "12345",
|
||||||
|
companyName: "Saleor",
|
||||||
|
countryArea: "Malopolskie",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(instance.serialize()).toEqual(
|
||||||
|
'{"overrides":{"usd":{"channel":{"slug":"usd"},"address":{"city":"Krakow","cityArea":"krowodrza","country":"poland","streetAddress1":"Some street","streetAddress2":"","postalCode":"12345","companyName":"Saleor","countryArea":"Malopolskie"}}}}'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Parses root schema", () => {
|
||||||
|
const instance = ConfigModel.parse(
|
||||||
|
'{"overrides":{"usd":{"channel":{"slug":"usd"},"address":{"city":"Krakow","cityArea":"krowodrza","country":"poland","streetAddress1":"Some street","streetAddress2":"","postalCode":"12345","companyName":"Saleor","countryArea":"Malopolskie"}}}}'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(instance.getOverridesArray()).toHaveLength(1);
|
||||||
|
expect(instance.getOverridesArray()[0].channel.slug).toEqual("usd");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Appends override", () => {
|
||||||
|
const instance = new ConfigModel();
|
||||||
|
|
||||||
|
expect(instance.getOverridesArray()).toHaveLength(0);
|
||||||
|
|
||||||
|
instance.addOverride("usd_USD", {
|
||||||
|
city: "Krakow",
|
||||||
|
cityArea: "krowodrza",
|
||||||
|
country: "poland",
|
||||||
|
streetAddress1: "Some street",
|
||||||
|
streetAddress2: "",
|
||||||
|
postalCode: "12345",
|
||||||
|
companyName: "Saleor",
|
||||||
|
countryArea: "Malopolskie",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(instance.getOverridesArray()).toHaveLength(1);
|
||||||
|
expect(instance.getOverridesArray()[0].channel.slug).toEqual("usd_USD");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Removes override", () => {
|
||||||
|
const instance = new ConfigModel({
|
||||||
|
overrides: {
|
||||||
|
usd: {
|
||||||
|
channel: {
|
||||||
|
slug: "usd",
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
city: "Krakow",
|
||||||
|
cityArea: "krowodrza",
|
||||||
|
country: "poland",
|
||||||
|
streetAddress1: "Some street",
|
||||||
|
streetAddress2: "",
|
||||||
|
postalCode: "12345",
|
||||||
|
companyName: "Saleor",
|
||||||
|
countryArea: "Malopolskie",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.removeOverride("usd");
|
||||||
|
|
||||||
|
expect(instance.getOverridesArray()).toHaveLength(0);
|
||||||
|
|
||||||
|
expect(instance.serialize()).toEqual(`{"overrides":{}}`);
|
||||||
|
});
|
||||||
|
});
|
137
apps/invoices/src/modules/app-configuration/config-v3.ts
Normal file
137
apps/invoices/src/modules/app-configuration/config-v3.ts
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { test } from "vitest";
|
||||||
|
import { EncryptedMetadataManager, SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||||
|
|
||||||
|
const AddressSchema = z.object({
|
||||||
|
companyName: z.string(),
|
||||||
|
cityArea: z.string(),
|
||||||
|
countryArea: z.string(),
|
||||||
|
streetAddress1: z.string(),
|
||||||
|
streetAddress2: z.string(),
|
||||||
|
postalCode: z.string(),
|
||||||
|
city: z.string(),
|
||||||
|
country: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ChannelSchema = z.object({
|
||||||
|
slug: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const AddressOverrideSchema = z.object({
|
||||||
|
address: AddressSchema,
|
||||||
|
channel: ChannelSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const RootConfigSchema = z.object({
|
||||||
|
overrides: z.record(AddressOverrideSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root model that can serialize to json and parse from json.
|
||||||
|
*
|
||||||
|
* Uses Zod to parse and validate
|
||||||
|
*
|
||||||
|
* Adds domain methods on top
|
||||||
|
*/
|
||||||
|
export class ConfigModel {
|
||||||
|
/**
|
||||||
|
* Stores its own data as deep, single json, structured and validated by zod
|
||||||
|
*/
|
||||||
|
private rootData: z.infer<typeof RootConfigSchema> = { overrides: {} };
|
||||||
|
|
||||||
|
constructor(initialConfig?: z.infer<typeof RootConfigSchema>) {
|
||||||
|
/**
|
||||||
|
* Sets its own initial state but also allows to inject - then validate
|
||||||
|
*/
|
||||||
|
if (initialConfig) {
|
||||||
|
this.rootData = RootConfigSchema.parse(initialConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can statically parse itself, ensures parse/serialize work together
|
||||||
|
*/
|
||||||
|
static parse(serialized: string) {
|
||||||
|
return new ConfigModel(RootConfigSchema.parse(JSON.parse(serialized)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes to json, even if some extra methods are saved (can be replaced/cleaned up if needed),
|
||||||
|
* zod will remove unknown members after parsing
|
||||||
|
*/
|
||||||
|
serialize() {
|
||||||
|
return JSON.stringify(this.rootData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain methods needed by app
|
||||||
|
*/
|
||||||
|
getOverridesArray() {
|
||||||
|
return Object.values(this.rootData.overrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain methods needed by app
|
||||||
|
*/
|
||||||
|
isChannelOverridden(slug: string) {
|
||||||
|
return Boolean(this.rootData.overrides[slug]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain methods needed by app
|
||||||
|
*/
|
||||||
|
addOverride(slug: string, address: z.infer<typeof AddressSchema>) {
|
||||||
|
/**
|
||||||
|
* Perform additional checks, for example implement "update" method and forbid to implicit override
|
||||||
|
*/
|
||||||
|
if (this.rootData.overrides[slug]) {
|
||||||
|
throw new Error("Channel override already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure input is correct. Use "satisfied" because zod accepts "unknown"
|
||||||
|
*/
|
||||||
|
this.rootData.overrides[slug] = AddressOverrideSchema.parse({
|
||||||
|
channel: {
|
||||||
|
slug: slug,
|
||||||
|
},
|
||||||
|
address: address,
|
||||||
|
} satisfies z.infer<typeof AddressOverrideSchema>);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return this to allow chaining, optional
|
||||||
|
*/
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain methods needed by app
|
||||||
|
*/
|
||||||
|
removeOverride(slug: string) {
|
||||||
|
delete this.rootData.overrides[slug];
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* model can be connected with MetadataManager to automatically fetch and parse data.
|
||||||
|
*
|
||||||
|
* So for app usage this can be the only "root source" used
|
||||||
|
*/
|
||||||
|
abstract class ConfigManager {
|
||||||
|
/**
|
||||||
|
* Uses metadata manager to read/write
|
||||||
|
*/
|
||||||
|
abstract metadataManager: SettingsManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can fetch config and parse it to domain model
|
||||||
|
*/
|
||||||
|
abstract loadConfig(): Promise<ConfigModel>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can serialize and save config in metadata
|
||||||
|
*/
|
||||||
|
abstract saveConfig(config: ConfigModel): Promise<void>;
|
||||||
|
}
|
|
@ -13,7 +13,8 @@
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true
|
"incremental": true,
|
||||||
|
"experimentalDecorators": true
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|
62
apps/taxes/playwright-report/index.html
Normal file
62
apps/taxes/playwright-report/index.html
Normal file
File diff suppressed because one or more lines are too long
8318
pnpm-lock.yaml
8318
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue