From 778d608b1696316cf8f301443771ec498a296048 Mon Sep 17 00:00:00 2001 From: Torben Sorensen Date: Tue, 25 Nov 2025 00:52:33 -0800 Subject: [PATCH] working ! testing ! --- package-lock.json | 111 ++++++++++++++++++++++++++++++++++++++++------ package.json | 2 + server.ts | 21 +++++++-- src/routes/ai.ts | 14 +++--- 4 files changed, 127 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1796d78b..b4fc3984 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "0.0.0", "dependencies": { "@google/genai": "^1.30.0", + "@types/connect-timeout": "^1.9.0", "bcrypt": "^5.1.1", + "connect-timeout": "^1.9.1", "cookie-parser": "^1.4.7", "date-fns": "^4.1.0", "express": "^5.1.0", @@ -4078,7 +4080,6 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -4100,12 +4101,20 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/connect-timeout": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@types/connect-timeout/-/connect-timeout-1.9.0.tgz", + "integrity": "sha512-tKAbro0/ATFeaSVa/N3Gv981+7KbuNQjoZEW8uI4NEdyxquWnylC5UTXwIOc2HD2q22ZjLr8H5YVKZL2+pGBGw==", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/cookie-parser": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", @@ -4220,7 +4229,6 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz", "integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -4232,7 +4240,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4245,7 +4252,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -4270,7 +4276,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, "license": "MIT" }, "node_modules/@types/ms": { @@ -4294,7 +4299,6 @@ "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -4417,14 +4421,12 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { @@ -4441,7 +4443,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4451,7 +4452,6 @@ "version": "1.15.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -4463,7 +4463,6 @@ "version": "0.17.6", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -6214,6 +6213,84 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/connect-timeout": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/connect-timeout/-/connect-timeout-1.9.1.tgz", + "integrity": "sha512-kDcadOXwOu+EEVs31iOu0TOg1yyRTqSNfyJaHYm5Z4K/hEIi9HJXSOWP9d+WQr/wff7wQJRh/HX63vK1+wBErw==", + "license": "MIT", + "dependencies": { + "http-errors": "~1.6.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect-timeout/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/connect-timeout/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/connect-timeout/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/connect-timeout/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/connect-timeout/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/connect-timeout/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "license": "ISC" + }, + "node_modules/connect-timeout/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -10579,6 +10656,15 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -13024,7 +13110,6 @@ "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, "license": "MIT" }, "node_modules/unpipe": { diff --git a/package.json b/package.json index f1839dd3..321a282c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ }, "dependencies": { "@google/genai": "^1.30.0", + "@types/connect-timeout": "^1.9.0", "bcrypt": "^5.1.1", + "connect-timeout": "^1.9.1", "cookie-parser": "^1.4.7", "date-fns": "^4.1.0", "express": "^5.1.0", diff --git a/server.ts b/server.ts index 858f07b0..97c165e7 100644 --- a/server.ts +++ b/server.ts @@ -1,5 +1,6 @@ // server.ts import express, { Request, Response, NextFunction } from 'express'; +import timeout from 'connect-timeout'; import cookieParser from 'cookie-parser'; import listEndpoints from 'express-list-endpoints'; import { getPool } from './src/services/db/connection'; @@ -53,9 +54,11 @@ app.use(express.urlencoded({ limit: '100mb', extended: true })); app.use(cookieParser()); // Middleware to parse cookies app.use(passport.initialize()); // Initialize Passport +// Add a request timeout middleware. This will help prevent requests from hanging indefinitely. +// We set a generous 5-minute timeout to accommodate slow AI processing for large flyers. +app.use(timeout('5m')); // --- Logging Middleware --- - const getDurationInMilliseconds = (start: [number, number]): number => { const NS_PER_SEC = 1e9; const NS_TO_MS = 1e6; @@ -66,6 +69,12 @@ const getDurationInMilliseconds = (start: [number, number]): number => { const requestLogger = (req: Request, res: Response, next: NextFunction) => { const start = process.hrtime(); const { method, originalUrl } = req; + + // If the request times out, log it. + if (req.timedout) { + logger.error(`REQUEST TIMEOUT: ${method} ${originalUrl} exceeded the 5m limit.`); + } + logger.debug(`[Request Logger] INCOMING: ${method} ${originalUrl}`); res.on('finish', () => { @@ -112,10 +121,16 @@ app.use('/api/users', userRouter); // Basic error handling middleware app.use((err: Error, req: Request, res: Response, next: NextFunction) => { - logger.error('Unhandled application error:', { error: err.stack }); + // Check if the error is from the timeout middleware + if (req.timedout) { + // The timeout event is already logged by the requestLogger, but we can add more detail here if needed. + // The response is handled by the timeout middleware itself, so we don't send another one. + return; + } + logger.error('Unhandled application error:', { error: err.stack, path: req.originalUrl }); // The 'next' parameter is required for Express to identify this as an error-handling middleware. // We log it here to satisfy the 'no-unused-vars' lint rule, as it's not called in this terminal handler. - logger.info('Terminal error handler invoked. The "next" function is part of the required signature.', { next: String(next) }); + // logger.info('Terminal error handler invoked. The "next" function is part of the required signature.', { next: String(next) }); if (!res.headersSent) { res.status(500).json({ message: 'Something broke!' }); } diff --git a/src/routes/ai.ts b/src/routes/ai.ts index ef85edf7..2dca2fa3 100644 --- a/src/routes/ai.ts +++ b/src/routes/ai.ts @@ -28,12 +28,16 @@ const upload = multer({ storage: storage }); * both authenticated and anonymous users to upload flyers. */ router.post('/process-flyer', optionalAuth, upload.array('flyerImages'), async (req: Request, res: Response, next: NextFunction) => { - // --- AI ROUTE DEBUG LOGGING --- - logger.debug('[API /ai/process-flyer] Request received.'); - logger.debug(`[API /ai/process-flyer] Files received: ${req.files ? (req.files as Express.Multer.File[]).length : 0}`); - logger.debug(`[API /ai/process-flyer] Body masterItems (first 50 chars): ${req.body.masterItems?.substring(0, 50)}...`); - // --- END DEBUG LOGGING --- try { + const files = req.files as Express.Multer.File[]; + const totalSize = files ? files.reduce((acc, file) => acc + file.size, 0) : 0; + const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2); + + // --- AI ROUTE DEBUG LOGGING --- + logger.debug('[API /ai/process-flyer] Request received.'); + logger.debug(`[API /ai/process-flyer] Files received: ${files ? files.length : 0}. Total size: ${totalSizeMB} MB.`); + // --- END DEBUG LOGGING --- + if (!req.files || !Array.isArray(req.files) || req.files.length === 0) { return res.status(400).json({ message: 'Flyer image files are required.' }); }