Skip to content

Commit 9128220

Browse files
authored
fix: refactor (#79)
* feat: refactor * chore: fix tests * chore: fix postdeploy test * fix: tweak catalog handler * chore: fix test * chore: fix test * chore: fix test
1 parent 49c2c30 commit 9128220

30 files changed

+702
-658
lines changed

src/catalog/handler.js

-77
This file was deleted.

src/index.js

+20-14
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,33 @@
1212

1313
import { errorResponse } from './utils/http.js';
1414
import { resolveConfig } from './utils/config.js';
15-
import content from './content/handler.js';
16-
import catalog from './catalog/handler.js';
17-
import configHandler from './config/handler.js';
15+
import handlers from './routes/index.js';
1816

1917
/**
20-
* @type {Record<string, (ctx: Context, request: Request) => Promise<Response>>}
18+
* @param {Request} req
2119
*/
22-
const handlers = {
23-
content,
24-
catalog,
25-
config: configHandler,
26-
// eslint-disable-next-line no-unused-vars
27-
graphql: async (ctx) => errorResponse(501, 'not implemented'),
28-
};
20+
async function parseData(req) {
21+
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
22+
return Object.fromEntries(new URL(req.url).searchParams.entries());
23+
}
24+
if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
25+
const text = await req.text();
26+
try {
27+
return JSON.parse(text);
28+
} catch {
29+
return text;
30+
}
31+
}
32+
return null;
33+
}
2934

3035
/**
3136
* @param {import("@cloudflare/workers-types/experimental").ExecutionContext} pctx
3237
* @param {Request} req
3338
* @param {Env} env
34-
* @returns {Context}
39+
* @returns {Promise<Context>}
3540
*/
36-
export function makeContext(pctx, req, env) {
41+
export async function makeContext(pctx, req, env) {
3742
/** @type {Context} */
3843
// @ts-ignore
3944
const ctx = pctx;
@@ -49,6 +54,7 @@ export function makeContext(pctx, req, env) {
4954
.map(([k, v]) => [k.toLowerCase(), v]),
5055
),
5156
};
57+
ctx.data = await parseData(req);
5258
return ctx;
5359
}
5460

