add metadata manager to shared package (#901)
This commit is contained in:
parent
8b3d9617e6
commit
c50797e836
11 changed files with 282 additions and 474 deletions
6
.changeset/great-mice-lie.md
Normal file
6
.changeset/great-mice-lie.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"saleor-app-segment": patch
|
||||
"saleor-app-cms-v2": patch
|
||||
---
|
||||
|
||||
Extracted MetadataManager creation to factory from shared package
|
5
.changeset/itchy-games-hear.md
Normal file
5
.changeset/itchy-games-hear.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/apps-shared": minor
|
||||
---
|
||||
|
||||
Added Metadata Manager factory that abstract creation of EncryptedMetadataManager from SDK
|
|
@ -1,101 +1,12 @@
|
|||
import {
|
||||
MetadataEntry,
|
||||
EncryptedMetadataManager,
|
||||
SettingsManager,
|
||||
} from "@saleor/app-sdk/settings-manager";
|
||||
import { Client, gql } from "urql";
|
||||
import {
|
||||
FetchAppDetailsDocument,
|
||||
FetchAppDetailsQuery,
|
||||
UpdateAppMetadataDocument,
|
||||
} from "../../../generated/graphql";
|
||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
||||
import { Client } from "urql";
|
||||
|
||||
gql`
|
||||
mutation UpdateAppMetadata($id: ID!, $input: [MetadataInput!]!) {
|
||||
updatePrivateMetadata(id: $id, input: $input) {
|
||||
item {
|
||||
privateMetadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
gql`
|
||||
query FetchAppDetails {
|
||||
app {
|
||||
id
|
||||
privateMetadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
gql`
|
||||
mutation RemoveMetadata($id: ID!, $keys: [String!]!) {
|
||||
deletePrivateMetadata(id: $id, keys: $keys) {
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export type SimpleGraphqlClient = Pick<Client, "mutation" | "query">;
|
||||
|
||||
async function fetchAllMetadata(client: SimpleGraphqlClient): Promise<MetadataEntry[]> {
|
||||
const { error, data } = await client
|
||||
.query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {})
|
||||
.toPromise();
|
||||
|
||||
if (error) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || [];
|
||||
}
|
||||
|
||||
async function mutateMetadata(
|
||||
client: SimpleGraphqlClient,
|
||||
metadata: MetadataEntry[],
|
||||
appId: string
|
||||
) {
|
||||
const { error: mutationError, data: mutationData } = await client
|
||||
.mutation(UpdateAppMetadataDocument, {
|
||||
id: appId,
|
||||
input: metadata,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (mutationError) {
|
||||
throw new Error(`Mutation error: ${mutationError.message}`);
|
||||
}
|
||||
|
||||
return (
|
||||
mutationData?.updatePrivateMetadata?.item?.privateMetadata.map((md) => ({
|
||||
key: md.key,
|
||||
value: md.value,
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||
|
||||
export const createSettingsManager = (
|
||||
client: SimpleGraphqlClient,
|
||||
appId: string
|
||||
client: Pick<Client, "query" | "mutation">,
|
||||
appId: string,
|
||||
): SettingsManager => {
|
||||
/*
|
||||
* EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
|
||||
* We recommend it for production, because all values are encrypted.
|
||||
* If your use case require plain text values, you can use MetadataManager.
|
||||
*/
|
||||
return new EncryptedMetadataManager({
|
||||
// Secret key should be randomly created for production and set as environment variable
|
||||
encryptionKey: process.env.SECRET_KEY!,
|
||||
fetchMetadata: () => fetchAllMetadata(client),
|
||||
mutateMetadata: (metadata) => mutateMetadata(client, metadata, appId),
|
||||
});
|
||||
return metadataManagerFactory.create(client, appId);
|
||||
};
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
export {};
|
||||
import { vi } from "vitest";
|
||||
|
||||
vi.stubEnv("SECRET_KEY", "TEST");
|
||||
|
|
|
@ -6,6 +6,7 @@ import { SimpleGraphqlClient } from "../metadata-manager";
|
|||
export const mockMetadataManager = {
|
||||
set: vi.fn().mockImplementation(async () => {}),
|
||||
get: vi.fn().mockImplementation(async () => {}),
|
||||
delete: vi.fn().mockImplementation(async () => {}),
|
||||
};
|
||||
|
||||
export const createSettingsManager = (client: SimpleGraphqlClient): SettingsManager => {
|
||||
|
|
|
@ -30,6 +30,7 @@ const appWebhooksResponseData: Pick<OperationResult<FetchOwnWebhooksQuery, any>,
|
|||
app: {
|
||||
webhooks: [
|
||||
{
|
||||
name: "W1",
|
||||
id: "w1",
|
||||
isActive: true,
|
||||
asyncEvents: [
|
||||
|
@ -53,6 +54,7 @@ describe("webhooksStatusHandler", () => {
|
|||
const webhooksTogglerServiceMock: IWebhookActivityTogglerService = {
|
||||
disableOwnWebhooks: vi.fn(),
|
||||
enableOwnWebhooks: vi.fn(),
|
||||
recreateOwnWebhooks: vi.fn(),
|
||||
};
|
||||
|
||||
const algoliaSearchProviderMock: Pick<SearchProvider, "ping"> = {
|
||||
|
@ -62,6 +64,7 @@ describe("webhooksStatusHandler", () => {
|
|||
const settingsManagerMock: SettingsManager = {
|
||||
get: vi.fn(),
|
||||
set: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
};
|
||||
|
||||
let handler: NextProtectedApiHandler;
|
||||
|
|
|
@ -1,101 +1,12 @@
|
|||
import {
|
||||
MetadataEntry,
|
||||
EncryptedMetadataManager,
|
||||
SettingsManager,
|
||||
} from "@saleor/app-sdk/settings-manager";
|
||||
import { Client, gql } from "urql";
|
||||
import {
|
||||
FetchAppDetailsDocument,
|
||||
FetchAppDetailsQuery,
|
||||
UpdateAppMetadataDocument,
|
||||
} from "../../../generated/graphql";
|
||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
||||
import { Client } from "urql";
|
||||
|
||||
gql`
|
||||
mutation UpdateAppMetadata($id: ID!, $input: [MetadataInput!]!) {
|
||||
updatePrivateMetadata(id: $id, input: $input) {
|
||||
item {
|
||||
privateMetadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
gql`
|
||||
query FetchAppDetails {
|
||||
app {
|
||||
id
|
||||
privateMetadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
gql`
|
||||
mutation RemoveMetadata($id: ID!, $keys: [String!]!) {
|
||||
deletePrivateMetadata(id: $id, keys: $keys) {
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export type SimpleGraphqlClient = Pick<Client, "mutation" | "query">;
|
||||
|
||||
async function fetchAllMetadata(client: SimpleGraphqlClient): Promise<MetadataEntry[]> {
|
||||
const { error, data } = await client
|
||||
.query<FetchAppDetailsQuery>(FetchAppDetailsDocument, {})
|
||||
.toPromise();
|
||||
|
||||
if (error) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || [];
|
||||
}
|
||||
|
||||
async function mutateMetadata(
|
||||
client: SimpleGraphqlClient,
|
||||
metadata: MetadataEntry[],
|
||||
appId: string,
|
||||
) {
|
||||
const { error: mutationError, data: mutationData } = await client
|
||||
.mutation(UpdateAppMetadataDocument, {
|
||||
id: appId,
|
||||
input: metadata,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (mutationError) {
|
||||
throw new Error(`Mutation error: ${mutationError.message}`);
|
||||
}
|
||||
|
||||
return (
|
||||
mutationData?.updatePrivateMetadata?.item?.privateMetadata.map((md) => ({
|
||||
key: md.key,
|
||||
value: md.value,
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||
|
||||
export const createSettingsManager = (
|
||||
client: SimpleGraphqlClient,
|
||||
client: Pick<Client, "query" | "mutation">,
|
||||
appId: string,
|
||||
): SettingsManager => {
|
||||
/*
|
||||
* EncryptedMetadataManager gives you interface to manipulate metadata and cache values in memory.
|
||||
* We recommend it for production, because all values are encrypted.
|
||||
* If your use case require plain text values, you can use MetadataManager.
|
||||
*/
|
||||
return new EncryptedMetadataManager({
|
||||
// Secret key should be randomly created for production and set as environment variable
|
||||
encryptionKey: process.env.SECRET_KEY!,
|
||||
fetchMetadata: () => fetchAllMetadata(client),
|
||||
mutateMetadata: (metadata) => mutateMetadata(client, metadata, appId),
|
||||
});
|
||||
return metadataManagerFactory.create(client, appId);
|
||||
};
|
||||
|
|
|
@ -5,3 +5,4 @@ export * from "./src/use-dashboard-notification";
|
|||
export * from "./src/logger";
|
||||
export * from "./src/saleor-version-compatibility-validator";
|
||||
export * from "./src/create-graphql-client";
|
||||
export * from "./src/metadata-manager";
|
||||
|
|
|
@ -9,9 +9,7 @@
|
|||
"eslint": "8.46.0",
|
||||
"pino": "^8.14.1",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"semver": "^7.5.1",
|
||||
"typescript": "5.1.6",
|
||||
"urql": "^4.0.4"
|
||||
"semver": "^7.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@material-ui/core": "^4.12.4",
|
||||
|
@ -27,6 +25,8 @@
|
|||
"next": "13.4.8",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"typescript": "5.1.6",
|
||||
"urql": "^4.0.4",
|
||||
"vite": "4.4.8",
|
||||
"vitest": "0.34.1"
|
||||
},
|
||||
|
@ -37,6 +37,7 @@
|
|||
"pino": "^8.14.1",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"urql": "^4.0.4"
|
||||
}
|
||||
}
|
||||
|
|
127
packages/shared/src/metadata-manager.ts
Normal file
127
packages/shared/src/metadata-manager.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import {
|
||||
MetadataEntry,
|
||||
EncryptedMetadataManager,
|
||||
MetadataManager,
|
||||
SettingsManager,
|
||||
} from "@saleor/app-sdk/settings-manager";
|
||||
import { Client, gql } from "urql";
|
||||
|
||||
const UpdateAppMetadataMutation = gql`
|
||||
mutation UpdateAppMetadata($id: ID!, $input: [MetadataInput!]!) {
|
||||
updatePrivateMetadata(id: $id, input: $input) {
|
||||
item {
|
||||
privateMetadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const FetchAppDetailsQuery = gql`
|
||||
query FetchAppDetails {
|
||||
app {
|
||||
id
|
||||
privateMetadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DeletePrivateMetadataMutation = gql`
|
||||
mutation RemovePrivateMetadata($id: ID!, $keys: [String!]!) {
|
||||
deletePrivateMetadata(id: $id, keys: $keys) {
|
||||
errors {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* To avoid a graphql-schema build step, manually set types for the queries and mutations.
|
||||
*/
|
||||
type FetchAppPrivateMetadataQuery = {
|
||||
__typename?: "Query";
|
||||
app?: {
|
||||
__typename?: "App";
|
||||
id: string;
|
||||
privateMetadata: Array<{ __typename?: "MetadataItem"; key: string; value: string }>;
|
||||
} | null;
|
||||
};
|
||||
|
||||
type UpdateAppPrivateMetadataMutation = {
|
||||
__typename?: "Mutation";
|
||||
updatePrivateMetadata?: {
|
||||
__typename?: "UpdatePrivateMetadata";
|
||||
item?: {
|
||||
__typename?: "App";
|
||||
privateMetadata: Array<{ __typename?: "MetadataItem"; key: string; value: string }>;
|
||||
} | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type MetadataManagerGraphqlClient = Pick<Client, "mutation" | "query">;
|
||||
|
||||
async function fetchAllPrivateMetadata(
|
||||
client: MetadataManagerGraphqlClient,
|
||||
): Promise<MetadataEntry[]> {
|
||||
const { error, data } = await client
|
||||
.query<FetchAppPrivateMetadataQuery>(FetchAppDetailsQuery, {})
|
||||
.toPromise();
|
||||
|
||||
if (error) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return data?.app?.privateMetadata.map((md) => ({ key: md.key, value: md.value })) || [];
|
||||
}
|
||||
|
||||
async function updatePrivateMetadata(
|
||||
client: MetadataManagerGraphqlClient,
|
||||
metadata: MetadataEntry[],
|
||||
appId: string,
|
||||
) {
|
||||
const { error: mutationError, data: mutationData } = await client
|
||||
.mutation<UpdateAppPrivateMetadataMutation>(UpdateAppMetadataMutation, {
|
||||
id: appId,
|
||||
input: metadata,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
if (mutationError) {
|
||||
throw new Error(`Mutation error: ${mutationError.message}`);
|
||||
}
|
||||
|
||||
return (
|
||||
mutationData?.updatePrivateMetadata?.item?.privateMetadata.map((md) => ({
|
||||
key: md.key,
|
||||
value: md.value,
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
|
||||
export class EncryptedMetadataManagerFactory {
|
||||
constructor(private encryptionKey: string) {
|
||||
if (!encryptionKey) {
|
||||
throw new Error("Encryption key is required");
|
||||
}
|
||||
}
|
||||
|
||||
create(client: MetadataManagerGraphqlClient, appId: string): SettingsManager {
|
||||
return new EncryptedMetadataManager({
|
||||
encryptionKey: this.encryptionKey,
|
||||
fetchMetadata: () => fetchAllPrivateMetadata(client),
|
||||
mutateMetadata: (metadata) => updatePrivateMetadata(client, metadata, appId),
|
||||
async deleteMetadata(keys) {
|
||||
await client.mutation(DeletePrivateMetadataMutation, {
|
||||
id: appId,
|
||||
keys: keys,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
396
pnpm-lock.yaml
396
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue