diff --git a/package-lock.json b/package-lock.json index 77a8eb3064b..b474b72bc23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,13 +36,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@sentry/nextjs": "^10.32.1", "@tailwindcss/typography": "^0.5.19", - "@types/d3": "^7.4.3", "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "d3": "^7.9.0", "date-fns": "^4.1.0", + "dompurify": "^3.3.1", "jszip": "^3.10.1", "lucide-react": "^0.556.0", "next": "^16.0.10", @@ -70,7 +70,12 @@ "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.5.2", "@types/bcryptjs": "^2.4.6", - "@types/node": "^20", + "@types/d3": "^7.4.3", + "@types/mdast": "^4.0.4", + "@types/mdx": "^2.0.13", + "@types/mysql": "^2.15.27", + "@types/node": "^20.19.33", + "@types/pg": "^8.16.0", "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^4.3.4", @@ -3644,6 +3649,17 @@ "@opentelemetry/api": "^1.3.0" } }, + "node_modules/@opentelemetry/instrumentation-pg/node_modules/@types/pg": { + "version": "8.15.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", + "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@opentelemetry/instrumentation-redis": { "version": "0.57.0", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.57.0.tgz", @@ -7876,6 +7892,7 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-array": "*", @@ -7914,12 +7931,14 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-axis": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -7929,6 +7948,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -7938,18 +7958,21 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-contour": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-array": "*", @@ -7960,18 +7983,21 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-dispatch": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-drag": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -7981,18 +8007,21 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-fetch": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-dsv": "*" @@ -8002,18 +8031,21 @@ "version": "3.0.10", "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-format": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-geo": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/geojson": "*" @@ -8023,12 +8055,14 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-color": "*" @@ -8038,30 +8072,35 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-polygon": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-quadtree": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-random": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-time": "*" @@ -8071,18 +8110,21 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-selection": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-shape": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -8092,24 +8134,28 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-time-format": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, "license": "MIT" }, "node_modules/@types/d3-transition": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-selection": "*" @@ -8119,6 +8165,7 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, "license": "MIT", "dependencies": { "@types/d3-interpolate": "*", @@ -8175,6 +8222,7 @@ "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, "license": "MIT" }, "node_modules/@types/hast": { @@ -8230,18 +8278,18 @@ } }, "node_modules/@types/node": { - "version": "20.19.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", - "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, "node_modules/@types/pg": { - "version": "8.15.6", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz", - "integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.16.0.tgz", + "integrity": "sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==", "license": "MIT", "dependencies": { "@types/node": "*", @@ -8291,8 +8339,7 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/@types/unist": { "version": "3.0.3", @@ -11352,11 +11399,10 @@ "peer": true }, "node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -14822,7 +14868,7 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.25.4", @@ -16061,6 +16107,16 @@ "marked": "14.0.0" } }, + "node_modules/monaco-editor/node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "peer": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -16328,17 +16384,6 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", - "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", diff --git a/package.json b/package.json index 2f654c986d4..19ecfe97083 100644 --- a/package.json +++ b/package.json @@ -57,13 +57,13 @@ "@radix-ui/react-tooltip": "^1.2.8", "@sentry/nextjs": "^10.32.1", "@tailwindcss/typography": "^0.5.19", - "@types/d3": "^7.4.3", "bcryptjs": "^3.0.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "d3": "^7.9.0", "date-fns": "^4.1.0", + "dompurify": "^3.3.1", "jszip": "^3.10.1", "lucide-react": "^0.556.0", "next": "^16.0.10", @@ -94,7 +94,12 @@ "@testing-library/react": "^16.1.0", "@testing-library/user-event": "^14.5.2", "@types/bcryptjs": "^2.4.6", - "@types/node": "^20", + "@types/d3": "^7.4.3", + "@types/mdast": "^4.0.4", + "@types/mdx": "^2.0.13", + "@types/mysql": "^2.15.27", + "@types/node": "^20.19.33", + "@types/pg": "^8.16.0", "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react": "^4.3.4", diff --git a/src/app/api/prompts/route.ts b/src/app/api/prompts/route.ts index 26b32814d8f..668ea6aadd4 100644 --- a/src/app/api/prompts/route.ts +++ b/src/app/api/prompts/route.ts @@ -8,6 +8,7 @@ import { generatePromptEmbedding, findAndSaveRelatedPrompts } from "@/lib/ai/emb import { generatePromptSlug } from "@/lib/slug"; import { checkPromptQuality } from "@/lib/ai/quality-check"; import { isSimilarContent, normalizeContent } from "@/lib/similarity"; +import { logger } from "@/lib/logger"; const promptSchema = z.object({ title: z.string().min(1).max(200), @@ -147,7 +148,7 @@ export async function POST(request: Request) { }); // Find similar content using our similarity algorithm - const similarPrompt = publicPrompts.find(p => isSimilarContent(content, p.content)); + const similarPrompt = publicPrompts.find((p: { id: string; slug: string | null; title: string; content: string; author: { username: string } }) => isSimilarContent(content, p.content)); if (similarPrompt) { return NextResponse.json( @@ -261,18 +262,18 @@ export async function POST(request: Request) { generatePromptEmbedding(prompt.id) .then(() => findAndSaveRelatedPrompts(prompt.id)) .catch((err) => - console.error("Failed to generate embedding/related prompts for:", prompt.id, err) + logger.error({ error: err, promptId: prompt.id }, "Failed to generate embedding/related prompts") ); } // Run quality check for auto-delist (non-blocking for public prompts) // This runs in the background and will delist the prompt if quality issues are found if (!isPrivate) { - console.log(`[Quality Check] Starting check for prompt ${prompt.id}`); + logger.info({ promptId: prompt.id }, "[Quality Check] Starting check"); checkPromptQuality(title, content, description).then(async (result) => { - console.log(`[Quality Check] Result for prompt ${prompt.id}:`, JSON.stringify(result)); + logger.info({ promptId: prompt.id, result }, "[Quality Check] Result"); if (result.shouldDelist && result.reason) { - console.log(`[Quality Check] Auto-delisting prompt ${prompt.id}: ${result.reason} - ${result.details}`); + logger.info({ promptId: prompt.id, reason: result.reason, details: result.details }, "[Quality Check] Auto-delisting prompt"); await db.prompt.update({ where: { id: prompt.id }, data: { @@ -281,13 +282,13 @@ export async function POST(request: Request) { delistReason: result.reason, }, }); - console.log(`[Quality Check] Prompt ${prompt.id} delisted successfully`); + logger.info({ promptId: prompt.id }, "[Quality Check] Prompt delisted successfully"); } }).catch((err) => { - console.error("[Quality Check] Failed to run quality check for prompt:", prompt.id, err); + logger.error({ error: err, promptId: prompt.id }, "[Quality Check] Failed to run quality check"); }); } else { - console.log(`[Quality Check] Skipped - prompt ${prompt.id} is private`); + logger.debug({ promptId: prompt.id }, "[Quality Check] Skipped - prompt is private"); } // Revalidate caches (prompts, categories, tags counts change) @@ -430,12 +431,15 @@ export async function GET(request: Request) { ]); // Transform to include voteCount and contributorCount, exclude internal fields - const prompts = promptsRaw.map(({ embedding: _e, isPrivate: _p, isUnlisted: _u, unlistedAt: _ua, deletedAt: _d, ...p }) => ({ - ...p, - voteCount: p._count.votes, - contributorCount: p._count.contributors, - contributors: p.contributors, - })); + const prompts = promptsRaw.map((promptRaw) => { + const { isPrivate, isUnlisted, unlistedAt, deletedAt, ...rest } = promptRaw as typeof promptsRaw[number]; + return { + ...rest, + voteCount: rest._count.votes, + contributorCount: rest._count.contributors, + contributors: rest.contributors, + }; + }); return NextResponse.json({ prompts, @@ -445,7 +449,7 @@ export async function GET(request: Request) { totalPages: Math.ceil(total / perPage), }); } catch (error) { - console.error("List prompts error:", error); + logger.error({ error }, "List prompts error"); return NextResponse.json( { error: "server_error", message: "Something went wrong" }, { status: 500 } diff --git a/src/app/embed/page.tsx b/src/app/embed/page.tsx index 3059ca52ab4..1fd38bcb7fc 100644 --- a/src/app/embed/page.tsx +++ b/src/app/embed/page.tsx @@ -4,6 +4,7 @@ import { useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState, useMemo } from "react"; import { cn } from "@/lib/utils"; import { RunPromptButton } from "@/components/prompts/run-prompt-button"; +import DOMPurify from "dompurify"; interface TreeNode { name: string; @@ -479,10 +480,15 @@ function EmbedContent() { }} > {config.prompt ? ( -

) : (

> { const aiSearchEnabled = await isAISearchEnabled(); if (!aiSearchEnabled) { - console.log("[improve-prompt] AI search is not enabled"); + logger.debug("[improve-prompt] AI search is not enabled"); return []; } @@ -96,7 +97,7 @@ async function findSimilarPrompts( take: 100, }); - console.log(`[improve-prompt] Found ${prompts.length} prompts with embeddings`); + logger.debug({ count: prompts.length }, "[improve-prompt] Found prompts with embeddings"); const SIMILARITY_THRESHOLD = 0.3; @@ -118,7 +119,7 @@ async function findSimilarPrompts( return scoredPrompts.slice(0, limit); } catch (error) { - console.error("[improve-prompt] Error finding similar prompts:", error); + logger.error({ error }, "[improve-prompt] Error finding similar prompts"); return []; } } diff --git a/src/lib/ai/quality-check.ts b/src/lib/ai/quality-check.ts index e7a82a51fbd..ba3494e5c63 100644 --- a/src/lib/ai/quality-check.ts +++ b/src/lib/ai/quality-check.ts @@ -1,5 +1,6 @@ import OpenAI from "openai"; import { loadPrompt, getSystemPrompt } from "./load-prompt"; +import { logger, logQualityCheck, logQualityCheckResult, logQualityCheckError } from "@/lib/logger"; const qualityCheckPrompt = loadPrompt("src/lib/ai/quality-check.prompt.yml"); @@ -74,19 +75,19 @@ export async function checkPromptQuality( content: string, description?: string | null ): Promise { - console.log(`[Quality Check] Checking: "${title}" (${content.length} chars)`); + logQualityCheck(title, content.length); // First, run basic length checks (no AI needed) const lengthCheck = checkLength(content); if (lengthCheck) { - console.log(`[Quality Check] Length check failed:`, lengthCheck); + logQualityCheckResult(lengthCheck); return lengthCheck; } // Check if OpenAI is available const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) { - console.log(`[Quality Check] No OpenAI API key - skipping AI check`); + logger.debug("[Quality Check] No OpenAI API key - skipping AI check"); // If no AI available, pass the check (avoid false positives) return { shouldDelist: false, @@ -96,7 +97,7 @@ export async function checkPromptQuality( }; } - console.log(`[Quality Check] Running AI check...`); + logger.debug("[Quality Check] Running AI check..."); try { const client = getOpenAIClient(); @@ -120,7 +121,7 @@ ${content}`; }); const responseText = response.choices[0]?.message?.content || "{}"; - console.log(`[Quality Check] AI response:`, responseText); + logger.debug({ responseText }, "[Quality Check] AI response"); try { const result = JSON.parse(responseText); @@ -142,7 +143,7 @@ ${content}`; details: result.details || "Quality check completed", }; } catch { - console.error("Failed to parse AI quality check response:", responseText); + logQualityCheckError(new Error("Failed to parse AI quality check response")); // On parse error, don't delist (avoid false positives) return { shouldDelist: false, @@ -152,7 +153,7 @@ ${content}`; }; } } catch (error) { - console.error("AI quality check error:", error); + logQualityCheckError(error); // On error, don't delist (avoid false positives) return { shouldDelist: false, diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts index d7d8c58f8d3..26ed0c52221 100644 --- a/src/lib/auth/index.ts +++ b/src/lib/auth/index.ts @@ -5,6 +5,12 @@ import { getConfig } from "@/lib/config"; import { initializePlugins, getAuthPlugin } from "@/lib/plugins"; import type { Adapter, AdapterUser } from "next-auth/adapters"; +// Extended interface for adapter user data with optional username fields +interface ExtendedAdapterUser extends AdapterUser { + username?: string; + githubUsername?: string; +} + // Initialize plugins before use initializePlugins(); @@ -40,12 +46,10 @@ function CustomPrismaAdapter(): Adapter { return { ...prismaAdapter, - async createUser(data: AdapterUser & { username?: string; githubUsername?: string }) { + async createUser(data: ExtendedAdapterUser) { // Use GitHub username if provided, otherwise generate one - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let username = (data as any).username; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const githubUsername = (data as any).githubUsername; // Immutable GitHub username + let username = data.username; + const githubUsername = data.githubUsername; if (!username) { username = await generateUsername(data.email, data.name); diff --git a/src/lib/config/index.ts b/src/lib/config/index.ts index 3599e874008..1b40f8644f2 100644 --- a/src/lib/config/index.ts +++ b/src/lib/config/index.ts @@ -1,3 +1,5 @@ +import { logger } from "@/lib/logger"; + export interface BrandingConfig { name: string; logo: string; @@ -224,3 +226,37 @@ export function getConfigSync(): PromptsConfig { } return cachedConfig; } + +/** + * Validate required environment variables at startup + * Throws an error if critical environment variables are missing + */ +export function validateEnvironment(): void { + const required = [ + 'DATABASE_URL', + 'NEXTAUTH_SECRET', + ]; + + const missing = required.filter(key => !process.env[key]); + if (missing.length > 0) { + throw new Error( + `Missing required environment variables: ${missing.join(', ')}. ` + + `Please check your .env file and ensure all required variables are set.` + ); + } + + // Warn about optional but recommended variables + const recommended = [ + 'NEXTAUTH_URL', + 'OPENAI_API_KEY', + 'GOOGLE_ANALYTICS_ID', + ]; + + const missingRecommended = recommended.filter(key => !process.env[key]); + if (missingRecommended.length > 0) { + logger.warn( + `Optional environment variables not set: ${missingRecommended.join(', ')}. ` + + `Some features may be limited.` + ); + } +} diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 00000000000..57f5e15c925 --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,48 @@ +import pino from "pino"; + +const isDevelopment = process.env.NODE_ENV === "development"; + +export const logger = pino({ + level: process.env.LOG_LEVEL || (isDevelopment ? "debug" : "info"), + // Use pretty print in development, JSON in production + transport: isDevelopment + ? { + target: "pino-pretty", + options: { + colorize: true, + translateTime: "HH:MM:ss Z", + ignore: "pid,hostname", + }, + } + : undefined, + // Add error serialization + serializers: { + err: pino.stdSerializers.err, + error: pino.stdSerializers.err, + }, +}); + +// Convenience methods for common logging patterns +export const logQualityCheck = (title: string, contentLength: number) => { + logger.info({ title, contentLength }, "[Quality Check] Checking prompt"); +}; + +export const logQualityCheckResult = (result: { shouldDelist: boolean; reason: string | null; details: string }) => { + logger.info(result, "[Quality Check] Result"); +}; + +export const logQualityCheckError = (error: unknown) => { + logger.error({ error }, "[Quality Check] Failed"); +}; + +export const logEmbeddingError = (error: unknown, context?: string) => { + logger.error({ error, context }, "[Embedding] Error"); +}; + +export const logWebhookError = (error: unknown, webhookName?: string) => { + logger.error({ error, webhookName }, "[Webhook] Error"); +}; + +export const logAIError = (error: unknown, operation: string) => { + logger.error({ error, operation }, `[AI] ${operation} failed`); +}; diff --git a/src/lib/plugins/index.ts b/src/lib/plugins/index.ts index e798e084856..abf25ec54d2 100644 --- a/src/lib/plugins/index.ts +++ b/src/lib/plugins/index.ts @@ -53,11 +53,3 @@ export async function getConfiguredAuthPlugins() { return plugins; } -/** - * @deprecated Use getConfiguredAuthPlugins() instead - * Get the first configured auth plugin based on prompts.config.ts - */ -export async function getConfiguredAuthPlugin() { - const plugins = await getConfiguredAuthPlugins(); - return plugins[0]; -} diff --git a/tsconfig.json b/tsconfig.json index cf9c65d3e06..771887b380d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "isolatedModules": true, "jsx": "react-jsx", "incremental": true, + "types": [], "plugins": [ { "name": "next"