diff --git a/.talismanrc b/.talismanrc deleted file mode 100644 index 21465b1..0000000 --- a/.talismanrc +++ /dev/null @@ -1,14 +0,0 @@ -fileignoreconfig: -- filename: .env.example - checksum: d4a3abad2962917f0d24649c00a516303b496b17e81dd187987bd851a5555862 -- filename: tests/environment.test.ts - checksum: 84e1960765aff963f406d2e1a6560c275186980af6c97b8b7ce6cc0e34c5dac0 -- filename: package-lock.json - checksum: 2ce52e43a6dda25266085878ca8c4afe80adcb282a35aff8a9fe608eb9479fe3 -- filename: src/config.ts - checksum: 35f866d826cf8dc6cc78a79cf864a24889db28cbe673f3f70f61ff010af31c78 -- filename: README.md - checksum: 27505e36b66c5ea20978b97c900f856aaaef590cca7288c05d87933e15516d63 -- filename: src/controllers/index.ts - checksum: c7091843e1cc001269cda74cdc64a87de93165624564dea3c84bc0fac3942f2a -version: "1.0" \ No newline at end of file diff --git a/live-preview-shopify-utils-1.0.0.tgz b/live-preview-shopify-utils-1.0.0.tgz deleted file mode 100644 index b52c17d..0000000 Binary files a/live-preview-shopify-utils-1.0.0.tgz and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 87f5e3c..e759b87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "shopify-live-preview-middleware", - "version": "1.0.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "shopify-live-preview-middleware", - "version": "1.0.0", + "version": "1.1.0", "dependencies": { "@contentstack/shopify-live-preview-sdk": "file:contentstack-shopify-live-preview-sdk-1.0.0.tgz", "@fastify/cors": "^8.3.0", + "@fastify/rate-limit": "^9.1.0", "@fastify/swagger": "^8.8.0", "@fastify/swagger-ui": "^3.0.0", "adm-zip": "^0.5.10", @@ -17,7 +18,6 @@ "dotenv": "^16.5.0", "fastify": "^4.21.0", "fastify-plugin": "^4.5.1", - "live-preview-shopify-utils": "file:live-preview-shopify-utils-1.0.0.tgz", "lodash": "^4.17.21", "pino-pretty": "^10.2.0" }, @@ -561,7 +561,7 @@ "node_modules/@contentstack/shopify-live-preview-sdk": { "version": "1.0.0", "resolved": "file:contentstack-shopify-live-preview-sdk-1.0.0.tgz", - "integrity": "sha512-MxQM4GTX8cxRsMUrgz/mHGi7T/xZnD4Lr/PKF68vyCcnWORKMqpEif79cN0lwDfLEhcfxXciYCKsKZ4JSuSMnw==", + "integrity": "sha512-D24KtUEn1nxMXE/IEtEpKlNrZc4eSRqNeDo/mFCEGDdGC+IhW4yXpCjUmh8zywGxz5JG6CqXcn39u/2wd0qbRQ==", "license": "MIT", "dependencies": { "@octokit/rest": "^21.1.1", @@ -1217,6 +1217,17 @@ "fast-deep-equal": "^3.1.3" } }, + "node_modules/@fastify/rate-limit": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@fastify/rate-limit/-/rate-limit-9.1.0.tgz", + "integrity": "sha512-h5dZWCkuZXN0PxwqaFQLxeln8/LNwQwH9popywmDCFdKfgpi4b/HoMH1lluy6P+30CG9yzzpSpwTCIPNB9T1JA==", + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.1", + "fastify-plugin": "^4.0.0", + "toad-cache": "^3.3.1" + } + }, "node_modules/@fastify/send": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", @@ -1470,9 +1481,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -4869,9 +4880,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -6087,9 +6098,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -6261,20 +6272,6 @@ "url": "https://opencollective.com/liquidjs" } }, - "node_modules/live-preview-shopify-utils": { - "version": "1.0.0", - "resolved": "file:live-preview-shopify-utils-1.0.0.tgz", - "integrity": "sha512-47GB57WMa/ji0WJezvBzrsqJ+R08Aa1KuMEwnnzpiN/2J73q6flUQ1dXkN5xbNymNqBcnt0B6UIaOiAampUi+A==", - "license": "MIT", - "dependencies": { - "@octokit/rest": "^21.1.1", - "adm-zip": "^0.5.10", - "axios": "^1.9.0", - "express": "^4.18.3", - "liquidjs": "^10.10.1", - "simple-git": "^3.22.0" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", diff --git a/package.json b/package.json index 09cff0e..9edc65c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shopify-live-preview-middleware", - "version": "1.0.0", + "version": "1.1.0", "description": "Shopify Live Preview Middleware Service", "type": "module", "main": "src/index.ts", @@ -23,6 +23,7 @@ "dependencies": { "@contentstack/shopify-live-preview-sdk": "file:contentstack-shopify-live-preview-sdk-1.0.0.tgz", "@fastify/cors": "^8.3.0", + "@fastify/rate-limit": "^9.1.0", "@fastify/swagger": "^8.8.0", "@fastify/swagger-ui": "^3.0.0", "adm-zip": "^0.5.10", @@ -30,7 +31,6 @@ "dotenv": "^16.5.0", "fastify": "^4.21.0", "fastify-plugin": "^4.5.1", - "live-preview-shopify-utils": "file:live-preview-shopify-utils-1.0.0.tgz", "lodash": "^4.17.21", "pino-pretty": "^10.2.0" }, diff --git a/src/config.ts b/src/config.ts index 76a5126..667ced9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -8,6 +8,10 @@ interface Config { environment: string; apiKey: string; previewUrl?: string; + rateLimit?: { + max: number; + timeWindow: string; + }; }; } @@ -21,5 +25,11 @@ export const config: Config = { environment: process.env.CONTENTSTACK_ENVIRONMENT || '', apiKey: process.env.CONTENTSTACK_API_KEY || '', previewUrl: process.env.CONTENTSTACK_PREVIEW_URL || 'https://rest-preview.contentstack.com', + rateLimit: { + // Set the maximum number of requests allowed per time window. + // Tries to read RATE_LIMIT_MAX from environment variables, falls back to 100 if not set or not a valid number. + max: parseInt(process.env.RATE_LIMIT_MAX || '100', 10) || 100, + timeWindow: process.env.RATE_LIMIT_TIME_WINDOW || '1 minute', + }, }, }; \ No newline at end of file diff --git a/src/controllers/githubSyncController.ts b/src/controllers/githubSyncController.ts index 6afe7fc..a3418e8 100644 --- a/src/controllers/githubSyncController.ts +++ b/src/controllers/githubSyncController.ts @@ -55,7 +55,7 @@ export const syncGithubRepoHandler = async (request: FastifyRequest<{ Body: Sync } } catch (error) { - request.log.error('Outer error in syncGithubRepoHandler:', error); + request.log.error('Outer error in syncGithubRepoHandler: %s', error instanceof Error ? error.message : String(error)); reply.status(500); return { error: 'Failed to process sync request' diff --git a/src/controllers/index.ts b/src/controllers/index.ts index bb23c52..9fce7f7 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -48,17 +48,11 @@ const engine = livePreviewShopify.getLiquidEngine(); export const getPreviewDataHandler = async (req: FastifyRequest<{ Body: PreviewDataRequestBody }>, res: FastifyReply) => { const { live_preview, ctUid, entryUid, locale, theme_variable } = req.body; const { liquid_path } = theme_variable; - - console.log("🚀 ~ getPreviewDataHandler ~ live_preview:", live_preview) - console.log("🚀 ~ getPreviewDataHandler ~ ctUid:", ctUid) - console.log("🚀 ~ getPreviewDataHandler ~ entryUid:", entryUid) - console.log("🚀 ~ getPreviewDataHandler ~ theme_variable:", theme_variable) const entryData: { schema: FieldSchema, entry: Entry } = await livePreviewShopify.fetchData(ctUid, entryUid, live_preview, locale) as { schema: FieldSchema, entry: Entry }; - console.log("🚀 ~ getPreviewDataHandler ~ entryData:", entryData) let shopifyData = { ...theme_variable?.payload }; - const keyBasedCt = livePreviewShopify.createContentTypeKeyBased(entryData.schema); + const keyBasedCt = livePreviewShopify.createContentTypeKeyBased([entryData.schema]); const updatedEntry = entryData.entry; if (_.get(shopifyData, 'product.metafields.contentstack_products', null)) { @@ -72,7 +66,9 @@ export const getPreviewDataHandler = async (req: FastifyRequest<{ Body: PreviewD const mappedShopifyData = await livePreviewShopify.getUpdatedMetaobject({ ...currentMetaobjects }, keyBasedCt, updatedEntry, { ctUid: ctUid, hash: live_preview }); shopifyData.metaobjects = mappedShopifyData.currentMetaobjects; } - + if(typeof liquid_path !== 'string') { + return res.status(400).send({ message: 'Invalid liquid path' }); + } const liquidFilePath = liquid_path.replace(/\./g, "/"); try { const newRenderedData = await engine.renderFile(liquidFilePath, shopifyData); diff --git a/src/controllers/viewsHealthController.ts b/src/controllers/viewsHealthController.ts index b1b8bf5..926e6ad 100644 --- a/src/controllers/viewsHealthController.ts +++ b/src/controllers/viewsHealthController.ts @@ -176,7 +176,7 @@ export const viewsHealthHandler = async (request: FastifyRequest, reply: Fastify return response; } catch (error) { - request.log.error('Error in viewsHealthHandler:', error); + request.log.error('Error in viewsHealthHandler: %s', error instanceof Error ? error.message : String(error)); const response: ViewsHealthResponse = { status: 'error', diff --git a/src/index.ts b/src/index.ts index b9809e5..414db04 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { fastify, FastifyInstance } from 'fastify'; import cors from '@fastify/cors'; +import rateLimit from '@fastify/rate-limit'; import swagger from '@fastify/swagger'; import swaggerUi from '@fastify/swagger-ui'; import 'dotenv/config'; @@ -25,6 +26,17 @@ server.register(cors, { credentials: true, }); +// Register rate limiting to prevent resource exhaustion +server.register(rateLimit, { + max: config.contentstack.rateLimit?.max || 100, // Maximum 100 requests per window + timeWindow: config.contentstack.rateLimit?.timeWindow || '1 minute', + errorResponseBuilder: () => ({ + statusCode: 429, + error: 'Too Many Requests', + message: 'Rate limit exceeded. Please try again later.', + }), +}); + // Register Swagger server.register(swagger, { swagger: {