Files
cannaiq/backend/dist/routes/api-tokens.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

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;