Compare commits
15 commits
invoices-t
...
main
Author | SHA1 | Date | |
---|---|---|---|
4b12982597 | |||
292a5bdb0c | |||
![]() |
653b98df86 | ||
![]() |
ae6dbb125b | ||
![]() |
6948fe41ca | ||
![]() |
2a22cf47f2 | ||
![]() |
927d2b3bb6 | ||
![]() |
5dee65ad2c | ||
![]() |
c6db32b40e | ||
![]() |
5d3d81d5c2 | ||
![]() |
52c46987f3 | ||
![]() |
1982d81f17 | ||
![]() |
767d0be722 | ||
![]() |
2951fb3ab6 | ||
![]() |
7e0755ec9e |
115 changed files with 73687 additions and 1412 deletions
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"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.
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
"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
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"saleor-app-klaviyo": patch
|
||||
---
|
||||
|
||||
Fixed error where config couldn't be saved
|
8
.changeset/pretty-pink-panda.md
Normal file
8
.changeset/pretty-pink-panda.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
#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
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"saleor-app-invoices": minor
|
||||
---
|
||||
|
||||
Replace text "loading" messages with skeletons
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"saleor-app-invoices": minor
|
||||
---
|
||||
|
||||
Redesigned app layout. Now app uses shared sections as other apps.
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"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,9 +6,12 @@ on:
|
|||
|
||||
jobs:
|
||||
assign_creator:
|
||||
if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Assign PR to creator
|
||||
uses: thomaseizinger/assign-pr-creator-action@v1.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
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"
|
||||
|
|
31
.github/workflows/changeset-checker.yml
vendored
Normal file
31
.github/workflows/changeset-checker.yml
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
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@0.2.2
|
||||
- uses: JamieMason/syncpack-github-action@c145cec44b3731b3fe8e859679e240d6ae011f0f
|
||||
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@v3
|
||||
- uses: streetsidesoftware/cspell-action@22e32eb3d70acf30e3fc09bd46edc1d30fb2d6db
|
||||
|
|
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@v2
|
||||
- uses: pnpm/action-setup@d882d12c64e032187b2edb46d3a0d003b7a43598
|
||||
name: Install pnpm
|
||||
- run: pnpm install
|
||||
- name: Create Release Pull Request
|
||||
uses: changesets/action@v1
|
||||
uses: changesets/action@f13b1baaa620fde937751f5d2c3572b9da32af23
|
||||
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@v2.2.4
|
||||
- uses: pnpm/action-setup@c3b53f6a16e57305370b4ae5a540c2077a1d50dd
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
|
|
@ -1 +1 @@
|
|||
* @saleor/appstore
|
||||
* @saleor/delivery-engineering-js
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
# 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.2",
|
||||
"version": "2.3.3",
|
||||
"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.1.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@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": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
# 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.7",
|
||||
"version": "1.7.8",
|
||||
"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": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
# 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.6",
|
||||
"version": "1.9.7",
|
||||
"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": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
|
@ -7,6 +7,12 @@ 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:
|
||||
|
@ -15,4 +21,5 @@ APP_LOG_LEVEL=info
|
|||
# If developped with tunnels, set this empty, it will fallback to default Next's localhost:3000
|
||||
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
|
||||
APP_IFRAME_BASE_URL=
|
||||
APP_API_BASE_URL=
|
||||
APP_API_BASE_URL=
|
||||
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
# 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.9",
|
||||
"version": "1.9.10",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@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": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
|
||||
import { APL, FileAPL, RedisAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
|
||||
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
|
||||
|
||||
const aplType = process.env.APL ?? "file";
|
||||
|
@ -6,6 +6,12 @@ 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,5 +1,7 @@
|
|||
{
|
||||
"extends": ["//"],
|
||||
"extends": [
|
||||
"//"
|
||||
],
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
|
@ -21,7 +23,8 @@
|
|||
"NEXT_PUBLIC_SENTRY_DSN",
|
||||
"SENTRY_ENVIRONMENT",
|
||||
"APP_IFRAME_BASE_URL",
|
||||
"APP_API_BASE_URL"
|
||||
"APP_API_BASE_URL",
|
||||
"REDIS_URL"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
# 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.15.7",
|
||||
"version": "1.16.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.1.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@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": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
|
@ -1,5 +1,18 @@
|
|||
# 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.8.6",
|
||||
"version": "1.9.0",
|
||||
"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": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
# 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
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
fragment ProductVariantWebhookPayload on ProductVariant {
|
||||
channel
|
||||
channelListings {
|
||||
channel {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
fragment ProductWebhookPayload on Product {
|
||||
channel
|
||||
channelListings {
|
||||
channel {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
subscription ProductCreated {
|
||||
event {
|
||||
... on ProductCreated {
|
||||
product {
|
||||
...ProductWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
subscription ProductDeleted {
|
||||
event {
|
||||
... on ProductDeleted {
|
||||
product {
|
||||
...ProductWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
subscription ProductUpdated {
|
||||
event {
|
||||
... on ProductUpdated {
|
||||
product {
|
||||
...ProductWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
subscription ProductVariantCreated {
|
||||
event {
|
||||
... on ProductVariantCreated {
|
||||
productVariant {
|
||||
...ProductVariantWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
subscription ProductVariantDeleted {
|
||||
event {
|
||||
... on ProductVariantDeleted {
|
||||
productVariant {
|
||||
...ProductVariantWebhookPayload
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
subscription ProductVariantUpdated {
|
||||
event {
|
||||
... on ProductVariantUpdated {
|
||||
productVariant {
|
||||
...ProductVariantWebhookPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,12 @@ const isSentryPropertiesInEnvironment =
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
|
||||
transpilePackages: [
|
||||
"@saleor/apps-shared",
|
||||
"@saleor/apps-ui",
|
||||
"@saleor/react-hook-form-macaw",
|
||||
"@saleor/webhook-utils",
|
||||
],
|
||||
};
|
||||
|
||||
const configWithSentry = withSentryConfig(
|
||||
|
@ -22,7 +27,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.0",
|
||||
"version": "1.12.2",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -13,12 +13,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.332.0",
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@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",
|
||||
|
@ -27,6 +28,7 @@
|
|||
"@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",
|
||||
|
@ -46,7 +48,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
7
apps/products-feed/scripts/migrations/README.md
Normal file
7
apps/products-feed/scripts/migrations/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# 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`
|
20
apps/products-feed/scripts/migrations/migration-utils.ts
Normal file
20
apps/products-feed/scripts/migrations/migration-utils.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/* 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();
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
/* 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();
|
|
@ -0,0 +1,30 @@
|
|||
/* 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();
|
29
apps/products-feed/scripts/migrations/update-webhooks.ts
Normal file
29
apps/products-feed/scripts/migrations/update-webhooks.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* 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 [];
|
||||
},
|
||||
});
|
||||
};
|
|
@ -2,7 +2,6 @@ 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";
|
||||
|
@ -106,17 +105,6 @@ 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);
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
||||
|
||||
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 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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
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");
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
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,9 +6,6 @@ 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";
|
||||
|
@ -16,6 +13,10 @@ 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)
|
||||
|
@ -157,23 +158,10 @@ 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, cursors, imageSize });
|
||||
productVariants = await fetchProductData({ client, channel, imageSize });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return res.status(400).end();
|
||||
|
|
|
@ -2,11 +2,6 @@ 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 }) {
|
||||
|
@ -31,13 +26,7 @@ export default createManifestHandler({
|
|||
supportUrl: "https://github.com/saleor/apps/discussions",
|
||||
tokenTargetUrl: `${apiBaseURL}/api/register`,
|
||||
version: packageJson.version,
|
||||
webhooks: [
|
||||
webhookProductCreated.getWebhookManifest(apiBaseURL),
|
||||
webhookProductDeleted.getWebhookManifest(apiBaseURL),
|
||||
webhookProductVariantCreated.getWebhookManifest(apiBaseURL),
|
||||
webhookProductVariantDeleted.getWebhookManifest(apiBaseURL),
|
||||
webhookProductVariantUpdated.getWebhookManifest(apiBaseURL),
|
||||
],
|
||||
webhooks: [],
|
||||
};
|
||||
|
||||
return manifest;
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
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);
|
|
@ -1,40 +0,0 @@
|
|||
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);
|
|
@ -1,40 +0,0 @@
|
|||
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);
|
|
@ -1,41 +0,0 @@
|
|||
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);
|
|
@ -1,41 +0,0 @@
|
|||
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);
|
|
@ -1,41 +0,0 @@
|
|||
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,5 +1,50 @@
|
|||
# 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,7 +10,26 @@ fragment ProductVariantData on ProductVariant {
|
|||
price {
|
||||
gross {
|
||||
amount
|
||||
currency
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
discount {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
onSale
|
||||
priceUndiscounted {
|
||||
gross {
|
||||
amount
|
||||
}
|
||||
net {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,12 @@ const isSentryPropertiesInEnvironment =
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
|
||||
transpilePackages: [
|
||||
"@saleor/apps-shared",
|
||||
"@saleor/apps-ui",
|
||||
"@saleor/webhook-utils",
|
||||
"@saleor/react-hook-form-macaw",
|
||||
],
|
||||
};
|
||||
|
||||
const configWithSentry = withSentryConfig(
|
||||
|
@ -22,7 +27,7 @@ const configWithSentry = withSentryConfig(
|
|||
tunnelRoute: "/monitoring",
|
||||
hideSourceMaps: true,
|
||||
disableLogger: true,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-search",
|
||||
"version": "1.15.0",
|
||||
"version": "1.16.0",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,12 +12,13 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@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",
|
||||
|
@ -43,7 +44,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/* eslint-disable turbo/no-undeclared-env-vars */
|
||||
|
||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
||||
import { WebhookActivityTogglerService } from "../../src/domain/WebhookActivityToggler.service";
|
||||
import { FetchOwnWebhooksDocument } from "../../generated/graphql";
|
||||
import { AuthData } from "@saleor/app-sdk/APL";
|
||||
|
||||
export const recreateWebhooks = async ({
|
||||
authData,
|
||||
dryRun,
|
||||
}: {
|
||||
authData: AuthData;
|
||||
dryRun: boolean;
|
||||
}) => {
|
||||
console.log("Working on env: ", authData.saleorApiUrl);
|
||||
|
||||
const client = createGraphQLClient({
|
||||
saleorApiUrl: authData.saleorApiUrl,
|
||||
token: authData.token,
|
||||
});
|
||||
const webhooks = await client
|
||||
.query(FetchOwnWebhooksDocument, {
|
||||
id: authData.appId,
|
||||
})
|
||||
.toPromise()
|
||||
.then((r) => r.data?.app?.webhooks);
|
||||
|
||||
if (!webhooks?.length) {
|
||||
console.error("The environment does not have any webhooks, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Use currently existing webhook data to determine a proper baseUrl and enabled state
|
||||
const targetUrl = webhooks[0].targetUrl;
|
||||
const enabled = webhooks[0].isActive;
|
||||
|
||||
const baseUrl = new URL(targetUrl).origin;
|
||||
|
||||
if (dryRun) {
|
||||
console.log("Necessary data gathered, skipping recreation of webhooks due to dry run mode");
|
||||
return;
|
||||
}
|
||||
|
||||
const webhookService = new WebhookActivityTogglerService(authData.appId, client);
|
||||
|
||||
try {
|
||||
await webhookService.recreateOwnWebhooks({ baseUrl, enableWebhooks: enabled });
|
||||
console.log("✅ Webhooks recreated successfully");
|
||||
} catch (e) {
|
||||
console.error("🛑 Failed to recreate webhooks: ", e);
|
||||
}
|
||||
};
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as dotenv from "dotenv";
|
||||
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
|
||||
import { recreateWebhooks } from "./recreate-webhooks";
|
||||
import { updateWebhooksScript } from "./update-webhooks";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
@ -21,7 +21,7 @@ const runMigration = async () => {
|
|||
});
|
||||
|
||||
for (const env of allEnvs) {
|
||||
await recreateWebhooks({ authData: env, dryRun: true });
|
||||
await updateWebhooksScript({ authData: env, dryRun: true });
|
||||
}
|
||||
|
||||
console.log("Migration dry run complete");
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as dotenv from "dotenv";
|
||||
import { fetchCloudAplEnvs, verifyRequiredEnvs } from "./migration-utils";
|
||||
import { recreateWebhooks } from "./recreate-webhooks";
|
||||
import { updateWebhooksScript } from "./update-webhooks";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
@ -21,7 +21,7 @@ const runMigration = async () => {
|
|||
});
|
||||
|
||||
for (const env of allEnvs) {
|
||||
await recreateWebhooks({ authData: env, dryRun: false });
|
||||
await updateWebhooksScript({ authData: env, dryRun: false });
|
||||
}
|
||||
|
||||
console.log("Migration complete");
|
||||
|
|
47
apps/search/scripts/migrations/update-webhooks.ts
Normal file
47
apps/search/scripts/migrations/update-webhooks.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* 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";
|
||||
import { appWebhooks } from "../../webhooks";
|
||||
|
||||
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 }) => {
|
||||
const webhooks = appDetails.webhooks;
|
||||
|
||||
if (!webhooks?.length) {
|
||||
console.info("The environment does not have any webhooks, skipping");
|
||||
return [];
|
||||
}
|
||||
|
||||
// All webhooks in this application are turned on or off. If any of them is enabled, we enable all of them.
|
||||
const enabled = webhooks.some((w) => w.isActive);
|
||||
|
||||
const targetUrl = appDetails.appUrl;
|
||||
|
||||
if (!targetUrl?.length) {
|
||||
throw new Error("App has no defined appUrl, skipping");
|
||||
}
|
||||
|
||||
const baseUrl = new URL(targetUrl).origin;
|
||||
|
||||
return appWebhooks.map((w) => ({ ...w.getWebhookManifest(baseUrl), enabled }));
|
||||
},
|
||||
});
|
||||
};
|
|
@ -78,9 +78,15 @@ 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,7 +134,25 @@ 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) || [],
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
# 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.2",
|
||||
"version": "1.0.3",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.1.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@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": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
# saleor-app-slack
|
||||
|
||||
## 1.8.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 2e29699: Updated Sentry package
|
||||
|
||||
## 1.8.6
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-slack",
|
||||
"version": "1.8.6",
|
||||
"version": "1.8.7",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"vitest": "0.34.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
# saleor-app-taxes
|
||||
|
||||
## 1.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- a32fe7c: 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.
|
||||
|
||||
### 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
|
||||
- 2951fb3: [skip ci]: Bump jotai from 2.0.0 to 2.4.2
|
||||
|
||||
## 1.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "saleor-app-taxes",
|
||||
"version": "1.15.2",
|
||||
"version": "1.16.0",
|
||||
"scripts": {
|
||||
"build": "pnpm generate && next build",
|
||||
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^2.9.10",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@saleor/app-sdk": "0.43.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@saleor/apps-ui": "workspace:*",
|
||||
|
@ -29,7 +29,7 @@
|
|||
"dotenv": "^16.3.1",
|
||||
"graphql": "16.7.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"jotai": "^2.0.0",
|
||||
"jotai": "^2.4.2",
|
||||
"jsdom": "^20.0.3",
|
||||
"next": "13.4.8",
|
||||
"pino": "^8.14.1",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"zod": "3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "4.0.1",
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/introspection": "4.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
|
|
|
@ -68,7 +68,8 @@
|
|||
"urql",
|
||||
"Protos",
|
||||
"pino",
|
||||
"IFRAME"
|
||||
"IFRAME",
|
||||
"dedupe"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"node_modules",
|
||||
|
@ -80,6 +81,7 @@
|
|||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/graphql.ts",
|
||||
"**/CHANGELOG.md"
|
||||
"**/CHANGELOG.md",
|
||||
"**/schema.graphql"
|
||||
]
|
||||
}
|
||||
|
|
12
package.json
12
package.json
|
@ -39,15 +39,15 @@
|
|||
"*.{ts,tsx,md,js,jsx}": "cspell --no-must-find-files"
|
||||
},
|
||||
"packageManager": "pnpm@8.7.4",
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@saleor/app-sdk": "0.43.1"
|
||||
}
|
||||
},
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*",
|
||||
"templates/*"
|
||||
],
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@saleor/app-sdk": "0.43.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
4
packages/webhook-utils/.eslintrc
Normal file
4
packages/webhook-utils/.eslintrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": ["saleor"]
|
||||
}
|
17
packages/webhook-utils/.graphqlrc.yml
Normal file
17
packages/webhook-utils/.graphqlrc.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
schema: graphql/schema.graphql
|
||||
documents: [graphql/**/*.graphql, src/**/*.ts, src/**/*.tsx]
|
||||
extensions:
|
||||
codegen:
|
||||
overwrite: true
|
||||
generates:
|
||||
generated/graphql.ts:
|
||||
config:
|
||||
dedupeFragments: true
|
||||
dedupeOperationSuffix: true
|
||||
plugins:
|
||||
- typescript
|
||||
- typescript-operations
|
||||
- typed-document-node
|
||||
generated/schema.graphql:
|
||||
plugins:
|
||||
- schema-ast
|
9
packages/webhook-utils/CHANGELOG.md
Normal file
9
packages/webhook-utils/CHANGELOG.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# @saleor/webhook-utils
|
||||
|
||||
## 0.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5dee65a: Updated dependencies:
|
||||
- @graphql-codegen/cli@5.0.0
|
||||
- 7e0755e: Introduced a new shared package with helpers used for webhook management.
|
22742
packages/webhook-utils/generated/graphql.ts
Normal file
22742
packages/webhook-utils/generated/graphql.ts
Normal file
File diff suppressed because it is too large
Load diff
24215
packages/webhook-utils/generated/schema.graphql
Normal file
24215
packages/webhook-utils/generated/schema.graphql
Normal file
File diff suppressed because it is too large
Load diff
16
packages/webhook-utils/graphql/fragments/AppDetails.graphql
Normal file
16
packages/webhook-utils/graphql/fragments/AppDetails.graphql
Normal file
|
@ -0,0 +1,16 @@
|
|||
fragment AppDetailsFragment on App {
|
||||
id
|
||||
appUrl
|
||||
name
|
||||
webhooks{
|
||||
...WebhookDetailsFragment
|
||||
}
|
||||
metadata{
|
||||
key
|
||||
value
|
||||
}
|
||||
privateMetadata{
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
fragment ChannelDetailsFragment on Channel {
|
||||
id
|
||||
slug
|
||||
name
|
||||
currencyCode
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
fragment ShopDetailsFragment on Shop {
|
||||
version
|
||||
domain {
|
||||
url
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
fragment WebhookDetailsFragment on Webhook {
|
||||
id
|
||||
isActive
|
||||
name
|
||||
targetUrl
|
||||
subscriptionQuery
|
||||
syncEvents{
|
||||
name
|
||||
eventType
|
||||
}
|
||||
asyncEvents {
|
||||
name
|
||||
eventType
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
mutation CreateAppWebhook($input: WebhookCreateInput!) {
|
||||
webhookCreate(input:$input){
|
||||
webhook{
|
||||
...WebhookDetailsFragment
|
||||
}
|
||||
errors{
|
||||
code
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
mutation ModifyAppWebhook($id: ID!, $input: WebhookUpdateInput!) {
|
||||
webhookUpdate(id: $id, input: $input) {
|
||||
errors {
|
||||
message
|
||||
}
|
||||
webhook {
|
||||
...WebhookDetailsFragment
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
mutation RemoveAppWebhook($id: ID!) {
|
||||
webhookDelete(id: $id){
|
||||
errors{
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
query GetAppDetailsAndWebhooksData {
|
||||
app{
|
||||
...AppDetailsFragment
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
query GetSaleorInstanceData {
|
||||
shop{
|
||||
...ShopDetailsFragment
|
||||
}
|
||||
channels{
|
||||
...ChannelDetailsFragment
|
||||
}
|
||||
}
|
24129
packages/webhook-utils/graphql/schema.graphql
Normal file
24129
packages/webhook-utils/graphql/schema.graphql
Normal file
File diff suppressed because it is too large
Load diff
5
packages/webhook-utils/index.ts
Normal file
5
packages/webhook-utils/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export * from "./src/operations/create-app-webhook";
|
||||
export * from "./src/operations/modify-app-webhook";
|
||||
export * from "./src/operations/remove-app-webhook";
|
||||
export * from "./src/operations/get-app-details-and-webhooks-data";
|
||||
export * from "./src/webhook-migration-runner";
|
46
packages/webhook-utils/package.json
Normal file
46
packages/webhook-utils/package.json
Normal file
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"name": "@saleor/webhook-utils",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
|
||||
"generate": "graphql-codegen",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@saleor/app-sdk": "0.41.1",
|
||||
"@saleor/apps-shared": "workspace:*",
|
||||
"@urql/exchange-auth": "^2.1.4",
|
||||
"eslint": "8.46.0",
|
||||
"graphql": "16.7.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"pino": "^8.14.1",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"semver": "^7.5.1",
|
||||
"urql": "^4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "5.0.0",
|
||||
"@graphql-codegen/schema-ast": "4.0.0",
|
||||
"@graphql-codegen/typed-document-node": "5.0.1",
|
||||
"@graphql-codegen/typescript": "4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "4.0.1",
|
||||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@saleor/app-sdk": "0.41.1",
|
||||
"@types/semver": "^7.5.0",
|
||||
"eslint-config-saleor": "workspace:*",
|
||||
"typescript": "5.1.6",
|
||||
"urql": "^4.0.4",
|
||||
"vite": "4.4.8",
|
||||
"vitest": "0.34.1"
|
||||
},
|
||||
"main": "index.ts",
|
||||
"peerDependencies": {
|
||||
"next": "13.3.0",
|
||||
"pino": "^8.14.1",
|
||||
"pino-pretty": "^10.0.0"
|
||||
},
|
||||
"saleor": {
|
||||
"schemaVersion": "3.7"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { Client } from "urql";
|
||||
import { WebhookEventTypeAsyncEnum, WebhookEventTypeSyncEnum } from "../generated/graphql";
|
||||
import { WebhookManifest } from "@saleor/app-sdk/types";
|
||||
import { createAppWebhook } from "./operations/create-app-webhook";
|
||||
|
||||
interface CreateAppWebhookFromManifestArgs {
|
||||
client: Client;
|
||||
webhookManifest: WebhookManifest;
|
||||
}
|
||||
|
||||
export const createAppWebhookFromManifest = async ({
|
||||
client,
|
||||
webhookManifest,
|
||||
}: CreateAppWebhookFromManifestArgs) => {
|
||||
return createAppWebhook({
|
||||
client,
|
||||
input: {
|
||||
asyncEvents: webhookManifest.asyncEvents as WebhookEventTypeAsyncEnum[],
|
||||
syncEvents: webhookManifest.syncEvents as WebhookEventTypeSyncEnum[],
|
||||
isActive: webhookManifest.isActive,
|
||||
name: webhookManifest.name,
|
||||
targetUrl: webhookManifest.targetUrl,
|
||||
query: webhookManifest.query,
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
import { Client } from "urql";
|
||||
import { WebhookDetailsFragment } from "../generated/graphql";
|
||||
import { createAppWebhook } from "./operations/create-app-webhook";
|
||||
|
||||
interface CreateAppWebhookFromWebhookDetailsFragmentArgs {
|
||||
client: Client;
|
||||
webhookDetails: WebhookDetailsFragment;
|
||||
}
|
||||
|
||||
export const createAppWebhookFromWebhookDetailsFragment = async ({
|
||||
client,
|
||||
webhookDetails,
|
||||
}: CreateAppWebhookFromWebhookDetailsFragmentArgs) => {
|
||||
return createAppWebhook({
|
||||
client,
|
||||
input: {
|
||||
asyncEvents: webhookDetails.asyncEvents.map((event) => event.eventType),
|
||||
syncEvents: webhookDetails.syncEvents.map((event) => event.eventType),
|
||||
isActive: webhookDetails.isActive,
|
||||
name: webhookDetails.name,
|
||||
targetUrl: webhookDetails.targetUrl,
|
||||
query: webhookDetails.subscriptionQuery,
|
||||
},
|
||||
});
|
||||
};
|
17
packages/webhook-utils/src/disable-webhook.ts
Normal file
17
packages/webhook-utils/src/disable-webhook.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Client } from "urql";
|
||||
import { modifyAppWebhook } from "./operations/modify-app-webhook";
|
||||
|
||||
interface DisableWebhookArgs {
|
||||
client: Client;
|
||||
webhookId: string;
|
||||
}
|
||||
|
||||
export const disableWebhook = async ({ client, webhookId }: DisableWebhookArgs) => {
|
||||
return modifyAppWebhook({
|
||||
client,
|
||||
webhookId,
|
||||
input: {
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
};
|
17
packages/webhook-utils/src/enable-webhook.ts
Normal file
17
packages/webhook-utils/src/enable-webhook.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Client } from "urql";
|
||||
import { modifyAppWebhook } from "./operations/modify-app-webhook";
|
||||
|
||||
interface enableWebhookArgs {
|
||||
client: Client;
|
||||
webhookId: string;
|
||||
}
|
||||
|
||||
export const enableWebhook = async ({ client, webhookId }: enableWebhookArgs) => {
|
||||
await modifyAppWebhook({
|
||||
client,
|
||||
webhookId,
|
||||
input: {
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,101 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { getWebhookIdsAndManifestsToUpdate } from "./get-webhook-ids-and-manifests-to-update";
|
||||
|
||||
describe("getWebhookIdsAndQueriesToUpdate", () => {
|
||||
it("Returns an empty list, when no data is passed", () => {
|
||||
expect(
|
||||
getWebhookIdsAndManifestsToUpdate({
|
||||
existingWebhooksPartial: [],
|
||||
newWebhookManifests: [],
|
||||
}),
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
it("Returns all of the entries, when new webhook manifests contain the same webhooks as existing list", () => {
|
||||
expect(
|
||||
getWebhookIdsAndManifestsToUpdate({
|
||||
existingWebhooksPartial: [
|
||||
{ id: "1", name: "webhook1" },
|
||||
{ id: "2", name: "webhook2" },
|
||||
],
|
||||
newWebhookManifests: [
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook1",
|
||||
query: "newQuery1",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook2",
|
||||
query: "newQuery2",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toStrictEqual([
|
||||
{
|
||||
webhookId: "1",
|
||||
webhookManifest: {
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook1",
|
||||
query: "newQuery1",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
webhookId: "2",
|
||||
webhookManifest: {
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook2",
|
||||
query: "newQuery2",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
it("Returns subset of entries, when existing webhook list contain some of them", () => {
|
||||
expect(
|
||||
getWebhookIdsAndManifestsToUpdate({
|
||||
existingWebhooksPartial: [{ id: "1", name: "webhook1" }],
|
||||
newWebhookManifests: [
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook1",
|
||||
query: "newQuery1",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook2",
|
||||
query: "newQuery2",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toStrictEqual([
|
||||
{
|
||||
webhookId: "1",
|
||||
webhookManifest: {
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook1",
|
||||
query: "newQuery1",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import { WebhookManifest } from "@saleor/app-sdk/types";
|
||||
|
||||
type WebhookPartial = { id: string; name: string };
|
||||
|
||||
interface GetWebhookIdsAndQueriesToUpdateArgs<T extends WebhookPartial> {
|
||||
newWebhookManifests: Array<WebhookManifest>;
|
||||
existingWebhooksPartial: Array<T>;
|
||||
}
|
||||
|
||||
type ReturnType = { webhookId: string; webhookManifest: WebhookManifest };
|
||||
|
||||
// Couples the webhook id with the manifest to update
|
||||
export const getWebhookIdsAndManifestsToUpdate = <T extends WebhookPartial>({
|
||||
newWebhookManifests,
|
||||
existingWebhooksPartial,
|
||||
}: GetWebhookIdsAndQueriesToUpdateArgs<T>): Array<ReturnType> => {
|
||||
return newWebhookManifests
|
||||
.map((webhookManifest) => {
|
||||
const existingWebhook = existingWebhooksPartial.find(
|
||||
(webhook) => webhook.name === webhookManifest.name,
|
||||
);
|
||||
|
||||
if (!existingWebhook) {
|
||||
// Theres no webhook with this name, so we cant start an update
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!webhookManifest.query) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
webhookId: existingWebhook.id,
|
||||
webhookManifest,
|
||||
};
|
||||
})
|
||||
.filter((data): data is ReturnType => data !== undefined); // Filter out undefined values and narrow down the type
|
||||
};
|
117
packages/webhook-utils/src/filters/webhooks-to-add.test.ts
Normal file
117
packages/webhook-utils/src/filters/webhooks-to-add.test.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { webhooksToAdd } from "./webhooks-to-add";
|
||||
|
||||
describe("webhooksToAdd", () => {
|
||||
it("Returns an empty list, when no data is passed", () => {
|
||||
expect(
|
||||
webhooksToAdd({
|
||||
existingWebhooksPartial: [],
|
||||
newWebhookManifests: [],
|
||||
}),
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
it("Returns empty list, when new webhook manifests contain the same webhooks as existing list", () => {
|
||||
expect(
|
||||
webhooksToAdd({
|
||||
existingWebhooksPartial: [
|
||||
{ id: "1", name: "webhook1" },
|
||||
{ id: "1", name: "webhook2" },
|
||||
],
|
||||
newWebhookManifests: [
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook1",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook2",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
it("Returns all of the manifests, when existing webhook list is empty", () => {
|
||||
expect(
|
||||
webhooksToAdd({
|
||||
existingWebhooksPartial: [],
|
||||
newWebhookManifests: [
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook1",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook2",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toStrictEqual([
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook1",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhook2",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
]);
|
||||
});
|
||||
it("Returns list with the new webhook to add, when it was not specified in the existing manifests", () => {
|
||||
expect(
|
||||
webhooksToAdd({
|
||||
existingWebhooksPartial: [{ id: "1", name: "webhookOld" }],
|
||||
newWebhookManifests: [
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhookOld",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhookNew",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toStrictEqual([
|
||||
{
|
||||
asyncEvents: [],
|
||||
isActive: true,
|
||||
name: "webhookNew",
|
||||
query: "",
|
||||
syncEvents: [],
|
||||
targetUrl: "",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
18
packages/webhook-utils/src/filters/webhooks-to-add.ts
Normal file
18
packages/webhook-utils/src/filters/webhooks-to-add.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { WebhookManifest } from "@saleor/app-sdk/types";
|
||||
|
||||
interface WebhooksToAddArgs {
|
||||
newWebhookManifests: Array<WebhookManifest>;
|
||||
existingWebhooksPartial: Array<{ id: string; name: string }>;
|
||||
}
|
||||
|
||||
export const webhooksToAdd = ({
|
||||
newWebhookManifests,
|
||||
existingWebhooksPartial,
|
||||
}: WebhooksToAddArgs) => {
|
||||
return newWebhookManifests.filter(
|
||||
(newWebhookManifest) =>
|
||||
!existingWebhooksPartial.find(
|
||||
(existingWebhook) => newWebhookManifest.name === existingWebhook.name,
|
||||
),
|
||||
);
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue