backend logging
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,6 +24,8 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
|
||||
53
backend/middleware/apiLogger.js
Normal file
53
backend/middleware/apiLogger.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const apiLogger = (req, res, next) => {
|
||||
const startTime = Date.now();
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
|
||||
const requestData = {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
userAgent: req.get('User-Agent'),
|
||||
ip: req.ip,
|
||||
userId: req.user?.id || 'anonymous',
|
||||
body: logger.sanitize(req.body),
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
headers: {
|
||||
'content-type': req.get('Content-Type'),
|
||||
'content-length': req.get('Content-Length'),
|
||||
'referer': req.get('Referer'),
|
||||
}
|
||||
};
|
||||
|
||||
reqLogger.info('API Request', requestData);
|
||||
|
||||
const originalSend = res.send;
|
||||
res.send = function(body) {
|
||||
const endTime = Date.now();
|
||||
const responseTime = endTime - startTime;
|
||||
|
||||
const responseData = {
|
||||
statusCode: res.statusCode,
|
||||
responseTime: `${responseTime}ms`,
|
||||
contentLength: res.get('Content-Length') || (body ? body.length : 0),
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
userId: req.user?.id || 'anonymous'
|
||||
};
|
||||
|
||||
if (res.statusCode >= 400 && res.statusCode < 500) {
|
||||
reqLogger.warn('API Response - Client Error', responseData);
|
||||
} else if (res.statusCode >= 500) {
|
||||
reqLogger.error('API Response - Server Error', responseData);
|
||||
} else {
|
||||
reqLogger.info('API Response - Success', responseData);
|
||||
}
|
||||
|
||||
return originalSend.call(this, body);
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = apiLogger;
|
||||
@@ -1,5 +1,6 @@
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { User } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const authenticateToken = async (req, res, next) => {
|
||||
// First try to get token from cookie
|
||||
@@ -43,7 +44,13 @@ const authenticateToken = async (req, res, next) => {
|
||||
});
|
||||
}
|
||||
|
||||
console.error("Auth middleware error:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Auth middleware error", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
tokenPresent: !!token,
|
||||
userId: req.user?.id
|
||||
});
|
||||
return res.status(403).json({
|
||||
error: "Invalid token",
|
||||
code: "INVALID_TOKEN",
|
||||
|
||||
33
backend/middleware/errorLogger.js
Normal file
33
backend/middleware/errorLogger.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const errorLogger = (err, req, res, next) => {
|
||||
// Create a request-specific logger with request ID
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
|
||||
// Log the error with context
|
||||
const errorContext = {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
userAgent: req.get('User-Agent'),
|
||||
ip: req.ip,
|
||||
userId: req.user?.id || 'anonymous',
|
||||
body: logger.sanitize(req.body),
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
statusCode: err.statusCode || 500,
|
||||
stack: err.stack,
|
||||
};
|
||||
|
||||
if (err.statusCode && err.statusCode < 500) {
|
||||
// Client errors (4xx)
|
||||
reqLogger.warn(`Client error: ${err.message}`, errorContext);
|
||||
} else {
|
||||
// Server errors (5xx)
|
||||
reqLogger.error(`Server error: ${err.message}`, errorContext);
|
||||
}
|
||||
|
||||
// Pass error to next middleware
|
||||
next(err);
|
||||
};
|
||||
|
||||
module.exports = errorLogger;
|
||||
@@ -1,3 +1,5 @@
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// HTTPS enforcement middleware
|
||||
const enforceHTTPS = (req, res, next) => {
|
||||
// Skip HTTPS enforcement in development
|
||||
@@ -20,11 +22,13 @@ const enforceHTTPS = (req, res, next) => {
|
||||
|
||||
// Log the redirect for monitoring
|
||||
if (req.headers.host !== allowedHost) {
|
||||
console.warn("[SECURITY] Host header mismatch during HTTPS redirect:", {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.warn("Host header mismatch during HTTPS redirect", {
|
||||
requestHost: req.headers.host,
|
||||
allowedHost,
|
||||
ip: req.ip,
|
||||
url: req.url,
|
||||
eventType: 'SECURITY_HOST_MISMATCH'
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,34 +74,21 @@ const addRequestId = (req, res, next) => {
|
||||
|
||||
// Log security events
|
||||
const logSecurityEvent = (eventType, details, req) => {
|
||||
const reqLogger = logger.withRequestId(req.id || "unknown");
|
||||
|
||||
const logEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
eventType,
|
||||
requestId: req.id || "unknown",
|
||||
ip: req.ip || req.connection.remoteAddress,
|
||||
userAgent: req.get("user-agent"),
|
||||
userId: req.user?.id || "anonymous",
|
||||
...details,
|
||||
};
|
||||
|
||||
// In production, this should write to a secure log file or service
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
console.log("[SECURITY]", JSON.stringify(logEntry));
|
||||
} else {
|
||||
console.log("[SECURITY]", eventType, details);
|
||||
}
|
||||
reqLogger.warn(`Security event: ${eventType}`, logEntry);
|
||||
};
|
||||
|
||||
// Sanitize error messages to prevent information leakage
|
||||
const sanitizeError = (err, req, res, next) => {
|
||||
// Log the full error internally
|
||||
console.error("Error:", {
|
||||
requestId: req.id,
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
userId: req.user?.id,
|
||||
});
|
||||
|
||||
// Send sanitized error to client
|
||||
const isDevelopment =
|
||||
process.env.NODE_ENV === "dev" || process.env.NODE_ENV === "development";
|
||||
|
||||
323
backend/package-lock.json
generated
323
backend/package-lock.json
generated
@@ -24,13 +24,16 @@
|
||||
"helmet": "^8.1.0",
|
||||
"jsdom": "^27.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"morgan": "^1.10.1",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"pg": "^8.16.3",
|
||||
"sequelize": "^6.37.7",
|
||||
"sequelize-cli": "^6.6.3",
|
||||
"stripe": "^18.4.0",
|
||||
"uuid": "^11.1.0"
|
||||
"uuid": "^11.1.0",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
@@ -607,6 +610,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@colors/colors": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
|
||||
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||
@@ -739,6 +751,17 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@dabh/diagnostics": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"colorspace": "1.1.x",
|
||||
"enabled": "2.0.x",
|
||||
"kuler": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@googlemaps/google-maps-services-js": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@googlemaps/google-maps-services-js/-/google-maps-services-js-3.4.2.tgz",
|
||||
@@ -1405,6 +1428,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/triple-beam": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
|
||||
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
@@ -1570,6 +1599,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -1731,6 +1766,24 @@
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-auth/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz",
|
||||
@@ -2163,6 +2216,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
|
||||
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.3",
|
||||
"color-string": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -2179,6 +2242,41 @@
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/color/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorspace": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
|
||||
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color": "^3.1.3",
|
||||
"text-hex": "1.0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -2603,6 +2701,12 @@
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
||||
},
|
||||
"node_modules/enabled": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
|
||||
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
@@ -2897,6 +3001,12 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fecha": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
||||
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
@@ -2920,6 +3030,15 @@
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/file-stream-rotator": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
|
||||
"integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moment": "^2.29.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -2971,6 +3090,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fn.name": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
|
||||
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
@@ -3615,6 +3740,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
|
||||
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -3704,7 +3835,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -4733,6 +4863,12 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/kuler": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
|
||||
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/leven": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
@@ -4803,6 +4939,23 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
|
||||
},
|
||||
"node_modules/logform": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
|
||||
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@colors/colors": "1.6.0",
|
||||
"@types/triple-beam": "^1.3.2",
|
||||
"fecha": "^4.2.0",
|
||||
"ms": "^2.1.1",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"triple-beam": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
@@ -5002,6 +5155,49 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/morgan": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
|
||||
"integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"basic-auth": "~2.0.1",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"on-headers": "~1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/morgan/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/morgan/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/morgan/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/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -5285,6 +5481,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-hash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
@@ -5307,6 +5512,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",
|
||||
@@ -5315,6 +5529,15 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/one-time": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
|
||||
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fn.name": "1.x.x"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
@@ -5972,6 +6195,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
@@ -6249,6 +6481,15 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
||||
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-update-notifier": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||
@@ -6366,6 +6607,15 @@
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stack-trace": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
@@ -6732,6 +6982,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/text-hex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "7.0.14",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.14.tgz",
|
||||
@@ -6815,6 +7071,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/triple-beam": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
|
||||
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tsscmp": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
|
||||
@@ -7120,6 +7385,60 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/winston": {
|
||||
"version": "3.17.0",
|
||||
"resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
|
||||
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@colors/colors": "^1.6.0",
|
||||
"@dabh/diagnostics": "^2.0.2",
|
||||
"async": "^3.2.3",
|
||||
"is-stream": "^2.0.0",
|
||||
"logform": "^2.7.0",
|
||||
"one-time": "^1.0.0",
|
||||
"readable-stream": "^3.4.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"stack-trace": "0.0.x",
|
||||
"triple-beam": "^1.3.0",
|
||||
"winston-transport": "^4.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/winston-daily-rotate-file": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
|
||||
"integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-stream-rotator": "^0.6.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"triple-beam": "^1.4.1",
|
||||
"winston-transport": "^4.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"winston": "^3"
|
||||
}
|
||||
},
|
||||
"node_modules/winston-transport": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
|
||||
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"logform": "^2.7.0",
|
||||
"readable-stream": "^3.6.2",
|
||||
"triple-beam": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wkx": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
|
||||
|
||||
@@ -36,13 +36,16 @@
|
||||
"helmet": "^8.1.0",
|
||||
"jsdom": "^27.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"morgan": "^1.10.1",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.3",
|
||||
"pg": "^8.16.3",
|
||||
"sequelize": "^6.37.7",
|
||||
"sequelize-cli": "^6.6.3",
|
||||
"stripe": "^18.4.0",
|
||||
"uuid": "^11.1.0"
|
||||
"uuid": "^11.1.0",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require("express");
|
||||
const jwt = require("jsonwebtoken");
|
||||
const { OAuth2Client } = require("google-auth-library");
|
||||
const { User } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const logger = require("../utils/logger");
|
||||
const {
|
||||
sanitizeInput,
|
||||
validateRegistration,
|
||||
@@ -84,6 +85,13 @@ router.post(
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User registration successful", {
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
user: {
|
||||
id: user.id,
|
||||
@@ -95,7 +103,13 @@ router.post(
|
||||
// Don't send token in response body for security
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Registration error:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Registration error", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
email: req.body.email,
|
||||
username: req.body.username
|
||||
});
|
||||
res.status(500).json({ error: "Registration failed. Please try again." });
|
||||
}
|
||||
}
|
||||
@@ -164,6 +178,12 @@ router.post(
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User login successful", {
|
||||
userId: user.id,
|
||||
email: user.email
|
||||
});
|
||||
|
||||
res.json({
|
||||
user: {
|
||||
id: user.id,
|
||||
@@ -175,7 +195,12 @@ router.post(
|
||||
// Don't send token in response body for security
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Login error", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
email: req.body.email
|
||||
});
|
||||
res.status(500).json({ error: "Login failed. Please try again." });
|
||||
}
|
||||
}
|
||||
@@ -271,6 +296,13 @@ router.post(
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Google authentication successful", {
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
isNewUser: !user.createdAt || (Date.now() - new Date(user.createdAt).getTime()) < 1000
|
||||
});
|
||||
|
||||
res.json({
|
||||
user: {
|
||||
id: user.id,
|
||||
@@ -298,7 +330,12 @@ router.post(
|
||||
.status(400)
|
||||
.json({ error: "Malformed Google token. Please try again." });
|
||||
}
|
||||
console.error("Google auth error:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Google auth error", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
tokenInfo: logger.sanitize({ idToken: req.body.idToken })
|
||||
});
|
||||
res
|
||||
.status(500)
|
||||
.json({ error: "Google authentication failed. Please try again." });
|
||||
@@ -341,6 +378,11 @@ router.post("/refresh", async (req, res) => {
|
||||
maxAge: 15 * 60 * 1000,
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Token refresh successful", {
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
res.json({
|
||||
user: {
|
||||
id: user.id,
|
||||
@@ -351,13 +393,23 @@ router.post("/refresh", async (req, res) => {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Token refresh error:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Token refresh error", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user?.id
|
||||
});
|
||||
res.status(401).json({ error: "Invalid or expired refresh token" });
|
||||
}
|
||||
});
|
||||
|
||||
// Logout endpoint
|
||||
router.post("/logout", (req, res) => {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User logout", {
|
||||
userId: req.user?.id || 'anonymous'
|
||||
});
|
||||
|
||||
// Clear cookies
|
||||
res.clearCookie("accessToken");
|
||||
res.clearCookie("refreshToken");
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require('express');
|
||||
const { Op } = require('sequelize');
|
||||
const { ItemRequest, ItemRequestResponse, User, Item } = require('../models');
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
@@ -38,6 +39,15 @@ router.get('/', async (req, res) => {
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item requests fetched", {
|
||||
search,
|
||||
status,
|
||||
requestsCount: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
requests: rows,
|
||||
totalPages: Math.ceil(count / limit),
|
||||
@@ -45,6 +55,12 @@ router.get('/', async (req, res) => {
|
||||
totalRequests: count
|
||||
});
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item requests fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
query: req.query
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -78,8 +94,20 @@ router.get('/my-requests', authenticateToken, async (req, res) => {
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User item requests fetched", {
|
||||
userId: req.user.id,
|
||||
requestsCount: requests.length
|
||||
});
|
||||
|
||||
res.json(requests);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("User item requests fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -115,8 +143,20 @@ router.get('/:id', async (req, res) => {
|
||||
return res.status(404).json({ error: 'Item request not found' });
|
||||
}
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item request fetched", {
|
||||
requestId: req.params.id,
|
||||
requesterId: request.requesterId
|
||||
});
|
||||
|
||||
res.json(request);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item request fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
requestId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -138,8 +178,22 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item request created", {
|
||||
requestId: request.id,
|
||||
requesterId: req.user.id,
|
||||
title: req.body.title
|
||||
});
|
||||
|
||||
res.status(201).json(requestWithRequester);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item request creation failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
requesterId: req.user.id,
|
||||
requestData: logger.sanitize(req.body)
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -168,8 +222,21 @@ router.put('/:id', authenticateToken, async (req, res) => {
|
||||
]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item request updated", {
|
||||
requestId: req.params.id,
|
||||
requesterId: req.user.id
|
||||
});
|
||||
|
||||
res.json(updatedRequest);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item request update failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
requestId: req.params.id,
|
||||
requesterId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -187,8 +254,22 @@ router.delete('/:id', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
await request.destroy();
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item request deleted", {
|
||||
requestId: req.params.id,
|
||||
requesterId: req.user.id
|
||||
});
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item request deletion failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
requestId: req.params.id,
|
||||
requesterId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -231,8 +312,22 @@ router.post('/:id/responses', authenticateToken, async (req, res) => {
|
||||
]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item request response created", {
|
||||
requestId: req.params.id,
|
||||
responseId: response.id,
|
||||
responderId: req.user.id
|
||||
});
|
||||
|
||||
res.status(201).json(responseWithDetails);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item request response creation failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
requestId: req.params.id,
|
||||
responderId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -277,8 +372,23 @@ router.put('/responses/:responseId/status', authenticateToken, async (req, res)
|
||||
]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item request response status updated", {
|
||||
responseId: req.params.responseId,
|
||||
newStatus: status,
|
||||
requesterId: req.user.id,
|
||||
requestFulfilled: status === 'accepted'
|
||||
});
|
||||
|
||||
res.json(updatedResponse);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item request response status update failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
responseId: req.params.responseId,
|
||||
requesterId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require("express");
|
||||
const { Op } = require("sequelize");
|
||||
const { Item, User, Rental } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const logger = require("../utils/logger");
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/", async (req, res) => {
|
||||
@@ -60,6 +61,14 @@ router.get("/", async (req, res) => {
|
||||
return itemData;
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Items search completed", {
|
||||
filters: { minPrice, maxPrice, city, zipCode, search },
|
||||
resultsCount: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit)
|
||||
});
|
||||
|
||||
res.json({
|
||||
items: itemsWithRoundedCoords,
|
||||
totalPages: Math.ceil(count / limit),
|
||||
@@ -67,6 +76,12 @@ router.get("/", async (req, res) => {
|
||||
totalItems: count,
|
||||
});
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Items search failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
query: req.query
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -87,8 +102,20 @@ router.get("/recommendations", authenticateToken, async (req, res) => {
|
||||
order: [["createdAt", "DESC"]],
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Recommendations fetched", {
|
||||
userId: req.user.id,
|
||||
recommendationsCount: recommendations.length
|
||||
});
|
||||
|
||||
res.json(recommendations);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Recommendations fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -120,12 +147,25 @@ router.get('/:id/reviews', async (req, res) => {
|
||||
? reviews.reduce((sum, review) => sum + review.itemRating, 0) / reviews.length
|
||||
: 0;
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item reviews fetched", {
|
||||
itemId: req.params.id,
|
||||
reviewsCount: reviews.length,
|
||||
averageRating
|
||||
});
|
||||
|
||||
res.json({
|
||||
reviews,
|
||||
averageRating,
|
||||
totalReviews: reviews.length
|
||||
});
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item reviews fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
itemId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -155,8 +195,20 @@ router.get("/:id", async (req, res) => {
|
||||
itemResponse.longitude = Math.round(parseFloat(itemResponse.longitude) * 100) / 100;
|
||||
}
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item fetched", {
|
||||
itemId: req.params.id,
|
||||
ownerId: item.ownerId
|
||||
});
|
||||
|
||||
res.json(itemResponse);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
itemId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -178,8 +230,22 @@ router.post("/", authenticateToken, async (req, res) => {
|
||||
],
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item created", {
|
||||
itemId: item.id,
|
||||
ownerId: req.user.id,
|
||||
itemName: req.body.name
|
||||
});
|
||||
|
||||
res.status(201).json(itemWithOwner);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item creation failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
ownerId: req.user.id,
|
||||
itemData: logger.sanitize(req.body)
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -208,8 +274,21 @@ router.put("/:id", authenticateToken, async (req, res) => {
|
||||
],
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item updated", {
|
||||
itemId: req.params.id,
|
||||
ownerId: req.user.id
|
||||
});
|
||||
|
||||
res.json(updatedItem);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item update failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
itemId: req.params.id,
|
||||
ownerId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -227,8 +306,22 @@ router.delete("/:id", authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
await item.destroy();
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item deleted", {
|
||||
itemId: req.params.id,
|
||||
ownerId: req.user.id
|
||||
});
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Item deletion failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
itemId: req.params.id,
|
||||
ownerId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ const router = express.Router();
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const rateLimiter = require("../middleware/rateLimiter");
|
||||
const googleMapsService = require("../services/googleMapsService");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
// Input validation middleware
|
||||
const validateInput = (req, res, next) => {
|
||||
@@ -34,8 +35,12 @@ const validateInput = (req, res, next) => {
|
||||
};
|
||||
|
||||
// Error handling middleware
|
||||
const handleServiceError = (error, res) => {
|
||||
console.error("Maps service error:", error.message);
|
||||
const handleServiceError = (error, res, req) => {
|
||||
const reqLogger = logger.withRequestId(req?.id);
|
||||
reqLogger.error("Maps service error", {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
if (error.message.includes("API key not configured")) {
|
||||
return res.status(503).json({
|
||||
@@ -87,17 +92,16 @@ router.post(
|
||||
);
|
||||
|
||||
// Log request for monitoring (without sensitive data)
|
||||
console.log(
|
||||
`Places Autocomplete: user=${
|
||||
req.user?.id || "anonymous"
|
||||
}, query_length=${input.length}, results=${
|
||||
result.predictions?.length || 0
|
||||
}`
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Places Autocomplete request", {
|
||||
userId: req.user?.id || "anonymous",
|
||||
queryLength: input.length,
|
||||
resultsCount: result.predictions?.length || 0
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
handleServiceError(error, res);
|
||||
handleServiceError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -127,15 +131,15 @@ router.post(
|
||||
const result = await googleMapsService.getPlaceDetails(placeId, options);
|
||||
|
||||
// Log request for monitoring
|
||||
console.log(
|
||||
`Place Details: user=${
|
||||
req.user?.id || "anonymous"
|
||||
}, placeId=${placeId.substring(0, 10)}...`
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Place Details request", {
|
||||
userId: req.user?.id || "anonymous",
|
||||
placeIdPrefix: placeId.substring(0, 10) + "..."
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
handleServiceError(error, res);
|
||||
handleServiceError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -165,15 +169,15 @@ router.post(
|
||||
const result = await googleMapsService.geocodeAddress(address, options);
|
||||
|
||||
// Log request for monitoring
|
||||
console.log(
|
||||
`Geocoding: user=${req.user?.id || "anonymous"}, address_length=${
|
||||
address.length
|
||||
}`
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Geocoding request", {
|
||||
userId: req.user?.id || "anonymous",
|
||||
addressLength: address.length
|
||||
});
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
handleServiceError(error, res);
|
||||
handleServiceError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
const { Message, User } = require('../models');
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
const logger = require('../utils/logger');
|
||||
const router = express.Router();
|
||||
|
||||
// Get all messages for the current user (inbox)
|
||||
@@ -17,8 +18,21 @@ router.get('/', authenticateToken, async (req, res) => {
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Messages inbox fetched", {
|
||||
userId: req.user.id,
|
||||
messageCount: messages.length
|
||||
});
|
||||
|
||||
res.json(messages);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Messages inbox fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -37,8 +51,21 @@ router.get('/sent', authenticateToken, async (req, res) => {
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Sent messages fetched", {
|
||||
userId: req.user.id,
|
||||
messageCount: messages.length
|
||||
});
|
||||
|
||||
res.json(messages);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Sent messages fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -86,8 +113,22 @@ router.get('/:id', authenticateToken, async (req, res) => {
|
||||
await message.update({ isRead: true });
|
||||
}
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Message fetched", {
|
||||
userId: req.user.id,
|
||||
messageId: req.params.id,
|
||||
markedAsRead: message.receiverId === req.user.id && !message.isRead
|
||||
});
|
||||
|
||||
res.json(message);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Message fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
messageId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -124,8 +165,23 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
}]
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Message sent", {
|
||||
senderId: req.user.id,
|
||||
receiverId: receiverId,
|
||||
messageId: message.id,
|
||||
isReply: !!parentMessageId
|
||||
});
|
||||
|
||||
res.status(201).json(messageWithSender);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Message send failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
senderId: req.user.id,
|
||||
receiverId: req.body.receiverId
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -145,8 +201,22 @@ router.put('/:id/read', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
await message.update({ isRead: true });
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Message marked as read", {
|
||||
userId: req.user.id,
|
||||
messageId: req.params.id
|
||||
});
|
||||
|
||||
res.json(message);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Message mark as read failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
messageId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -160,8 +230,20 @@ router.get('/unread/count', authenticateToken, async (req, res) => {
|
||||
isRead: false
|
||||
}
|
||||
});
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Unread message count fetched", {
|
||||
userId: req.user.id,
|
||||
unreadCount: count
|
||||
});
|
||||
|
||||
res.json({ count });
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Unread message count fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ const { Rental, Item, User } = require("../models"); // Import from models/index
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const FeeCalculator = require("../utils/feeCalculator");
|
||||
const RefundService = require("../services/refundService");
|
||||
const logger = require("../utils/logger");
|
||||
const router = express.Router();
|
||||
|
||||
// Helper function to check and update review visibility
|
||||
@@ -67,7 +68,12 @@ router.get("/my-rentals", authenticateToken, async (req, res) => {
|
||||
|
||||
res.json(rentals);
|
||||
} catch (error) {
|
||||
console.error("Error in my-rentals route:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error in my-rentals route", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: "Failed to fetch rentals" });
|
||||
}
|
||||
});
|
||||
@@ -90,7 +96,12 @@ router.get("/my-listings", authenticateToken, async (req, res) => {
|
||||
|
||||
res.json(rentals);
|
||||
} catch (error) {
|
||||
console.error("Error in my-listings route:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error in my-listings route", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: "Failed to fetch listings" });
|
||||
}
|
||||
});
|
||||
@@ -125,7 +136,13 @@ router.get("/:id", authenticateToken, async (req, res) => {
|
||||
|
||||
res.json(rental);
|
||||
} catch (error) {
|
||||
console.error("Error fetching rental:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error fetching rental", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
rentalId: req.params.id,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: "Failed to fetch rental" });
|
||||
}
|
||||
});
|
||||
@@ -355,7 +372,13 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
res.json(updatedRental);
|
||||
return;
|
||||
} catch (paymentError) {
|
||||
console.error("Payment failed during approval:", paymentError);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Payment failed during approval", {
|
||||
error: paymentError.message,
|
||||
stack: paymentError.stack,
|
||||
rentalId: req.params.id,
|
||||
userId: req.user.id
|
||||
});
|
||||
// Keep rental as pending, but inform of payment failure
|
||||
return res.status(400).json({
|
||||
error: "Payment failed during approval",
|
||||
@@ -538,7 +561,15 @@ router.post("/calculate-fees", authenticateToken, async (req, res) => {
|
||||
display: displayFees,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error calculating fees:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error calculating fees", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
startDate: req.query.startDate,
|
||||
endDate: req.query.endDate,
|
||||
itemId: req.query.itemId
|
||||
});
|
||||
res.status(500).json({ error: "Failed to calculate fees" });
|
||||
}
|
||||
});
|
||||
@@ -566,7 +597,12 @@ router.get("/earnings/status", authenticateToken, async (req, res) => {
|
||||
|
||||
res.json(ownerRentals);
|
||||
} catch (error) {
|
||||
console.error("Error getting earnings status:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error getting earnings status", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -580,7 +616,13 @@ router.get("/:id/refund-preview", authenticateToken, async (req, res) => {
|
||||
);
|
||||
res.json(preview);
|
||||
} catch (error) {
|
||||
console.error("Error getting refund preview:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error getting refund preview", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
rentalId: req.params.id,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -618,7 +660,13 @@ router.post("/:id/cancel", authenticateToken, async (req, res) => {
|
||||
refund: result.refund,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error cancelling rental:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error cancelling rental", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
rentalId: req.params.id,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(400).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require("express");
|
||||
const { authenticateToken } = require("../middleware/auth");
|
||||
const { User, Item } = require("../models");
|
||||
const StripeService = require("../services/stripeService");
|
||||
const logger = require("../utils/logger");
|
||||
const router = express.Router();
|
||||
|
||||
// Get checkout session status
|
||||
@@ -11,6 +12,14 @@ router.get("/checkout-session/:sessionId", async (req, res) => {
|
||||
|
||||
const session = await StripeService.getCheckoutSession(sessionId);
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Stripe checkout session retrieved", {
|
||||
sessionId: sessionId,
|
||||
status: session.status,
|
||||
payment_status: session.payment_status,
|
||||
metadata: session.metadata,
|
||||
});
|
||||
|
||||
res.json({
|
||||
status: session.status,
|
||||
payment_status: session.payment_status,
|
||||
@@ -19,7 +28,12 @@ router.get("/checkout-session/:sessionId", async (req, res) => {
|
||||
metadata: session.metadata,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error retrieving checkout session:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Stripe checkout session retrieval failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
sessionId: sessionId,
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -51,12 +65,23 @@ router.post("/accounts", authenticateToken, async (req, res) => {
|
||||
stripeConnectedAccountId: account.id,
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Stripe connected account created", {
|
||||
userId: req.user.id,
|
||||
stripeConnectedAccountId: account.id,
|
||||
});
|
||||
|
||||
res.json({
|
||||
stripeConnectedAccountId: account.id,
|
||||
success: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating connected account:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Stripe connected account creation failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -84,12 +109,25 @@ router.post("/account-links", authenticateToken, async (req, res) => {
|
||||
returnUrl
|
||||
);
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Stripe account link created", {
|
||||
userId: req.user.id,
|
||||
stripeConnectedAccountId: user.stripeConnectedAccountId,
|
||||
expiresAt: accountLink.expires_at,
|
||||
});
|
||||
|
||||
res.json({
|
||||
url: accountLink.url,
|
||||
expiresAt: accountLink.expires_at,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating account link:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Stripe account link creation failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
stripeConnectedAccountId: user?.stripeConnectedAccountId,
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -107,6 +145,14 @@ router.get("/account-status", authenticateToken, async (req, res) => {
|
||||
user.stripeConnectedAccountId
|
||||
);
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Stripe account status retrieved", {
|
||||
userId: req.user.id,
|
||||
stripeConnectedAccountId: user.stripeConnectedAccountId,
|
||||
detailsSubmitted: accountStatus.details_submitted,
|
||||
payoutsEnabled: accountStatus.payouts_enabled,
|
||||
});
|
||||
|
||||
res.json({
|
||||
accountId: accountStatus.id,
|
||||
detailsSubmitted: accountStatus.details_submitted,
|
||||
@@ -115,13 +161,22 @@ router.get("/account-status", authenticateToken, async (req, res) => {
|
||||
requirements: accountStatus.requirements,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error getting account status:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Stripe account status retrieval failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
stripeConnectedAccountId: user?.stripeConnectedAccountId,
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Create embedded setup checkout session for collecting payment method
|
||||
router.post("/create-setup-checkout-session", authenticateToken, async (req, res) => {
|
||||
router.post(
|
||||
"/create-setup-checkout-session",
|
||||
authenticateToken,
|
||||
async (req, res) => {
|
||||
try {
|
||||
const { rentalData } = req.body;
|
||||
|
||||
@@ -140,8 +195,8 @@ router.post("/create-setup-checkout-session", authenticateToken, async (req, res
|
||||
email: user.email,
|
||||
name: `${user.firstName} ${user.lastName}`,
|
||||
metadata: {
|
||||
userId: user.id.toString()
|
||||
}
|
||||
userId: user.id.toString(),
|
||||
},
|
||||
});
|
||||
|
||||
stripeCustomerId = customer.id;
|
||||
@@ -151,23 +206,40 @@ router.post("/create-setup-checkout-session", authenticateToken, async (req, res
|
||||
}
|
||||
|
||||
// Add rental data to metadata if provided
|
||||
const metadata = rentalData ? {
|
||||
rentalData: JSON.stringify(rentalData)
|
||||
} : {};
|
||||
const metadata = rentalData
|
||||
? {
|
||||
rentalData: JSON.stringify(rentalData),
|
||||
}
|
||||
: {};
|
||||
|
||||
const session = await StripeService.createSetupCheckoutSession({
|
||||
customerId: stripeCustomerId,
|
||||
metadata
|
||||
metadata,
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Stripe setup checkout session created", {
|
||||
userId: req.user.id,
|
||||
stripeCustomerId: stripeCustomerId,
|
||||
sessionId: session.id,
|
||||
hasRentalData: !!rentalData,
|
||||
});
|
||||
|
||||
res.json({
|
||||
clientSecret: session.client_secret,
|
||||
sessionId: session.id
|
||||
sessionId: session.id,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating setup checkout session:", error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Stripe setup checkout session creation failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
stripeCustomerId: user?.stripeCustomerId,
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require('express');
|
||||
const { User, UserAddress } = require('../models'); // Import from models/index.js to get models with associations
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
const { uploadProfileImage } = require('../middleware/upload');
|
||||
const logger = require('../utils/logger');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const router = express.Router();
|
||||
@@ -11,8 +12,20 @@ router.get('/profile', authenticateToken, async (req, res) => {
|
||||
const user = await User.findByPk(req.user.id, {
|
||||
attributes: { exclude: ['password'] }
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User profile fetched", {
|
||||
userId: req.user.id
|
||||
});
|
||||
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("User profile fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -24,8 +37,20 @@ router.get('/addresses', authenticateToken, async (req, res) => {
|
||||
where: { userId: req.user.id },
|
||||
order: [['isPrimary', 'DESC'], ['createdAt', 'ASC']]
|
||||
});
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User addresses fetched", {
|
||||
userId: req.user.id,
|
||||
addressCount: addresses.length
|
||||
});
|
||||
|
||||
res.json(addresses);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("User addresses fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -36,8 +61,21 @@ router.post('/addresses', authenticateToken, async (req, res) => {
|
||||
...req.body,
|
||||
userId: req.user.id
|
||||
});
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User address created", {
|
||||
userId: req.user.id,
|
||||
addressId: address.id
|
||||
});
|
||||
|
||||
res.status(201).json(address);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("User address creation failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
addressData: logger.sanitize(req.body)
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -55,8 +93,22 @@ router.put('/addresses/:id', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
await address.update(req.body);
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User address updated", {
|
||||
userId: req.user.id,
|
||||
addressId: req.params.id
|
||||
});
|
||||
|
||||
res.json(address);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("User address update failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
addressId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -74,8 +126,22 @@ router.delete('/addresses/:id', authenticateToken, async (req, res) => {
|
||||
}
|
||||
|
||||
await address.destroy();
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("User address deleted", {
|
||||
userId: req.user.id,
|
||||
addressId: req.params.id
|
||||
});
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("User address deletion failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id,
|
||||
addressId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -126,8 +192,19 @@ router.get('/:id', async (req, res) => {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Public user profile fetched", {
|
||||
requestedUserId: req.params.id
|
||||
});
|
||||
|
||||
res.json(user);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Public user profile fetch failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
requestedUserId: req.params.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
@@ -185,7 +262,11 @@ router.put('/profile', authenticateToken, async (req, res) => {
|
||||
router.post('/profile/image', authenticateToken, (req, res) => {
|
||||
uploadProfileImage(req, res, async (err) => {
|
||||
if (err) {
|
||||
console.error('Upload error:', err);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Profile image upload error", {
|
||||
error: err.message,
|
||||
userId: req.user.id
|
||||
});
|
||||
return res.status(400).json({ error: err.message });
|
||||
}
|
||||
|
||||
@@ -201,7 +282,12 @@ router.post('/profile/image', authenticateToken, (req, res) => {
|
||||
try {
|
||||
await fs.unlink(oldImagePath);
|
||||
} catch (unlinkErr) {
|
||||
console.error('Error deleting old image:', unlinkErr);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.warn("Error deleting old profile image", {
|
||||
error: unlinkErr.message,
|
||||
userId: req.user.id,
|
||||
oldImagePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,13 +296,24 @@ router.post('/profile/image', authenticateToken, (req, res) => {
|
||||
profileImage: req.file.filename
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Profile image uploaded successfully", {
|
||||
userId: req.user.id,
|
||||
filename: req.file.filename
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: 'Profile image uploaded successfully',
|
||||
filename: req.file.filename,
|
||||
imageUrl: `/uploads/profiles/${req.file.filename}`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Database update error:', error);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Profile image database update failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
userId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: 'Failed to update profile image' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@ const path = require("path");
|
||||
const helmet = require("helmet");
|
||||
const { sequelize } = require("./models"); // Import from models/index.js to ensure associations are loaded
|
||||
const { cookieParser } = require("./middleware/csrf");
|
||||
const logger = require("./utils/logger");
|
||||
const morgan = require("morgan");
|
||||
|
||||
const authRoutes = require("./routes/auth");
|
||||
const userRoutes = require("./routes/users");
|
||||
@@ -34,6 +36,8 @@ const {
|
||||
sanitizeError,
|
||||
} = require("./middleware/security");
|
||||
const { generalLimiter } = require("./middleware/rateLimiter");
|
||||
const errorLogger = require("./middleware/errorLogger");
|
||||
const apiLogger = require("./middleware/apiLogger");
|
||||
|
||||
// Apply security middleware
|
||||
app.use(enforceHTTPS);
|
||||
@@ -60,6 +64,12 @@ app.use(
|
||||
// Cookie parser for CSRF
|
||||
app.use(cookieParser);
|
||||
|
||||
// HTTP request logging
|
||||
app.use(morgan('combined', { stream: logger.stream }));
|
||||
|
||||
// API request/response logging
|
||||
app.use("/api/", apiLogger);
|
||||
|
||||
// General rate limiting for all routes
|
||||
app.use("/api/", generalLimiter);
|
||||
|
||||
@@ -107,6 +117,7 @@ app.get("/", (req, res) => {
|
||||
});
|
||||
|
||||
// Error handling middleware (must be last)
|
||||
app.use(errorLogger);
|
||||
app.use(sanitizeError);
|
||||
|
||||
const PORT = process.env.PORT || 5000;
|
||||
@@ -114,15 +125,16 @@ const PORT = process.env.PORT || 5000;
|
||||
sequelize
|
||||
.sync({ alter: true })
|
||||
.then(() => {
|
||||
console.log("Database synced");
|
||||
logger.info("Database synced successfully");
|
||||
|
||||
// Start the payout processor
|
||||
const payoutJobs = PayoutProcessor.startScheduledPayouts();
|
||||
logger.info("Payout processor started");
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
logger.info(`Server is running on port ${PORT}`, { port: PORT, environment: env });
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Unable to sync database:", err);
|
||||
logger.error("Unable to sync database", { error: err.message, stack: err.stack });
|
||||
});
|
||||
|
||||
137
backend/utils/logger.js
Normal file
137
backend/utils/logger.js
Normal file
@@ -0,0 +1,137 @@
|
||||
const winston = require('winston');
|
||||
const DailyRotateFile = require('winston-daily-rotate-file');
|
||||
|
||||
// Define log levels and colors
|
||||
const logLevels = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
http: 3,
|
||||
debug: 4,
|
||||
};
|
||||
|
||||
winston.addColors({
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
info: 'green',
|
||||
http: 'magenta',
|
||||
debug: 'white',
|
||||
});
|
||||
|
||||
// Determine log level based on environment
|
||||
const level = () => {
|
||||
const env = process.env.NODE_ENV || 'dev';
|
||||
const isDevelopment = env === 'dev' || env === 'development';
|
||||
return isDevelopment ? 'debug' : process.env.LOG_LEVEL || 'info';
|
||||
};
|
||||
|
||||
// Define log format
|
||||
const logFormat = winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
|
||||
winston.format.colorize({ all: true }),
|
||||
winston.format.printf((info) => {
|
||||
if (info.stack) {
|
||||
return `${info.timestamp} ${info.level}: ${info.message}\n${info.stack}`;
|
||||
}
|
||||
return `${info.timestamp} ${info.level}: ${info.message}`;
|
||||
}),
|
||||
);
|
||||
|
||||
// Define JSON format for file logging
|
||||
const jsonFormat = winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
);
|
||||
|
||||
// Create transports array
|
||||
const transports = [
|
||||
// Console transport for development
|
||||
new winston.transports.Console({
|
||||
level: level(),
|
||||
format: logFormat,
|
||||
}),
|
||||
|
||||
// Daily rotate file for all logs
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/application-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
level: 'info',
|
||||
format: jsonFormat,
|
||||
}),
|
||||
|
||||
// Daily rotate file for error logs only
|
||||
new DailyRotateFile({
|
||||
filename: 'logs/error-%DATE%.log',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
level: 'error',
|
||||
format: jsonFormat,
|
||||
}),
|
||||
];
|
||||
|
||||
// Create the logger
|
||||
const logger = winston.createLogger({
|
||||
level: level(),
|
||||
levels: logLevels,
|
||||
format: jsonFormat,
|
||||
transports,
|
||||
exceptionHandlers: [
|
||||
new winston.transports.File({ filename: 'logs/exceptions.log' })
|
||||
],
|
||||
rejectionHandlers: [
|
||||
new winston.transports.File({ filename: 'logs/rejections.log' })
|
||||
],
|
||||
});
|
||||
|
||||
// Create a stream object for Morgan
|
||||
logger.stream = {
|
||||
write: (message) => {
|
||||
// Remove trailing newline from Morgan and log as http level
|
||||
logger.http(message.trim());
|
||||
},
|
||||
};
|
||||
|
||||
// Add request ID correlation function
|
||||
logger.withRequestId = (requestId) => {
|
||||
return {
|
||||
error: (message, meta = {}) => logger.error(message, { ...meta, requestId }),
|
||||
warn: (message, meta = {}) => logger.warn(message, { ...meta, requestId }),
|
||||
info: (message, meta = {}) => logger.info(message, { ...meta, requestId }),
|
||||
http: (message, meta = {}) => logger.http(message, { ...meta, requestId }),
|
||||
debug: (message, meta = {}) => logger.debug(message, { ...meta, requestId }),
|
||||
};
|
||||
};
|
||||
|
||||
// Add sanitization helper for sensitive data
|
||||
logger.sanitize = (data) => {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const sensitiveFields = [
|
||||
'password', 'token', 'secret', 'key', 'authorization', 'cookie',
|
||||
'ssn', 'credit_card', 'cvv', 'pin', 'account_number'
|
||||
];
|
||||
|
||||
const sanitized = JSON.parse(JSON.stringify(data));
|
||||
|
||||
const sanitizeObject = (obj) => {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const lowerKey = key.toLowerCase();
|
||||
if (sensitiveFields.some(field => lowerKey.includes(field))) {
|
||||
obj[key] = '[REDACTED]';
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
sanitizeObject(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
sanitizeObject(sanitized);
|
||||
return sanitized;
|
||||
};
|
||||
|
||||
module.exports = logger;
|
||||
Reference in New Issue
Block a user