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>
266 lines
8.9 KiB
JavaScript
266 lines
8.9 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const express_1 = require("express");
|
|
const middleware_1 = require("../auth/middleware");
|
|
const migrate_1 = require("../db/migrate");
|
|
const crypto_1 = __importDefault(require("crypto"));
|
|
const router = (0, express_1.Router)();
|
|
router.use(middleware_1.authMiddleware);
|
|
// Generate secure random token
|
|
function generateToken() {
|
|
return crypto_1.default.randomBytes(32).toString('hex');
|
|
}
|
|
// Get all API tokens
|
|
router.get('/', (0, middleware_1.requireRole)('superadmin', 'admin'), async (req, res) => {
|
|
try {
|
|
const result = await migrate_1.pool.query(`
|
|
SELECT
|
|
t.*,
|
|
u.email as created_by_email,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM api_token_usage
|
|
WHERE token_id = t.id
|
|
AND created_at > NOW() - INTERVAL '24 hours'
|
|
) as requests_24h,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM api_token_usage
|
|
WHERE token_id = t.id
|
|
AND created_at > NOW() - INTERVAL '7 days'
|
|
) as requests_7d,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM api_token_usage
|
|
WHERE token_id = t.id
|
|
) as total_requests
|
|
FROM api_tokens t
|
|
LEFT JOIN users u ON t.user_id = u.id
|
|
ORDER BY t.created_at DESC
|
|
`);
|
|
res.json({ tokens: result.rows });
|
|
}
|
|
catch (error) {
|
|
console.error('Error fetching API tokens:', error);
|
|
res.status(500).json({ error: 'Failed to fetch API tokens' });
|
|
}
|
|
});
|
|
// Get single API token
|
|
router.get('/:id', (0, middleware_1.requireRole)('superadmin', 'admin'), async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const result = await migrate_1.pool.query(`
|
|
SELECT
|
|
t.*,
|
|
u.email as created_by_email
|
|
FROM api_tokens t
|
|
LEFT JOIN users u ON t.user_id = u.id
|
|
WHERE t.id = $1
|
|
`, [id]);
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Token not found' });
|
|
}
|
|
res.json({ token: result.rows[0] });
|
|
}
|
|
catch (error) {
|
|
console.error('Error fetching API token:', error);
|
|
res.status(500).json({ error: 'Failed to fetch API token' });
|
|
}
|
|
});
|
|
// Create new API token
|
|
router.post('/', (0, middleware_1.requireRole)('superadmin', 'admin'), async (req, res) => {
|
|
try {
|
|
const { name, description, rate_limit, allowed_endpoints, expires_at } = req.body;
|
|
const userId = req.user.userId;
|
|
if (!name) {
|
|
return res.status(400).json({ error: 'Name is required' });
|
|
}
|
|
const token = generateToken();
|
|
const result = await migrate_1.pool.query(`
|
|
INSERT INTO api_tokens (
|
|
name,
|
|
token,
|
|
description,
|
|
user_id,
|
|
rate_limit,
|
|
allowed_endpoints,
|
|
expires_at
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING *
|
|
`, [
|
|
name,
|
|
token,
|
|
description || null,
|
|
userId,
|
|
rate_limit || 100,
|
|
allowed_endpoints || null,
|
|
expires_at || null
|
|
]);
|
|
res.status(201).json({
|
|
token: result.rows[0],
|
|
message: 'API token created successfully. Save this token securely - it cannot be retrieved later.'
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Error creating API token:', error);
|
|
res.status(500).json({ error: 'Failed to create API token' });
|
|
}
|
|
});
|
|
// Update API token
|
|
router.put('/:id', (0, middleware_1.requireRole)('superadmin', 'admin'), async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, description, active, rate_limit, allowed_endpoints, expires_at } = req.body;
|
|
const result = await migrate_1.pool.query(`
|
|
UPDATE api_tokens
|
|
SET
|
|
name = COALESCE($1, name),
|
|
description = COALESCE($2, description),
|
|
active = COALESCE($3, active),
|
|
rate_limit = COALESCE($4, rate_limit),
|
|
allowed_endpoints = COALESCE($5, allowed_endpoints),
|
|
expires_at = COALESCE($6, expires_at)
|
|
WHERE id = $7
|
|
RETURNING *
|
|
`, [name, description, active, rate_limit, allowed_endpoints, expires_at, id]);
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Token not found' });
|
|
}
|
|
res.json({ token: result.rows[0] });
|
|
}
|
|
catch (error) {
|
|
console.error('Error updating API token:', error);
|
|
res.status(500).json({ error: 'Failed to update API token' });
|
|
}
|
|
});
|
|
// Delete API token
|
|
router.delete('/:id', (0, middleware_1.requireRole)('superadmin'), async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const result = await migrate_1.pool.query('DELETE FROM api_tokens WHERE id = $1 RETURNING *', [id]);
|
|
if (result.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Token not found' });
|
|
}
|
|
res.json({ message: 'API token deleted successfully' });
|
|
}
|
|
catch (error) {
|
|
console.error('Error deleting API token:', error);
|
|
res.status(500).json({ error: 'Failed to delete API token' });
|
|
}
|
|
});
|
|
// Get token usage statistics
|
|
router.get('/:id/usage', (0, middleware_1.requireRole)('superadmin', 'admin'), async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { days = 7 } = req.query;
|
|
// Get hourly usage for the past N days
|
|
const hourlyUsage = await migrate_1.pool.query(`
|
|
SELECT
|
|
DATE_TRUNC('hour', created_at) as hour,
|
|
COUNT(*) as requests,
|
|
AVG(response_time_ms) as avg_response_time,
|
|
SUM(CASE WHEN status_code >= 200 AND status_code < 300 THEN 1 ELSE 0 END) as successful_requests,
|
|
SUM(CASE WHEN status_code >= 400 THEN 1 ELSE 0 END) as failed_requests
|
|
FROM api_token_usage
|
|
WHERE token_id = $1
|
|
AND created_at > NOW() - INTERVAL '${parseInt(days)} days'
|
|
GROUP BY hour
|
|
ORDER BY hour DESC
|
|
`, [id]);
|
|
// Get endpoint usage
|
|
const endpointUsage = await migrate_1.pool.query(`
|
|
SELECT
|
|
endpoint,
|
|
method,
|
|
COUNT(*) as requests,
|
|
AVG(response_time_ms) as avg_response_time
|
|
FROM api_token_usage
|
|
WHERE token_id = $1
|
|
AND created_at > NOW() - INTERVAL '${parseInt(days)} days'
|
|
GROUP BY endpoint, method
|
|
ORDER BY requests DESC
|
|
LIMIT 20
|
|
`, [id]);
|
|
// Get recent requests
|
|
const recentRequests = await migrate_1.pool.query(`
|
|
SELECT
|
|
endpoint,
|
|
method,
|
|
status_code,
|
|
response_time_ms,
|
|
ip_address,
|
|
created_at
|
|
FROM api_token_usage
|
|
WHERE token_id = $1
|
|
ORDER BY created_at DESC
|
|
LIMIT 100
|
|
`, [id]);
|
|
res.json({
|
|
hourly_usage: hourlyUsage.rows,
|
|
endpoint_usage: endpointUsage.rows,
|
|
recent_requests: recentRequests.rows
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Error fetching token usage:', error);
|
|
res.status(500).json({ error: 'Failed to fetch token usage' });
|
|
}
|
|
});
|
|
// Get overall API usage statistics
|
|
router.get('/stats/overview', (0, middleware_1.requireRole)('superadmin', 'admin'), async (req, res) => {
|
|
try {
|
|
const { days = 7 } = req.query;
|
|
const stats = await migrate_1.pool.query(`
|
|
SELECT
|
|
COUNT(DISTINCT token_id) as active_tokens,
|
|
COUNT(*) as total_requests,
|
|
AVG(response_time_ms) as avg_response_time,
|
|
SUM(CASE WHEN status_code >= 200 AND status_code < 300 THEN 1 ELSE 0 END) as successful_requests,
|
|
SUM(CASE WHEN status_code >= 400 THEN 1 ELSE 0 END) as failed_requests
|
|
FROM api_token_usage
|
|
WHERE created_at > NOW() - INTERVAL '${parseInt(days)} days'
|
|
`);
|
|
// Top tokens by usage
|
|
const topTokens = await migrate_1.pool.query(`
|
|
SELECT
|
|
t.id,
|
|
t.name,
|
|
COUNT(u.id) as requests,
|
|
AVG(u.response_time_ms) as avg_response_time
|
|
FROM api_tokens t
|
|
LEFT JOIN api_token_usage u ON t.id = u.token_id
|
|
WHERE u.created_at > NOW() - INTERVAL '${parseInt(days)} days'
|
|
GROUP BY t.id, t.name
|
|
ORDER BY requests DESC
|
|
LIMIT 10
|
|
`);
|
|
// Most used endpoints
|
|
const topEndpoints = await migrate_1.pool.query(`
|
|
SELECT
|
|
endpoint,
|
|
method,
|
|
COUNT(*) as requests,
|
|
AVG(response_time_ms) as avg_response_time
|
|
FROM api_token_usage
|
|
WHERE created_at > NOW() - INTERVAL '${parseInt(days)} days'
|
|
GROUP BY endpoint, method
|
|
ORDER BY requests DESC
|
|
LIMIT 10
|
|
`);
|
|
res.json({
|
|
overview: stats.rows[0],
|
|
top_tokens: topTokens.rows,
|
|
top_endpoints: topEndpoints.rows
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error('Error fetching API stats:', error);
|
|
res.status(500).json({ error: 'Failed to fetch API stats' });
|
|
}
|
|
});
|
|
exports.default = router;
|