Files
multiverse/jumpa-iam/app/api/admin/telemetry/route.ts
T

78 lines
2.9 KiB
TypeScript

import { NextResponse } from 'next/server';
import { db } from "@/drizzle/db";
import { networkTelemetry, quantumLogs, liveKillSwitches } from "@/drizzle/schema";
import { cookies } from 'next/headers';
import jwt from 'jsonwebtoken';
import { desc, count, sql, eq } from 'drizzle-orm';
export const dynamic = 'force-dynamic';
interface DecodedToken {
userId: string;
email: string;
role: string;
tenantId: string;
}
// PANOPTICON: Tenant Admin Telemetry API (Sandboxed View)
// Returns ONLY data scoped to the caller's tenant_id.
export async function GET() {
try {
const cookieStore = await cookies();
const token = cookieStore.get('jumpa_token')?.value;
if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as DecodedToken;
if (decoded.role !== 'admin' && decoded.role !== 'superadmin') {
return NextResponse.json({ error: 'Admin Access Required' }, { status: 403 });
}
const tenantId = decoded.tenantId;
// 1. Telemetry scoped to tenant (last 50)
const recentTelemetry = await db.select().from(networkTelemetry)
.where(eq(networkTelemetry.tenantId, tenantId))
.orderBy(desc(networkTelemetry.timestamp))
.limit(50);
// 2. Quantum logs scoped to tenant (last 100)
const recentLogs = await db.select().from(quantumLogs)
.where(eq(quantumLogs.tenantId, tenantId))
.orderBy(desc(quantumLogs.nanoTimestamp))
.limit(100);
// 3. Kill switches scoped to tenant
const activeKills = await db.select().from(liveKillSwitches)
.where(eq(liveKillSwitches.tenantId, tenantId));
// 4. Aggregated stats (tenant-scoped)
const [telemetryCount] = await db.select({ total: count() }).from(networkTelemetry)
.where(eq(networkTelemetry.tenantId, tenantId));
const [logsCount] = await db.select({ total: count() }).from(quantumLogs)
.where(eq(quantumLogs.tenantId, tenantId));
// 5. Bandwidth aggregate (tenant-scoped)
const bandwidthAgg = await db.select({
totalBytes: sql<string>`COALESCE(SUM(CAST(traffic_bytes AS BIGINT)), 0)`,
avgResponseMs: sql<string>`COALESCE(AVG(CAST(response_time_ms AS NUMERIC)), 0)`,
}).from(networkTelemetry)
.where(eq(networkTelemetry.tenantId, tenantId));
return NextResponse.json({
telemetry: recentTelemetry,
logs: recentLogs,
kills: activeKills,
stats: {
totalTelemetryRecords: telemetryCount?.total || 0,
totalLogRecords: logsCount?.total || 0,
activeKillSwitches: activeKills.length,
totalBandwidthBytes: bandwidthAgg[0]?.totalBytes || '0',
avgResponseTimeMs: parseFloat(String(bandwidthAgg[0]?.avgResponseMs || '0')).toFixed(2),
},
});
} catch (error: unknown) {
console.error('[TENANT TELEMETRY ERROR]', error);
return NextResponse.json({ error: 'Telemetry Sync Failed' }, { status: 500 });
}
}