diff --git a/astro.config.mjs b/astro.config.mjs index 56560db..778d936 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -14,13 +14,7 @@ export default defineConfig({ 'process.env.PRIMARY_REDIS_URL': JSON.stringify(process.env.PRIMARY_REDIS_URL || process.env.REDIS_URL), 'process.env.REPLICA_REDIS_URL_1': JSON.stringify(process.env.REPLICA_REDIS_URL_1 || process.env.REPLICA_REDIS_URL), 'process.env.REPLICA_REDIS_URL_2': JSON.stringify(process.env.REPLICA_REDIS_URL_2), - 'process.env.REPLICA_REDIS_URL_3': JSON.stringify(process.env.REPLICA_REDIS_URL_3), - 'process.env.VERCEL': JSON.stringify(process.env.VERCEL || false) - }, - server: { - watch: { - usePolling: true, - }, - }, + 'process.env.REPLICA_REDIS_URL_3': JSON.stringify(process.env.REPLICA_REDIS_URL_3) + } } }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2f1a161..35af01f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,14 @@ "dependencies": { "@astrojs/rss": "^4.0.11", "@astrojs/tailwind": "^6.0.0", - "@astrojs/vercel": "^8.1.0", + "@astrojs/vercel": "^8.1.1", "@floating-ui/dom": "^1.6.13", "@types/node": "^22.13.9", "@vercel/analytics": "^1.5.0", "@vercel/kv": "^3.0.0", - "astro": "^5.4.1", + "astro": "^5.4.2", "feed": "^4.2.2", - "ioredis": "^5.5.0", + "ioredis": "^5.6.0", "tailwindcss": "^3.4.1" } }, @@ -122,14 +122,14 @@ } }, "node_modules/@astrojs/vercel": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-8.1.0.tgz", - "integrity": "sha512-qaztQ0WCg8epe9+3PS7+eTbukNcwDtfjgN/QeWk0+wmmWHpEBlhzUA17Vmk5OINBX/Tf0bzXgydDWrAVG+/irQ==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@astrojs/vercel/-/vercel-8.1.1.tgz", + "integrity": "sha512-4tTib9LkAtg7tqac4L+pnWEveUmYyWRXpHivaVV3lHKeQjB28KUZ+YXtR/VUG6/xpMduo+OCOOg0l1zOPq/e4w==", "dependencies": { "@astrojs/internal-helpers": "0.6.0", - "@vercel/analytics": "^1.4.1", + "@vercel/analytics": "^1.5.0", "@vercel/edge": "^1.2.1", - "@vercel/nft": "^0.29.0", + "@vercel/nft": "^0.29.2", "@vercel/routing-utils": "^5.0.4", "esbuild": "^0.25.0", "tinyglobby": "^0.2.12" @@ -1486,8 +1486,6 @@ "version": "1.34.3", "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.34.3.tgz", "integrity": "sha512-VT25TyODGy/8ljl7GADnJoMmtmJ1F8d84UXfGonRRF8fWYJz7+2J6GzW+a6ETGtk4OyuRTt7FRSvFG5GvrfSdQ==", - "optional": true, - "peer": true, "dependencies": { "crypto-js": "^4.2.0" } @@ -1534,10 +1532,21 @@ "resolved": "https://registry.npmjs.org/@vercel/edge/-/edge-1.2.1.tgz", "integrity": "sha512-1++yncEyIAi68D3UEOlytYb1IUcIulMWdoSzX2h9LuSeeyR7JtaIgR8DcTQ6+DmYOQn+5MCh6LY+UmK6QBByNA==" }, + "node_modules/@vercel/kv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vercel/kv/-/kv-3.0.0.tgz", + "integrity": "sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==", + "dependencies": { + "@upstash/redis": "^1.34.0" + }, + "engines": { + "node": ">=14.6" + } + }, "node_modules/@vercel/nft": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.1.tgz", - "integrity": "sha512-6239JJM1V9b3OjvZIjbe+47/hGxMr44FEzlbTErrqzebCaoKAHK+yvahn3gdNacybDUbTxVF8Zuh0vqaeM8aKQ==", + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.2.tgz", + "integrity": "sha512-A/Si4mrTkQqJ6EXJKv5EYCDQ3NL6nJXxG8VGXePsaiQigsomHYQC9xSpX8qGk7AEZk4b1ssbYIqJ0ISQQ7bfcA==", "dependencies": { "@mapbox/node-pre-gyp": "^2.0.0", "@rollup/pluginutils": "^5.1.3", @@ -1546,7 +1555,7 @@ "async-sema": "^3.1.1", "bindings": "^1.4.0", "estree-walker": "2.0.2", - "glob": "^7.1.3", + "glob": "^10.4.5", "graceful-fs": "^4.2.9", "node-gyp-build": "^4.2.2", "picomatch": "^4.0.2", @@ -1745,9 +1754,9 @@ } }, "node_modules/astro": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/astro/-/astro-5.4.1.tgz", - "integrity": "sha512-z9BiBz8pV7pZVn1evZv/9bJil3A9UZNyEjDFH3IjD87ioz95HHGLgbjRbbqF1EwSy8JOLKlMNyuQPqjrX8xqWw==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.4.2.tgz", + "integrity": "sha512-9Z3fAniIRJaK/o43OroZA1wHUIU+qHiOR9ovlVT/2XQaN25QRXScIsKWlFp0G/zrx5OuuoJ+QnaoHHW061u26A==", "dependencies": { "@astrojs/compiler": "^2.10.4", "@astrojs/internal-helpers": "0.6.0", @@ -1782,7 +1791,7 @@ "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", - "mrmime": "^2.0.0", + "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", @@ -1794,19 +1803,19 @@ "shiki": "^1.29.2", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.12", - "tsconfck": "^3.1.4", + "tsconfck": "^3.1.5", "ultrahtml": "^1.5.3", "unist-util-visit": "^5.0.0", - "unstorage": "^1.14.4", + "unstorage": "^1.15.0", "vfile": "^6.0.3", "vite": "^6.2.0", - "vitefu": "^1.0.5", + "vitefu": "^1.0.6", "which-pm": "^3.0.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", - "yocto-spinner": "^0.2.0", - "zod": "^3.24.1", - "zod-to-json-schema": "^3.24.1", + "yocto-spinner": "^0.2.1", + "zod": "^3.24.2", + "zod-to-json-schema": "^3.24.3", "zod-to-ts": "^1.2.0" }, "bin": { @@ -1848,6 +1857,20 @@ "crypto-js": "^4.2.0" } }, + "node_modules/astro/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/astro/node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -1856,17 +1879,29 @@ "@types/estree": "^1.0.0" } }, + "node_modules/astro/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/astro/node_modules/unstorage": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.14.4.tgz", - "integrity": "sha512-1SYeamwuYeQJtJ/USE1x4l17LkmQBzg7deBJ+U9qOBoHo15d1cDxG4jM31zKRgF7pG0kirZy4wVMX6WL6Zoscg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.15.0.tgz", + "integrity": "sha512-m40eHdGY/gA6xAPqo8eaxqXgBuzQTlAKfmB1iF7oCKXE1HfwHwzDJBywK+qQGn52dta+bPlZluPF7++yR3p/bg==", "dependencies": { "anymatch": "^3.1.3", - "chokidar": "^3.6.0", + "chokidar": "^4.0.3", "destr": "^2.0.3", - "h3": "^1.13.0", + "h3": "^1.15.0", "lru-cache": "^10.4.3", - "node-fetch-native": "^1.6.4", + "node-fetch-native": "^1.6.6", "ofetch": "^1.4.1", "ufo": "^1.5.4" }, @@ -1874,21 +1909,21 @@ "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", - "@azure/identity": "^4.5.0", + "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3", - "@deno/kv": ">=0.8.4", + "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", - "@vercel/blob": ">=0.27.0", + "@vercel/blob": ">=0.27.1", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", - "uploadthing": "^7.4.1" + "uploadthing": "^7.4.4" }, "peerDependenciesMeta": { "@azure/app-configuration": { @@ -2056,12 +2091,11 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -2324,11 +2358,6 @@ "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, "node_modules/consola": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.0.tgz", @@ -2364,9 +2393,9 @@ } }, "node_modules/crossws": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.3.tgz", - "integrity": "sha512-/71DJT3xJlqSnBr83uGJesmVHSzZEvgxHt/fIKxBAAngqMHmnBWQNxCphVxxJ2XL3xleu5+hJD6IQ3TglBedcw==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.4.tgz", + "integrity": "sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==", "dependencies": { "uncrypto": "^0.1.3" } @@ -2374,9 +2403,7 @@ "node_modules/crypto-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "optional": true, - "peer": true + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/cssesc": { "version": "3.0.0", @@ -2789,11 +2816,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2832,20 +2854,19 @@ "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2868,20 +2889,19 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/h3": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.14.0.tgz", - "integrity": "sha512-ao22eiONdgelqcnknw0iD645qW0s9NnrJHr5OBz4WOMdBdycfSas1EQf1wXRsm+PcB2Yoj43pjBPwqIpJQTeWg==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.1.tgz", + "integrity": "sha512-+ORaOBttdUm1E2Uu/obAyCguiI7MbBvsLTndc3gyK3zU+SYLoZXlyCP9Xgy0gikkGufFLTZXCXD6+4BsufnmHA==", "dependencies": { "cookie-es": "^1.2.2", - "crossws": "^0.3.2", + "crossws": "^0.3.3", "defu": "^6.1.4", "destr": "^2.0.3", "iron-webcrypto": "^1.2.1", - "ohash": "^1.1.4", + "node-mock-http": "^1.0.0", "radix3": "^1.1.2", "ufo": "^1.5.4", - "uncrypto": "^0.1.3", - "unenv": "^1.10.0" + "uncrypto": "^0.1.3" } }, "node_modules/hasown": { @@ -3102,25 +3122,10 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/ioredis": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.5.0.tgz", - "integrity": "sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz", + "integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==", "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -4220,26 +4225,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { @@ -4277,9 +4274,9 @@ } }, "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "engines": { "node": ">=10" } @@ -4370,6 +4367,11 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-mock-http": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.0.tgz", + "integrity": "sha512-0uGYQ1WQL1M5kKvGRXWQ3uZCHtLTO8hln3oBjIusM75WoesZ909uQJs/Hb946i2SS+Gsrhkaa6iAO17jRIv6DQ==" + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -4431,19 +4433,6 @@ "ufo": "^1.5.4" } }, - "node_modules/ohash": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", - "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/oniguruma-to-es": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", @@ -4568,14 +4557,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4615,11 +4596,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" }, - "node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5193,47 +5169,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/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==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.32.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz", @@ -5580,47 +5515,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/sucrase/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==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -5774,9 +5668,9 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/tsconfck": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.4.tgz", - "integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", + "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", "bin": { "tsconfck": "bin/tsconfck.js" }, @@ -5842,18 +5736,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" }, - "node_modules/unenv": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/unenv/-/unenv-1.10.0.tgz", - "integrity": "sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==", - "dependencies": { - "consola": "^3.2.3", - "defu": "^6.1.4", - "mime": "^3.0.0", - "node-fetch-native": "^1.6.4", - "pathe": "^1.1.2" - } - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -6069,9 +5951,9 @@ } }, "node_modules/vite": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", - "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.1.tgz", + "integrity": "sha512-n2GnqDb6XPhlt9B8olZPrgMD/es/Nd1RdChF6CBD/fHW6pUyUTt2sQW2fPRX5GiD9XEa6+8A6A4f2vT6pSsE7Q==", "dependencies": { "esbuild": "^0.25.0", "postcss": "^8.5.3", @@ -6139,9 +6021,9 @@ } }, "node_modules/vitefu": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.5.tgz", - "integrity": "sha512-h4Vflt9gxODPFNGPwp4zAMZRpZR7eslzwH2c5hn5kNZ5rhnKyRJ50U+yGCdc2IRaBs8O4haIgLNGrV5CrpMsCA==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", + "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", "workspaces": [ "tests/deps/*", "tests/projects/*" @@ -6309,11 +6191,6 @@ "node": ">=8" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, "node_modules/xml-js": { "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", @@ -6369,9 +6246,9 @@ } }, "node_modules/yocto-spinner": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.0.tgz", - "integrity": "sha512-Qu6WAqNLGleB687CCGcmgHIo8l+J19MX/32UrSMfbf/4L8gLoxjpOYoiHT1asiWyqvjRZbgvOhLlvne6E5Tbdw==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.1.tgz", + "integrity": "sha512-lHHxjh0bXaLgdJy3cNnVb/F9myx3CkhrvSOEVTkaUgNMXnYFa2xYPVhtGnqhh3jErY2gParBOHallCbc7NrlZQ==", "dependencies": { "yoctocolors": "^2.1.1" }, @@ -6394,17 +6271,17 @@ } }, "node_modules/zod": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", - "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", - "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz", + "integrity": "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==", "peerDependencies": { "zod": "^3.24.1" } diff --git a/package.json b/package.json index de31832..a9fcfa5 100644 --- a/package.json +++ b/package.json @@ -13,14 +13,14 @@ "dependencies": { "@astrojs/rss": "^4.0.11", "@astrojs/tailwind": "^6.0.0", - "@astrojs/vercel": "^8.1.0", + "@astrojs/vercel": "^8.1.1", "@floating-ui/dom": "^1.6.13", "@types/node": "^22.13.9", "@vercel/analytics": "^1.5.0", "@vercel/kv": "^3.0.0", - "astro": "^5.4.1", + "astro": "^5.4.2", "feed": "^4.2.2", - "ioredis": "^5.5.0", + "ioredis": "^5.6.0", "tailwindcss": "^3.4.1" } } diff --git a/src/lib/db.ts b/src/lib/db.ts index 5fd885a..a903508 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -7,16 +7,9 @@ interface RedisNetworkError extends Error { code?: string; } -// Primary Redis configuration - with more robust fallbacks and logging +// Primary Redis configuration const PRIMARY_REDIS_URL = import.meta.env.PRIMARY_REDIS_URL || process.env.PRIMARY_REDIS_URL || process.env.REDIS_URL; -// Log Redis URL status (without exposing sensitive data) -console.log(`Redis URLs status: Primary=${Boolean(PRIMARY_REDIS_URL)}`); - -if (!PRIMARY_REDIS_URL) { - console.warn('WARNING: No Redis URL configured. Database functionality will not work properly.'); -} - // Multiple replicas support const REPLICA_URLS = [ import.meta.env.REPLICA_REDIS_URL_1 || process.env.REPLICA_REDIS_URL_1 || import.meta.env.REPLICA_REDIS_URL || process.env.REPLICA_REDIS_URL, @@ -24,8 +17,6 @@ const REPLICA_URLS = [ import.meta.env.REPLICA_REDIS_URL_3 || process.env.REPLICA_REDIS_URL_3, ].filter(Boolean) as string[]; -console.log(`Found ${REPLICA_URLS.length} Redis replica URLs`); - // Create a mapping of URLs to friendly names for logging const redisInstanceNames = new Map(); @@ -53,8 +44,8 @@ let replicaClients: Map = new Map(); const DEFAULT_CACHE_TTL = 60 * 60 * 24 * 30; // 30 days const MAX_RETRIES = 5; const BASE_DELAY_MS = 100; -const IS_VERCEL = process.env.VERCEL || import.meta.env.VERCEL || false; -const VERCEL_CONNECTION_TIMEOUT = 25000; // 25 seconds for Vercel +const IS_VERCEL = process.env.VERCEL || false; +const VERCEL_CONNECTION_TIMEOUT = 15000; // 15 seconds for Vercel const LOCAL_CONNECTION_TIMEOUT = 5000; // 5 seconds for local // Improved connection handling with exponential backoff @@ -62,34 +53,26 @@ async function createRedisClient(url: string): Promise { if (!url) throw new Error('Redis URL is not set'); const redisName = getRedisName(url); - console.log(`Connecting to ${redisName} Redis instance in ${IS_VERCEL ? 'Vercel' : 'local'} environment`); // Parse URL to extract authentication info const options = { retryStrategy: (times) => { - if (times >= MAX_RETRIES) { - console.error(`${redisName} max retries (${MAX_RETRIES}) reached, giving up`); - return null; - } + if (times >= MAX_RETRIES) return null; const delay = Math.min(BASE_DELAY_MS * Math.pow(2, times), 5000); console.log(`${redisName} retry attempt ${times}, next try in ${delay}ms`); return delay + Math.random() * 200; // Add jitter }, - maxRetriesPerRequest: 3, // Limit retries for queued commands - enableOfflineQueue: true, // Enable command queuing + maxRetriesPerRequest: null, // Allow retries for queued commands + enableOfflineQueue: true, // Enable command queuing enableReadyCheck: true, - connectTimeout: IS_VERCEL ? VERCEL_CONNECTION_TIMEOUT : LOCAL_CONNECTION_TIMEOUT, + connectTimeout: 15000, // Increase connection timeout to 15 seconds for Vercel's serverless environment disconnectTimeout: 10000, // Increase disconnect timeout to 10 seconds keepAlive: 10000, // Send keepalive every 10 seconds - commandTimeout: 15000, // Command timeout + commandTimeout: 10000, // Command timeout retryMaxDelay: 5000, // Maximum delay between retries reconnectOnError: (err) => { const targetErrors = ['READONLY', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND']; - const shouldReconnect = targetErrors.some(e => err.message.includes(e)); - if (shouldReconnect) { - console.log(`Reconnecting due to error: ${err.message}`); - } - return shouldReconnect; + return targetErrors.some(e => err.message.includes(e)); } }; @@ -103,60 +86,138 @@ export async function getRedisClient(): Promise { console.log('Running in Vercel environment - using optimized Redis connection strategy'); } - // Check if Redis is configured - if (!PRIMARY_REDIS_URL && REPLICA_URLS.length === 0) { - throw new Error('No Redis URLs configured. Please check your environment variables in the Vercel dashboard.'); - } - - try { - // Try primary first - if (!primaryClient) { - console.log('Creating new primary Redis client'); - if (!PRIMARY_REDIS_URL) { - throw new Error('Primary Redis URL is not set. Please check your environment variables.'); - } - primaryClient = await createRedisClient(PRIMARY_REDIS_URL); - } else if (primaryClient.status !== 'ready') { - console.log('Primary Redis client not ready, reconnecting'); - try { - await primaryClient.disconnect(); - } catch (e) { - console.error('Error disconnecting primary client:', e); - } - primaryClient = await createRedisClient(PRIMARY_REDIS_URL); - } - + // Try primary first + if (primaryClient?.status === 'ready') { try { // Add timeout to ping const pingPromise = primaryClient.ping(); const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Ping timeout')), 5000) + setTimeout(() => reject(new Error('Ping timeout')), 3000) ); await Promise.race([pingPromise, timeoutPromise]); return primaryClient; } catch (error) { - console.error('Primary Redis client ping failed:', error); - throw error; + console.warn('Primary Redis connection failed, will try replicas:', error); + // Fall through to try replicas } - } catch (primaryError) { - console.error('Error connecting to primary Redis:', primaryError); + } + + // Try replicas in sequence + for (const [url, client] of replicaClients.entries()) { + if (client.status === 'ready') { + try { + // Add timeout to ping + const pingPromise = client.ping(); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Ping timeout')), 3000) + ); + + await Promise.race([pingPromise, timeoutPromise]); + console.log(`Using ${getRedisName(url)}`); + return client; + } catch (error) { + console.warn(`${getRedisName(url)} connection failed:`, error); + // Continue to next replica + } + } + } + + // Reconnect to primary + try { + console.log('Initializing new primary Redis connection...'); + primaryClient = await createRedisClient(PRIMARY_REDIS_URL!); + + primaryClient + .on('ready', () => console.log('Primary Redis connection ready')) + .on('error', (err: RedisNetworkError) => { + // Log more details for connection errors + if (err.code === 'ETIMEDOUT') { + console.error('Primary Redis connection timeout. This is common in serverless environments like Vercel. Check your network connectivity and Redis server availability.'); + } else if (err.code === 'ECONNREFUSED') { + console.error('Primary Redis connection refused. Ensure the Redis server is running and the URL is correct.'); + } else if (err.code === 'ENOTFOUND') { + console.error('Primary Redis host not found. Check your PRIMARY_REDIS_URL environment variable.'); + } else { + console.error('Primary Redis error:', err); + } + }) + .on('reconnecting', () => console.log('Primary Redis reconnecting')) + .on('end', () => console.log('Primary Redis connection closed')); + + return primaryClient; + } catch (error) { + console.error('Failed to connect to primary Redis:', error); // Special handling for Vercel ETIMEDOUT errors - if (IS_VERCEL && primaryError instanceof Error) { - const errorMsg = primaryError.message; - - if (errorMsg.includes('ETIMEDOUT')) { - console.log('Encountered ETIMEDOUT in Vercel environment. This is a common issue with Vercel serverless functions and Redis.'); - throw new Error('Redis connection timed out. Please ensure your Redis service allows connections from Vercel IP ranges. See Vercel documentation for IP ranges used by their serverless functions.'); - } else if (errorMsg.includes('ECONNREFUSED')) { - throw new Error('Redis connection refused. Please check if your Redis service is running and accessible from Vercel.'); - } else if (errorMsg.includes('ENOTFOUND')) { - throw new Error('Redis host not found. Please check your PRIMARY_REDIS_URL environment variable.'); + if (IS_VERCEL && error instanceof Error && error.message.includes('ETIMEDOUT')) { + console.log('Encountered ETIMEDOUT in Vercel environment. Retrying with longer timeout...'); + try { + // Try one more time with increased timeout + const tempClient = new Redis(PRIMARY_REDIS_URL!, { + connectTimeout: 20000, // Increased timeout for last-ditch effort + retryStrategy: () => null // No retries for this attempt + }); + + // Wait for ready or error + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + tempClient.disconnect(); + reject(new Error('Final connection attempt timed out')); + }, 20000); + + tempClient.once('ready', () => { + clearTimeout(timeout); + resolve(true); + }); + + tempClient.once('error', (err) => { + clearTimeout(timeout); + reject(err); + }); + }); + + // If we get here, connection was successful + primaryClient = tempClient; + return tempClient; + } catch (finalError) { + console.error('Final Redis connection attempt failed:', finalError); } } - throw primaryError; + // Try connecting to any replica that might be available + for (const url of REPLICA_URLS) { + try { + const redisName = getRedisName(url); + console.log(`Trying to connect to ${redisName}...`); + const client = await createRedisClient(url); + + client + .on('ready', () => console.log(`${redisName} connection ready`)) + .on('error', (err: RedisNetworkError) => { + // Log more details for connection errors + if (err.code === 'ETIMEDOUT') { + console.error(`${redisName} connection timeout. This is common in serverless environments like Vercel. Check your network connectivity and Redis server availability.`); + } else if (err.code === 'ECONNREFUSED') { + console.error(`${redisName} connection refused. Ensure the Redis server is running and the URL is correct.`); + } else if (err.code === 'ENOTFOUND') { + console.error(`${redisName} host not found. Check your Redis URL environment variables.`); + } else { + console.error(`${redisName} error:`, err); + } + }) + .on('reconnecting', () => console.log(`${redisName} reconnecting`)) + .on('end', () => console.log(`${redisName} connection closed`)); + + replicaClients.set(url, client); + return client; + } catch (replicaError) { + console.error(`Failed to connect to ${getRedisName(url)}:`, replicaError); + // Continue to next replica + } + } + + throw new Error('All Redis connections failed'); } } @@ -277,57 +338,24 @@ export async function warmChapterCache(slugs: string[], lang: string): Promise { - if (!feedId) throw new Error('Feed ID is required'); - if (!Array.isArray(slugs) || slugs.length === 0) throw new Error('Slugs array is required and cannot be empty'); - if (!lang) throw new Error('Language is required'); - const mapping = { slugs, lang, created_at: new Date().toISOString() }; - console.log(`Attempting to store feed mapping with ID: ${feedId}, language: ${lang}, and ${slugs.length} slugs`); - - // Check if Redis is configured - if (!PRIMARY_REDIS_URL && REPLICA_URLS.length === 0) { - console.error('No Redis URLs configured. Cannot store feed mapping.'); - throw new Error('Database connection not configured. Please check your environment variables.'); - } - try { await writeToAllRedis(async (redis) => { - console.log(`Writing feed mapping to Redis instance...`); - - try { - await redis.set( - feedId, - JSON.stringify(mapping), - 'EX', - DEFAULT_CACHE_TTL - ); - console.log(`Successfully stored feed mapping in Redis instance`); - } catch (redisError: any) { - console.error(`Redis set operation failed:`, redisError); - throw new Error(`Redis operation failed: ${redisError.message || 'Unknown Redis error'}`); - } + await redis.set( + feedId, + JSON.stringify(mapping), + 'EX', + DEFAULT_CACHE_TTL + ); }); - - console.log(`Feed mapping successfully stored with ID: ${feedId}`); - } catch (error: any) { + } catch (error) { console.error('Error storing feed mapping:', error); - - // Check for common connection issues - const errorMsg = error.message || ''; - if (errorMsg.includes('ECONNREFUSED')) { - throw new Error('Unable to connect to the Redis database. Connection refused.'); - } else if (errorMsg.includes('ETIMEDOUT')) { - throw new Error('Connection to Redis database timed out. This may be due to network issues or firewall settings.'); - } else if (errorMsg.includes('ENOTFOUND')) { - throw new Error('Redis host not found. Please check your database URL configuration.'); - } else { - throw new Error(`Failed to store feed mapping: ${errorMsg}`); - } + throw new Error('Failed to store feed mapping'); } } diff --git a/src/pages/api/create-feed.ts b/src/pages/api/create-feed.ts index 2eeb00c..68db19d 100644 --- a/src/pages/api/create-feed.ts +++ b/src/pages/api/create-feed.ts @@ -7,52 +7,18 @@ const feedMappings = new Map(); export const POST: APIRoute = async ({ request }) => { try { - console.log("Handling create-feed API request"); - - if (!request.body) { - console.error("Request has no body"); - return new Response("Request body is required", { status: 400 }); - } - - let body; - try { - body = await request.json(); - } catch (parseError) { - console.error("Failed to parse request JSON:", parseError); - return new Response("Invalid JSON in request body", { status: 400 }); - } - - const { slugs, lang } = body; + const { slugs, lang } = await request.json(); if (!Array.isArray(slugs) || slugs.length === 0) { - console.error("Invalid slugs array:", slugs); return new Response("Invalid slugs array", { status: 400 }); } - if (!lang) { - console.error("Language not specified"); - return new Response("Language is required", { status: 400 }); - } - - console.log(`Creating feed for ${slugs.length} slugs in language: ${lang}`); - // Create a unique hash based on slugs and language const dataString = `${slugs.sort().join(',')}:${lang}`; const hash = createHash('md5').update(dataString).digest('hex').slice(0, 8); - try { - // Store the mapping in Redis - await storeFeedMapping(hash, slugs, lang); - console.log(`Successfully created feed with ID: ${hash}`); - } catch (dbError: any) { - console.error("Database error while storing feed mapping:", dbError); - return new Response(`Database error: ${dbError.message || "Unknown error"}`, { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - }); - } + // Store the mapping in Vercel KV + await storeFeedMapping(hash, slugs, lang); return new Response(JSON.stringify({ feedId: hash }), { status: 200, @@ -60,18 +26,9 @@ export const POST: APIRoute = async ({ request }) => { 'Content-Type': 'application/json' } }); - } catch (error: any) { - console.error('Unexpected error creating feed:', error); - return new Response(JSON.stringify({ - error: "Internal server error", - message: error.message || "Unknown error", - stack: process.env.NODE_ENV === 'development' ? error.stack : undefined - }), { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - }); + } catch (error) { + console.error('Error creating feed:', error); + return new Response("Internal server error", { status: 500 }); } };