saleor-apps-redis_apl/apps/cms-v2/src/modules/providers/datocms/datocms-client.ts
Lukasz Ostrowski 70cb741f88
CMS App v2 (#721)
* initial setup

* GRaphql setup

* [skip ci]

* Manifest and register endpoints

* Add config schemas

* contentful client

* contentful client

* [skip ci] trpc setup

* metadata manager and contentful router

* Configuration config

* contentful config - adding providers

* add provider page

* wip contentful form

* contentful form

* list providrs

* edit contentful form

* [skip ci]

* [skip ci] replace contentful sdk

* replace contentful lib

* Delete contetnful provider feature

* variant created webhook draft

* channel provider connection model

* Channel connections ui

* adding new connection

* connections ui (adding)

* [skip ci] wip edit conn

* removing connection

* rebuild modal

* refactor providers

* implement update product webhook

* add deleting product

* [skip ci] wip

* refactor contentful router

* refactor wip

* refactor config

* webhooks processor

* webhook delegations

* bulk sync section

* bulk sync page

* gql for imports

* [skip ci] bulk import contentful

* temp variant sync list with rate limiters

* wip

* wip

* wip

* new frontend for uploading

* update zod

* print config keys

* wip

* [skip ci] datocms init

* dato add provdier page

* dato form skeleton

* dato  display content type select

* full dato form

* ButtonsBox extraction

* edit dato config form

* update product in dato

* [skip ci]

* extract bulk sync processor

* dato bulk update

* [skip ci] product updated webhook

* product webhook

* crud operations router

* update cruds

* refactor webhook operations

* refactors

* refactors

* helper texts

* [skip ci] deps

* Init

* fix macaw icon

* unify app skd

* unify nextjs

* strapi setup

* fix strapi types

* strapi upload product

* strapi delete product

* strapi product updated webhook

* processor for bulk sync strapi

* shared add provider page

* refactors

* refactors

* wrap providers into folder

* refactors

* refactors

* refactors

* pnpm lock

* add logs

* name configuration mapping name

* form configurable side notes

* remove commentns

* wip providers resolver working

* central config for providers resolving

* tests wip

* remove vscode condig

* cleanup

* provider meta extract

* some tests for contentufl

* contentful client test

* more tests for contentful

* strapi helper texts

* builderio setup

* builderio form

* builderio client draft

* validate connection modal

* Add sending product to builderio

* rename builder field

* add public api key for read access

* update products

* Builder.io - bulk sync

* Fix manifest

* cr fixes

* Make strapi to work with multiple products

* Github actions
2023-07-18 10:19:21 +02:00

181 lines
5.1 KiB
TypeScript

import { buildClient, Client, SimpleSchemaTypes, ApiError } from "@datocms/cma-client-browser";
import { WebhookProductVariantFragment } from "../../../../generated/graphql";
import { createLogger } from "@saleor/apps-shared";
import { z } from "zod";
import * as Sentry from "@sentry/nextjs";
import { DatocmsProviderConfig } from "@/modules/configuration/schemas/datocms-provider.schema";
type Context = {
configuration: DatocmsProviderConfig.FullShape;
variant: WebhookProductVariantFragment;
};
/*
* todo error handling
*/
export class DatoCMSClient {
private client: Client;
private logger = createLogger({ name: "DatoCMSClient" });
constructor(opts: { apiToken: string }) {
this.client = buildClient({ apiToken: opts.apiToken });
}
getContentTypes() {
this.logger.trace("Trying to get content types");
return this.client.itemTypes.list();
}
getFieldsForContentType({ itemTypeID }: { itemTypeID: string }) {
this.logger.trace("Trying to get fields for a content type");
return this.client.fields.list({ type: "item_type", id: itemTypeID });
}
private getItemBySaleorVariantId({
variantIdFieldName: variantFieldName,
variantID,
contentType,
}: {
variantIdFieldName: string;
variantID: string;
contentType: string;
}) {
this.logger.trace("Trying to fetch item by Saleor variant ID", { variantID: variantID });
return this.client.items.list({
filter: {
type: contentType,
fields: {
[variantFieldName]: {
eq: variantID,
},
},
},
});
}
private mapVariantToDatoCMSFields({
configuration,
variant,
}: Context): SimpleSchemaTypes.ItemCreateSchema {
const fieldsMap = configuration.productVariantFieldsMapping;
return {
item_type: { type: "item_type", id: configuration.itemType },
[fieldsMap.variantName]: variant.name,
[fieldsMap.productId]: variant.product.id,
[fieldsMap.productName]: variant.product.name,
[fieldsMap.productSlug]: variant.product.slug,
[fieldsMap.variantId]: variant.id,
[fieldsMap.channels]: JSON.stringify(variant.channelListings),
};
}
async deleteProductVariant({ configuration, variant }: Context) {
this.logger.debug("Trying to delete product variant");
const remoteProducts = await this.getItemBySaleorVariantId({
variantIdFieldName: configuration.productVariantFieldsMapping.variantId,
variantID: variant.id,
contentType: configuration.itemType,
});
if (remoteProducts.length > 1) {
this.logger.warn(
"More than 1 variant with the same ID found in the CMS. Will remove all of them, but this should not happen if unique field was set"
);
}
if (remoteProducts.length === 0) {
this.logger.trace("No product found in Datocms, skipping deletion");
return;
}
return Promise.all(
remoteProducts.map((p) => {
return this.client.items.rawDestroy(p.id);
})
);
}
uploadProductVariant(context: Context) {
this.logger.debug("Trying to upload product variant");
return this.client.items.create(this.mapVariantToDatoCMSFields(context));
}
async updateProductVariant({ configuration, variant }: Context) {
const products = await this.getItemBySaleorVariantId({
variantIdFieldName: configuration.productVariantFieldsMapping.variantId,
variantID: variant.id,
contentType: configuration.itemType,
});
if (products.length > 1) {
this.logger.warn(
"Found more than one product variant with the same ID. Will update all of them, but this should not happen if unique field was set",
{
variantID: variant.id,
}
);
}
return Promise.all(
products.map((product) => {
this.logger.trace("Trying to update variant", { datoID: product.id });
return this.client.items.update(
product.id,
this.mapVariantToDatoCMSFields({
configuration,
variant,
})
);
})
);
}
upsertProduct({ configuration, variant }: Context) {
this.logger.debug("Trying to upsert product variant");
const DatoErrorBody = z.object({
data: z.array(
z.object({
validation: z.object({
attributes: z.object({
details: z.object({
code: z.string(),
}),
}),
}),
})
),
});
return this.uploadProductVariant({ configuration, variant }).catch((err: ApiError) => {
try {
const errorBody = DatoErrorBody.parse(err.response.body);
const isUniqueIdError = errorBody.data.find(
(d) => d.validation.attributes.details.code === "VALIDATION_UNIQUE"
);
if (isUniqueIdError) {
return this.updateProductVariant({ configuration, variant });
} else {
throw new Error(JSON.stringify(err.cause));
}
} catch (e) {
Sentry.captureException("Invalid error shape from DatoCMS", (c) => {
return c.setExtra("error", err);
});
throw new Error(err.humanMessage ?? "DatoCMS error - can upload product variant");
}
});
}
}