@@ -60,7 +66,7 @@ export default {
6066
* @returns {Promise<Response>}
6167
*/
6268
async fetch(request, env, pctx) {
63-
const ctx = makeContext(pctx, request, env);
69+
const ctx = await makeContext(pctx, request, env);
6470

6571
try {
6672
const overrides = Object.fromEntries(ctx.url.searchParams.entries());

src/catalog/storage/r2.js src/routes/catalog/StorageClient.js

+101-32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 Adobe. All rights reserved.
2+
* Copyright 2025 Adobe. All rights reserved.
33
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
44
* you may not use this file except in compliance with the License. You may obtain a copy
55
* of the License at http://www.apache.org/licenses/LICENSE-2.0
@@ -14,17 +14,31 @@ import { callPreviewPublish } from '../../utils/admin.js';
1414
import { BatchProcessor } from '../../utils/batch.js';
1515
import { errorWithResponse } from '../../utils/http.js';
1616

17-
/* eslint-disable no-await-in-loop, max-len */
18-
1917
export default class StorageClient {
2018
/**
21-
* Constructs a new StorageClient instance.
22-
* @param {Context} ctx - The context object
23-
* @param {Config} config - The configuration object.
19+
* @param {Context} ctx
20+
* @returns {StorageClient}
21+
*/
22+
static fromContext(ctx) {
23+
if (!ctx.attributes.storageClient) {
24+
ctx.attributes.storageClient = new StorageClient(ctx);
25+
}
26+
return ctx.attributes.storageClient;
27+
}
28+
29+
/** @type {Context} */
30+
ctx = undefined;
31+
32+
/**
33+
* @param {Context} ctx
2434
*/
25-
constructor(ctx, config) {
35+
constructor(ctx) {
2636
this.ctx = ctx;
27-
this.config = config;
37+
}
38+
39+
/** @type {Config} */
40+
get config() {
41+
return this.ctx.config;
2842
}
2943

3044
/**
@@ -33,11 +47,21 @@ export default class StorageClient {
3347
* @returns {Promise<Product>} - A promise that resolves to the product.
3448
*/
3549
async fetchProduct(sku) {
36-
const { log } = this.ctx;
37-
const key = `${this.config.org}/${this.config.site}/${this.config.storeCode}/${this.config.storeViewCode}/products/${sku}.json`;
50+
const {
51+
log,
52+
env,
53+
config: {
54+
org,
55+
site,
56+
storeCode,
57+
storeViewCode,
58+
},
59+
} = this.ctx;
60+
61+
const key = `${org}/${site}/${storeCode}/${storeViewCode}/products/${sku}.json`;
3862
log.debug('Fetching product from R2:', key);
3963

40-
const object = await this.ctx.env.CATALOG_BUCKET.get(key);
64+
const object = await env.CATALOG_BUCKET.get(key);
4165
if (!object) {
4266
// Product not found in R2
4367
throw errorWithResponse(404, 'Product not found');
@@ -74,29 +98,38 @@ export default class StorageClient {
7498
* @returns {Promise<Partial<BatchResult>[]>} - Resolves with an array of save results.
7599
*/
76100
async storeProductsBatch(batch) {
101+
const {
102+
env,
103+
log,
104+
config: {
105+
org,
106+
site,
107+
storeCode,
108+
storeViewCode,
109+
},
110+
} = this.ctx;
111+
77112
const storePromises = batch.map(async (product) => {
78-
const { sku, name } = product;
79-
const key = `${this.config.org}/${this.config.site}/${this.config.storeCode}/${this.config.storeViewCode}/products/${sku}.json`;
113+
const { sku, name, urlKey } = product;
114+
const key = `${org}/${site}/${storeCode}/${storeViewCode}/products/${sku}.json`;
80115
const body = JSON.stringify(product);
81116

82117
try {
83118
const customMetadata = { sku, name };
84-
85-
const { urlKey } = product;
86119
if (urlKey) {
87120
customMetadata.urlKey = urlKey;
88121
}
89122

90123
// Attempt to save the product
91-
const putResponse = await this.ctx.env.CATALOG_BUCKET.put(key, body, {
124+
const putResponse = await env.CATALOG_BUCKET.put(key, body, {
92125
httpMetadata: { contentType: 'application/json' },
93126
customMetadata,
94127
});
95128

96129
// If urlKey exists, save the urlKey metadata
97130
if (urlKey) {
98-
const metadataKey = `${this.config.org}/${this.config.site}/${this.config.storeCode}/${this.config.storeViewCode}/urlkeys/${urlKey}`;
99-
await this.ctx.env.CATALOG_BUCKET.put(metadataKey, '', {
131+
const metadataKey = `${org}/${site}/${storeCode}/${storeViewCode}/urlkeys/${urlKey}`;
132+
await env.CATALOG_BUCKET.put(metadataKey, '', {
100133
httpMetadata: { contentType: 'application/octet-stream' },
101134
customMetadata,
102135
});
@@ -116,7 +149,7 @@ export default class StorageClient {
116149

117150
return result;
118151
} catch (error) {
119-
this.ctx.log.error(`Error storing product SKU: ${sku}:`, error);
152+
log.error(`Error storing product SKU: ${sku}:`, error);
120153
return {
121154
sku,
122155
status: error.code || 500,
@@ -152,10 +185,16 @@ export default class StorageClient {
152185
* @returns {Promise<Partial<BatchResult>[]>} - Resolves with an array of deletion results.
153186
*/
154187
async deleteProductsBatch(batch) {
155-
const { log, env } = this.ctx;
156188
const {
157-
org, site, storeCode, storeViewCode,
158-
} = this.config;
189+
log,
190+
env,
191+
config: {
192+
org,
193+
site,
194+
storeCode,
195+
storeViewCode,
196+
},
197+
} = this.ctx;
159198

160199
const deletionPromises = batch.map(async (sku) => {
161200
try {
@@ -178,7 +217,7 @@ export default class StorageClient {
178217
await env.CATALOG_BUCKET.delete(urlKeyPath);
179218
}
180219

181-
const adminResponse = await callPreviewPublish(this.config, 'DELETE', sku, urlKey);
220+
const adminResponse = await callPreviewPublish(this.ctx.config, 'DELETE', sku, urlKey);
182221
/**
183222
* @type {Partial<BatchResult>}
184223
*/
@@ -209,9 +248,19 @@ export default class StorageClient {
209248
* @returns {Promise<string>} - A promise that resolves to the SKU.
210249
*/
211250
async lookupSku(urlKey) {
251+
const {
252+
env,
253+
config: {
254+
org,
255+
site,
256+
storeCode,
257+
storeViewCode,
258+
},
259+
} = this.ctx;
260+
212261
// Make a HEAD request to retrieve the SKU from metadata based on the URL key
213-
const urlKeyPath = `${this.config.org}/${this.config.site}/${this.config.storeCode}/${this.config.storeViewCode}/urlkeys/${urlKey}`;
214-
const headResponse = await this.ctx.env.CATALOG_BUCKET.head(urlKeyPath);
262+
const urlKeyPath = `${org}/${site}/${storeCode}/${storeViewCode}/urlkeys/${urlKey}`;
263+
const headResponse = await env.CATALOG_BUCKET.head(urlKeyPath);
215264

216265
if (!headResponse || !headResponse.customMetadata?.sku) {
217266
// SKU not found for the provided URL key
@@ -227,10 +276,20 @@ export default class StorageClient {
227276
* @returns {Promise<string | undefined>} - A promise that resolves to the URL key or undefined.
228277
*/
229278
async lookupUrlKey(sku) {
279+
const {
280+
env,
281+
config: {
282+
org,
283+
site,
284+
storeCode,
285+
storeViewCode,
286+
},
287+
} = this.ctx;
288+
230289
// Construct the path to the product JSON file
231-
const productKey = `${this.config.org}/${this.config.site}/${this.config.storeCode}/${this.config.storeViewCode}/products/${sku}.json`;
290+
const productKey = `${org}/${site}/${storeCode}/${storeViewCode}/products/${sku}.json`;
232291

233-
const headResponse = await this.ctx.env.CATALOG_BUCKET.head(productKey);
292+
const headResponse = await env.CATALOG_BUCKET.head(productKey);
234293
if (!headResponse || !headResponse.customMetadata) {
235294
return undefined;
236295
}
@@ -249,9 +308,18 @@ export default class StorageClient {
249308
* @returns {Promise<Product[]>} - A promise that resolves to the products.
250309
*/
251310
async listAllProducts() {
252-
const bucket = this.ctx.env.CATALOG_BUCKET;
253-
const listResponse = await bucket.list({
254-
prefix: `${this.config.org}/${this.config.site}/${this.config.storeCode}/${this.config.storeViewCode}/products/`,
311+
const {
312+
env,
313+
config: {
314+
org,
315+
site,
316+
storeCode,
317+
storeViewCode,
318+
},
319+
} = this.ctx;
320+
321+
const listResponse = await env.CATALOG_BUCKET.list({
322+
prefix: `${org}/${site}/${storeCode}/${storeViewCode}/products/`,
255323
});
256324
const files = listResponse.objects;
257325

@@ -271,18 +339,19 @@ export default class StorageClient {
271339

272340
// Process each chunk sequentially
273341
for (const chunk of fileChunks) {
342+
// eslint-disable-next-line no-await-in-loop
274343
const chunkResults = await Promise.all(
275344
chunk.map(async (file) => {
276345
const objectKey = file.key;
277346

278-
const headResponse = await bucket.head(objectKey);
347+
const headResponse = await env.CATALOG_BUCKET.head(objectKey);
279348
if (headResponse) {
280349
const { customMetadata } = headResponse;
281350
const { sku } = customMetadata;
282351
return {
283352
...customMetadata,
284353
links: {
285-
product: `${this.ctx.url.origin}/${this.config.org}/${this.config.site}/catalog/${this.config.storeCode}/${this.config.storeViewCode}/product/${sku}`,
354+
product: `${this.ctx.url.origin}/${org}/${site}/catalog/${storeCode}/${storeViewCode}/product/${sku}`,
286355
},
287356
};
288357
} else {

0 commit comments

Comments
 (0)