Files
mini/src/pages/api/god/logs.ts

79 lines
2.3 KiB
TypeScript

import type { APIRoute } from 'astro';
import { pool } from '@/lib/db';
/**
* Live Log Streaming (SSE)
* Stream database activity logs
*/
function validateGodToken(request: Request): boolean {
const token = request.headers.get('X-God-Token') ||
request.headers.get('Authorization')?.replace('Bearer ', '') ||
new URL(request.url).searchParams.get('token');
const godToken = process.env.GOD_MODE_TOKEN || import.meta.env.GOD_MODE_TOKEN;
if (!godToken) return true;
return token === godToken;
}
export const GET: APIRoute = async ({ request }) => {
if (!validateGodToken(request)) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
const url = new URL(request.url);
const lines = parseInt(url.searchParams.get('lines') || '100');
try {
// Get recent pg_stat_activity as "logs"
const result = await pool.query(`
SELECT
pid,
usename,
application_name,
state,
query,
state_change,
EXTRACT(EPOCH FROM (now() - query_start)) as duration_seconds
FROM pg_stat_activity
WHERE datname = current_database()
AND pid != pg_backend_pid()
ORDER BY query_start DESC
LIMIT $1
`, [lines]);
// Format as log entries
const logs = result.rows.map(row => ({
timestamp: row.state_change,
pid: row.pid,
user: row.usename,
app: row.application_name,
state: row.state,
duration: `${Math.round(row.duration_seconds)}s`,
query: row.query?.substring(0, 200)
}));
return new Response(JSON.stringify({
logs,
count: logs.length,
requested: lines,
timestamp: new Date().toISOString()
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error: any) {
return new Response(JSON.stringify({
error: 'Failed to fetch logs',
details: error.message
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
};