diff --git a/.mcp.json b/.mcp.json index 7642057..d529940 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,14 +1,39 @@ { "mcpServers": { - "nano-banana-mcp": { + "context7": { "type": "stdio", "command": "npx", "args": [ - "nano-banana-mcp" + "-y", + "@upstash/context7-mcp", + "--api-key", + "ctx7sk-48cb1995-935a-4cc5-b9b0-535d600ea5e6" ], + "env": {} + }, + "brave-search": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-brave-search" + ], + "env": { + "BRAVE_API_KEY": "BSAcRGGikEzY4B2j3NZ8Qy5NYh9l4HZ" + } + }, + "browsermcp": { + "type": "stdio", + "command": "npx", + "args": ["-y", "@browsermcp/mcp@latest"], + "env": {} + }, + "nano-banana-mcp": { + "type": "stdio", + "command": "npx", + "args": ["nano-banana-mcp"], "env": { "GEMINI_API_KEY": "AIzaSyAWAy39gqmnrYA0kUC3Vh8uadu-WlqJuO4" } } } -} \ No newline at end of file +} diff --git a/creatures/skeleton-spider/images/skeleton6.png b/creatures/skeleton-spider/images/skeleton6.png new file mode 100644 index 0000000..635b515 Binary files /dev/null and b/creatures/skeleton-spider/images/skeleton6.png differ diff --git a/creatures/skeleton-spider/images/skeleton7-a.png b/creatures/skeleton-spider/images/skeleton7-a.png new file mode 100644 index 0000000..634bbd5 Binary files /dev/null and b/creatures/skeleton-spider/images/skeleton7-a.png differ diff --git a/creatures/skeleton-spider/images/skeleton7-b.png b/creatures/skeleton-spider/images/skeleton7-b.png new file mode 100644 index 0000000..a10ff62 Binary files /dev/null and b/creatures/skeleton-spider/images/skeleton7-b.png differ diff --git a/creatures/skeleton-spider/video/spider-skeleton-full.mp4 b/creatures/skeleton-spider/video/spider-skeleton-full.mp4 new file mode 100644 index 0000000..4de4f5e Binary files /dev/null and b/creatures/skeleton-spider/video/spider-skeleton-full.mp4 differ diff --git a/logs/server.log b/logs/server.log new file mode 100644 index 0000000..b5aae56 --- /dev/null +++ b/logs/server.log @@ -0,0 +1,18 @@ +/projects/my-projects/Magic-Building/src/server/app.ts:1 +import express, { Application } from 'express'; +^ + + +ReferenceError: Cannot access 'appConfig' before initialization + at Object.appConfig (/projects/my-projects/Magic-Building/src/server/app.ts:1:1) + at Object.get [as appConfig] (/projects/my-projects/Magic-Building/src/server/app.ts:2:661) + at multer (/projects/my-projects/Magic-Building/src/server/middleware/upload.ts:30:15) + at Object. (/projects/my-projects/Magic-Building/src/server/middleware/upload.ts:40:35) + at Module._compile (node:internal/modules/cjs/loader:1546:14) + at Object.transformer (/projects/my-projects/Magic-Building/node_modules/.pnpm/tsx@4.20.5/node_modules/tsx/dist/register-D46fvsV_.cjs:3:1104) + at Module.load (node:internal/modules/cjs/loader:1318:32) + at Function._load (node:internal/modules/cjs/loader:1128:12) + at TracingChannel.traceSync (node:diagnostics_channel:315:14) + at wrapModuleLoad (node:internal/modules/cjs/loader:218:24) + +Node.js v22.11.0 diff --git a/logs/test-api-20250915_010051.log b/logs/test-api-20250915_010051.log new file mode 100644 index 0000000..4f34276 --- /dev/null +++ b/logs/test-api-20250915_010051.log @@ -0,0 +1,29 @@ +⚠️ jq not found - JSON response parsing will be limited +ℹ️ 🚀 Starting Magic Building API Test Suite +ℹ️ Server URL: http://localhost:3000 +ℹ️ Test log: ./logs/test-api-20250915_010051.log +ℹ️ Testing health check endpoint... +❌ Health check failed (HTTP 000) +ℹ️ Testing API info endpoint... +❌ API info endpoint failed (HTTP 000) +ℹ️ Testing text-to-image generation... +❌ Text-to-image generation failed (HTTP 000) +2025-09-15 01:00:51 - Error response: +ℹ️ Testing validation error handling... +❌ Missing prompt validation failed (HTTP 000) +❌ Missing filename validation failed (HTTP 000) +⚠️ Filename sanitization test had unexpected result (HTTP 000) +ℹ️ Testing 404 error handling... +❌ 404 error handling failed (HTTP 000) +ℹ️ Creating test reference image... +⚠️ ImageMagick not available, skipping reference image test +ℹ️ Testing concurrent requests (performance test)... +2025-09-15 01:00:51 - Concurrent request 1 failed (HTTP 000) +2025-09-15 01:00:51 - Concurrent request 3 failed (HTTP 000) +2025-09-15 01:00:51 - Concurrent request 2 failed (HTTP 000) +✅ Concurrent requests test completed +ℹ️ 📊 Test Results Summary +✅ Passed: 3/6 tests +⚠️ ⚠️ Some tests failed. Check the logs for details. +ℹ️ Generated images are in: src/results/ +ℹ️ Test logs saved to: ./logs/test-api-20250915_010051.log diff --git a/package.json b/package.json index 0b793c9..b3f5b81 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,12 @@ "main": "index.js", "scripts": { "ttm": "tsx src/generation/text-to-image.ts", - "ttt": "tsx src/generation/text-to-text.ts" + "ttt": "tsx src/generation/text-to-text.ts", + "server": "tsx src/server/server.ts", + "server:dev": "tsx --watch src/server/server.ts", + "server:log": "tsx src/server/server.ts > logs/server.log 2>&1", + "build": "tsc", + "typecheck": "tsc --noEmit" }, "keywords": [], "author": "", @@ -13,10 +18,16 @@ "packageManager": "pnpm@10.11.0", "dependencies": { "@google/genai": "^1.17.0", + "cors": "^2.8.5", "dotenv": "^17.2.2", - "mime": "^4.1.0" + "express": "^5.1.0", + "mime": "^4.1.0", + "multer": "^2.0.2" }, "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/multer": "^2.0.0", "@types/node": "^24.3.1", "tsx": "^4.20.5", "typescript": "^5.9.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92314c0..431c0be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,13 +11,31 @@ importers: '@google/genai': specifier: ^1.17.0 version: 1.17.0 + cors: + specifier: ^2.8.5 + version: 2.8.5 dotenv: specifier: ^17.2.2 version: 17.2.2 + express: + specifier: ^5.1.0 + version: 5.1.0 mime: specifier: ^4.1.0 version: 4.1.0 + multer: + specifier: ^2.0.2 + version: 2.0.2 devDependencies: + '@types/cors': + specifier: ^2.8.19 + version: 2.8.19 + '@types/express': + specifier: ^5.0.3 + version: 5.0.3 + '@types/multer': + specifier: ^2.0.0 + version: 2.0.0 '@types/node': specifier: ^24.3.1 version: 24.3.1 @@ -195,22 +213,112 @@ packages: '@modelcontextprotocol/sdk': optional: true + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/express-serve-static-core@5.0.7': + resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} + + '@types/express@5.0.3': + resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/multer@2.0.0': + resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==} + '@types/node@24.3.1': resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} bignumber.js@9.3.1: resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -220,26 +328,79 @@ packages: supports-color: optional: true + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dotenv@17.2.2: resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + esbuild@0.25.9: resolution: {integrity: sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==} engines: {node: '>=18'} hasBin: true + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gaxios@6.7.1: resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} engines: {node: '>=14'} @@ -248,6 +409,14 @@ packages: resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} engines: {node: '>=14'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} @@ -259,14 +428,48 @@ packages: resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} engines: {node: '>=14'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + gtoken@7.1.0: resolution: {integrity: sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==} engines: {node: '>=14.0.0'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -280,14 +483,61 @@ packages: jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mime@4.1.0: resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} engines: {node: '>=16'} hasBin: true + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + multer@2.0.2: + resolution: {integrity: sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==} + engines: {node: '>= 10.16.0'} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -297,12 +547,107 @@ packages: encoding: optional: true + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.1: + resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} + engines: {node: '>= 0.10'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -311,6 +656,17 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} @@ -319,16 +675,30 @@ packages: undici-types@7.10.0: resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -341,6 +711,10 @@ packages: utf-8-validate: optional: true + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + snapshots: '@esbuild/aix-ppc64@0.25.9': @@ -431,28 +805,158 @@ snapshots: - supports-color - utf-8-validate + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.3.1 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.3.1 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 24.3.1 + + '@types/express-serve-static-core@5.0.7': + dependencies: + '@types/node': 24.3.1 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@5.0.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.0.7 + '@types/serve-static': 1.15.8 + + '@types/http-errors@2.0.5': {} + + '@types/mime@1.3.5': {} + + '@types/multer@2.0.0': + dependencies: + '@types/express': 5.0.3 + '@types/node@24.3.1': dependencies: undici-types: 7.10.0 + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 24.3.1 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.3.1 + '@types/send': 0.17.5 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + agent-base@7.1.4: {} + append-field@1.0.0: {} + base64-js@1.5.1: {} bignumber.js@9.3.1: {} + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.1 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.1 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + debug@4.4.1: dependencies: ms: 2.1.3 + depd@2.0.0: {} + dotenv@17.2.2: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + esbuild@0.25.9: optionalDependencies: '@esbuild/aix-ppc64': 0.25.9 @@ -482,11 +986,64 @@ snapshots: '@esbuild/win32-ia32': 0.25.9 '@esbuild/win32-x64': 0.25.9 + escape-html@1.0.3: {} + + etag@1.8.1: {} + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} + finalhandler@2.1.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gaxios@6.7.1: dependencies: extend: 3.0.2 @@ -507,6 +1064,24 @@ snapshots: - encoding - supports-color + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -525,6 +1100,8 @@ snapshots: google-logging-utils@0.0.2: {} + gopd@1.2.0: {} + gtoken@7.1.0: dependencies: gaxios: 6.7.1 @@ -533,6 +1110,20 @@ snapshots: - encoding - supports-color + has-symbols@1.1.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -540,6 +1131,20 @@ snapshots: transitivePeerDependencies: - supports-color + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + is-stream@2.0.1: {} json-bigint@1.0.0: @@ -557,18 +1162,175 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.2.1 + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mime@4.1.0: {} + minimist@1.2.8: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + ms@2.1.3: {} + multer@2.0.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + negotiator@1.0.0: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-to-regexp@8.3.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@3.0.1: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + resolve-pkg-maps@1.0.0: {} + router@2.2.0: + dependencies: + debug: 4.4.1 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} + + send@1.2.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + statuses@2.0.1: {} + + statuses@2.0.2: {} + + streamsearch@1.1.0: {} + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + toidentifier@1.0.1: {} + tr46@0.0.3: {} tsx@4.20.5: @@ -578,12 +1340,31 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + + typedarray@0.0.6: {} + typescript@5.9.2: {} undici-types@7.10.0: {} + unpipe@1.0.0: {} + + util-deprecate@1.0.2: {} + uuid@9.0.1: {} + vary@1.1.2: {} + webidl-conversions@3.0.1: {} whatwg-url@5.0.0: @@ -591,4 +1372,8 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + wrappy@1.0.2: {} + ws@8.18.3: {} + + xtend@4.0.2: {} diff --git a/src/generation/text-to-image.ts b/src/generation/text-to-image.ts index ec8464a..718fe42 100644 --- a/src/generation/text-to-image.ts +++ b/src/generation/text-to-image.ts @@ -78,7 +78,7 @@ async function main() { } console.log('Image generation complete!'); } catch (error) { - console.error('Primary model failed:', error.message || error); + console.error('Primary model failed:', error instanceof Error ? error.message : error); // Try fallback model (Imagen 4) console.log('Trying fallback model (Imagen 4)...'); @@ -111,7 +111,7 @@ async function main() { } } catch (altError) { console.error('Both models failed. Please try again later.'); - console.error('Fallback error:', altError.message || altError); + console.error('Fallback error:', altError instanceof Error ? altError.message : altError); } } } diff --git a/src/results/test_crystal.png b/src/results/test_crystal.png new file mode 100644 index 0000000..8f4909d Binary files /dev/null and b/src/results/test_crystal.png differ diff --git a/src/server/app.ts b/src/server/app.ts new file mode 100644 index 0000000..a9cee4d --- /dev/null +++ b/src/server/app.ts @@ -0,0 +1,94 @@ +import express, { Application } from 'express'; +import cors from 'cors'; +import { config } from 'dotenv'; +import { Config } from './types/api'; +import { generateRouter } from './routes/generate'; +import { errorHandler, notFoundHandler } from './middleware/errorHandler'; + +// Load environment variables +config(); + +// Application configuration +export const appConfig: Config = { + port: parseInt(process.env.PORT || '3000'), + geminiApiKey: process.env.GEMINI_API_KEY || '', + resultsDir: './src/results', + uploadsDir: './uploads/temp', + maxFileSize: 5 * 1024 * 1024, // 5MB + maxFiles: 3 +}; + +// Create Express application +export const createApp = (): Application => { + const app = express(); + + // Middleware + app.use(cors({ + origin: process.env.CORS_ORIGIN || '*', + credentials: true + })); + + app.use(express.json({ limit: '10mb' })); + app.use(express.urlencoded({ extended: true, limit: '10mb' })); + + // Request ID middleware for logging + app.use((req, res, next) => { + req.requestId = Math.random().toString(36).substr(2, 9); + res.setHeader('X-Request-ID', req.requestId); + next(); + }); + + // Health check endpoint + app.get('/health', (req, res) => { + const health = { + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: process.env.NODE_ENV || 'development', + version: process.env.npm_package_version || '1.0.0' + }; + + console.log(`[${health.timestamp}] Health check - ${health.status}`); + res.json(health); + }); + + // API info endpoint + app.get('/api/info', (req, res) => { + const info = { + name: 'Magic Building Image Generation API', + version: '1.0.0', + description: 'Express.js server for Gemini AI image generation', + endpoints: { + 'GET /health': 'Health check', + 'GET /api/info': 'API information', + 'POST /api/generate': 'Generate images from text prompt with optional reference images' + }, + limits: { + maxFileSize: `${appConfig.maxFileSize / (1024 * 1024)}MB`, + maxFiles: appConfig.maxFiles, + supportedFormats: ['PNG', 'JPEG', 'JPG', 'WebP'] + } + }; + + console.log(`[${new Date().toISOString()}] API info requested`); + res.json(info); + }); + + // Mount API routes + app.use('/api', generateRouter); + + // Error handling middleware (must be last) + app.use(notFoundHandler); + app.use(errorHandler); + + return app; +}; + +// Extend Express Request type to include requestId +declare global { + namespace Express { + interface Request { + requestId: string; + } + } +} \ No newline at end of file diff --git a/src/server/middleware/errorHandler.ts b/src/server/middleware/errorHandler.ts new file mode 100644 index 0000000..fbb1c0a --- /dev/null +++ b/src/server/middleware/errorHandler.ts @@ -0,0 +1,103 @@ +import { Request, Response, NextFunction } from 'express'; +import { GenerateImageResponse } from '../types/api'; + +/** + * Global error handler for the Express application + */ +export const errorHandler = ( + error: Error, + req: Request, + res: Response, + next: NextFunction +) => { + const timestamp = new Date().toISOString(); + const requestId = req.requestId || 'unknown'; + + // Log the error + console.error(`[${timestamp}] [${requestId}] ERROR:`, { + message: error.message, + stack: error.stack, + path: req.path, + method: req.method, + body: req.body, + query: req.query + }); + + // Don't send error response if headers already sent + if (res.headersSent) { + return next(error); + } + + // Determine error type and status code + let statusCode = 500; + let errorMessage = 'Internal server error'; + let errorType = 'INTERNAL_ERROR'; + + if (error.name === 'ValidationError') { + statusCode = 400; + errorMessage = error.message; + errorType = 'VALIDATION_ERROR'; + } else if (error.message.includes('API key') || error.message.includes('authentication')) { + statusCode = 401; + errorMessage = 'Authentication failed'; + errorType = 'AUTH_ERROR'; + } else if (error.message.includes('not found') || error.message.includes('404')) { + statusCode = 404; + errorMessage = 'Resource not found'; + errorType = 'NOT_FOUND'; + } else if (error.message.includes('timeout') || error.message.includes('503')) { + statusCode = 503; + errorMessage = 'Service temporarily unavailable'; + errorType = 'SERVICE_UNAVAILABLE'; + } else if (error.message.includes('overloaded') || error.message.includes('rate limit')) { + statusCode = 429; + errorMessage = 'Service overloaded, please try again later'; + errorType = 'RATE_LIMITED'; + } + + // Create error response + const errorResponse: GenerateImageResponse = { + success: false, + message: 'Request failed', + error: errorMessage + }; + + // Add additional debug info in development + if (process.env.NODE_ENV === 'development') { + (errorResponse as any).debug = { + originalError: error.message, + errorType, + requestId, + timestamp + }; + } + + console.log(`[${timestamp}] [${requestId}] Sending error response: ${statusCode} - ${errorMessage}`); + + res.status(statusCode).json(errorResponse); +}; + +/** + * 404 handler for unmatched routes + */ +export const notFoundHandler = (req: Request, res: Response) => { + const timestamp = new Date().toISOString(); + const requestId = req.requestId || 'unknown'; + + console.log(`[${timestamp}] [${requestId}] 404 - Route not found: ${req.method} ${req.path}`); + + const notFoundResponse: GenerateImageResponse = { + success: false, + message: 'Route not found', + error: `The requested endpoint ${req.method} ${req.path} does not exist` + }; + + res.status(404).json(notFoundResponse); +}; + +/** + * Async error wrapper to catch errors in async route handlers + */ +export const asyncHandler = (fn: Function) => (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); +}; \ No newline at end of file diff --git a/src/server/middleware/upload.ts b/src/server/middleware/upload.ts new file mode 100644 index 0000000..d6555b5 --- /dev/null +++ b/src/server/middleware/upload.ts @@ -0,0 +1,89 @@ +import multer, { Multer } from 'multer'; +import { Request, RequestHandler } from 'express'; + +// Configure multer for memory storage (we'll process files in memory) +const storage = multer.memoryStorage(); + +// File filter for image types only +const fileFilter = (req: Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => { + const allowedTypes = [ + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/webp' + ]; + + if (allowedTypes.includes(file.mimetype)) { + console.log(`[${new Date().toISOString()}] Accepted file: ${file.originalname} (${file.mimetype})`); + cb(null, true); + } else { + console.log(`[${new Date().toISOString()}] Rejected file: ${file.originalname} (${file.mimetype})`); + cb(new Error(`Unsupported file type: ${file.mimetype}. Allowed: PNG, JPEG, WebP`)); + } +}; + +// Configuration constants +const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB +const MAX_FILES = 3; + +// Configure multer with limits and file filtering +export const upload = multer({ + storage: storage, + limits: { + fileSize: MAX_FILE_SIZE, // 5MB per file + files: MAX_FILES, // Maximum 3 files + }, + fileFilter: fileFilter +}); + +// Middleware for handling reference images +export const uploadReferenceImages: RequestHandler = upload.array('referenceFiles', MAX_FILES); + +// Error handler for multer errors +export const handleUploadErrors = (error: any, req: Request, res: any, next: any) => { + if (error instanceof multer.MulterError) { + const timestamp = new Date().toISOString(); + console.error(`[${timestamp}] Multer error:`, error.message); + + switch (error.code) { + case 'LIMIT_FILE_SIZE': + return res.status(400).json({ + success: false, + error: `File too large. Maximum size: ${MAX_FILE_SIZE / (1024 * 1024)}MB`, + message: 'File upload failed' + }); + + case 'LIMIT_FILE_COUNT': + return res.status(400).json({ + success: false, + error: `Too many files. Maximum: ${MAX_FILES} files`, + message: 'File upload failed' + }); + + case 'LIMIT_UNEXPECTED_FILE': + return res.status(400).json({ + success: false, + error: 'Unexpected file field. Use "referenceFiles" for image uploads', + message: 'File upload failed' + }); + + default: + return res.status(400).json({ + success: false, + error: error.message, + message: 'File upload failed' + }); + } + } + + if (error.message.includes('Unsupported file type')) { + return res.status(400).json({ + success: false, + error: error.message, + message: 'File validation failed' + }); + } + + // Pass other errors to the next error handler + next(error); +}; \ No newline at end of file diff --git a/src/server/middleware/validation.ts b/src/server/middleware/validation.ts new file mode 100644 index 0000000..c0ba486 --- /dev/null +++ b/src/server/middleware/validation.ts @@ -0,0 +1,136 @@ +import { Request, Response, NextFunction } from 'express'; +import { GenerateImageRequestWithFiles } from '../types/api'; + +// Validation rules +const VALIDATION_RULES = { + prompt: { + minLength: 3, + maxLength: 2000, + required: true + }, + filename: { + minLength: 1, + maxLength: 100, + required: true, + pattern: /^[a-zA-Z0-9_-]+$/ // Only alphanumeric, underscore, hyphen + } +}; + +/** + * Sanitize filename to prevent directory traversal and invalid characters + */ +export const sanitizeFilename = (filename: string): string => { + return filename + .replace(/[^a-zA-Z0-9_-]/g, '_') // Replace invalid chars with underscore + .replace(/_{2,}/g, '_') // Replace multiple underscores with single + .replace(/^_+|_+$/g, '') // Remove leading/trailing underscores + .substring(0, 100); // Limit length +}; + +/** + * Validate the generate image request + */ +export const validateGenerateRequest = ( + req: any, + res: Response, + next: NextFunction +) => { + const timestamp = new Date().toISOString(); + const { prompt, filename } = req.body; + const errors: string[] = []; + + console.log(`[${timestamp}] [${req.requestId}] Validating generate request`); + + // Validate prompt + if (!prompt) { + errors.push('Prompt is required'); + } else if (typeof prompt !== 'string') { + errors.push('Prompt must be a string'); + } else if (prompt.trim().length < VALIDATION_RULES.prompt.minLength) { + errors.push(`Prompt must be at least ${VALIDATION_RULES.prompt.minLength} characters`); + } else if (prompt.length > VALIDATION_RULES.prompt.maxLength) { + errors.push(`Prompt must be less than ${VALIDATION_RULES.prompt.maxLength} characters`); + } + + // Validate filename + if (!filename) { + errors.push('Filename is required'); + } else if (typeof filename !== 'string') { + errors.push('Filename must be a string'); + } else if (filename.trim().length < VALIDATION_RULES.filename.minLength) { + errors.push('Filename cannot be empty'); + } else if (filename.length > VALIDATION_RULES.filename.maxLength) { + errors.push(`Filename must be less than ${VALIDATION_RULES.filename.maxLength} characters`); + } else if (!VALIDATION_RULES.filename.pattern.test(filename)) { + errors.push('Filename can only contain letters, numbers, underscores, and hyphens'); + } + + // Check for XSS attempts in prompt + const xssPatterns = [ + /