Files
cannaiq/backend/dist/middleware/apiTokenTracker.js
Kelly 66e07b2009 fix(monitor): remove non-existent worker columns from job_run_logs query
The job_run_logs table tracks scheduled job orchestration, not individual
worker jobs. Worker info (worker_id, worker_hostname) belongs on
dispensary_crawl_jobs, not job_run_logs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 18:45:05 -07:00

95 lines
3.0 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.trackApiUsage = trackApiUsage;
exports.checkRateLimit = checkRateLimit;
const migrate_1 = require("../db/migrate");
async function trackApiUsage(req, res, next) {
// Only track if authenticated via API token
if (!req.apiToken) {
return next();
}
const startTime = Date.now();
req.startTime = startTime;
// Get request size
const requestSize = req.headers['content-length']
? parseInt(req.headers['content-length'])
: 0;
// Capture original res.json to measure response
const originalJson = res.json.bind(res);
let responseSize = 0;
res.json = function (body) {
responseSize = JSON.stringify(body).length;
return originalJson(body);
};
// Track after response is sent
res.on('finish', async () => {
const responseTime = Date.now() - startTime;
try {
await migrate_1.pool.query(`
INSERT INTO api_token_usage (
token_id,
endpoint,
method,
status_code,
response_time_ms,
request_size,
response_size,
ip_address,
user_agent
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
`, [
req.apiToken.id,
req.path,
req.method,
res.statusCode,
responseTime,
requestSize,
responseSize,
req.ip,
req.headers['user-agent'] || null
]);
// Update last_used_at
await migrate_1.pool.query('UPDATE api_tokens SET last_used_at = CURRENT_TIMESTAMP WHERE id = $1', [req.apiToken.id]);
}
catch (error) {
console.error('Error tracking API usage:', error);
}
});
next();
}
// Rate limiting check
async function checkRateLimit(req, res, next) {
if (!req.apiToken) {
return next();
}
const { id, rate_limit } = req.apiToken;
try {
// Count requests in the last minute
const result = await migrate_1.pool.query(`
SELECT COUNT(*) as request_count
FROM api_token_usage
WHERE token_id = $1
AND created_at > NOW() - INTERVAL '1 minute'
`, [id]);
const requestCount = parseInt(result.rows[0].request_count);
if (requestCount >= rate_limit) {
return res.status(429).json({
error: 'Rate limit exceeded',
limit: rate_limit,
current: requestCount,
retry_after: 60
});
}
// Add rate limit headers
res.setHeader('X-RateLimit-Limit', rate_limit.toString());
res.setHeader('X-RateLimit-Remaining', (rate_limit - requestCount).toString());
res.setHeader('X-RateLimit-Reset', new Date(Date.now() + 60000).toISOString());
next();
}
catch (error) {
console.error('Error checking rate limit:', error);
next();
}
}