Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bbc7a7f8f9 | ||
![]() |
7fa1648349 |
118 changed files with 1196 additions and 1007 deletions
5
.changeset/honest-geese-crash.md
Normal file
5
.changeset/honest-geese-crash.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-taxes": minor
|
||||
---
|
||||
|
||||
Added logs for AvaTax and TaxJar. Logs are stored in the app metadata. Only the last 100 events are stored. Each provider configuration has its own logs. You can get to them by a new button "Logs" in the provider table.
|
15
.changeset/mighty-tips-hug.md
Normal file
15
.changeset/mighty-tips-hug.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
"saleor-app-emails-and-messages": patch
|
||||
"saleor-app-data-importer": patch
|
||||
"saleor-app-products-feed": patch
|
||||
"saleor-app-invoices": patch
|
||||
"saleor-app-klaviyo": patch
|
||||
"saleor-app-segment": patch
|
||||
"saleor-app-cms-v2": patch
|
||||
"saleor-app-search": patch
|
||||
"saleor-app-slack": patch
|
||||
"saleor-app-taxes": patch
|
||||
"saleor-app-crm": patch
|
||||
---
|
||||
|
||||
Updated Sentry package
|
5
.changeset/nine-rivers-flow.md
Normal file
5
.changeset/nine-rivers-flow.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-klaviyo": patch
|
||||
---
|
||||
|
||||
Fixed error where config couldn't be saved
|
5
.changeset/orange-actors-eat.md
Normal file
5
.changeset/orange-actors-eat.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-search": patch
|
||||
---
|
||||
|
||||
Webhook migration scripts has been moved to the shared package.
|
|
@ -1,8 +0,0 @@
|
|||
#changelog
|
||||
---
|
||||
"apps": minor
|
||||
---
|
||||
### Added
|
||||
|
||||
- `apps/emails-and-messages/.env.template`: Described the new environment variable and how it works
|
||||
- `apps/emails-and-messages/src/saleor-app.ts`: Added case "redis" for switch(AplType), which takes advantage of the [RedisAPL PR](https://github.com/saleor/app-sdk/pull/287) I submitted
|
5
.changeset/real-pigs-promise.md
Normal file
5
.changeset/real-pigs-promise.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-invoices": minor
|
||||
---
|
||||
|
||||
Replace text "loading" messages with skeletons
|
5
.changeset/silver-windows-accept.md
Normal file
5
.changeset/silver-windows-accept.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@saleor/webhook-utils": patch
|
||||
---
|
||||
|
||||
Introduced a new shared package with helpers used for webhook management.
|
5
.changeset/twelve-pianos-relate.md
Normal file
5
.changeset/twelve-pianos-relate.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-invoices": minor
|
||||
---
|
||||
|
||||
Redesigned app layout. Now app uses shared sections as other apps.
|
5
.changeset/wicked-llamas-talk.md
Normal file
5
.changeset/wicked-llamas-talk.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-klaviyo": minor
|
||||
---
|
||||
|
||||
Improved app layout to match modern style.
|
9
.github/workflows/assign-pr.yml
vendored
9
.github/workflows/assign-pr.yml
vendored
|
@ -6,12 +6,9 @@ on:
|
|||
|
||||
jobs:
|
||||
assign_creator:
|
||||
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Assign PR to creator
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
CREATOR: ${{ github.event.pull_request.user.login }}
|
||||
run: gh pr edit "$PR_URL" --add-assignee "$CREATOR"
|
||||
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
31
.github/workflows/changeset-checker.yml
vendored
31
.github/workflows/changeset-checker.yml
vendored
|
@ -1,31 +0,0 @@
|
|||
name: Changesets
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- labeled
|
||||
- edited
|
||||
- synchronize
|
||||
branches-ignore:
|
||||
- 'changeset-release/**'
|
||||
jobs:
|
||||
changeset_check:
|
||||
name: Changeset added to the PR
|
||||
# Adding 'skip changesets' label to the PR will skip this job
|
||||
if: ${{ !contains( github.event.pull_request.labels.*.name, 'skip changeset') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# check out full history
|
||||
fetch-depth: 0
|
||||
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Changeset added
|
||||
run: npx changeset status --since origin/main
|
2
.github/workflows/check-deps.yml
vendored
2
.github/workflows/check-deps.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- uses: JamieMason/syncpack-github-action@c145cec44b3731b3fe8e859679e240d6ae011f0f
|
||||
- uses: JamieMason/syncpack-github-action@0.2.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
package-manager: "pnpm"
|
||||
|
|
2
.github/workflows/check-spelling.yml
vendored
2
.github/workflows/check-spelling.yml
vendored
|
@ -7,4 +7,4 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: streetsidesoftware/cspell-action@22e32eb3d70acf30e3fc09bd46edc1d30fb2d6db
|
||||
- uses: streetsidesoftware/cspell-action@v3
|
||||
|
|
4
.github/workflows/prepare-release.yml
vendored
4
.github/workflows/prepare-release.yml
vendored
|
@ -25,11 +25,11 @@ jobs:
|
|||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- uses: pnpm/action-setup@d882d12c64e032187b2edb46d3a0d003b7a43598
|
||||
- uses: pnpm/action-setup@v2
|
||||
name: Install pnpm
|
||||
- run: pnpm install
|
||||
- name: Create Release Pull Request
|
||||
uses: changesets/action@f13b1baaa620fde937751f5d2c3572b9da32af23
|
||||
uses: changesets/action@v1
|
||||
id: changesets
|
||||
with:
|
||||
title: 🚀 Release apps
|
||||
|
|
2
.github/workflows/unit-tests.yml
vendored
2
.github/workflows/unit-tests.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
|
|
@ -1 +1 @@
|
|||
* @saleor/delivery-engineering-js
|
||||
* @saleor/appstore
|
|
@ -1,14 +1,5 @@
|
|||
# saleor-app-cms-v2
|
||||
|
||||
## 2.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 2.3.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-cms-v2",
|
||||
"version": "2.3.3",
|
||||
"version": "2.3.2",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@datocms/cma-client-browser": "2.0.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@saleor/app-sdk": "0.43.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@saleor/apps-ui": "workspace:*",
|
||||
|
@ -48,7 +48,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
|
@ -57,6 +57,7 @@
|
|||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
|
|
1
apps/cms-v2/reset.d.ts
vendored
Normal file
1
apps/cms-v2/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -19,7 +19,7 @@ export class AppConfig {
|
|||
connections: [],
|
||||
};
|
||||
|
||||
constructor(initialData?: RootConfig.Shape) {
|
||||
constructor(initialData?: RootConfig.Shape | unknown) {
|
||||
if (initialData) {
|
||||
this.rootData = RootConfig.Schema.parse(initialData);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { BuilderIoProviderConfig } from "@/modules/configuration";
|
|||
import { WebhookProductVariantFragment } from "../../../../generated/graphql";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { FieldsMapper } from "../fields-mapper";
|
||||
import { z } from "zod";
|
||||
|
||||
// https://www.builder.io/c/docs/write-api
|
||||
export class BuilderIoClient {
|
||||
|
@ -43,7 +44,7 @@ export class BuilderIoClient {
|
|||
|
||||
private async updateProductVariantCall(
|
||||
builderIoEntryId: string,
|
||||
variant: WebhookProductVariantFragment
|
||||
variant: WebhookProductVariantFragment,
|
||||
) {
|
||||
try {
|
||||
const response = await fetch(this.endpoint + `/${builderIoEntryId}`, {
|
||||
|
@ -71,13 +72,13 @@ export class BuilderIoClient {
|
|||
{
|
||||
entriesToUpdate,
|
||||
},
|
||||
"Trying to update variants in builder.io with following IDs"
|
||||
"Trying to update variants in builder.io with following IDs",
|
||||
);
|
||||
|
||||
return Promise.all(
|
||||
entriesToUpdate.map((id) => {
|
||||
return this.updateProductVariantCall(id, variant);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -94,7 +95,7 @@ export class BuilderIoClient {
|
|||
return Promise.all(
|
||||
entriesToUpdate.map((id) => {
|
||||
return this.updateProductVariantCall(id, variant);
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -112,8 +113,8 @@ export class BuilderIoClient {
|
|||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.config.privateApiKey}`,
|
||||
},
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -127,15 +128,23 @@ export class BuilderIoClient {
|
|||
variantID: variantId,
|
||||
variantFieldMapping: this.config.productVariantFieldsMapping.variantId,
|
||||
},
|
||||
"Trying to fetch variant from Builder.io"
|
||||
"Trying to fetch variant from Builder.io",
|
||||
);
|
||||
|
||||
const expectedSchema = z.object({
|
||||
results: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
return fetch(
|
||||
`https://cdn.builder.io/api/v3/content/${this.config.modelName}?apiKey=${this.config.publicApiKey}&query.data.${this.config.productVariantFieldsMapping.variantId}.$eq=${variantId}&limit=10&includeUnpublished=false&cacheSeconds=0`
|
||||
`https://cdn.builder.io/api/v3/content/${this.config.modelName}?apiKey=${this.config.publicApiKey}&query.data.${this.config.productVariantFieldsMapping.variantId}.$eq=${variantId}&limit=10&includeUnpublished=false&cacheSeconds=0`,
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((res) => expectedSchema.parse(res.json()))
|
||||
.then((data) => {
|
||||
return data.results.map((result: any) => result.id) as string[];
|
||||
return data.results.map((result) => result.id) as string[];
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error(err, "Failed to fetch builder.io entry id");
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
# saleor-app-crm
|
||||
|
||||
## 1.7.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 1.7.7
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-crm",
|
||||
"version": "1.7.8",
|
||||
"version": "1.7.7",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
|
@ -51,6 +51,7 @@
|
|||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/mailchimp__mailchimp_marketing": "^3.0.7",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
|
|
1
apps/crm/reset.d.ts
vendored
Normal file
1
apps/crm/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -23,7 +23,7 @@ export const AppBridgePersistence = {
|
|||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(storageItem as string);
|
||||
return JSON.parse(storageItem as string) as AppBridgeStorageState;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { MailchimpConfigSettingsManagerV1 } from "./mailchimp-config-settings-manager";
|
||||
import { MailchimpConfigSettingsManagerV1, MailchimpConfigType } from "./mailchimp-config-settings-manager";
|
||||
import { Client } from "urql";
|
||||
import { SettingsManager, SettingsValue } from "@saleor/app-sdk/settings-manager";
|
||||
|
||||
|
@ -70,9 +70,11 @@ describe("MailchimpConfigSettingsManagerV1", () => {
|
|||
dc: "us41",
|
||||
});
|
||||
|
||||
const parsedSetValue = JSON.parse(valueHasBeenSet!);
|
||||
const parsedSetValue = JSON.parse(valueHasBeenSet!) as {
|
||||
config: MailchimpConfigType
|
||||
};
|
||||
|
||||
expect(parsedSetValue.config.customerCreateEvent.enabled).toBe(false);
|
||||
expect(parsedSetValue.config.customerCreateEvent?.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it("Calls settings manager with default customerCreateEvent setting to be disabled", async () => {
|
||||
|
@ -90,9 +92,11 @@ describe("MailchimpConfigSettingsManagerV1", () => {
|
|||
dc: "us41",
|
||||
});
|
||||
|
||||
const parsedSetValue = JSON.parse(valueHasBeenSet!);
|
||||
const parsedSetValue = JSON.parse(valueHasBeenSet!) as {
|
||||
config: MailchimpConfigType
|
||||
};
|
||||
|
||||
expect(parsedSetValue.config.customerCreateEvent.enabled).toBe(false);
|
||||
expect(parsedSetValue.config.customerCreateEvent?.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it(".get returns null if data doesnt match schema", async () => {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { NextApiHandler } from "next";
|
||||
import { MailchimpClientOAuth } from "../../../../modules/mailchimp/mailchimp-client";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { z } from "zod";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
export const getBaseUrl = (headers: { [name: string]: string | string[] | undefined }): string => {
|
||||
const { host, "x-forwarded-proto": protocol = "http" } = headers;
|
||||
|
@ -8,6 +10,17 @@ export const getBaseUrl = (headers: { [name: string]: string | string[] | undefi
|
|||
return `${protocol}://${host}`;
|
||||
};
|
||||
|
||||
const tokenResponseSchema = z.object({
|
||||
access_token: z.string().min(1),
|
||||
});
|
||||
|
||||
const metadataResponseSchema = z.object({
|
||||
dc: z.string().min(1),
|
||||
login: z.object({
|
||||
email: z.string().min(1),
|
||||
}),
|
||||
});
|
||||
|
||||
const handler: NextApiHandler = async (req, res) => {
|
||||
const baseUrl = getBaseUrl(req.headers);
|
||||
|
||||
|
@ -28,25 +41,46 @@ const handler: NextApiHandler = async (req, res) => {
|
|||
}),
|
||||
});
|
||||
|
||||
const { access_token } = await tokenResponse.json();
|
||||
let accessToken: string;
|
||||
|
||||
logger.debug({ access_token }, "Received mailchimp access_token");
|
||||
try {
|
||||
const tokenResponseJson = await tokenResponse.json();
|
||||
const parsedTokenResponse = tokenResponseSchema.parse(tokenResponseJson);
|
||||
|
||||
const metadataResponse = await fetch("https://login.mailchimp.com/oauth2/metadata", {
|
||||
headers: {
|
||||
Authorization: `OAuth ${access_token}`,
|
||||
},
|
||||
});
|
||||
accessToken = parsedTokenResponse.access_token;
|
||||
} catch {
|
||||
Sentry.captureException(
|
||||
"Mailchimp token response doesnt contain access_token or can't be fetched",
|
||||
);
|
||||
|
||||
const metadata = await metadataResponse.json();
|
||||
return res.status(500).end();
|
||||
}
|
||||
|
||||
const mc = new MailchimpClientOAuth(metadata.dc, access_token);
|
||||
logger.debug({ access_token: accessToken }, "Received mailchimp access_token");
|
||||
|
||||
await mc.ping();
|
||||
try {
|
||||
const metadataResponse = await fetch("https://login.mailchimp.com/oauth2/metadata", {
|
||||
headers: {
|
||||
Authorization: `OAuth ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
return res.redirect(
|
||||
`/configuration/mailchimp/oauth-success?token=${access_token}&email=${metadata.login.email}&dc=${metadata.dc}`
|
||||
); // todo maybe move to cookie
|
||||
const metadataJson = await metadataResponse.json();
|
||||
|
||||
const parsedMetadata = metadataResponseSchema.parse(metadataJson);
|
||||
|
||||
const mc = new MailchimpClientOAuth(parsedMetadata.dc, accessToken);
|
||||
|
||||
await mc.ping();
|
||||
|
||||
return res.redirect(
|
||||
`/configuration/mailchimp/oauth-success?token=${accessToken}&email=${parsedMetadata.login.email}&dc=${parsedMetadata.dc}`,
|
||||
);
|
||||
} catch {
|
||||
Sentry.captureException("Mailchimp oauth metadata cant be fetched or is malformed");
|
||||
|
||||
return res.status(500).end();
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
# saleor-app-data-importer
|
||||
|
||||
## 1.9.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 1.9.6
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-data-importer",
|
||||
"version": "1.9.7",
|
||||
"version": "1.9.6",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -39,7 +39,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
@ -49,6 +49,7 @@
|
|||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/dot-object": "^2.1.2",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
|
|
1
apps/data-importer/reset.d.ts
vendored
Normal file
1
apps/data-importer/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -7,12 +7,6 @@ APL=
|
|||
REST_APL_ENDPOINT=
|
||||
REST_APL_TOKEN=
|
||||
|
||||
# To use Redis as an APL store, set APP_API_BASE_URL and REDIS_URL.
|
||||
# URL is in format redis[s]://[[username][:password]@][host][:port][/db-number],
|
||||
# so for example redis://alice:foobared@awesome.redis.server:6380
|
||||
# For saleor-platform, thats: `redis://redis:6379/1`
|
||||
REDIS_URL=
|
||||
|
||||
APP_LOG_LEVEL=info
|
||||
|
||||
# Local development variables. When developped locally with Saleor inside docker, these can be set to:
|
||||
|
@ -22,4 +16,3 @@ APP_LOG_LEVEL=info
|
|||
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
|
||||
APP_IFRAME_BASE_URL=
|
||||
APP_API_BASE_URL=
|
||||
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
# saleor-app-emails-and-messages
|
||||
|
||||
## 1.9.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 1.9.9
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-emails-and-messages",
|
||||
"version": "1.9.10",
|
||||
"version": "1.9.9",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@monaco-editor/react": "^4.4.6",
|
||||
"@saleor/app-sdk": "0.43.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
|
@ -51,7 +51,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
@ -61,6 +61,7 @@
|
|||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/html-to-text": "^9.0.0",
|
||||
"@types/mjml": "^4.7.0",
|
||||
"@types/nodemailer": "^6.4.7",
|
||||
|
|
1
apps/emails-and-messages/reset.d.ts
vendored
Normal file
1
apps/emails-and-messages/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -1,4 +1,4 @@
|
|||
import { APL, FileAPL, RedisAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
|
||||
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
|
||||
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
|
||||
|
||||
const aplType = process.env.APL ?? "file";
|
||||
|
@ -6,12 +6,6 @@ const aplType = process.env.APL ?? "file";
|
|||
export let apl: APL;
|
||||
|
||||
switch (aplType) {
|
||||
case "redis": {
|
||||
if (!process.env.REDIS_URL) throw new Error("Missing redis url");
|
||||
if (!process.env.APP_API_BASE_URL)
|
||||
throw new Error("Redis relies on APP_API_BASE_URL to store keys, please set env variable");
|
||||
apl = new RedisAPL(new URL(process.env.REDIS_URL), process.env.APP_API_BASE_URL);
|
||||
}
|
||||
case "upstash":
|
||||
apl = new UpstashAPL();
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
{
|
||||
"extends": [
|
||||
"//"
|
||||
],
|
||||
"extends": ["//"],
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
|
@ -23,8 +21,7 @@
|
|||
"NEXT_PUBLIC_SENTRY_DSN",
|
||||
"SENTRY_ENVIRONMENT",
|
||||
"APP_IFRAME_BASE_URL",
|
||||
"APP_API_BASE_URL",
|
||||
"REDIS_URL"
|
||||
"APP_API_BASE_URL"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,5 @@
|
|||
# saleor-app-invoices
|
||||
|
||||
## 1.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 4aee4e1: Replace text "loading" messages with skeletons
|
||||
- 4aee4e1: Redesigned app layout. Now app uses shared sections as other apps.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 1.15.7
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-invoices",
|
||||
"version": "1.16.0",
|
||||
"version": "1.15.7",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@saleor/app-sdk": "0.43.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@saleor/apps-ui": "workspace:*",
|
||||
|
@ -42,7 +42,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
1
apps/invoices/reset.d.ts
vendored
Normal file
1
apps/invoices/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -37,7 +37,7 @@ const runMigration = async () => {
|
|||
.catch((e) => {
|
||||
console.error("❌ Error removing metadata", e);
|
||||
});
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
console.log(results);
|
||||
|
|
|
@ -44,7 +44,7 @@ const runMigration = async () => {
|
|||
.catch((e) => {
|
||||
console.log(
|
||||
`🚫 failed to create empty config for ${env.saleorApiUrl}. Env may not exist.`,
|
||||
e.message
|
||||
e.message,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ const runMigration = async () => {
|
|||
.catch((e) => {
|
||||
console.error("🚫 Failed to migrate ", env.saleorApiUrl, e);
|
||||
});
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ const runReport = async () => {
|
|||
metadata: metadata,
|
||||
env: env.saleorApiUrl,
|
||||
}));
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const report = results.map((r: any) => ({
|
||||
|
|
|
@ -47,7 +47,7 @@ export const appConfigurationRouter = router({
|
|||
.input(
|
||||
z.object({
|
||||
channelSlug: z.string(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const appConfigV2 =
|
||||
|
|
|
@ -17,7 +17,10 @@ export interface AppConfigurator {
|
|||
export class PrivateMetadataAppConfiguratorV1 implements AppConfigurator {
|
||||
private metadataKey = "app-config";
|
||||
|
||||
constructor(private metadataManager: SettingsManager, private saleorApiUrl: string) {}
|
||||
constructor(
|
||||
private metadataManager: SettingsManager,
|
||||
private saleorApiUrl: string,
|
||||
) {}
|
||||
|
||||
getConfig(): Promise<AppConfigV1 | undefined> {
|
||||
return this.metadataManager.get(this.metadataKey, this.saleorApiUrl).then((data) => {
|
||||
|
|
|
@ -18,13 +18,13 @@ describe("config-v1-to-v2-migration.service", () => {
|
|||
service = new ConfigV1ToV2MigrationService(mockClient, "https://example.com/graphql/");
|
||||
|
||||
vi.spyOn(service.configMetadataManager, "set").mockImplementationOnce(async () =>
|
||||
Promise.resolve()
|
||||
Promise.resolve(),
|
||||
);
|
||||
});
|
||||
|
||||
it("Returns a pure V2 config if V1 config is not present", async () => {
|
||||
vi.spyOn(service.metadataV1AppConfigurator, "getConfig").mockImplementationOnce(async () =>
|
||||
Promise.resolve(undefined)
|
||||
Promise.resolve(undefined),
|
||||
);
|
||||
|
||||
const migrationResult = await service.migrate();
|
||||
|
@ -41,7 +41,7 @@ describe("config-v1-to-v2-migration.service", () => {
|
|||
address: getMockAddress(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const migrationResult = await service.migrate();
|
||||
|
@ -49,13 +49,13 @@ describe("config-v1-to-v2-migration.service", () => {
|
|||
expect(migrationResult.getChannelsOverrides()).toEqual(
|
||||
expect.objectContaining({
|
||||
"default-channel": expect.objectContaining(getMockAddress()),
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("Runs a beforeSave callback and saves modified state in metadata - missing v1 config scenario", async () => {
|
||||
vi.spyOn(service.metadataV1AppConfigurator, "getConfig").mockImplementationOnce(async () =>
|
||||
Promise.resolve(undefined)
|
||||
Promise.resolve(undefined),
|
||||
);
|
||||
|
||||
const beforeSaveCb = vi.fn().mockImplementationOnce((config: AppConfigV2) => {
|
||||
|
@ -79,7 +79,7 @@ describe("config-v1-to-v2-migration.service", () => {
|
|||
address: getMockAddress(),
|
||||
},
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const beforeSaveCb = vi.fn().mockImplementationOnce((config: AppConfigV2) => {
|
||||
|
|
|
@ -10,12 +10,15 @@ export class ConfigV1ToV2MigrationService {
|
|||
configMetadataManager: AppConfigV2MetadataManager;
|
||||
metadataV1AppConfigurator: PrivateMetadataAppConfiguratorV1;
|
||||
|
||||
constructor(private client: SimpleGraphqlClient, private saleorApiUrl: string) {
|
||||
constructor(
|
||||
private client: SimpleGraphqlClient,
|
||||
private saleorApiUrl: string,
|
||||
) {
|
||||
this.settingsManager = createSettingsManager(client);
|
||||
this.configMetadataManager = new AppConfigV2MetadataManager(this.settingsManager);
|
||||
this.metadataV1AppConfigurator = new PrivateMetadataAppConfiguratorV1(
|
||||
this.settingsManager,
|
||||
this.saleorApiUrl
|
||||
this.saleorApiUrl,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ describe("ConfigV1ToV2Transformer", function () {
|
|||
expect(v2.getChannelsOverrides()).toEqual(
|
||||
expect.objectContaining({
|
||||
"default-channel": getMockAddress(),
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -42,7 +42,7 @@ describe("ConfigV1ToV2Transformer", function () {
|
|||
expect.objectContaining({
|
||||
"default-channel": getMockAddress(),
|
||||
"custom-channel": getMockAddress(),
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -66,7 +66,7 @@ describe("ConfigV1ToV2Transformer", function () {
|
|||
...getMockAddress(),
|
||||
city: "",
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
|
|||
constructor(
|
||||
private settings = {
|
||||
locale: "en-US",
|
||||
}
|
||||
},
|
||||
) {}
|
||||
async generate(input: {
|
||||
order: OrderPayloadFragment;
|
||||
|
@ -19,17 +19,7 @@ export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
|
|||
const { invoiceNumber, order, companyAddressData, filename } = input;
|
||||
|
||||
const microinvoiceInstance = new Microinvoice({
|
||||
style: {
|
||||
/*
|
||||
* header: {
|
||||
* image: {
|
||||
* path: "./examples/logo.png",
|
||||
* width: 50,
|
||||
* height: 19,
|
||||
* },
|
||||
* },
|
||||
*/
|
||||
},
|
||||
style: {},
|
||||
data: {
|
||||
invoice: {
|
||||
name: `Invoice ${invoiceNumber}`,
|
||||
|
@ -63,12 +53,6 @@ export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
|
|||
order.billingAddress?.country.country,
|
||||
],
|
||||
},
|
||||
/*
|
||||
* {
|
||||
* label: "Tax Identifier",
|
||||
* value: "todo",
|
||||
* },
|
||||
*/
|
||||
],
|
||||
|
||||
seller: [
|
||||
|
@ -84,28 +68,9 @@ export class MicroinvoiceInvoiceGenerator implements InvoiceGenerator {
|
|||
companyAddressData.countryArea,
|
||||
],
|
||||
},
|
||||
/*
|
||||
* {
|
||||
* label: "Tax Identifier",
|
||||
* value: "todo",
|
||||
* },
|
||||
*/
|
||||
],
|
||||
|
||||
legal: [
|
||||
/*
|
||||
* {
|
||||
* value: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
|
||||
* weight: "bold",
|
||||
* color: "primary",
|
||||
* },
|
||||
* {
|
||||
* value: "sed do eiusmod tempor incididunt ut labore et dolore magna.",
|
||||
* weight: "bold",
|
||||
* color: "secondary",
|
||||
* },
|
||||
*/
|
||||
],
|
||||
legal: [],
|
||||
|
||||
details: {
|
||||
header: [
|
||||
|
|
10
apps/invoices/src/modules/shop-info/shop-address.ts
Normal file
10
apps/invoices/src/modules/shop-info/shop-address.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export type ShopAddress = {
|
||||
city: string;
|
||||
cityArea: string;
|
||||
companyName: string;
|
||||
country: string;
|
||||
countryArea: string;
|
||||
postalCode: string;
|
||||
streetAddress1: string;
|
||||
streetAddress2: string;
|
||||
};
|
|
@ -156,7 +156,7 @@ const invoiceNumberGenerator = new InvoiceNumberGenerator();
|
|||
export const handler: NextWebhookApiHandler<InvoiceRequestedPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
context,
|
||||
) => {
|
||||
const { authData, payload, baseUrl } = context;
|
||||
const logger = createLogger({ domain: authData.saleorApiUrl, url: baseUrl });
|
||||
|
@ -176,7 +176,7 @@ export const handler: NextWebhookApiHandler<InvoiceRequestedPayloadFragment> = a
|
|||
*/
|
||||
const invoiceName = invoiceNumberGenerator.generateFromOrder(
|
||||
order as OrderPayloadFragment,
|
||||
InvoiceNumberGenerationStrategy.localizedDate("en-US") // todo connect locale -> where from?
|
||||
InvoiceNumberGenerationStrategy.localizedDate("en-US"), // todo connect locale -> where from?
|
||||
);
|
||||
|
||||
Sentry.addBreadcrumb({
|
||||
|
@ -270,7 +270,7 @@ export const handler: NextWebhookApiHandler<InvoiceRequestedPayloadFragment> = a
|
|||
await new InvoiceCreateNotifier(client).notifyInvoiceCreated(
|
||||
orderId,
|
||||
invoiceName,
|
||||
uploadedFileUrl
|
||||
uploadedFileUrl,
|
||||
);
|
||||
|
||||
Sentry.addBreadcrumb({
|
||||
|
|
|
@ -1,18 +1,5 @@
|
|||
# saleor-app-klaviyo
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 4aee4e1: Improved app layout to match modern style.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
- 4aee4e1: Fixed error where config couldn't be saved
|
||||
|
||||
## 1.8.6
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-klaviyo",
|
||||
"version": "1.9.0",
|
||||
"version": "1.8.6",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -35,7 +35,7 @@
|
|||
"vitest": "0.34.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
@ -43,6 +43,7 @@
|
|||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@graphql-codegen/typescript-urql": "3.7.3",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
"autoprefixer": "^10.4.7",
|
||||
|
|
1
apps/klaviyo/reset.d.ts
vendored
Normal file
1
apps/klaviyo/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -56,13 +56,13 @@ export const customerCreatedWebhook = new SaleorAsyncWebhook<CustomerCreatedWebh
|
|||
event: "CUSTOMER_CREATED",
|
||||
apl: saleorApp.apl,
|
||||
query: UntypedCustomerCreatedDocument,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const handler: NextWebhookApiHandler<CustomerCreatedWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
context,
|
||||
) => {
|
||||
console.debug("customerCreatedWebhook handler called");
|
||||
|
||||
|
@ -94,7 +94,8 @@ const handler: NextWebhookApiHandler<CustomerCreatedWebhookPayloadFragment> = as
|
|||
const klaviyoResponse = await klaviyoClient.send(klaviyoMetric, userEmail, payload);
|
||||
|
||||
if (klaviyoResponse.status !== 200) {
|
||||
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
|
||||
const klaviyoMessage =
|
||||
` Message: ${((await klaviyoResponse.json()) as { message: string })?.message}.` || "";
|
||||
|
||||
console.debug("Klaviyo returned error: ", klaviyoMessage);
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ export const fulfillmentCreatedWebhook =
|
|||
const handler: NextWebhookApiHandler<FulfillmentCreatedWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
context,
|
||||
) => {
|
||||
console.debug("fulfillmentCreatedWebhook handler called");
|
||||
|
||||
|
@ -98,7 +98,8 @@ const handler: NextWebhookApiHandler<FulfillmentCreatedWebhookPayloadFragment> =
|
|||
const klaviyoResponse = await klaviyoClient.send(klaviyoMetric, userEmail, payload);
|
||||
|
||||
if (klaviyoResponse.status !== 200) {
|
||||
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
|
||||
const klaviyoMessage =
|
||||
` Message: ${((await klaviyoResponse.json()) as { message: string })?.message}.` || "";
|
||||
|
||||
console.debug("Klaviyo returned error: ", klaviyoMessage);
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ export const orderCreatedWebhook = new SaleorAsyncWebhook<OrderCreatedWebhookPay
|
|||
const handler: NextWebhookApiHandler<OrderCreatedWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
context,
|
||||
) => {
|
||||
console.debug("orderCreatedWebhook handler called");
|
||||
|
||||
|
@ -69,7 +69,8 @@ const handler: NextWebhookApiHandler<OrderCreatedWebhookPayloadFragment> = async
|
|||
const klaviyoResponse = await klaviyoClient.send(klaviyoMetric, userEmail, payload);
|
||||
|
||||
if (klaviyoResponse.status !== 200) {
|
||||
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
|
||||
const klaviyoMessage =
|
||||
` Message: ${((await klaviyoResponse.json()) as { message: string })?.message}.` || "";
|
||||
|
||||
console.debug("Klaviyo returned error: ", klaviyoMessage);
|
||||
return res.status(500).json({
|
||||
|
|
|
@ -38,7 +38,7 @@ export const orderFullyPaidWebhook = new SaleorAsyncWebhook<OrderFullyPaidWebhoo
|
|||
const handler: NextWebhookApiHandler<OrderFullyPaidWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
context,
|
||||
) => {
|
||||
console.debug("orderFullyPaidWebhook handler called");
|
||||
|
||||
|
@ -70,7 +70,8 @@ const handler: NextWebhookApiHandler<OrderFullyPaidWebhookPayloadFragment> = asy
|
|||
const klaviyoResponse = await klaviyoClient.send(klaviyoMetric, userEmail, payload);
|
||||
|
||||
if (klaviyoResponse.status !== 200) {
|
||||
const klaviyoMessage = ` Message: ${(await klaviyoResponse.json())?.message}.` || "";
|
||||
const klaviyoMessage =
|
||||
` Message: ${((await klaviyoResponse.json()) as { message: string })?.message}.` || "";
|
||||
|
||||
console.debug("Klaviyo returned error: ", klaviyoMessage);
|
||||
|
||||
|
|
|
@ -1,21 +1,5 @@
|
|||
# saleor-app-products-feed
|
||||
|
||||
## 1.12.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ae6dbb1: Removed webhooks on product changes used for feed cache due to changed max execution time.
|
||||
- ae6dbb1: Changed Vercel's maximum execution time to be 5 minutes for feed generation. This should help with the previous limits of 60s, that was not enough for feed to be generated.
|
||||
|
||||
## 1.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
fragment ProductVariantWebhookPayload on ProductVariant {
|
||||
channel
|
||||
channelListings {
|
||||
channel {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
fragment ProductWebhookPayload on Product {
|
||||
channel
|
||||
channelListings {
|
||||
channel {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
subscription ProductCreated {
|
||||
event {
|
||||
... on ProductCreated {
|
||||
product {
|
||||
...ProductWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
subscription ProductDeleted {
|
||||
event {
|
||||
... on ProductDeleted {
|
||||
product {
|
||||
...ProductWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
subscription ProductUpdated {
|
||||
event {
|
||||
... on ProductUpdated {
|
||||
product {
|
||||
...ProductWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
subscription ProductVariantCreated {
|
||||
event {
|
||||
... on ProductVariantCreated {
|
||||
productVariant {
|
||||
...ProductVariantWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
subscription ProductVariantDeleted {
|
||||
event {
|
||||
... on ProductVariantDeleted {
|
||||
productVariant {
|
||||
...ProductVariantWebhookPayload
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
subscription ProductVariantUpdated {
|
||||
event {
|
||||
... on ProductVariantUpdated {
|
||||
productVariant {
|
||||
...ProductVariantWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,12 +6,7 @@ const isSentryPropertiesInEnvironment =
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
transpilePackages: [
|
||||
"@saleor/apps-shared",
|
||||
"@saleor/apps-ui",
|
||||
"@saleor/react-hook-form-macaw",
|
||||
"@saleor/webhook-utils",
|
||||
],
|
||||
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
|
||||
};
|
||||
|
||||
const configWithSentry = withSentryConfig(
|
||||
|
@ -27,7 +22,7 @@ const configWithSentry = withSentryConfig(
|
|||
tunnelRoute: "/monitoring",
|
||||
hideSourceMaps: true,
|
||||
disableLogger: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-products-feed",
|
||||
"version": "1.12.2",
|
||||
"version": "1.12.0",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -13,13 +13,12 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.332.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@saleor/app-sdk": "0.43.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@saleor/apps-ui": "workspace:*",
|
||||
"@saleor/macaw-ui": "0.8.0-pre.127",
|
||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||
"@saleor/webhook-utils": "workspace:*",
|
||||
"@sentry/nextjs": "7.67.0",
|
||||
"@tanstack/react-query": "4.29.19",
|
||||
"@trpc/client": "10.38.1",
|
||||
|
@ -28,7 +27,6 @@
|
|||
"@trpc/server": "10.38.1",
|
||||
"@urql/exchange-auth": "^2.1.4",
|
||||
"@vitejs/plugin-react": "4.0.4",
|
||||
"dotenv": "^16.3.1",
|
||||
"fast-xml-parser": "^4.0.15",
|
||||
"graphql": "16.7.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
|
@ -48,7 +46,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
@ -58,6 +56,7 @@
|
|||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
"eslint": "8.46.0",
|
||||
|
|
1
apps/products-feed/reset.d.ts
vendored
Normal file
1
apps/products-feed/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -1,7 +0,0 @@
|
|||
# Webhook migration scripts
|
||||
|
||||
Test migration with dry run, operation will not modify any data:
|
||||
`npx tsx scripts/migrations/run-webhooks-migration-dry-run.ts`
|
||||
|
||||
To start the migration run command:
|
||||
`npx tsx scripts/migrations/run-webhooks-migration.ts`
|
|
@ -1,20 +0,0 @@
|
|||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
|
||||
import { SaleorCloudAPL } from "@saleor/app-sdk/APL";
|
||||
|
||||
export const verifyRequiredEnvs = () => {
|
||||
const requiredEnvs = ["SALEOR_CLOUD_TOKEN", "SALEOR_CLOUD_RESOURCE_URL"];
|
||||
|
||||
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,30 +0,0 @@
|
|||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
|
||||
import * as dotenv from "dotenv";
|
||||
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
|
||||
import { updateWebhooksScript } from "./update-webhooks";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const runMigration = async () => {
|
||||
console.log("Starting webhooks migration (dry run)");
|
||||
|
||||
verifyRequiredEnvs();
|
||||
|
||||
console.log("Envs verified, fetching envs");
|
||||
|
||||
const allEnvs = await fetchCloudAplEnvs().catch((r) => {
|
||||
console.error("Could not fetch instances from the APL");
|
||||
console.error(r);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
for (const env of allEnvs) {
|
||||
await updateWebhooksScript({ authData: env, dryRun: true });
|
||||
}
|
||||
|
||||
console.log("Migration dry run complete");
|
||||
};
|
||||
|
||||
runMigration();
|
|
@ -1,30 +0,0 @@
|
|||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
|
||||
import * as dotenv from "dotenv";
|
||||
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
|
||||
import { updateWebhooksScript } from "./update-webhooks";
|
||||
|
||||
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("Could not fetch instances from the APL");
|
||||
console.error(r);
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
for (const env of allEnvs) {
|
||||
await updateWebhooksScript({ authData: env, dryRun: false });
|
||||
}
|
||||
|
||||
console.log("Migration complete");
|
||||
};
|
||||
|
||||
runMigration();
|
|
@ -1,29 +0,0 @@
|
|||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
|
||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
||||
import { AuthData } from "@saleor/app-sdk/APL";
|
||||
import { webhookMigrationRunner } from "@saleor/webhook-utils";
|
||||
|
||||
export const updateWebhooksScript = async ({
|
||||
authData,
|
||||
dryRun,
|
||||
}: {
|
||||
authData: AuthData;
|
||||
dryRun: boolean;
|
||||
}) => {
|
||||
console.log("Working on env: ", authData.saleorApiUrl);
|
||||
|
||||
const client = createGraphQLClient({
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
token: authData.token,
|
||||
});
|
||||
|
||||
await webhookMigrationRunner({
|
||||
client,
|
||||
dryRun,
|
||||
getManifests: async ({ appDetails }) => {
|
||||
// Products feed application has currently no webhooks, so we return empty array
|
||||
return [];
|
||||
},
|
||||
});
|
||||
};
|
|
@ -94,7 +94,6 @@ describe("AppConfig", function () {
|
|||
expect(
|
||||
() =>
|
||||
new AppConfig({
|
||||
// @ts-expect-error
|
||||
foo: "bar",
|
||||
}),
|
||||
).toThrow();
|
||||
|
|
|
@ -72,7 +72,7 @@ export class AppConfig {
|
|||
imageSize: imageSizeFieldSchema.parse(undefined),
|
||||
};
|
||||
|
||||
constructor(initialData?: RootConfig) {
|
||||
constructor(initialData?: RootConfig | unknown) {
|
||||
if (initialData) {
|
||||
try {
|
||||
this.rootData = rootAppConfigSchema.parse(initialData);
|
||||
|
|
|
@ -2,6 +2,7 @@ import { router } from "../trpc/trpc-server";
|
|||
import { protectedClientProcedure } from "../trpc/protected-client-procedure";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
|
||||
import { updateCacheForConfigurations } from "../metadata-cache/update-cache-for-configurations";
|
||||
import { AppConfigSchema, imageSizeInputSchema, titleTemplateInputSchema } from "./app-config";
|
||||
import { z } from "zod";
|
||||
import { createS3ClientFromConfiguration } from "../file-storage/s3/create-s3-client-from-configuration";
|
||||
|
@ -105,6 +106,17 @@ export const appConfigurationRouter = router({
|
|||
}) => {
|
||||
const config = await getConfig();
|
||||
|
||||
/**
|
||||
* TODO Check if this has to run, once its cached, it should be invalidated by webhooks only.
|
||||
*
|
||||
* But this operation isn't expensive and users will not continuously save this form
|
||||
*/
|
||||
await updateCacheForConfigurations({
|
||||
client: apiClient,
|
||||
channelsSlugs: [input.channelSlug],
|
||||
saleorApiUrl: saleorApiUrl,
|
||||
});
|
||||
|
||||
logger.debug({ channel: input.channelSlug }, "Updated cache for channel");
|
||||
|
||||
config.setChannelUrls(input.channelSlug, input.urls);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||
import { z } from "zod";
|
||||
|
||||
export class CacheConfigurator {
|
||||
private metadataKeyPrefix = "cursor-cache-";
|
||||
|
||||
constructor(
|
||||
private metadataManager: SettingsManager,
|
||||
private saleorApiUrl: string,
|
||||
) {}
|
||||
|
||||
private constructKey(channel: string) {
|
||||
return this.metadataKeyPrefix + channel;
|
||||
}
|
||||
|
||||
get({ channel }: { channel: string }): Promise<string[] | undefined> {
|
||||
return this.metadataManager.get(this.constructKey(channel), this.saleorApiUrl).then((data) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return z.array(z.string()).parse(JSON.parse(data));
|
||||
} catch (e) {
|
||||
throw new Error("Invalid metadata value, can't be parsed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
set({ channel, value }: { channel: string; value: string[] }): Promise<void> {
|
||||
return this.metadataManager.set({
|
||||
key: this.constructKey(channel),
|
||||
value: JSON.stringify(value),
|
||||
domain: this.saleorApiUrl,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { CacheConfigurator } from "./cache-configurator";
|
||||
import { createSettingsManager } from "../../lib/metadata-manager";
|
||||
import { getCursors } from "../google-feed/fetch-product-data";
|
||||
import { Client } from "urql";
|
||||
|
||||
interface UpdateCacheForConfigurationsArgs {
|
||||
client: Client;
|
||||
saleorApiUrl: string;
|
||||
channelsSlugs: string[];
|
||||
}
|
||||
|
||||
export const updateCacheForConfigurations = async ({
|
||||
client,
|
||||
channelsSlugs,
|
||||
saleorApiUrl,
|
||||
}: UpdateCacheForConfigurationsArgs) => {
|
||||
const logger = createLogger({ saleorApiUrl: saleorApiUrl });
|
||||
|
||||
logger.debug("Updating the cursor cache");
|
||||
|
||||
const cache = new CacheConfigurator(createSettingsManager(client), saleorApiUrl);
|
||||
|
||||
const cacheUpdatePromises = channelsSlugs.map(async (channel) => {
|
||||
const cursors = await getCursors({ client, channel });
|
||||
|
||||
await cache.set({ channel, value: cursors });
|
||||
});
|
||||
|
||||
await Promise.all(cacheUpdatePromises);
|
||||
|
||||
logger.debug("Cursor cache updated");
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
import { GraphqlClientFactory } from "../../lib/create-graphql-client";
|
||||
import { updateCacheForConfigurations } from "./update-cache-for-configurations";
|
||||
import { AuthData } from "@saleor/app-sdk/APL";
|
||||
import {
|
||||
ProductVariantWebhookPayloadFragment,
|
||||
ProductWebhookPayloadFragment,
|
||||
} from "../../../generated/graphql";
|
||||
import { NextApiResponse } from "next";
|
||||
|
||||
type ChannelFragment =
|
||||
| Pick<ProductWebhookPayloadFragment, "channel" | "channelListings">
|
||||
| Pick<ProductVariantWebhookPayloadFragment, "channel" | "channelListings">;
|
||||
|
||||
export const updateCacheOnWebhook = async ({
|
||||
channels,
|
||||
authData,
|
||||
res,
|
||||
}: {
|
||||
authData: AuthData;
|
||||
channels: ChannelFragment;
|
||||
res: NextApiResponse;
|
||||
}) => {
|
||||
const client = GraphqlClientFactory.fromAuthData(authData);
|
||||
|
||||
const channelsSlugs = [
|
||||
channels.channel,
|
||||
...(channels.channelListings?.map((cl) => cl.channel.slug) ?? []),
|
||||
].filter((c) => c) as string[];
|
||||
|
||||
if (channelsSlugs.length === 0) {
|
||||
return res.status(200).end();
|
||||
}
|
||||
|
||||
await updateCacheForConfigurations({
|
||||
channelsSlugs,
|
||||
client,
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
});
|
||||
|
||||
return res.status(200).end();
|
||||
};
|
|
@ -6,6 +6,9 @@ import { fetchProductData } from "../../../../../modules/google-feed/fetch-produ
|
|||
import { GoogleFeedSettingsFetcher } from "../../../../../modules/google-feed/get-google-feed-settings";
|
||||
import { generateGoogleXmlFeed } from "../../../../../modules/google-feed/generate-google-xml-feed";
|
||||
import { fetchShopData } from "../../../../../modules/google-feed/fetch-shop-data";
|
||||
import { CacheConfigurator } from "../../../../../modules/metadata-cache/cache-configurator";
|
||||
import { createSettingsManager } from "../../../../../lib/metadata-manager";
|
||||
import { GraphqlClientFactory } from "../../../../../lib/create-graphql-client";
|
||||
import { uploadFile } from "../../../../../modules/file-storage/s3/upload-file";
|
||||
import { createS3ClientFromConfiguration } from "../../../../../modules/file-storage/s3/create-s3-client-from-configuration";
|
||||
import { getFileDetails } from "../../../../../modules/file-storage/s3/get-file-details";
|
||||
|
@ -13,10 +16,6 @@ import { getDownloadUrl, getFileName } from "../../../../../modules/file-storage
|
|||
import { RootConfig } from "../../../../../modules/app-configuration/app-config";
|
||||
import { z, ZodError } from "zod";
|
||||
|
||||
export const config = {
|
||||
maxDuration: 5 * 60, // 5 minutes
|
||||
};
|
||||
|
||||
// By default we cache the feed for 5 minutes. This can be changed by setting the FEED_CACHE_MAX_AGE
|
||||
const FEED_CACHE_MAX_AGE = process.env.FEED_CACHE_MAX_AGE
|
||||
? parseInt(process.env.FEED_CACHE_MAX_AGE, 10)
|
||||
|
@ -158,10 +157,23 @@ export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
|
||||
logger.debug("Generating a new feed");
|
||||
|
||||
const cacheClient = GraphqlClientFactory.fromAuthData(authData);
|
||||
|
||||
if (!cacheClient) {
|
||||
logger.error("Can't create the gql client");
|
||||
return res.status(500).end();
|
||||
}
|
||||
|
||||
// get cached cursors
|
||||
const cache = new CacheConfigurator(createSettingsManager(cacheClient), authData.saleorApiUrl);
|
||||
|
||||
const cursors = await cache.get({ channel });
|
||||
|
||||
// TODO: instead of separate variants, use group id https://support.google.com/merchants/answer/6324507?hl=en
|
||||
let productVariants: GoogleFeedProductVariantFragment[] = [];
|
||||
|
||||
try {
|
||||
productVariants = await fetchProductData({ client, channel, imageSize });
|
||||
productVariants = await fetchProductData({ client, channel, cursors, imageSize });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return res.status(400).end();
|
||||
|
|
|
@ -2,6 +2,11 @@ import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
|
|||
import { AppManifest } from "@saleor/app-sdk/types";
|
||||
|
||||
import packageJson from "../../../package.json";
|
||||
import { webhookProductCreated } from "./webhooks/product_created";
|
||||
import { webhookProductDeleted } from "./webhooks/product_deleted";
|
||||
import { webhookProductVariantCreated } from "./webhooks/product_variant_created";
|
||||
import { webhookProductVariantDeleted } from "./webhooks/product_variant_deleted";
|
||||
import { webhookProductVariantUpdated } from "./webhooks/product_variant_updated";
|
||||
|
||||
export default createManifestHandler({
|
||||
async manifestFactory({ appBaseUrl }) {
|
||||
|
@ -26,7 +31,13 @@ export default createManifestHandler({
|
|||
supportUrl: "https://github.com/saleor/apps/discussions",
|
||||
tokenTargetUrl: `${apiBaseURL}/api/register`,
|
||||
version: packageJson.version,
|
||||
webhooks: [],
|
||||
webhooks: [
|
||||
webhookProductCreated.getWebhookManifest(apiBaseURL),
|
||||
webhookProductDeleted.getWebhookManifest(apiBaseURL),
|
||||
webhookProductVariantCreated.getWebhookManifest(apiBaseURL),
|
||||
webhookProductVariantDeleted.getWebhookManifest(apiBaseURL),
|
||||
webhookProductVariantUpdated.getWebhookManifest(apiBaseURL),
|
||||
],
|
||||
};
|
||||
|
||||
return manifest;
|
||||
|
|
41
apps/products-feed/src/pages/api/webhooks/product_created.ts
Normal file
41
apps/products-feed/src/pages/api/webhooks/product_created.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import {
|
||||
ProductCreatedDocument,
|
||||
ProductWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const webhookProductCreated = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
|
||||
webhookPath: "api/webhooks/product_created",
|
||||
event: "PRODUCT_CREATED",
|
||||
apl: saleorApp.apl,
|
||||
query: ProductCreatedDocument,
|
||||
// todo make it disabled by default, enable when app is configured
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const logger = createLogger({
|
||||
service: "webhook-product_created",
|
||||
});
|
||||
|
||||
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
) => {
|
||||
await updateCacheOnWebhook({
|
||||
authData: context.authData,
|
||||
channels: context.payload,
|
||||
res,
|
||||
});
|
||||
};
|
||||
|
||||
export default webhookProductCreated.createHandler(handler);
|
40
apps/products-feed/src/pages/api/webhooks/product_deleted.ts
Normal file
40
apps/products-feed/src/pages/api/webhooks/product_deleted.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import {
|
||||
ProductDeletedDocument,
|
||||
ProductWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const webhookProductDeleted = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
|
||||
webhookPath: "api/webhooks/product_deleted",
|
||||
event: "PRODUCT_DELETED",
|
||||
apl: saleorApp.apl,
|
||||
query: ProductDeletedDocument,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const logger = createLogger({
|
||||
service: "webhook_product_deleted",
|
||||
});
|
||||
|
||||
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
) => {
|
||||
await updateCacheOnWebhook({
|
||||
authData: context.authData,
|
||||
channels: context.payload,
|
||||
res,
|
||||
});
|
||||
};
|
||||
|
||||
export default webhookProductDeleted.createHandler(handler);
|
40
apps/products-feed/src/pages/api/webhooks/product_updated.ts
Normal file
40
apps/products-feed/src/pages/api/webhooks/product_updated.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import {
|
||||
ProductUpdatedDocument,
|
||||
ProductWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const webhookProductUpdated = new SaleorAsyncWebhook<ProductWebhookPayloadFragment>({
|
||||
webhookPath: "api/webhooks/product_updated",
|
||||
event: "PRODUCT_UPDATED",
|
||||
apl: saleorApp.apl,
|
||||
query: ProductUpdatedDocument,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const logger = createLogger({
|
||||
service: "webhookProductUpdatedWebhookHandler",
|
||||
});
|
||||
|
||||
export const handler: NextWebhookApiHandler<ProductWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
) => {
|
||||
await updateCacheOnWebhook({
|
||||
authData: context.authData,
|
||||
channels: context.payload,
|
||||
res,
|
||||
});
|
||||
};
|
||||
|
||||
export default webhookProductUpdated.createHandler(handler);
|
|
@ -0,0 +1,41 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import {
|
||||
ProductVariantCreatedDocument,
|
||||
ProductVariantWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const webhookProductVariantCreated =
|
||||
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
|
||||
webhookPath: "api/webhooks/product_variant_created",
|
||||
event: "PRODUCT_VARIANT_CREATED",
|
||||
apl: saleorApp.apl,
|
||||
query: ProductVariantCreatedDocument,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const logger = createLogger({
|
||||
service: "PRODUCT_VARIANT_CREATED webhook",
|
||||
});
|
||||
|
||||
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
) => {
|
||||
await updateCacheOnWebhook({
|
||||
authData: context.authData,
|
||||
channels: context.payload,
|
||||
res,
|
||||
});
|
||||
};
|
||||
|
||||
export default webhookProductVariantCreated.createHandler(handler);
|
|
@ -0,0 +1,41 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import {
|
||||
ProductVariantDeletedDocument,
|
||||
ProductVariantWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const webhookProductVariantDeleted =
|
||||
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
|
||||
webhookPath: "api/webhooks/product_variant_deleted",
|
||||
event: "PRODUCT_VARIANT_DELETED",
|
||||
apl: saleorApp.apl,
|
||||
query: ProductVariantDeletedDocument,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const logger = createLogger({
|
||||
service: "PRODUCT_VARIANT_DELETED",
|
||||
});
|
||||
|
||||
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
) => {
|
||||
await updateCacheOnWebhook({
|
||||
authData: context.authData,
|
||||
channels: context.payload,
|
||||
res,
|
||||
});
|
||||
};
|
||||
|
||||
export default webhookProductVariantDeleted.createHandler(handler);
|
|
@ -0,0 +1,41 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import {
|
||||
ProductVariantUpdatedDocument,
|
||||
ProductVariantWebhookPayloadFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
import { saleorApp } from "../../../saleor-app";
|
||||
import { updateCacheOnWebhook } from "../../../modules/metadata-cache/update-cache-on-webhook";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const webhookProductVariantUpdated =
|
||||
new SaleorAsyncWebhook<ProductVariantWebhookPayloadFragment>({
|
||||
webhookPath: "api/webhooks/product_variant_updated",
|
||||
event: "PRODUCT_VARIANT_UPDATED",
|
||||
apl: saleorApp.apl,
|
||||
query: ProductVariantUpdatedDocument,
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
const logger = createLogger({
|
||||
service: "webhookProductVariantUpdatedWebhookHandler",
|
||||
});
|
||||
|
||||
export const handler: NextWebhookApiHandler<ProductVariantWebhookPayloadFragment> = async (
|
||||
req,
|
||||
res,
|
||||
context
|
||||
) => {
|
||||
await updateCacheOnWebhook({
|
||||
authData: context.authData,
|
||||
channels: context.payload,
|
||||
res,
|
||||
});
|
||||
};
|
||||
|
||||
export default webhookProductVariantUpdated.createHandler(handler);
|
|
@ -1,50 +1,5 @@
|
|||
# saleor-app-search
|
||||
|
||||
## 1.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 1982d81: Added a new `pricing` field to the Algolia object representation. It now passes variant pricing representation from GraphQL:
|
||||
|
||||
```graphQL
|
||||
price {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
discount {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
onSale
|
||||
priceUndiscounted {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
- 7e0755e: Webhook migration scripts has been moved to the shared package.
|
||||
- Updated dependencies [5dee65a]
|
||||
- Updated dependencies [7e0755e]
|
||||
- @saleor/webhook-utils@0.0.1
|
||||
|
||||
## 1.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -10,26 +10,7 @@ fragment ProductVariantData on ProductVariant {
|
|||
price {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
discount {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
onSale
|
||||
priceUndiscounted {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
currency
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-search",
|
||||
"version": "1.16.0",
|
||||
"version": "1.15.0",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@saleor/app-sdk": "0.43.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@saleor/apps-ui": "workspace:*",
|
||||
|
@ -44,7 +44,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
@ -52,6 +52,7 @@
|
|||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@graphql-codegen/typescript-urql": "3.7.3",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
"@vitejs/plugin-react": "4.0.4",
|
||||
|
|
1
apps/search/reset.d.ts
vendored
Normal file
1
apps/search/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
|
@ -78,15 +78,9 @@ export class AlgoliaSearchProvider implements SearchProvider {
|
|||
"productId",
|
||||
"inStock",
|
||||
"categories",
|
||||
"grossPrice",
|
||||
"attributes",
|
||||
"collections",
|
||||
"pricing.price.net",
|
||||
"pricing.price.gross",
|
||||
"pricing.discount.net",
|
||||
"pricing.discount.gross",
|
||||
"pricing.priceUndiscounted.net",
|
||||
"pricing.priceUndiscounted.gross",
|
||||
"pricing.onSale",
|
||||
],
|
||||
attributeForDistinct: "productId",
|
||||
numericAttributesForFiltering: ["grossPrice"],
|
||||
|
|
|
@ -134,25 +134,7 @@ export function productAndVariantToAlgolia({
|
|||
descriptionPlaintext: EditorJsPlaintextRenderer({ stringData: product.description }),
|
||||
slug: product.slug,
|
||||
thumbnail: product.thumbnail?.url,
|
||||
/**
|
||||
* Deprecated
|
||||
*/
|
||||
grossPrice: listing?.price?.amount,
|
||||
pricing: {
|
||||
price: {
|
||||
net: variant.pricing?.price?.net.amount,
|
||||
gross: variant.pricing?.price?.gross.amount,
|
||||
},
|
||||
onSale: variant.pricing?.onSale,
|
||||
discount: {
|
||||
net: variant.pricing?.discount?.net.amount,
|
||||
gross: variant.pricing?.discount?.gross.amount,
|
||||
},
|
||||
priceUndiscounted: {
|
||||
net: variant.pricing?.priceUndiscounted?.net.amount,
|
||||
gross: variant.pricing?.priceUndiscounted?.gross.amount,
|
||||
},
|
||||
},
|
||||
inStock,
|
||||
categories: categoryHierarchicalFacets(variant),
|
||||
collections: product.collections?.map((collection) => collection.name) || [],
|
||||
|
|
|
@ -7,7 +7,9 @@ export const useWebhooksStatus = () => {
|
|||
const fetch: typeof window.fetch = useAuthenticatedFetch();
|
||||
|
||||
const fetchFn = useCallback(() => {
|
||||
return fetch("/api/webhooks-status").then((resp) => resp.json());
|
||||
return fetch("/api/webhooks-status").then(
|
||||
(resp) => resp.json() as unknown as WebhooksStatusResponse,
|
||||
);
|
||||
/**
|
||||
* fetch from SDK is not wrapped with memo todo
|
||||
*/
|
||||
|
|
|
@ -27,7 +27,7 @@ export class AppConfig {
|
|||
},
|
||||
};
|
||||
|
||||
constructor(initialData?: AppConfigRootSchemaFields) {
|
||||
constructor(initialData?: AppConfigRootSchemaFields | unknown) {
|
||||
if (initialData) {
|
||||
this.rootData = AppConfigRootSchema.parse(initialData);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { NextProtectedApiHandler } from "@saleor/app-sdk/handlers/next";
|
||||
import { NextProtectedApiHandler, ProtectedHandlerContext } from "@saleor/app-sdk/handlers/next";
|
||||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { Client, OperationResult } from "urql";
|
||||
|
@ -8,11 +8,12 @@ import { IWebhookActivityTogglerService } from "../../domain/WebhookActivityTogg
|
|||
import { SearchProvider } from "../../lib/searchProvider";
|
||||
import { webhooksStatusHandlerFactory } from "../../pages/api/webhooks-status";
|
||||
import { AppConfig } from "../../modules/configuration/configuration";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
/**
|
||||
* Context provided from ProtectedApiHandler to handler body
|
||||
*/
|
||||
const mockWebhookContext = {
|
||||
const mockWebhookContext: ProtectedHandlerContext = {
|
||||
authData: {
|
||||
appId: "app-id",
|
||||
domain: "domain.saleor.io",
|
||||
|
@ -20,6 +21,10 @@ const mockWebhookContext = {
|
|||
saleorApiUrl: "https://domain.saleor.io/graphql",
|
||||
},
|
||||
baseUrl: "localhost:3000",
|
||||
user: {
|
||||
email: "",
|
||||
userPermissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
const appWebhooksResponseData: Pick<OperationResult<FetchOwnWebhooksQuery, any>, "data"> = {
|
||||
|
@ -90,6 +95,7 @@ describe("webhooksStatusHandler", () => {
|
|||
it("Disables webhooks if Algolia settings are not saved in Saleor Metadata", async function () {
|
||||
const { req, res } = createMocks({});
|
||||
|
||||
// @ts-expect-error - mock doesnt contain next-specific fields
|
||||
await handler(req, res, mockWebhookContext);
|
||||
|
||||
expect(webhooksTogglerServiceMock.disableOwnWebhooks).toHaveBeenCalled();
|
||||
|
@ -113,6 +119,7 @@ describe("webhooksStatusHandler", () => {
|
|||
|
||||
const { req, res } = createMocks({});
|
||||
|
||||
// @ts-expect-error - mock doesnt contain next-specific fields
|
||||
await handler(req, res, mockWebhookContext);
|
||||
|
||||
expect(webhooksTogglerServiceMock.disableOwnWebhooks).toHaveBeenCalled();
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
# saleor-app-segment
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5d3d81d: Bumped @hookform/resolvers from 2.9.11 to 3.3.1
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-segment",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.2",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@saleor/app-sdk": "0.43.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@saleor/apps-ui": "workspace:*",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
|
@ -54,6 +54,7 @@
|
|||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@total-typescript/ts-reset": "^0.5.1",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
"eslint": "8.46.0",
|
||||
|
|
1
apps/segment/reset.d.ts
vendored
Normal file
1
apps/segment/reset.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
import "@total-typescript/ts-reset";
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue