@reactionary/source 0.0.37 → 0.0.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env-template +10 -0
- package/.github/workflows/pull-request.yml +5 -3
- package/core/package.json +1 -2
- package/core/src/client/client.ts +4 -2
- package/core/src/index.ts +3 -9
- package/core/src/providers/analytics.provider.ts +6 -1
- package/core/src/providers/base.provider.ts +33 -3
- package/core/src/providers/cart.provider.ts +7 -1
- package/core/src/providers/category.provider.ts +91 -0
- package/core/src/providers/identity.provider.ts +5 -1
- package/core/src/providers/inventory.provider.ts +4 -0
- package/core/src/providers/price.provider.ts +54 -0
- package/core/src/providers/product.provider.ts +4 -0
- package/core/src/providers/search.provider.ts +6 -0
- package/core/src/schemas/capabilities.schema.ts +3 -2
- package/core/src/schemas/models/base.model.ts +42 -1
- package/core/src/schemas/models/cart.model.ts +27 -3
- package/core/src/schemas/models/category.model.ts +23 -0
- package/core/src/schemas/models/identifiers.model.ts +29 -1
- package/core/src/schemas/models/inventory.model.ts +6 -2
- package/core/src/schemas/models/price.model.ts +11 -3
- package/core/src/schemas/models/search.model.ts +4 -2
- package/core/src/schemas/queries/category.query.ts +32 -0
- package/core/src/schemas/queries/index.ts +9 -0
- package/core/src/schemas/queries/inventory.query.ts +18 -3
- package/core/src/schemas/session.schema.ts +13 -2
- package/examples/next/.swcrc +30 -0
- package/examples/next/eslint.config.mjs +21 -0
- package/examples/next/index.d.ts +6 -0
- package/examples/next/next-env.d.ts +5 -0
- package/examples/next/next.config.js +20 -0
- package/examples/next/project.json +9 -0
- package/examples/next/public/.gitkeep +0 -0
- package/examples/next/public/favicon.ico +0 -0
- package/examples/next/src/app/global.css +0 -0
- package/examples/next/src/app/layout.tsx +18 -0
- package/examples/next/src/app/page.module.scss +2 -0
- package/examples/next/src/app/page.tsx +51 -0
- package/examples/next/src/instrumentation.ts +9 -0
- package/examples/next/tsconfig.json +44 -0
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -1
- package/examples/node/src/basic/basic-node-setup.spec.ts +0 -1
- package/otel/README.md +152 -172
- package/otel/package.json +0 -1
- package/otel/src/index.ts +15 -5
- package/otel/src/metrics.ts +3 -3
- package/otel/src/test/otel.spec.ts +8 -0
- package/otel/src/trace-decorator.ts +87 -108
- package/otel/src/tracer.ts +3 -3
- package/package.json +2 -1
- package/providers/commercetools/package.json +1 -0
- package/providers/commercetools/src/core/initialize.ts +7 -3
- package/providers/commercetools/src/providers/cart.provider.ts +84 -8
- package/providers/commercetools/src/providers/category.provider.ts +244 -0
- package/providers/commercetools/src/providers/index.ts +7 -0
- package/providers/commercetools/src/providers/inventory.provider.ts +31 -14
- package/providers/commercetools/src/providers/price.provider.ts +74 -18
- package/providers/commercetools/src/providers/product.provider.ts +19 -15
- package/providers/commercetools/src/providers/search.provider.ts +9 -7
- package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
- package/providers/commercetools/src/schema/configuration.schema.ts +1 -1
- package/providers/commercetools/src/test/cart.provider.spec.ts +119 -0
- package/providers/commercetools/src/test/category.provider.spec.ts +180 -0
- package/providers/commercetools/src/test/price.provider.spec.ts +80 -0
- package/providers/commercetools/src/test/product.provider.spec.ts +29 -14
- package/providers/commercetools/src/test/search.provider.spec.ts +51 -9
- package/providers/commercetools/src/test/test-utils.ts +35 -0
- package/providers/commercetools/tsconfig.lib.json +1 -1
- package/providers/fake/jest.config.ts +10 -0
- package/providers/fake/src/core/initialize.ts +15 -1
- package/providers/fake/src/index.ts +2 -9
- package/providers/fake/src/providers/cart.provider.ts +74 -15
- package/providers/fake/src/providers/category.provider.ts +152 -0
- package/providers/fake/src/providers/index.ts +8 -0
- package/providers/fake/src/providers/inventory.provider.ts +23 -9
- package/providers/fake/src/providers/price.provider.ts +46 -6
- package/providers/fake/src/providers/search.provider.ts +13 -4
- package/providers/fake/src/schema/capabilities.schema.ts +4 -2
- package/providers/fake/src/schema/configuration.schema.ts +5 -0
- package/providers/fake/src/test/cart.provider.spec.ts +126 -0
- package/providers/fake/src/test/category.provider.spec.ts +134 -0
- package/providers/fake/src/test/price.provider.spec.ts +80 -0
- package/providers/fake/src/test/test-utils.ts +42 -0
- package/providers/fake/tsconfig.json +4 -0
- package/providers/fake/tsconfig.lib.json +3 -1
- package/providers/fake/tsconfig.spec.json +16 -0
- package/trpc/package.json +1 -2
- package/trpc/src/client.ts +1 -3
- package/trpc/src/integration.spec.ts +16 -10
- package/trpc/src/transparent-client.spec.ts +23 -17
- package/tsconfig.base.json +2 -0
- package/core/src/decorators/trpc.decorators.ts +0 -144
- package/otel/src/sdk.ts +0 -57
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { CategoryProvider, Cache, Category, Session, CategoryIdentifier, PaginationOptions, createPaginatedResponseSchema, CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories } from "@reactionary/core";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { CommercetoolsConfiguration } from "../schema/configuration.schema";
|
|
4
|
+
import { CommercetoolsClient } from "../core/client";
|
|
5
|
+
import { ByProjectKeyCategoriesRequestBuilder, CategoryPagedQueryResponse, Category as CTCategory } from "@commercetools/platform-sdk";
|
|
6
|
+
import { traced } from "@reactionary/otel";
|
|
7
|
+
|
|
8
|
+
export class CommercetoolsCategoryProvider<
|
|
9
|
+
T extends Category = Category,
|
|
10
|
+
> extends CategoryProvider<T> {
|
|
11
|
+
|
|
12
|
+
protected config: CommercetoolsConfiguration;
|
|
13
|
+
|
|
14
|
+
constructor(config: CommercetoolsConfiguration, schema: z.ZodType<T>, cache: Cache) {
|
|
15
|
+
super(schema, cache);
|
|
16
|
+
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected getClient(session: Session): ByProjectKeyCategoriesRequestBuilder {
|
|
21
|
+
const client = new CommercetoolsClient(this.config).getClient(session.identity.token).withProjectKey({ projectKey: this.config.projectKey }).categories();
|
|
22
|
+
return client;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Look it up by the category ID (key in commercetools), and if not there, return a placeholder.
|
|
27
|
+
* @param id
|
|
28
|
+
* @param session
|
|
29
|
+
* @returns
|
|
30
|
+
*/
|
|
31
|
+
@traced()
|
|
32
|
+
public override async getById(payload: CategoryQueryById, session: Session): Promise<T> {
|
|
33
|
+
const client = this.getClient(session);
|
|
34
|
+
try {
|
|
35
|
+
const response = await client.withKey({ key: payload.id.key }).get().execute();
|
|
36
|
+
return this.parseSingle(response.body, session);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`Error fetching category by ID ${payload.id.key}:`, error);
|
|
39
|
+
const dummyCategory = this.newModel();
|
|
40
|
+
dummyCategory.meta.placeholder = true;
|
|
41
|
+
dummyCategory.identifier = { key: payload.id.key };
|
|
42
|
+
return dummyCategory;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the category by slug, in the users current locale.
|
|
48
|
+
* @param slug
|
|
49
|
+
* @param session
|
|
50
|
+
* @returns
|
|
51
|
+
*/
|
|
52
|
+
@traced()
|
|
53
|
+
public override async getBySlug(payload: CategoryQueryBySlug, session: Session): Promise<T | null> {
|
|
54
|
+
const client = this.getClient(session);
|
|
55
|
+
try {
|
|
56
|
+
const response = await client.get({
|
|
57
|
+
queryArgs: {
|
|
58
|
+
where: `slug(${session.languageContext.locale}=:slug)`,
|
|
59
|
+
'var.slug': payload.slug,
|
|
60
|
+
storeProjection: session.storeIdentifier.key ,
|
|
61
|
+
limit: 1,
|
|
62
|
+
withTotal: false,
|
|
63
|
+
}
|
|
64
|
+
}).execute();
|
|
65
|
+
if (response.body.results.length === 0) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return this.parseSingle(response.body.results[0], session);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`Error fetching category by slug:`, error);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns the breadcrumb path to the category, i.e. all parents up to the root.
|
|
77
|
+
* The returned order is from root to leaf.
|
|
78
|
+
* @param id
|
|
79
|
+
* @param session
|
|
80
|
+
* @returns
|
|
81
|
+
*/
|
|
82
|
+
@traced()
|
|
83
|
+
public override async getBreadcrumbPathToCategory(payload: CategoryQueryForBreadcrumb, session: Session): Promise<T[]> {
|
|
84
|
+
const client = this.getClient(session);
|
|
85
|
+
const path: T[] = [];
|
|
86
|
+
try {
|
|
87
|
+
const response = await client.withKey({ key: payload.id.key }).get({
|
|
88
|
+
queryArgs: {
|
|
89
|
+
expand: 'ancestors[*]'
|
|
90
|
+
}
|
|
91
|
+
}).execute();
|
|
92
|
+
|
|
93
|
+
const category = this.parseSingle(response.body, session);
|
|
94
|
+
for(const anc of response.body.ancestors || []) {
|
|
95
|
+
if (anc.obj) {
|
|
96
|
+
const parsedAnc = this.parseSingle(anc.obj, session);
|
|
97
|
+
path.push(parsedAnc);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
path.push(category);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`Error fetching category path for ${payload.id.key}:`, error);
|
|
103
|
+
}
|
|
104
|
+
return path;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns a page of child categories for the given parent category ID.
|
|
110
|
+
* You must provide the pagination options to control the size of the result set.
|
|
111
|
+
* If you expect your frontend will load many many categories, consider adding a "load more" button, or lazy load the next page.
|
|
112
|
+
*
|
|
113
|
+
* This is cached by ID + page number + page size + locale + store
|
|
114
|
+
* @param id
|
|
115
|
+
* @param paginationOptions
|
|
116
|
+
* @param session
|
|
117
|
+
* @returns
|
|
118
|
+
*/
|
|
119
|
+
@traced()
|
|
120
|
+
public override async findChildCategories(payload: CategoryQueryForChildCategories, session: Session) {
|
|
121
|
+
|
|
122
|
+
// ok, so for Commercetools we can't actually query by the parents key, so we have to first resolve the key to an ID, then query by that.
|
|
123
|
+
// This is a bit of a pain, but we can cache the result of the first lookup for a short period to mitigate it.
|
|
124
|
+
|
|
125
|
+
const client = this.getClient(session);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const parentCategory = await client.withKey({ key: payload.parentId.key }).get().execute();
|
|
129
|
+
|
|
130
|
+
// it is true, we could just do a withKey and get the children directly, but that would not be paginated.
|
|
131
|
+
// So we do it the hard way, and query by parent ID instead.
|
|
132
|
+
// This also means we can sort the results, which is nice.
|
|
133
|
+
|
|
134
|
+
const response = await client.get({
|
|
135
|
+
queryArgs: {
|
|
136
|
+
where: 'parent(id = :parentId)',
|
|
137
|
+
'var.parentId': parentCategory.body.id,
|
|
138
|
+
limit: payload.paginationOptions.pageSize,
|
|
139
|
+
offset: (payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize,
|
|
140
|
+
sort: 'orderHint asc',
|
|
141
|
+
storeProjection: session.storeIdentifier.key ,
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
.execute();
|
|
145
|
+
|
|
146
|
+
const result = this.parsePaginatedResult(response.body, session);
|
|
147
|
+
result.meta = {
|
|
148
|
+
cache: { hit: false, key: this.generateCacheKeyPaginatedResult('children-of-' + payload.parentId.key, result, session) },
|
|
149
|
+
placeholder: false
|
|
150
|
+
};
|
|
151
|
+
return result;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(`Error fetching category path for ${payload.parentId.key}:`, error);
|
|
154
|
+
}
|
|
155
|
+
return createPaginatedResponseSchema(this.schema).parse({});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@traced()
|
|
159
|
+
public override async findTopCategories(payload: CategoryQueryForTopCategories, session: Session) {
|
|
160
|
+
|
|
161
|
+
const client = this.getClient(session);
|
|
162
|
+
try {
|
|
163
|
+
const response = await client.get({
|
|
164
|
+
queryArgs: {
|
|
165
|
+
where: 'parent is not defined',
|
|
166
|
+
limit: payload.paginationOptions.pageSize,
|
|
167
|
+
offset: (payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize,
|
|
168
|
+
sort: 'orderHint asc',
|
|
169
|
+
storeProjection: session.storeIdentifier.key ,
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
.execute();
|
|
173
|
+
|
|
174
|
+
const result = this.parsePaginatedResult(response.body, session);
|
|
175
|
+
result.meta = {
|
|
176
|
+
cache: { hit: false, key: this.generateCacheKeyPaginatedResult('top', result, session) },
|
|
177
|
+
placeholder: false
|
|
178
|
+
};
|
|
179
|
+
return result;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(`Error fetching category top categories:`, error);
|
|
182
|
+
}
|
|
183
|
+
return createPaginatedResponseSchema(this.schema).parse({});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Handler for parsing a response from a remote provider and converting it
|
|
191
|
+
* into the typed domain model.
|
|
192
|
+
*/
|
|
193
|
+
@traced()
|
|
194
|
+
protected override parseSingle(_body: unknown, session: Session): T {
|
|
195
|
+
const body = _body as CTCategory;
|
|
196
|
+
const languageContext = session.languageContext;
|
|
197
|
+
|
|
198
|
+
const model = this.newModel();
|
|
199
|
+
|
|
200
|
+
model.identifier = { key: body.key! };
|
|
201
|
+
model.name = body.name[languageContext.locale] || 'No Name';
|
|
202
|
+
model.slug = body.slug ? (body.slug[languageContext.locale] || '') : '';
|
|
203
|
+
model.text = body.description ? (body.description[languageContext.locale] || '') : '';
|
|
204
|
+
|
|
205
|
+
model.parentCategory = body.parent && body.parent.obj && body.parent.obj?.key ? { key: body.parent.obj.key } : undefined;
|
|
206
|
+
|
|
207
|
+
model.images = (body.assets || []).filter(asset => asset.sources.length > 0).filter(x => x.sources[0].contentType?.startsWith('image/')).map( (asset) => {
|
|
208
|
+
return {
|
|
209
|
+
sourceUrl: asset.sources[0].uri,
|
|
210
|
+
altText: asset.description?.[languageContext.locale] || asset.name[languageContext.locale] || '',
|
|
211
|
+
height: asset.sources[0].dimensions?.h || 0,
|
|
212
|
+
width: asset.sources[0].dimensions?.w || 0,
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
model.meta = {
|
|
217
|
+
cache: { hit: false, key: this.generateCacheKeySingle(model.identifier, session) },
|
|
218
|
+
placeholder: false
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return this.assert(model);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@traced()
|
|
226
|
+
protected override parsePaginatedResult(_body: unknown, session: Session) {
|
|
227
|
+
const body = _body as CategoryPagedQueryResponse;
|
|
228
|
+
|
|
229
|
+
const items = body.results.map(x => this.parseSingle(x, session));
|
|
230
|
+
|
|
231
|
+
const result = createPaginatedResponseSchema(this.schema).parse({
|
|
232
|
+
meta: {
|
|
233
|
+
cache: { hit: false, key: 'unknown' },
|
|
234
|
+
placeholder: false
|
|
235
|
+
},
|
|
236
|
+
pageNumber: Math.floor(body.offset / body.count) + 1,
|
|
237
|
+
pageSize: body.count,
|
|
238
|
+
totalCount: body.total || 0,
|
|
239
|
+
totalPages: Math.ceil((body.total ?? 0) / body.count),
|
|
240
|
+
items: items
|
|
241
|
+
});
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
InventoryQuery,
|
|
5
5
|
Session,
|
|
6
6
|
Cache,
|
|
7
|
+
LanguageContext,
|
|
7
8
|
} from '@reactionary/core';
|
|
8
9
|
import z from 'zod';
|
|
9
10
|
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
10
11
|
import { CommercetoolsClient } from '../core/client';
|
|
11
|
-
|
|
12
|
+
import { InventoryEntry as CTInventory } from '@commercetools/platform-sdk';
|
|
12
13
|
export class CommercetoolsInventoryProvider<
|
|
13
14
|
T extends Inventory = Inventory
|
|
14
15
|
> extends InventoryProvider<T> {
|
|
@@ -37,24 +38,40 @@ export class CommercetoolsInventoryProvider<
|
|
|
37
38
|
.inventory()
|
|
38
39
|
.get({
|
|
39
40
|
queryArgs: {
|
|
40
|
-
where:
|
|
41
|
-
'var.sku': payload.sku,
|
|
41
|
+
where: `sku=${payload.sku}`,
|
|
42
42
|
},
|
|
43
43
|
})
|
|
44
44
|
.execute();
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
return this.parseSingle(remote.body, session);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected override parseSingle(_body: unknown, session: Session): T {
|
|
50
|
+
const body = _body as CTInventory;
|
|
51
|
+
const model = this.newModel();
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
model.identifier = {
|
|
54
|
+
sku: { key: body.sku },
|
|
55
|
+
channelId: {
|
|
56
|
+
key: body.supplyChannel?.id || 'online'
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
model.sku = body.sku;
|
|
60
|
+
model.quantity = body.availableQuantity;
|
|
52
61
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
if (model.quantity > 0 ) {
|
|
63
|
+
model.status = 'inStock';
|
|
64
|
+
} else {
|
|
65
|
+
model.status = 'outOfStock';
|
|
66
|
+
}
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
model.meta = {
|
|
69
|
+
cache: { hit: false, key: this.generateCacheKeySingle(model.identifier, session) },
|
|
70
|
+
placeholder: false
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return this.assert(model);
|
|
59
74
|
}
|
|
60
|
-
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Price, PriceProvider, PriceQueryBySku, Session, Cache } from '@reactionary/core';
|
|
1
|
+
import { Price, PriceProvider, PriceQueryBySku, Session, Cache, Currency, TieredPriceSchema, MonetaryAmountSchema, TieredPrice } from '@reactionary/core';
|
|
2
2
|
import z from 'zod';
|
|
3
3
|
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
4
4
|
import { CommercetoolsClient } from '../core/client';
|
|
5
|
-
|
|
5
|
+
import { StandalonePrice as CTPrice } from '@commercetools/platform-sdk';
|
|
6
6
|
export class CommercetoolsPriceProvider<
|
|
7
7
|
T extends Price = Price
|
|
8
8
|
> extends PriceProvider<T> {
|
|
@@ -14,48 +14,104 @@ export class CommercetoolsPriceProvider<
|
|
|
14
14
|
this.config = config;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
public getClient(session: Session) {
|
|
18
|
+
return new CommercetoolsClient(this.config).getClient(session.identity?.token).withProjectKey({ projectKey: this.config.projectKey }).standalonePrices();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
public override async getBySKUs(payload: PriceQueryBySku[], session: Session): Promise<T[]> {
|
|
23
|
+
|
|
24
|
+
const client = this.getClient(session);
|
|
25
|
+
|
|
26
|
+
// AND (validFrom is not defined OR validFrom <= now()) AND (validUntil is not defined OR validUntil >= now())
|
|
27
|
+
const queryArgs = {
|
|
28
|
+
where: 'sku in (:skus)',
|
|
29
|
+
'var.skus': payload.map(p => p.sku.key),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
const response = await client.get({
|
|
34
|
+
queryArgs,
|
|
35
|
+
}).execute();
|
|
36
|
+
|
|
37
|
+
const result = [];
|
|
38
|
+
for(const p of payload) {
|
|
39
|
+
const matched = response.body.results.filter(x => x.sku === p.sku.key && x.value.currencyCode === session.languageContext.currencyCode);
|
|
40
|
+
if (matched && matched.length > 0) {
|
|
41
|
+
result.push(this.parseSingle(matched[0], session));
|
|
42
|
+
} else {
|
|
43
|
+
result.push(this.getEmptyPriceResult(p.sku.key, session.languageContext.currencyCode ));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
17
51
|
public override async getBySKU(
|
|
18
52
|
payload: PriceQueryBySku,
|
|
19
53
|
session: Session
|
|
20
54
|
): Promise<T> {
|
|
21
|
-
const client =
|
|
22
|
-
|
|
23
|
-
);
|
|
24
|
-
|
|
55
|
+
const client = this.getClient(session);
|
|
56
|
+
// AND (validFrom is not defined OR validFrom <= now()) AND (validUntil is not defined OR validUntil >= now())
|
|
25
57
|
const queryArgs = {
|
|
26
58
|
where: 'sku=:sku',
|
|
27
59
|
'var.sku': payload.sku.key,
|
|
28
60
|
};
|
|
29
61
|
|
|
30
62
|
const remote = await client
|
|
31
|
-
.withProjectKey({ projectKey: this.config.projectKey })
|
|
32
|
-
.standalonePrices()
|
|
33
63
|
.get({
|
|
34
64
|
queryArgs,
|
|
35
65
|
})
|
|
36
66
|
.execute();
|
|
37
67
|
|
|
68
|
+
|
|
69
|
+
const matched = remote.body.results.filter(x => x.value.currencyCode === session.languageContext.currencyCode);
|
|
70
|
+
if (matched && matched.length > 0) {
|
|
71
|
+
return this.parseSingle(matched[0], session);
|
|
72
|
+
}
|
|
73
|
+
return this.getEmptyPriceResult(payload.sku.key, session.languageContext.currencyCode );
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
protected override parseSingle(_body: unknown, session: Session): T {
|
|
79
|
+
const body = _body as CTPrice;
|
|
80
|
+
|
|
38
81
|
const base = this.newModel();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
82
|
+
|
|
83
|
+
base.unitPrice = {
|
|
84
|
+
value: (body.value.centAmount / 100),
|
|
85
|
+
currency: body.value.currencyCode as Currency,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (body.tiers && body.tiers.length > 0) {
|
|
89
|
+
const p = body.tiers.map(x => {
|
|
90
|
+
const tp: TieredPrice = TieredPriceSchema.parse({});
|
|
91
|
+
tp.minimumQuantity = x.minimumQuantity;
|
|
92
|
+
tp.price = {
|
|
93
|
+
value: (x.value.centAmount / 100),
|
|
94
|
+
currency: x.value.currencyCode as Currency,
|
|
95
|
+
};
|
|
96
|
+
return tp;
|
|
97
|
+
});
|
|
98
|
+
base.tieredPrices = p;
|
|
46
99
|
}
|
|
47
100
|
|
|
48
101
|
base.identifier = {
|
|
49
102
|
sku: {
|
|
50
|
-
key:
|
|
103
|
+
key: body.sku
|
|
51
104
|
}
|
|
52
105
|
};
|
|
53
106
|
|
|
54
107
|
base.meta = {
|
|
55
|
-
cache: { hit: false, key:
|
|
108
|
+
cache: { hit: false, key: this.generateCacheKeySingle(base.identifier, session) },
|
|
56
109
|
placeholder: false
|
|
57
110
|
};
|
|
58
111
|
|
|
59
112
|
return this.assert(base);
|
|
113
|
+
|
|
60
114
|
}
|
|
61
|
-
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
}
|
|
@@ -22,31 +22,31 @@ export class CommercetoolsProductProvider<
|
|
|
22
22
|
this.config = config;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
public getClient(session: Session) {
|
|
26
|
+
return new CommercetoolsClient(this.config).getClient(session.identity?.token).withProjectKey({ projectKey: this.config.projectKey }).productProjections();
|
|
27
|
+
}
|
|
28
|
+
|
|
25
29
|
public override async getById(
|
|
26
30
|
payload: ProductQueryById,
|
|
27
|
-
|
|
31
|
+
session: Session
|
|
28
32
|
): Promise<T> {
|
|
29
|
-
const client =
|
|
33
|
+
const client = this.getClient(session);
|
|
30
34
|
|
|
31
35
|
const remote = await client
|
|
32
|
-
.withProjectKey({ projectKey: this.config.projectKey })
|
|
33
|
-
.productProjections()
|
|
34
36
|
.withId({ ID: payload.id })
|
|
35
37
|
.get()
|
|
36
38
|
.execute();
|
|
37
39
|
|
|
38
|
-
return this.
|
|
40
|
+
return this.parseSingle(remote.body, session);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
public override async getBySlug(
|
|
42
44
|
payload: ProductQueryBySlug,
|
|
43
|
-
|
|
45
|
+
session: Session
|
|
44
46
|
): Promise<T> {
|
|
45
|
-
const client =
|
|
47
|
+
const client = this.getClient(session);
|
|
46
48
|
|
|
47
49
|
const remote = await client
|
|
48
|
-
.withProjectKey({ projectKey: this.config.projectKey })
|
|
49
|
-
.productProjections()
|
|
50
50
|
.get({
|
|
51
51
|
queryArgs: {
|
|
52
52
|
where: 'slug(en-US = :slug)',
|
|
@@ -59,18 +59,19 @@ export class CommercetoolsProductProvider<
|
|
|
59
59
|
throw new Error(`Product with slug '${payload.slug}' not found`);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
return this.
|
|
62
|
+
return this.parseSingle(remote.body.results[0], session);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
protected
|
|
65
|
+
protected override parseSingle(dataIn: unknown, session: Session): T {
|
|
66
|
+
const data = dataIn as ProductProjection;
|
|
66
67
|
const base = this.newModel();
|
|
67
68
|
|
|
68
69
|
base.identifier = { key: data.id };
|
|
69
|
-
base.name = data.name[
|
|
70
|
-
base.slug = data.slug[
|
|
70
|
+
base.name = data.name[session.languageContext.locale];
|
|
71
|
+
base.slug = data.slug[session.languageContext.locale];
|
|
71
72
|
|
|
72
73
|
if (data.description) {
|
|
73
|
-
base.description = data.description[
|
|
74
|
+
base.description = data.description[session.languageContext.locale];
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
if (data.masterVariant.images && data.masterVariant.images.length > 0) {
|
|
@@ -91,10 +92,13 @@ export class CommercetoolsProductProvider<
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
base.meta = {
|
|
94
|
-
cache: { hit: false, key:
|
|
95
|
+
cache: { hit: false, key: this.generateCacheKeySingle(base.identifier, session) },
|
|
95
96
|
placeholder: false
|
|
96
97
|
};
|
|
97
98
|
|
|
98
99
|
return this.assert(base);
|
|
99
100
|
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
100
104
|
}
|
|
@@ -23,7 +23,7 @@ export class CommercetoolsSearchProvider<
|
|
|
23
23
|
|
|
24
24
|
public override async queryByTerm(
|
|
25
25
|
payload: SearchQueryByTerm,
|
|
26
|
-
|
|
26
|
+
session: Session
|
|
27
27
|
): Promise<T> {
|
|
28
28
|
const client = new CommercetoolsClient(this.config).createAnonymousClient();
|
|
29
29
|
|
|
@@ -34,16 +34,16 @@ export class CommercetoolsSearchProvider<
|
|
|
34
34
|
.get({
|
|
35
35
|
queryArgs: {
|
|
36
36
|
limit: payload.search.pageSize,
|
|
37
|
-
offset: payload.search.
|
|
38
|
-
[
|
|
37
|
+
offset: (payload.search.page - 1) * payload.search.pageSize,
|
|
38
|
+
[`text.${session.languageContext.locale}`]: payload.search.term,
|
|
39
39
|
},
|
|
40
40
|
})
|
|
41
41
|
.execute();
|
|
42
42
|
|
|
43
|
-
return this.parseSearchResult(remote, payload);
|
|
43
|
+
return this.parseSearchResult(remote, payload, session);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
protected parseSearchResult(remote: unknown, payload: SearchQueryByTerm): T {
|
|
46
|
+
protected parseSearchResult(remote: unknown, payload: SearchQueryByTerm, session: Session): T {
|
|
47
47
|
const result = this.newModel();
|
|
48
48
|
const remoteData = remote as { body: { results: Array<{ id: string; name: Record<string, string>; slug?: Record<string, string>; masterVariant: { images?: Array<{ url?: string }> } }>; total?: number } };
|
|
49
49
|
|
|
@@ -52,8 +52,8 @@ export class CommercetoolsSearchProvider<
|
|
|
52
52
|
for (const p of remoteData.body.results) {
|
|
53
53
|
const product: SearchResultProduct = {
|
|
54
54
|
identifier: { key: p.id },
|
|
55
|
-
name: p.name[
|
|
56
|
-
slug: p.slug?.[
|
|
55
|
+
name: p.name[session.languageContext.locale] || p.id,
|
|
56
|
+
slug: p.slug?.[session.languageContext.locale] || p.id,
|
|
57
57
|
image: p.masterVariant.images?.[0]?.url || 'https://placehold.co/400'
|
|
58
58
|
};
|
|
59
59
|
|
|
@@ -68,4 +68,6 @@ export class CommercetoolsSearchProvider<
|
|
|
68
68
|
|
|
69
69
|
return this.assert(result);
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
|
|
71
73
|
}
|
|
@@ -7,7 +7,8 @@ export const CommercetoolsCapabilitiesSchema = CapabilitiesSchema.pick({
|
|
|
7
7
|
identity: true,
|
|
8
8
|
cart: true,
|
|
9
9
|
inventory: true,
|
|
10
|
-
price: true
|
|
10
|
+
price: true,
|
|
11
|
+
category: true,
|
|
11
12
|
}).partial();
|
|
12
13
|
|
|
13
14
|
export type CommercetoolsCapabilities = z.infer<typeof CommercetoolsCapabilitiesSchema>;
|
|
@@ -8,4 +8,4 @@ export const CommercetoolsConfigurationSchema = z.looseInterface({
|
|
|
8
8
|
clientSecret: z.string()
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
|
|
11
|
+
export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
|