feat: enrich traffic API with external actor ranking and focused event streams
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s
This commit is contained in:
@@ -5,6 +5,13 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
const MONITOR_TOKEN = process.env.TRAFFIC_MONITOR_TOKEN;
|
||||
|
||||
function asRecordJson(value: unknown): Record<string, unknown> | undefined {
|
||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
if (MONITOR_TOKEN) {
|
||||
const token = request.headers.get("x-traffic-token");
|
||||
@@ -13,9 +20,11 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
const since = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
const query = request.nextUrl.searchParams;
|
||||
const minutes = Math.max(parseInt(query.get("minutes") || "1440", 10), 5);
|
||||
const since = new Date(Date.now() - minutes * 60 * 1000);
|
||||
|
||||
const [summaryRows, actorSummaryRows, totalRows, latestEvents] = await Promise.all([
|
||||
const [summaryRows, actorSummaryRows, externalActorRows, totalRows, latestEvents] = await Promise.all([
|
||||
prisma.auditEvent.groupBy({
|
||||
by: ["action"],
|
||||
where: {
|
||||
@@ -30,6 +39,16 @@ export async function GET(request: NextRequest) {
|
||||
},
|
||||
_count: { _all: true },
|
||||
}),
|
||||
prisma.auditEvent.groupBy({
|
||||
by: ["actorId"],
|
||||
where: {
|
||||
createdAt: { gte: since },
|
||||
action: {
|
||||
startsWith: "EXTERNAL_",
|
||||
},
|
||||
},
|
||||
_count: { _all: true },
|
||||
}),
|
||||
prisma.auditEvent.count({
|
||||
where: { createdAt: { gte: since } },
|
||||
}),
|
||||
@@ -40,7 +59,7 @@ export async function GET(request: NextRequest) {
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: 80,
|
||||
take: 120,
|
||||
select: {
|
||||
id: true,
|
||||
action: true,
|
||||
@@ -63,6 +82,14 @@ export async function GET(request: NextRequest) {
|
||||
actorSummaryRows.map((row) => [row.actorType, row._count._all])
|
||||
);
|
||||
|
||||
const externalActorSummary = externalActorRows
|
||||
.map((row) => ({
|
||||
actorId: row.actorId || "unknown",
|
||||
events: row._count._all,
|
||||
}))
|
||||
.sort((a, b) => b.events - a.events)
|
||||
.slice(0, 20);
|
||||
|
||||
const channelSummary = Object.entries(actionSummary).reduce(
|
||||
(acc, [action, count]) => {
|
||||
if (action.startsWith("EXTERNAL_")) {
|
||||
@@ -75,16 +102,16 @@ export async function GET(request: NextRequest) {
|
||||
{ external: 0, internal: 0 } as Record<string, number>
|
||||
);
|
||||
|
||||
const externalEventTypes = Object.entries(actionSummary).filter(([action]) => action.startsWith("EXTERNAL_")).map(([action, count]) => ({ action, count }));
|
||||
const internalEventTypes = Object.entries(actionSummary).filter(([action]) => !action.startsWith("EXTERNAL_")).map(([action, count]) => ({ action, count }));
|
||||
const externalEventTypes = Object.entries(actionSummary)
|
||||
.filter(([action]) => action.startsWith("EXTERNAL_"))
|
||||
.map(([action, count]) => ({ action, count }));
|
||||
|
||||
const internalEventTypes = Object.entries(actionSummary)
|
||||
.filter(([action]) => !action.startsWith("EXTERNAL_"))
|
||||
.map(([action, count]) => ({ action, count }));
|
||||
|
||||
const recentEvents = latestEvents.map((event) => {
|
||||
// metadata is JSONB in Prisma; read only known optional keys for monitoring views.
|
||||
const metadata =
|
||||
event.metadata && typeof event.metadata === "object" && !Array.isArray(event.metadata)
|
||||
? (event.metadata as Record<string, unknown>)
|
||||
: undefined;
|
||||
|
||||
const metadata = asRecordJson(event.metadata);
|
||||
return {
|
||||
id: event.id,
|
||||
action: event.action,
|
||||
@@ -99,15 +126,25 @@ export async function GET(request: NextRequest) {
|
||||
};
|
||||
});
|
||||
|
||||
const recentExternalEvents = recentEvents.filter((event) =>
|
||||
event.action.startsWith("EXTERNAL_")
|
||||
);
|
||||
|
||||
const recentInternalEvents = recentEvents.filter(
|
||||
(event) => !event.action.startsWith("EXTERNAL_")
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
period_hours: 24,
|
||||
period_minutes: minutes,
|
||||
total_events: totalRows,
|
||||
action_summary: actionSummary,
|
||||
channel_summary: channelSummary,
|
||||
actor_summary: actorSummary,
|
||||
external_actor_summary: externalActorSummary,
|
||||
external_event_types: externalEventTypes,
|
||||
internal_event_types: internalEventTypes,
|
||||
recent_events: recentEvents,
|
||||
recent_external_events: recentExternalEvents,
|
||||
recent_internal_events: recentInternalEvents,
|
||||
updated_at: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user