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/LICENSE b/LICENSE index 9dea702..ecd01a2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2025 Contentstack +Copyright (c) 2021-2026 Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal 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..14de024 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", + "@contentstack/shopify-live-preview-sdk": "^1.1.0", "@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" }, @@ -559,9 +559,9 @@ "license": "MIT" }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@contentstack/shopify-live-preview-sdk/-/shopify-live-preview-sdk-1.1.0.tgz", + "integrity": "sha512-VCPEGPY6QQeAYlgORBkju30RxhVLX1LnQ5VZBBp75TPAWLxQD9fCBUS3XXxAN1CoY4fLIX9EVFhaqURyi9gZpg==", "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": { @@ -3096,23 +3107,23 @@ "license": "Apache-2.0" }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -3128,12 +3139,41 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -4233,39 +4273,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -4869,9 +4909,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 +6127,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 +6301,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", @@ -7172,12 +7198,12 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -7223,16 +7249,45 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", "engines": { "node": ">= 0.8" } diff --git a/package.json b/package.json index 09cff0e..fb1a06c 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", @@ -21,8 +21,9 @@ "format": "prettier --write \"src/**/*.ts\"" }, "dependencies": { - "@contentstack/shopify-live-preview-sdk": "file:contentstack-shopify-live-preview-sdk-1.0.0.tgz", + "@contentstack/shopify-live-preview-sdk": "^1.1.0", "@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: {