9
9
* OF ANY KIND, either express or implied. See the License for the specific language
10
10
* governing permissions and limitations under the License.
11
11
*/
12
-
13
12
// @ts -check
14
13
14
+ import { errorResponse , makeContext } from './util.js' ;
15
+ import getProductQueryCS from './queries/cs-product.js' ;
16
+ import getProductQueryCore from './queries/core-product.js' ;
17
+ import HTML_TEMPLATE from './templates/html.js' ;
18
+
15
19
/**
16
20
* @type {Record<string, Config> }
17
21
*/
@@ -21,54 +25,10 @@ const TENANT_CONFIGS = {
21
25
magentoEnvironmentId : '97034e45-43a5-48ab-91ab-c9b5a98623a8' ,
22
26
magentoWebsiteCode : 'base' ,
23
27
magentoStoreViewCode : 'default' ,
28
+ coreEndpoint : 'https://www.visualcomfort.com/graphql' ,
24
29
} ,
25
30
} ;
26
31
27
- /**
28
- * @param {TemplateStringsArray } strs
29
- * @param {...any } params
30
- * @returns {string }
31
- */
32
- export function gql ( strs , ...params ) {
33
- let res = '' ;
34
- strs . forEach ( ( s , i ) => {
35
- res += s ;
36
- if ( i < params . length ) {
37
- res += params [ i ] ;
38
- }
39
- } ) ;
40
- return res . replace ( / ( \\ r \\ n | \\ n | \\ r ) / gm, ' ' ) . replace ( / \s + / g, ' ' ) . trim ( ) ;
41
- }
42
-
43
- /**
44
- * @param {number } status
45
- * @param {string } xError
46
- * @param {string|Record<string,unknown> } [body='']
47
- * @returns
48
- */
49
- function errorResponse ( status , xError , body = '' ) {
50
- return new Response ( typeof body === 'object' ? JSON . stringify ( body ) : body , {
51
- status,
52
- headers : { 'x-error' : xError } ,
53
- } ) ;
54
- }
55
-
56
- /**
57
- * @param {import("@cloudflare/workers-types/experimental").ExecutionContext } pctx
58
- * @param {Request } req
59
- * @param {Record<string, string> } env
60
- * @returns {Context }
61
- */
62
- function makeContext ( pctx , req , env ) {
63
- /** @type {Context } */
64
- // @ts -ignore
65
- const ctx = pctx ;
66
- ctx . env = env ;
67
- ctx . url = new URL ( req . url ) ;
68
- ctx . log = console ;
69
- return ctx ;
70
- }
71
-
72
32
/**
73
33
* @param {string } tenant
74
34
* @param {Partial<Config> } [overrides={}]
@@ -89,140 +49,50 @@ function lookupConfig(tenant, overrides) {
89
49
* @param {string } sku
90
50
* @param {Config } config
91
51
*/
92
- async function fetchProduct ( sku , config ) {
93
- const query = gql `{
94
- products(
95
- skus: ["${ sku } "]
96
- ) {
97
- __typename
98
- id
99
- sku
100
- name
101
- metaTitle
102
- metaDescription
103
- metaKeyword
104
- description
105
- url
106
- urlKey
107
- shortDescription
108
- url
109
- addToCartAllowed
110
- inStock
111
- images(roles: []) {
112
- url
113
- label
114
- roles
115
- __typename
116
- }
117
- attributes(roles: []) {
118
- name
119
- label
120
- value
121
- roles
122
- __typename
123
- }
124
- ... on SimpleProductView {
125
- price {
126
- final {
127
- amount {
128
- value
129
- currency
130
- __typename
131
- }
132
- __typename
133
- }
134
- regular {
135
- amount {
136
- value
137
- currency
138
- __typename
139
- }
140
- __typename
141
- }
142
- roles
143
- __typename
144
- }
145
- __typename
146
- }
147
- ... on ComplexProductView {
148
- options {
149
- id
150
- title
151
- required
152
- values {
153
- id
154
- title
155
- ... on ProductViewOptionValueProduct {
156
- product {
157
- sku
158
- name
159
- __typename
160
- }
161
- __typename
162
- }
163
- ... on ProductViewOptionValueSwatch {
164
- type
165
- value
166
- __typename
167
- }
168
- __typename
169
- }
170
- __typename
171
- }
172
- priceRange {
173
- maximum {
174
- final {
175
- amount {
176
- value
177
- currency
178
- __typename
179
- }
180
- __typename
181
- }
182
- regular {
183
- amount {
184
- value
185
- currency
186
- __typename
187
- }
188
- __typename
189
- }
190
- roles
191
- __typename
192
- }
193
- minimum {
194
- final {
195
- amount {
196
- value
197
- currency
198
- __typename
199
- }
200
- __typename
201
- }
202
- regular {
203
- amount {
204
- value
205
- currency
206
- __typename
207
- }
208
- __typename
209
- }
210
- roles
211
- __typename
212
- }
213
- __typename
214
- }
215
- __typename
216
- }
217
- }
218
- }` ;
52
+ async function fetchProductCS ( sku , config ) {
53
+ const query = getProductQueryCS ( { sku } ) ;
219
54
220
55
const resp = await fetch ( `https://catalog-service.adobe.io/graphql?query=${ encodeURIComponent ( query ) } ` , {
221
- // method: 'POST',
222
- // body: query,
223
56
headers : {
224
- origin : 'https://adobecommerce.live' ,
225
- // 'content-type':'application/json',
57
+ origin : 'https://api.adobecommerce.live' ,
58
+ 'x-api-key' : config . apiKey ,
59
+ 'Magento-Environment-Id' : config . magentoEnvironmentId ,
60
+ 'Magento-Website-Code' : config . magentoWebsiteCode ,
61
+ 'Magento-Store-View-Code' : config . magentoStoreViewCode ,
62
+ } ,
63
+ } ) ;
64
+ if ( ! resp . ok ) {
65
+ console . warn ( 'failed to fetch product: ' , resp . status ) ;
66
+ return resp ;
67
+ }
68
+
69
+ const json = await resp . json ( ) ;
70
+ try {
71
+ const [ product ] = json . data . products ;
72
+ if ( ! product ) {
73
+ return errorResponse ( 404 , 'could not find product' , json . errors ) ;
74
+ }
75
+ return product ;
76
+ } catch ( e ) {
77
+ console . error ( 'failed to parse product: ' , e ) ;
78
+ return errorResponse ( 500 , 'failed to parse product response' ) ;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @param {{ urlKey: string } | { sku: string } } opt
84
+ * @param {Config } config
85
+ */
86
+ // eslint-disable-next-line no-unused-vars
87
+ async function fetchProductCore ( opt , config ) {
88
+ const query = getProductQueryCore ( opt ) ;
89
+ if ( ! config . coreEndpoint ) {
90
+ return errorResponse ( 400 , 'coreEndpoint not configured' ) ;
91
+ }
92
+
93
+ const resp = await fetch ( `${ config . coreEndpoint } ?query=${ encodeURIComponent ( query ) } ` , {
94
+ headers : {
95
+ origin : 'https://api.adobecommerce.live' ,
226
96
'x-api-key' : config . apiKey ,
227
97
'Magento-Environment-Id' : config . magentoEnvironmentId ,
228
98
'Magento-Website-Code' : config . magentoWebsiteCode ,
@@ -248,68 +118,7 @@ async function fetchProduct(sku, config) {
248
118
}
249
119
250
120
function resolvePDPTemplate ( product ) {
251
- return /* html */ `
252
- <!DOCTYPE html>
253
- <html>
254
- <head>
255
- <title>${ product . metaTitle || product . name } </title>
256
- <meta property="description" content="${ product . metaDescription || product . description } ">
257
- <meta property="og:title" content="${ product . metaTitle || product . name } ">
258
- <meta property="og:image" content="${ product . images [ 0 ] . url } ">
259
- <meta property="og:image:secure_url" content="${ product . images [ 0 ] . url } ">
260
- <meta name="twitter:card" content="summary_large_image">
261
- <meta name="twitter:title" content="${ product . metaTitle || product . name } ">
262
- <meta name="twitter:image" content="${ product . images [ 0 ] . url } ">
263
- <meta name="viewport" content="width=device-width, initial-scale=1">
264
- <script src="/scripts/aem.js" type="module"></script>
265
- <script src="/scripts/scripts.js" type="module"></script>
266
- <link rel="stylesheet" href="/styles/styles.css">
267
- </head>
268
- <body>
269
- <header></header>
270
- <main>
271
- <div>
272
- <h1>${ product . name } </h1>
273
- <div class="product-gallery">
274
- <div>
275
- ${ product . images . map ( ( img ) => `
276
- <div>
277
- <picture>
278
- <source type="image/webp" srcset="${ img . url } " alt="" media="(min-width: 600px)">
279
- <source type="image/webp" srcset="${ img . url } ">
280
- <source type="image/png" srcset="${ img . url } " media="(min-width: 600px)">
281
- <img loading="lazy" alt="${ img . label } " src="${ img . url } ">
282
- </picture>
283
- </div>` ) . join ( '\n' ) }
284
- </div>
285
- </div>
286
- <div class="product-attributes">
287
- ${ product . attributes . map ( ( attr ) => `
288
- <div>
289
- <div>${ attr . name } </div>
290
- <div>${ attr . label } </div>
291
- <div>${ attr . value } </div>
292
- </div>` ) . join ( '\n' ) }
293
- </div>
294
- <div class="product-options">
295
- ${ product . options . map ( ( opt ) => `
296
- <div>
297
- <div>${ opt . id } </div>
298
- <div>${ opt . title } </div>
299
- <div>${ opt . required === true ? 'required' : '' } </div>
300
- </div>
301
- ${ opt . values . map ( ( val ) => `
302
- <div>
303
- <div>${ val . id } </div>
304
- <div>${ val . title } </div>
305
- </div>` ) . join ( '\n' ) } `) . join ( '\n' ) }
306
- </div>
307
- </div>
308
- </main>
309
- <footer></footer>
310
- </body>
311
- </html>
312
- ` ;
121
+ return HTML_TEMPLATE ( product ) ;
313
122
}
314
123
315
124
/**
@@ -322,12 +131,14 @@ async function handlePDPRequest(ctx) {
322
131
return errorResponse ( 404 , 'missing sku' ) ;
323
132
}
324
133
325
- const config = lookupConfig ( tenant , { } ) ; // TODO: allow config overrides from query params
134
+ const overrides = Object . fromEntries ( ctx . url . searchParams . entries ( ) ) ;
135
+ const config = lookupConfig ( tenant , overrides ) ;
326
136
if ( ! config ) {
327
137
return errorResponse ( 404 , 'config not found' ) ;
328
138
}
329
139
330
- const product = await fetchProduct ( sku , config ) ;
140
+ // const product = await fetchProductCore({ sku }, config);
141
+ const product = await fetchProductCS ( sku , config ) ;
331
142
const html = resolvePDPTemplate ( product ) ;
332
143
return new Response ( html , {
333
144
status : 200 ,
0 commit comments